The passr icon: a white key on a blue tile

pass, with a face on it.

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-tui

MIT · free · macOS and Linux · and in Chrome

passr120×32
 ⚿ 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.

It is still just pass.

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.

No crypto of its own.

passr implements no cryptography. The TUI shells out to pass; the extension's helper runs gpg. Your key, your agent, your pinentry.

Nothing leaves the machine.

No account, no sync server, no analytics. Neither half makes a network request, and nothing decrypted is ever written to disk.

The terminal

Hidden until you ask.

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 — github/personal120×32
 ⚿ 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.

Nothing decrypts to list

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.

Holding j is cheap

Decryption waits 90ms after you stop on an entry, so scrolling through a hundred of them does not start a hundred gpg processes.

An honest strength meter

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

On the clipboard for 45 seconds.

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 — github/personal120×32
 ⚿ 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

/ and a few letters.

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 — /work120×32
 ⚿ 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

Every change is a pass command.

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 — new entry120×32
 ⚿ 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                                  
n starts in the folder you are standing in. Generate with a length and a symbols switch, or type one in; extra lines become fields.
passr — edit120×32
 ⚿ 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                                  
e opens the decrypted text as it is, first line and all. ⌃S re-encrypts it through pass.

Passphrases

It gets out of pinentry's way.

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.

  1. 01

    Try quietly

    Every decrypt starts with --pinentry-mode error. If gpg-agent already has your passphrase — the usual case — that succeeds and the screen never flickers.

  2. 02

    Hand over the terminal

    If gpg needs to ask, passr suspends its own renderer — alternate screen, raw mode and all — and lets pinentry draw.

  3. 03

    Take it back

    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.

  4. 04

    Forget on request

    ⌃L drops every decrypted entry passr is holding and stops decrypting until you press enter. q does the same on the way out.

passr — locked120×32
 ⚿ 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

The right login is already at the top.

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/…

github.com/login
github.com
  • Matches for github.com
  • github/personal
  • github/work
  • All
  • aws/prod
  • aws/staging
  • banking/first-mesa
  • banking/stripe
  • cloudflare
  • email/fastmail
  • email/work
  • figma
  • linear
  • npm
  • recovery-codes
  • servers/db-01
  • servers/nas
  • servers/vps
  • social/bluesky
  • social/mastodon
  • wifi/home
  • wifi/studio
↵ fill⌘C pass⌘U user⌘O otp

Sign in

to github.com

Username or email

Password

Sign in

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.

How entries find their site

Matching reads the entry's path, so any of the usual ways of filing a login works:

  • accounts.example.comthe host itself
  • example.com/juniperthe domain as a folder
  • example/juniperthe brand as a folder
  • aws/prodon signin.aws.amazon.com
  • social/blueskyon bsky.app, by alias

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 entry format it expects

The conventions pass users already follow. No extensions required.

correct-horse-battery-staple
login: juniper@example.com
otpauth://totp/Example:juniper?secret=JBSW…
  • Password: the first line.
  • Username: a login:, username:, user: or email: line — or, failing that, the entry's name, so github/juniper fills juniper.
  • One-time codes: from an otpauth:// line. passr computes the TOTP itself, so pass-otp is not needed.

Under the popup

A browser cannot run gpg. So a helper does.

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.

  1. 01

    popup

    asks for a list, or for one entry

  2. 02

    Chrome

    native messaging, by name

  3. 03

    passr_host.py

    one request, then exits

  4. 04

    gpg

    your key, your agent

nativeMessaging

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.

activeTab

Lets it fill the tab you opened it on, and only that one, only then. There is no standing access to any site.

scripting

Injects one small function when you press Enter, which finds the login fields and sets their values.

storage

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

Both halves, no mouse required.

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.

Moving

Up and downjk
Top, bottomgG
Search/
Clear searchesc

Reading

Reveal the password↵
Toggle itspace
Next fieldtab
Copy the fieldc

Writing

New entryn
Edite
Rename or mover
Deleted
Lock⌃L

In the browser

Open passr⌥⇧P
Move↑↓
Fill username and password↵
Copy password⌘C
Copy username⌘U
Copy one-time code⌘O
passr — keys120×32
 ⚿ 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

Small on purpose.

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

What it will not do.

Better to read this now than to find out on a login page.

macOS and Linux only

Both halves need a Unix pass and gpg. There is no Windows build of either.

The TUI runs on Bun

Its renderer reaches native code through Bun's FFI. npm installs a copy of Bun alongside it if you do not already have one.

The browser needs a GUI pinentry

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.

Chromium browsers, not Firefox

The helper registers itself with Chrome, Brave, Edge, Vivaldi, Arc and Chromium. Firefox and Safari are not supported.

The extension reads; it does not write

It fills and copies. Creating, editing, renaming and deleting are the TUI's job, or pass's.

Lock is a privacy screen

⌃L makes passr forget everything it decrypted and stop decrypting. It does not lock gpg-agent, which may still hold your passphrase.

Bring your own store.

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-tui

Then 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 | bash

Then 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