HOMEPAGE  

pit

skip commit history
21 commits on 2026-10-16
17 commits on 2026-10-14
git
readme.txt
pit - gpg-agent pinentry that prompts in a tmux popup

tmux
  - popup on the most recently active client, over the current window
  - other attached clients get a status-line note saying where it is
  - popup title names the command that asked and its pane, e.g.
    "git commit -S @ work:2.1" (from the pid gpg-agent passes as owner)
  - sized to fit the prompt
  - styles: see config, [tmux] style
  - in place in a pane, instead of a popup: see config, [tmux] inline
  - passphrase, repeat/confirm-new-passphrase, confirm and message dialogs
  - wrong-passphrase and mismatch errors shown inline
  - prompts that only give a fingerprint (ssh keys without a comment) get
    the key's OpenPGP user ID next to it, if gpg knows the keygrip
  - smartcard PIN prompts, which only give the card number, get the user
    ID of the key with subkeys on that card
  - enter submits, esc/ctrl-c cancels, backspace deletes, ctrl-u clears;
    button mnemonics in confirm dialogs
  - honours gpg-agent's pinentry timeout
  - popup closes when gpg-agent kills the pinentry; no leftover files

config
  ~/.config/pit/config.toml ($XDG_CONFIG_HOME/pit/config.toml), read for
  each prompt. TOML, strings only; a key that doesn't parse or isn't known
  is skipped, so a typo never stops the prompt.

    echo = "marker"     # marker|dots|none (default marker; marker
                        # changes per key without revealing length)
    hook = '<command>'  # see below

    [tmux]
    style = "bar"       # bar|box|line, the popup (default bar)
    inline = "auto"     # auto|never (default auto)

  inline: auto draws the prompt in place under the command that asked,
  instead of in a popup, when that command is in the foreground of its own
  pane and nothing full-screen is showing there. never doesn't ask in a
  pane at all, even if the popup can't open. Either way, a terminal outside
  tmux is asked inline, even with tmux running.
  A terminal in raw mode (a full-screen program reading keys) is never
  asked: it could take some of the passphrase. Outside tmux that means the
  popup, or no prompt if no client is attached. Nor is one where another
  program in the foreground job reads the terminal (e.g. `cat | gpg -d`, or
  `cat` there while gpg asks from elsewhere), when gpg-agent says who asked.
  Not caught: one that opens /dev/tty itself without raw mode, which is rare.

  hook: run with sh when a prompt opens and closes (once the open run is
  done), in the background, with PIT_EVENT (open|close), PIT_TITLE,
  PIT_ORIGIN (the command and pane that asked) and PIT_WHERE ("popup
  <client tty>" or "inline <tty>"); never the passphrase. A command line can
  hold secrets, though, so think twice before sending PIT_ORIGIN anywhere
  that keeps or shows it (a notification history, a lock screen). e.g.
    hook = '''
    [ "$PIT_EVENT" = open ] &&
      notify-send "gpg wants a passphrase" "$PIT_TITLE"
    '''

screenshots
  bar (default), along the bottom of the client:

    ┌─ gpg · git commit -S @ work:2.0 ─────────────────────────────────────┐
    │ Please enter the passphrase to unlock the OpenPGP secret key         │
    │ "Alice <alice@example.org>" · 255-bit EDDSA key, ID 3AA5C34371567BD2 │
    │ created 2024-03-01.                                                  │
    │ Passphrase: [•·•·]                                                   │
    └──────────────────────────────────────────────────────────────────────┘
    [work] 0:zsh- 2:git*

  box, centred:

    ┌─ gpg · git commit -S @ work:2.0 ──────────────────────────────┐
    │ Please enter the passphrase to unlock the OpenPGP secret key: │
    │ "Alice <alice@example.org>"                                   │
    │ 255-bit EDDSA key, ID 3AA5C34371567BD2,                       │
    │ created 2024-03-01.                                           │
    │                                                               │
    │ Passphrase: [•••·]                                            │
    └───────────────────────────────────────────────────────────────┘

  line, the description summed up in the title:

    ┌─ gpg · Alice <alice@example.org> ───┐
    │ Passphrase: [••·•]                  │
    └─────────────────────────────────────┘

  echo marker, dots, none (after typing 7 characters):

    │ Passphrase: [•··•]                 │
    │ Passphrase: •••••••                │
    │ Passphrase:                        │

  wrong passphrase:

    ┌─ gpg · git commit -S @ work:2.0 ──────────────────────────────┐
    │ Please enter the passphrase to unlock the OpenPGP secret key: │
    │ "Alice <alice@example.org>"                                   │
    │ 255-bit EDDSA key, ID 3AA5C34371567BD2,                       │
    │ created 2024-03-01.                                           │
    │                                                               │
    │ Bad Passphrase (try 2 of 3)                                   │
    │ Passphrase: [····]                                            │
    └───────────────────────────────────────────────────────────────┘

  new passphrase, after a mismatch:

    ┌─ gpg · git commit -S @ work:2.0 ─────────────────┐
    │ Please enter a passphrase to protect the new key │
    │                                                  │
    │ does not match - try again                       │
    │ Passphrase: [····]                               │
    └──────────────────────────────────────────────────┘

  confirm:

    ┌─ gpg · git commit -S @ work:2.0 ───────────────────┐
    │ An ssh process requested the use of key            │
    │ SHA256:Xg5lV0wHqR6c8eZ9o1C3pJ2bDkF4aT7nYyLmQsUvWiE │
    │ (alice@laptop)                                     │
    │ Do you want to allow this?                         │
    │                                                    │
    │ ⏎ Allow   d Deny   esc Cancel                      │
    └────────────────────────────────────────────────────┘

  message:

    ┌─ gpg · git commit -S @ work:2.0 ─────────┐
    │ No card found. Insert your OpenPGP card. │
    │                                          │
    │ ⏎ OK                                     │
    └──────────────────────────────────────────┘

  status-line note on the other attached clients:

    pinentry: gpg - prompt open on /dev/pts/15

  in place, outside tmux:

    ~/src/notes $ git commit -S -m 'Fix typo'
     Please enter the passphrase to unlock the OpenPGP secret key
     "Alice <alice@example.org>" · 255-bit EDDSA key, ID 3AA5C34371567BD2
     created 2024-03-01.
     Passphrase: [••··]

fallback
  - command run in a terminal outside tmux: prompt drawn in place on that
    terminal and erased afterwards, even if a tmux client is attached
  - no tmux client attached: same, on gpg's terminal
  - popup can't open (e.g. client already has one): same, on gpg's terminal;
    a pane only with inline auto and nothing full-screen showing there
  - ssh: add to ~/.ssh/config so the agent knows ssh's terminal:
      Match host * exec "GPG_TTY=$(tty <&2) gpg-connect-agent UPDATESTARTUPTTY /bye >/dev/null"

error
  - no tmux client and no known terminal: "No pinentry" error to gpg
  - terminal known but unopenable and no tmux client: same error
  - popup closed from tmux mid-prompt: cancelled

testing
  - cargo test: unit tests, plus the binary driven through ptys and an
    isolated tmux server (skipped if tmux isn't installed)
  - cargo test --test gpg_agent -- --ignored: through a real gpg-agent
  - PIT_TEST_SHOW=1 cargo test -- --nocapture: print the screen at each step
  - PIT_TEST_LIVE=1 cargo test: replay the tests on this terminal, at its
    size, one at a time, typing slowly (PIT_TEST_SLOW=ms per key, default 120)
  - cargo test --test screenshots -- --ignored --nocapture --test-threads=1:
    print the screenshots above