passr is a terminal UI and a Chrome extension for pass, the standard unix password manager. It is a front end and nothing more — your store stays a folder of gpg files that pass, git and every other client still understand.
$npm install -g passr-tuiMIT · free · macOS and Linux · and in Chrome
⚿ passr · ~/.password-store 20 entries ⌕ press / to search │ aws ──────────────────────────────── │ github/personal prod │ ─────────────────────────────────────────────────────────────────────────── staging │ banking ──────────────────────────── │ ╭──────────────────────────────────────────────────────────────────────────╮ first-mesa │ │ PASSWORD hidden │ stripe │ │ •••••••••••••••••••••••• │ cloudflare │ │ ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ excellent · ~158 bits · 24 chars │ email ────────────────────────────── │ ╰──────────────────────────────────────────────────────────────────────────╯ fastmail │ work │ FIELDS figma │ username junipervale github ───────────────────────────── │ url https://github.com ▌ personal │ otpauth •••••••••••••••••••••••••••••••• work │ recovery ••••••••••••••••••••••••••••••• linear │ npm │ NOTES recovery-codes │ Personal account. SSH key for this one lives on the servers ──────────────────────────── │ laptop only — see servers/nas for the backup copy. db-01 │ nas │ vps │ social ───────────────────────────── │ bluesky │ mastodon │ wifi ─────────────────────────────── │ home │ studio │ │ ─────────────────────────────────────────────────────────────────────────── │ modified 3d ago · 450 B · github/personal / search ↵ reveal c copy n new e edit r rename d delete ? keys q quit
Every terminal on this page is a real frame: passr was run in a pseudo-terminal against a throwaway GPG key and a seeded store, and each cell it painted was recorded, colour and all. The accounts are invented. The rendering is not.
No import, no new vault, no format of its own. passr reads the ~/.password-store you already have, and writes to it only through pass — so git commits happen exactly as they always did.
passr implements no cryptography. The TUI shells out to pass; the extension's helper runs gpg. Your key, your agent, your pinentry.
No account, no sync server, no analytics. Neither half makes a network request, and nothing decrypted is ever written to disk.
The terminal
Your store down the left, grouped by folder the way pass ls draws it. Move onto an entry and it decrypts in the background — but the password stays dots until you press ↵, so opening passr with someone beside you does not put a password on the screen.
⚿ passr · ~/.password-store 20 entries ⌕ press / to search │ aws ──────────────────────────────── │ github/personal prod │ ─────────────────────────────────────────────────────────────────────────── staging │ banking ──────────────────────────── │ ╭──────────────────────────────────────────────────────────────────────────╮ first-mesa │ │ PASSWORD visible │ stripe │ │ Hv2@xN8q!Kd5rTz3Wm7sLp9e │ cloudflare │ │ ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ excellent · ~158 bits · 24 chars │ email ────────────────────────────── │ ╰──────────────────────────────────────────────────────────────────────────╯ fastmail │ work │ FIELDS figma │ username junipervale github ───────────────────────────── │ url https://github.com ▌ personal │ otpauth //totp/GitHub:junipervale?secret=GEZDGNBVGY3TQOJQ&issuer=GitHub work │ recovery 9c4f-2b1e, 7a0d-e5c3, 41fb-8d62 linear │ npm │ NOTES recovery-codes │ Personal account. SSH key for this one lives on the servers ──────────────────────────── │ laptop only — see servers/nas for the backup copy. db-01 │ nas │ vps │ social ───────────────────────────── │ bluesky │ mastodon │ wifi ─────────────────────────────── │ home │ studio │ │ ─────────────────────────────────────────────────────────────────────────── │ modified 3d ago · 450 B · github/personal / search ↵ reveal c copy n new e edit r rename d delete ? keys q quit
↵ reveals. The first line is the password; every key: value line after it becomes a field, and the rest is notes. Fields whose names look secret — otp, token, pin, recovery — stay masked until you reveal them too.
The list is a walk of the store directory for *.gpg files — the same thing pass ls does — so it opens instantly and never asks for a passphrase.
Decryption waits 90ms after you stop on an entry, so scrolling through a hundred of them does not start a hundred gpg processes.
A rough entropy estimate from the character sets and length, with a discount for repetition. Enough to tell hunter2 from something pass generate made.
Copying
tab walks a cursor through the fields and c copies whichever one it is on. The password goes through pass show -c, which owns the copy and the wipe. Any other field gets the same treatment from passr itself — and the wipe only fires if the clipboard still holds what passr put there, so it never eats something you copied since.
⚿ passr · ~/.password-store 20 entries ⌕ press / to search │ aws ──────────────────────────────── │ github/personal prod │ ─────────────────────────────────────────────────────────────────────────── staging │ banking ──────────────────────────── │ ╭──────────────────────────────────────────────────────────────────────────╮ first-mesa │ │ PASSWORD visible │ stripe │ │ Hv2@xN8q!Kd5rTz3Wm7sLp9e │ cloudflare │ │ ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ excellent · ~158 bits · 24 chars │ email ────────────────────────────── │ ╰──────────────────────────────────────────────────────────────────────────╯ fastmail │ work │ FIELDS figma │ ▌username junipervale github ───────────────────────────── │ url https://github.com ▌ personal │ otpauth //totp/GitHub:junipervale?secret=GEZDGNBVGY3TQOJQ&issuer=GitHub work │ recovery 9c4f-2b1e, 7a0d-e5c3, 41fb-8d62 linear │ npm │ NOTES recovery-codes │ Personal account. SSH key for this one lives on the servers ──────────────────────────── │ laptop only — see servers/nas for the backup copy. db-01 │ nas │ vps │ social ───────────────────────────── │ bluesky │ mastodon │ wifi ─────────────────────────────── │ home │ studio │ │ ─────────────────────────────────────────────────────────────────────────── │ modified 3d ago · 450 B · github/personal ✓ username copied — clears in 45s
The cursor on username, then c. The hint bar gives way to the confirmation for a few seconds and comes back.
Search
A fuzzy matcher tuned for paths. A contiguous hit beats a scattered one, a hit at the start of a segment beats one in the middle, and a hit in the entry's own name beats one in its folder — so work lands on email/work and github/work before anything that merely contains the letters.
While you type, the list goes flat and ranked, with the matched letters picked out. ↵ keeps the filter and hands the keyboard back; esc clears it and the folders return.
⚿ passr · ~/.password-store 2/20 entries ⌕ work │ ▌email/work │ email/work github/work │ ─────────────────────────────────────────────────────────────────────────── │ │ ╭──────────────────────────────────────────────────────────────────────────╮ │ │ PASSWORD hidden │ │ │ •••••••••••• │ │ │ ▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱▱ strong · ~79 bits · 12 chars │ │ ╰──────────────────────────────────────────────────────────────────────────╯ │ │ FIELDS │ username j.vale@marrowandfinch.example │ url https://mail.marrowandfinch.example │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ─────────────────────────────────────────────────────────────────────────── │ modified 3mo ago · 251 B · email/work ↑↓ move ↵ select esc clear
Writing
passr never writes into the store itself. A new entry is pass generate or pass insert, an edit is pass insert -m, a rename is pass mv and a delete is pass rm — which means per-folder .gpg-id recipients are respected, and if your store is a git repository every one of them lands as a commit.
⚿ passr · ~/.password-store 20 entries ⌕ press / to search │ aws ──────────────────────────────── │ email/work prod │ ─────────────────────────────────────────────────────────────────────────── staging │ banking ──────────────────────────── │ ╭──────────────────────────────────────────────────────────────────────────╮ first-mesa ╭─ New entry ────────────────────────────────────────────────────────╮ │ stripe │ │ │ cloudflare │ NAME │ │ email ──────────────────│ hosting/fly │──────────────────────╯ fastmail │ │ ▌ work │ │ figma │ SECRET │ github ─────────────────│ ◉ generate · ○ type it │ personal │ length 24 symbols on │ work │ │ linear │ EXTRA LINES │ npm │ ╭──────────────────────────────────────────────────────────────╮ │ recovery-codes │ │ username: you@example.com │ │ servers ────────────────│ │ url: https://… │ │ db-01 │ │ │ │ nas │ ╰──────────────────────────────────────────────────────────────╯ │ vps │ │ social ─────────────────│ │ bluesky │ tab next field ctrl+s create esc cancel │ mastodon │ │ wifi ───────────────────╰────────────────────────────────────────────────────────────────────╯ home │ studio │ │ ─────────────────────────────────────────────────────────────────────────── │ modified 3mo ago · 251 B · email/work / search ↵ reveal c copy n new e edit r rename d delete ? keys q quit
⚿ passr · ~/.password-store 20 entries ⌕ press / to search │ aws ──────────────────────────────── │ email/work prod │ ─────────────────────────────────────────────────────────────────────────── staging │ banking ──────────────────────────── │ ╭──────────────────────────────────────────────────────────────────────────╮ first-mesa │ │ PASSWORD hidden │ stripe ╭─ Edit email/work ──────────────────────────────────────────────────────╮ │ cloudflare │ │ │ email ────────────────│ ╭──────────────────────────────────────────────────────────────────╮ │────────────────────╯ fastmail │ │ Qp7#Lm2vZ9xR │ │ ▌ work │ │ username: j.vale@marrowandfinch.example │ │ figma │ │ url: https://mail.marrowandfinch.example │ │ github ───────────────│ │ │ │ personal │ │ │ │ work │ │ │ │ linear │ │ │ │ npm │ │ │ │ recovery-codes │ │ │ │ servers ──────────────│ │ │ │ db-01 │ ╰──────────────────────────────────────────────────────────────────╯ │ nas │ │ vps │ │ social ───────────────│ ctrl+s save esc discard first line is the password │ bluesky │ │ mastodon ╰────────────────────────────────────────────────────────────────────────╯ wifi ─────────────────────────────── │ home │ studio │ │ ─────────────────────────────────────────────────────────────────────────── │ modified 3mo ago · 251 B · email/work / search ↵ reveal c copy n new e edit r rename d delete ? keys q quit
pass.Passphrases
A TUI and a terminal pinentry both want the same screen, which is where most front ends for pass fall over. passr asks gpg first whether it can decrypt without asking you, and only hands over the terminal when the answer is no.
Every decrypt starts with --pinentry-mode error. If gpg-agent already has your passphrase — the usual case — that succeeds and the screen never flickers.
If gpg needs to ask, passr suspends its own renderer — alternate screen, raw mode and all — and lets pinentry draw.
When pinentry is done, passr resumes and repaints over whatever it left behind. The secret itself comes back over a pipe, never through the terminal.
⌃L drops every decrypted entry passr is holding and stops decrypting until you press enter. q does the same on the way out.
⚿ passr · ~/.password-store locked · 20 entries ⌕ press / to search │ aws ──────────────────────────────── │ email/work prod │ ─────────────────────────────────────────────────────────────────────────── staging │ banking ──────────────────────────── │ ⚿ locked first-mesa │ Nothing decrypted is being held. Press enter to unlock. stripe │ cloudflare │ email ────────────────────────────── │ fastmail │ ▌ work │ figma │ github ───────────────────────────── │ personal │ work │ linear │ npm │ recovery-codes │ servers ──────────────────────────── │ db-01 │ nas │ vps │ social ───────────────────────────── │ bluesky │ mastodon │ wifi ─────────────────────────────── │ home │ studio │ │ ─────────────────────────────────────────────────────────────────────────── │ modified 3mo ago · 251 B · email/work ✓ Locked — decrypted secrets forgotten
⌃L — for when someone walks up behind you. It is a privacy screen, not a crypto lock: gpg-agent keeps whatever it had cached.
In the browser
passr for Chrome reads the same store. Open it on a login page with ⌥⇧P and the entries that match the site are waiting above everything else; ↵ fills the username and password. There is nothing to configure — it matches on how you already named your entries.
Entry filed under the brand: github/…
Sign in
to github.com
Username or email
Password
Try it: pick a site, type to search, use the arrow keys, press ↵. This is the extension's own matching and ranking code, run against the same invented store as the terminal above. Nothing is decrypted, because there is nothing here to decrypt.
Matching reads the entry's path, so any of the usual ways of filing a login works:
A form that asks for the username on one page and the password on the next still works: with no password field in sight, passr fills just the username.
The conventions pass users already follow. No extensions required.
correct-horse-battery-staple login: juniper@example.com otpauth://totp/Example:juniper?secret=JBSW…
login:, username:, user: or email: line — or, failing that, the entry's name, so github/juniper fills juniper.otpauth:// line. passr computes the TOTP itself, so pass-otp is not needed.Under the popup
Chrome's native messaging lets an extension talk to a program registered on your machine, by name. passr's is a single Python file with no dependencies beyond the standard library. It lists entries, decrypts one when asked, and exits — one request per launch, nothing left running.
popup
asks for a list, or for one entry
Chrome
native messaging, by name
passr_host.py
one request, then exits
gpg
your key, your agent
Talks to the helper on your machine, which lists and decrypts entries with your own gpg. It is the only way credentials reach the extension.
Lets it fill the tab you opened it on, and only that one, only then. There is no standing access to any site.
Injects one small function when you press Enter, which finds the login fields and sets their values.
Keeps the list of entry names — not their contents — in session storage, so the popup opens instantly. Gone when the browser closes.
That is the whole manifest. No background worker, no content scripts, no host permissions — the extension is a popup, and it is not running when the popup is closed.
Keyboard
Vim keys in the terminal, and in the browser the same four chords every time. Press ? in the TUI and it will list its own.
⚿ passr · ~/.password-store 20 entries ⌕ press / to search │ aws ──────────────────────────────── │ email/work prod │ ─────────────────────────────────────────────────────────────────────────── staging │ banking ──────────────────────────── │ ╭──────────────────────────────────────────────────────────────────────────╮ first-mesa ╭─ Keys ─────────────────────────────────────────────────╮ │ stripe │ │ │ cloudflare │ ↑ ↓ j k move selection │chars │ email ────────────────────────│ g / G jump to top / bottom │────────────────────────────╯ fastmail │ / search entries │ ▌ work │ esc clear search / close │ figma │ enter reveal password │ github ───────────────────────│ space toggle reveal │ personal │ tab move field cursor │ work │ c copy selected field to clipboard │ linear │ n new entry │ npm │ e edit entry contents │ recovery-codes │ r rename entry │ servers ──────────────────────│ d delete entry │ db-01 │ ctrl+l lock — forget decrypted secrets │ nas │ ? toggle this help │ vps │ q ctrl+c quit │ social ───────────────────────│ │ bluesky │ esc close │ mastodon │ │ wifi ─────────────────────────╰────────────────────────────────────────────────────────╯ home │ studio │ │ ─────────────────────────────────────────────────────────────────────────── │ modified 3mo ago · 251 B · email/work / search ↵ reveal c copy n new e edit r rename d delete ? keys q quit
Underneath
The TUI is React rendered into terminal cells by @opentui/react, on Bun. The extension is a Manifest V3 popup in plain JavaScript and a Python helper. Neither has a dependency that touches your secrets.
Fine print
Better to read this now than to find out on a login page.
Both halves need a Unix pass and gpg. There is no Windows build of either.
Its renderer reaches native code through Bun's FFI. npm installs a copy of Bun alongside it if you do not already have one.
Chrome starts the helper with no terminal, so a TTY-only pinentry cannot ask for your passphrase. Install pinentry-mac or pinentry-gnome3, or unlock the key in a terminal first.
The helper registers itself with Chrome, Brave, Edge, Vivaldi, Arc and Chromium. Firefox and Safari are not supported.
It fills and copies. Creating, editing, renaming and deleting are the TUI's job, or pass's.
⌃L makes passr forget everything it decrypted and stop decrypting. It does not lock gpg-agent, which may still hold your passphrase.
Both halves need pass and a store it has already initialised. Install either one, or both — they never talk to each other, only to pass.
The terminal
From npm. It brings its own Bun if you do not have one.
$npm install -g passr-tuiThen run passr. It reads $PASSWORD_STORE_DIR if you set it.
The browser
First the helper, which registers itself with every Chromium browser it finds:
$curl -fsSL https://raw.githubusercontent.com/wes/passr-chrome/main/install.sh | bashThen the extension. The Chrome Web Store listing is on its way; until it lands, open chrome://extensions, turn on Developer mode, and load the extension/ folder unpacked.
216 installs from npmsince 26 July 2026, 19 in the last month