npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

passport-bridge

v0.16.8

Published

Passport CLI for governed terminal and CI agents, plus the local stdio-to-cloud MCP bridge.

Readme

Passport CLI and bridge

This package installs two binaries that reach a Passport workspace through its governed gateway:

  • passport: CLI for terminal agents, scripts, and CI.
  • passport-bridge: stdio bridge for agents that launch a local MCP server, so the agent config holds no long-lived token.

Member profiles get the same tool access, agent selection, guardrails, approvals, and activity logging as native agents. Agent keys keep their pass, selection, guardrails, and audit trail but never raise a member approval prompt.

Install

npm install --global passport-bridge

Requires Node.js 22.23.2 or newer. npx passport-bridge@latest scan and npx passport-bridge@latest init need no install and run on Node.js 20 or newer; scan also needs no account. @latest keeps an older global install from running instead. The CLI makes no background registry request, telemetry, or self-update; check with npm view passport-bridge version and upgrade with npm install --global passport-bridge@latest.

Quick start

New here? One command sets everything up:

npx passport-bridge@latest init

It scans for keys your agents can reach, signs you in (or up), connects the apps it found, shows the environments Passport placed, adds Passport to your agents, turns on the guard, and sets Autopilot: how much agents can do on their own. See Set up with init.

Or step by step:

passport login
passport status
passport search "open issues"
passport describe github_search_issues
passport run github_search_issues '{"query":"SSO"}'
  • First time? Bare passport login opens /cli in your browser. Sign in with Google, GitHub, or an emailed code, then connect a workspace you belong to or create one. No workspace id needed.
  • Confirm that the code in the browser matches the one in your terminal. Nothing connects until you click Connect.
  • Re-signing in? Bare passport login reuses the workspace of the active profile, the sole CLI profile, or the one workspace all saved profiles share. With several workspaces saved it lists them and asks for --workspace.
  • --workspace ws_example signs in to one known workspace (the device flow). Self-hosted: add --cloud https://passport.example.com (origin only). A server without browser sign-in keeps asking for --workspace.
  • --no-open (or PASSPORT_NO_OPEN=1) prints the sign-in link instead of opening a browser. Use it over SSH and in headless sessions; open the link on any device.
  • Remote servers must use HTTPS. Plain HTTP works only on localhost, 127.0.0.0/8, or ::1.
  • Requires a gateway reporting serverInfo passport 0.5.0 or newer with the four-tool CLI protocol; older servers fail before any tool call.
  • passport help [command] documents every option. help, version, profiles, use, and completion work offline.

Commands

| Command | Purpose | | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | init [--yes] [--move-keys] [--no-open] [--cloud <origin>] [--skip-apps\|--skip-agents\|--skip-guard] [--verbose] [--json] | Set up Passport for this computer's agents in one pass. Every step asks first, can be skipped, and is safe to run again. | | login | Browser sign-in or sign-up (or device sign-in with --workspace); saves a rotating CLI credential and makes the profile active. | | logout | Revoke the credential and always remove the local profile (warns if revocation fails). | | profiles / use <name> | List stored profiles / select the active one. Offline. | | status | Workspace, member, agent, and app connection states, plus Autopilot presets, team floors, and how old this computer's copy of your rules is. | | doctor | Check Node, credential-file safety, HTTPS policy, /healthz, auth, serverInfo, and CLI protocol. Exit 1 on a failed check, with the full report. | | ready | Readiness preflight for automation; gate on its ready field and exit code. | | search [query] [--app <key>] | Search permitted tools. Empty query lists the full catalog. | | describe <tool> | One exact definition with its full parameter schema. | | run <tool> [json] | Run a tool through Passport governance. | | connect [app...] [--alias <name>] [--open] [--wait <s>] | Show member sign-in links and optionally wait for completion. | | resources [uri] [--out <file>] | List or read MCP resources. | | prompts [name] [--set k=v] [--out <file>] | List or get MCP prompts. | | scan [--path <dir>] [--verbose] [--check] | Find keys your agents can reach on this computer and what each can do. Local only, no account. --check scans files only (not the shell environment) and exits 5 on a high or medium key. | | discover | Scan local MCP configuration and submit a privacy-minimized fleet report. Exit 5 on a managed-policy violation. | | hook | Records the CLIs agents run and, with --guard, decides each one before it runs from your Autopilot rules (install, uninstall, status). | | wait <approval-id> [--timeout <s>] | Wait for a person to decide an approval request (default 100 seconds). | | rules test "<command>" [--environment <kind>] | What Autopilot decides for a command, why, and which environment it acts on. | | sessions [--status running\|idle\|ended] | Your agents' recent sessions with what Passport decided. | | undo <action-id>\|--last [--option <id>] [--yes] | Undo an action Passport took a backup before, within 7 days. See Undo. | | setup --org <pek_key> [--yes] [--agents <list>] [--lock [--strict]] | Set up every agent on this computer for your company without asking anything (MDM and scripts); --lock (root) locks the guard on. See Company rollout. | | setup | Install and maintain assigned team workflows as managed skills. | | session | Run NDJSON commands over one authenticated MCP session. | | access | Signed managed access: keygen, actions, run, invoke, status, cancel. | | completion <bash\|zsh\|fish> | Print a completion script. Offline. | | help [command], version | Help; package version and local profile context. |

Global options: --profile <name>, --json, --timeout <seconds> (default 30), --quiet, --caller <id>.

Tool arguments

Use exactly one form:

passport run github_search_issues '{"query":"SSO","limit":10}'
printf '%s' '{"query":"SSO"}' | passport run github_search_issues --args -
passport run github_search_issues --set query=SSO --set-json limit=10

--set values are strings; --set-json takes any JSON value. Both repeat.

Approvals and connections

passport run github_create_issue '{"title":"Customer report"}' --wait 90
passport connect github --wait 120
  • An approval-required call exits 4 with its approval ID. --wait <s> retries only after a definite approval_pending response.
  • The CLI never retries after a network failure or timeout, since the outcome is unknown.
  • connect with no app names uses one aggregate link when several apps need sign-in. Agent keys get guidance but cannot create sign-in links.

Resources and prompts

passport resources passport+github://welcome
passport resources passport+github://image --out image.png
passport prompts github_summarize --set note=welcome

Human mode prints text content. Binary content needs --out <file> or --json.

Key scan

npx passport-bridge@latest scan       # or: passport scan
passport scan --path ../api --verbose
passport scan --check                 # CI and pre-commit: files only, exit 5 on a high or medium key

Reads agent MCP configs (Claude Desktop, Claude Code, Cursor, VS Code, Codex), CLI sign-in files (GitHub CLI, Railway, Vercel, Wrangler, AWS, Stripe CLI, Supabase, npm), well-known shell variable names, and .env* files up to three folders deep in the project. It never prints or saves any part of a key, opens no network connection, needs no profile, and skips .env files in your home folder. Sign-ins held by the OS keychain are named but not flagged. passport-bridge scan and passport-bridge init run the passport CLI's own implementation.

Set up with init

npx passport-bridge@latest init              # or: passport init
npx passport-bridge@latest init --yes        # accept every default; needed without a terminal. Keys stay put
npx passport-bridge@latest init --yes --move-keys  # also move CLI keys into Passport and sign those CLIs out
npx passport-bridge@latest init --yes --json # one summary object with each step's status

Eight steps, each reusing the command that owns it:

  1. Scan: the same local scan as passport scan, as one summary line (--verbose prints the table).

  2. Sign in: reuses the active or only CLI profile, asks which one when there are several, or runs the browser sign-in from passport login.

  3. Apps: Railway, Vercel, Supabase, Cloudflare, GitHub, and Stripe, when the scan found a key or its CLI is on PATH. Missing apps are added through the same catalog rules as the web (an app your workspace reviews first says ! Stripe needs approval from your admin.), then one sign-in page covers the rest. In a terminal init waits until they connect (Enter stops waiting); without one it prints the page and Next: passport connect --wait 300 and moves on.

  4. Keys: for each CLI sign-in on this computer, the same question as passport keys move: Store your Railway account token in Passport and sign the Railway CLI out on this computer? [y/n]. It has no default: Enter asks again (Type y or n.). --yes never moves a key: the step prints Keys stayed on this computer. Run passport keys move to move them. Add --move-keys to move every key it finds unasked; the closing summary then says which (Moved your Railway key into Passport and signed that CLI out on this computer.) and which old keys to rotate (Rotate the old Railway key once your agents don't need it., only when a key moved in that run). When Passport already holds a different key for that CLI, an interactive run asks Replace the Railway key in Passport with the one on this computer? [y/n] (no default): yes replaces it and signs the CLI out, keeping links; no prints Kept the key in Passport. This computer's Railway CLI stays signed in. --yes never replaces a key. Quiet when there's nothing to move.

  5. Environments: the environments Passport has placed for your apps, each with its kind: Found 3 environments: production (Railway), preview (Vercel), qa (Railway). Anything Passport can't place counts as Production; change it any time in Rules. With no app connected yet it says Environments appear once an app is connected.

  6. Agents: Passport's one MCP URL with this workspace as its hint (https://passportmcp.com/mcp?ws=<workspace>), with each agent's own browser sign-in; a server that doesn't serve /mcp yet (read from its OAuth protected-resource metadata) gets the workspace's own URL (https://<host>/w/<workspace>/mcp). An existing entry at either URL for this workspace counts as set up, so no token lands in a config file. Claude Code and Codex through their own CLIs (claude mcp add --transport http --scope user, codex mcp add --url), Cursor through ~/.cursor/mcp.json, VS Code through code --add-mcp or its user mcp.json. Claude Desktop connects through Settings → Connectors, so init prints that step.

  7. Guard: asks Apply your Autopilot rules before each shell command in <agents>? [Y/n], then runs passport hook install --guard for Claude Code, Codex, and Cursor (Codex: blocks only), pointing at the launcher $PASSPORT_HOME/cli/bin/passport (passport.cmd on Windows), which runs a stable copy of the CLI in $PASSPORT_HOME/cli/<version>/ (never npm's cache), or at a global passport-bridge of the same version or newer. An upgrade rewrites the launcher and the cli/current link in one rename each, so hooks follow without being edited; the launcher runs the recorded node, or the node on PATH once that one is removed. A hook from an older init that names a version folder is replaced where it stands, next to your other hooks. Once the new copy is in place, older copies are deleted unless a hook still runs them.

  8. Autopilot: how much agents can do on their own, per environment:

    Autopilot How much can agents do on their own?
              Production   Careful     reads run, changes ask
              Staging      Balanced    reads and changes run, deletes ask
              Development  Hands-off   everything except irreversible runs
              Keep these? [Y/n]

    n chooses per environment; --yes keeps the defaults. A team floor stricter than a default is shown with why (Your team requires Careful or stricter in Staging.), and choices below it aren't offered. A CLI credential can only make presets stricter; a looser pick says so in one line and keeps the current ones. A re-run shows the presets (already set up) without asking. The closing summary says what will ask: Autopilot on. You'll be asked only for Production changes, deletes outside Development, and irreversible actions. A server without Autopilot gets the earlier Safety step instead.

What changes on disk, and how to undo it:

| Where | What | Undo | | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | ------------------------------------------------- | | ~/.passport/credentials.json | A cli@<workspace> profile, as passport login writes | passport logout | | ~/.passport/cli/ | A copy of this CLI, the bin/passport launchers hooks run, and a current link | Delete the folder (after removing the hooks) | | ~/.claude.json | A passport MCP server entry, via claude mcp add | claude mcp remove --scope user passport | | ~/.codex/config.toml | [mcp_servers.passport], via codex mcp add | codex mcp remove passport | | ~/.cursor/mcp.json | mcpServers.passport | Delete that entry | | VS Code user mcp.json | servers.passport | MCP: List Servers, then remove it | | ~/.claude/settings.json, ~/.codex/hooks.json, ~/.cursor/hooks.json | Passport's audit and guard hooks | passport hook uninstall --agent <agent> --apply |

Before editing a file it didn't create, init says which file and keeps one copy of the original as <file>.passport-backup. It changes only Passport's own entries, writes atomically, refuses a malformed file with one line, and leaves an existing Passport entry alone unless it points at npm's cache or a file that's gone. Ctrl-C at any point stops cleanly; run init again to pick up where it left off. A question only takes keys typed after it appears, so an Enter pressed during a pause never answers it. Any wait longer than a moment shows what it's for in the step's row (Sign in Checking your sign-in…); off a terminal nothing extra prints. If Passport can't be reached, init says Couldn't reach Passport at <origin>. Check your connection, then run passport init again. and exits 1. Without a terminal, init needs --yes (exit 6 otherwise), and --json needs --yes. It never edits your shell profile: it prints the one PATH line to add if you want passport everywhere.

Company rollout

npx passport-bridge@latest setup --org pek_example --yes          # Jamf, Kandji, Intune, or any script
npx passport-bridge@latest setup --org pek_example --agents claude-code,codex

init for MDM: it never asks anything and never signs in. It installs the stable copy of the CLI, adds Passport to every detected agent (Claude Code, Cursor, Codex, VS Code; Claude Desktop gets the connector step) at the one URL with the enrollment key as its hint (https://passportmcp.com/mcp?enroll=pek_…), turns on the guard and audit hooks for Claude Code, Codex, and Cursor, and records the key in $PASSPORT_HOME/enrollment.json (mode 600). It prints one line per agent:

Passport setup for passportmcp.com (enrollment pek_…mple)
Claude Code   ✓ Passport added · guard on
Cursor        ✓ Passport already set up · guard on
Codex         ✓ Passport added · sign in: codex mcp login passport · guard on
Each agent asks you to sign in to Passport the first time it connects.

The first line names the workspace the key pre-selects (GET /api/auth/enrollment/<key>, public, no credential). It exits 0 even with no agents (No supported agents found. Nothing to set up.), so an MDM run never fails a computer without them. A malformed key or --agents list (--clients still works) exits 6, and so does a key the server doesn't know or has revoked, with the server's one line (This enrollment key isn't valid. or This enrollment key was revoked. Ask your admin for a new one.) before anything changes. passport login on that computer later passes the recorded key: as the browser sign-in hint (POST /api/auth/cli/start { enroll }) and for rollout attribution when the CLI credential is minted (enrollmentKey). A server that doesn't serve the one URL yet still gets the CLI, hooks, and key, and the agents wait for passport init. Run it again any time: it changes nothing that's in place and repairs what drifted (a removed hook, an entry pointing at npm's cache). The enrollment key is a workspace hint, not a credential: people still sign in as themselves. --cloud <origin> (or PASSPORT_CLOUD) points it at a self-hosted Passport. --json prints {version,origin,universal,url?,workspace?:{id,name},enrollment,clients:[{agent,name,mcp,guard,message?,next?}]}.

sudo npx passport-bridge@latest setup --org pek_example --yes --lock            # lock the guard on
sudo npx passport-bridge@latest setup --org pek_example --yes --lock --strict   # and allow only managed hooks

--lock, run as root, puts the guard and audit hooks in each agent's managed settings instead of the per-person steps (as root those would write into root's home), so people can't turn them off (src/cliSetupLock.ts, src/lockedHooks.ts, a checked copy of shared/lockedHooks.ts). It writes a root-owned launcher (/Library/Application Support/Passport/bin/passport on macOS, /opt/passport/bin/passport on Linux, 0755) that sets PASSPORT_HOOK_VIA=managed and runs the person's own $PASSPORT_HOME/cli/bin/passport; without one it lets the command run, or with --strict blocks it with Passport's guard isn't set up for you yet. Run: npx passport-bridge@latest init. Then per agent: Claude Code gets the drop-in managed-settings.d/50-passport.json; Codex's /etc/codex/requirements.toml gets Passport's [features]/[hooks] lines between # >>> passport and # <<< passport; Cursor's system hooks.json gets Passport's two entries beside everyone else's. --strict adds allowManagedHooksOnly and onFailure: "block" (Claude Code), allow_managed_hooks_only = true (Codex), and failClosed (Cursor). A file it can't merge safely (someone else's drop-in or [hooks], hooks = false, disableAllHooks, invalid JSON) is left untouched with the fix. Atomic writes, files 0644. Without root, or on Windows, it writes nothing and prints Locking needs administrator rights. Run it with sudo, or deploy these through MDM: with each path. --clients narrows it; --strict without --lock exits 6. It exits 0 and prints one line per agent (Claude Code Locked · <path>, Already locked, or ! Not locked: <reason>); --json prints {version,lock:{strict,shim,agents:[{agent,state,path,message?}]}} with state one of locked, already_locked, conflict, needs_admin, failed. A hook from the person's own settings stands down when managed settings already run Passport's guard (src/managedHooks.ts). PASSPORT_MANAGED_ROOT puts every managed path under a folder (tests). Vendor docs: code.claude.com/docs/en/settings, code.claude.com/docs/en/hooks, learn.chatgpt.com/docs/enterprise/managed-configuration, cursor.com/docs/agent/hooks.

Team workflows

Codex and Claude Code on macOS and Linux. Requires a member profile.

passport setup list
passport setup install <setup-id> <workflow-key> --agent codex          # preview
passport setup install <setup-id> <workflow-key> --agent codex --apply
passport setup installed
passport setup rollback <skill-name> --apply
passport setup remove <skill-name> --apply

Changes preview unless --apply is passed. Personal edits are preserved. installed, remove, and rollback work offline. Installing a workflow never grants app permissions or provisions accounts.

Shell hook

passport hook install --agent claude-code --guard --apply     # or codex, cursor
passport hook status
passport hook uninstall --agent claude-code --guard --apply   # guard only

Two hooks per agent. passport hook runs after each shell command and records the vendor, a coarse verb, a risk class, and (with Autopilot) the environment name the flags gave and the guard's decision in Activity; the server works out the class and cause. passport hook guard runs before it and decides from the member's Autopilot rules (src/cliHookGuard.ts):

  • Allow (Claude Code permissionDecision: "allow", Cursor permission: "allow") only when every part of the line is understood and allowed: vendor commands in src/cliHookVendors.ts read with confidence, and everyday commands (below). Substitutions, heredocs, variables in a path, unknown wrappers or environment prefixes, scripts, aliases, make, and programs Passport doesn't know mean no decision, so the agent's own permission rules apply. Codex can't receive allow; its own approvals apply (see passport sync).
  • Ask or deny when any recognized command needs it. The strictest part of a compound command wins and names itself. --help and --dry-run never ask.
  • Each reason names its cause in one line: Passport: railway redeploy changes Production. Approve to run it., or Blocked by Passport: Team rule, no deletes in Production. Ask an admin to run it, or request an exception in Passport.

Speed. Hooks must never slow agents down. The launcher $PASSPORT_HOME/cli/bin/passport (written by src/initInstall.ts) answers plain reads and local dev commands in POSIX sh without starting Node (src/launcherFastPath.ts): ls, cat, rg, git status, cargo check, and the rest of a fixed list, joined only by single pipes, with relative paths that stay in the folder and pass through no symlink or hidden file. It answers only while cli-policy/fastpath.ok is armed: a full guard run arms it for five minutes when the cached rules give those commands exactly the launcher's answer (src/hookFastPath.ts), and a policy or profile change removes it. Each answer appends one line (agent, session id, program names, never an argument) to cli-policy/fastpath.spool, which the next full run counts. Anything else, or any doubt, starts Node with the same input, through dist/hookMain.js, which loads only the guard. src/launcherFastPath.test.ts holds the launcher to the full guard's exact answer across both corpora and generated variants. PASSPORT_HOOK_FAST=0 turns it off. The after-hook and the guard's own report never wait on the network: they queue to $PASSPORT_HOME/hook-outbox/ and a detached passport hook flush sends them within the budget (src/hookOutbox.ts). Each guard run records its timings (numbers only, src/cliSpeed.ts), sent every five minutes with the answered counts. npm run bench:guard times it all on the compiled CLI.

Script reading (src/cliScriptRead.ts, src/cliScriptText.ts). For the project's own code, ./x.sh, bash x.sh, source x.sh, make <targets> (prerequisites first, variables resolved once), and npm|pnpm|yarn run <name> or npm test (with pre/post scripts), the guard reads the file and classifies every line like a typed command; the strictest finding wins and says where it came from (railway up changes Production (from --environment, in deploy.sh); Sessions shows · from deploy.sh). A line it can't follow makes that part Partly checked: no allow, reported as unchecked, with a hash (opaqueIds) instead of its name, keyed with 32 random bytes made on the computer and kept in $PASSPORT_HOME/opaque-salt (0600), never sent; suggestion counts are therefore per computer. Limits: 256 KB a file, 20 files and 3 levels a command, 30 ms a call. Results are cached in $PASSPORT_HOME/scan-cache.json (0600) by path, mtime, and size, then SHA-256; findings only. The after-hook reads it again and reports drift when it changed in between. Package scripts no longer take the launcher's fast path, since Node reads them. With a warm cache, a 60-line script adds a few milliseconds; bench:guard --check fails over 20 ms.

Everyday commands (src/cliHookLocal.ts, tables in src/localCommands.ts, localTools.ts, localFiles.ts, localGit.ts, localNetwork.ts). Local commands are placed by what they touch on this computer. The project folder (the git root, else the agent's folder; never / or the home folder) is Development: reads such as ls, rg, git status, test runners, linters, builds, and type checks are Reads; git commit, formatters with --write, package scripts, and mv/cp are Changes; rm, git clean -fd, and git reset --hard are Deletes. Package installs (npm install, pip install, brew install) are Changes in Other apps. git push to the default branch (origin's HEAD, else main/master) is a Change in Production, to any other branch a Change in Development; deleting a remote branch (--delete, :branch) is a Delete in Other apps because teammates share it. Temporary folders (/tmp, $TMPDIR) count as Development; deleting one whole is Irreversible. Outside the project: deleting the home folder, /, system paths, or the whole project is Irreversible; other writes are Changes and deletes are Deletes in Other apps; writes to keys, shell startup files, or an agent's own settings are Deletes, and reads of them are no decision. curl/wget GETs are Reads and anything that sends data is a Change, in Other apps (Development for localhost). docker ps/logs read, run/build/compose up change, rm/prune delete. sudo never runs silently. The project's own code run directly (node scripts/check.mjs, npx tsx scripts/x.ts, bash scripts/ci.sh, ./scripts/ci.sh, make test) is a Change in Development, like a package script; Passport can't read inside it, so it is never allowed and never recorded as opaque. tsx --test and ./node_modules/.bin/<tool> are their tools, bash -lc '…' and sh -c '…' are read as the line inside, and Codex's apply_patch <<'PATCH' is a change to the files the patch names. A checkout an agent keeps for itself (~/.codex/worktrees/…, ~/.cursor/worktrees/…) is the project, not the agent's settings. Symlinks are resolved before deciding, cd is followed through &&, ;, and subshells, and redirects are writes where they land. The server keeps a generated copy of the tables (node scripts/generate-local-commands.mjs) for rules test on the web. Allowed project work and reads are counted on the machine ("Answered for you") rather than recorded one by one; installs, pushes, and anything that asks or blocks are recorded like vendor commands, with the environment kind and never a path.

The environment comes from each vendor's own flags (src/cliHookEnvironment.ts): Railway -e/--environment, Wrangler and Sentry -e/--env, Vercel --prod/--target (a deploy without either is a preview), Supabase --local/--linked/--project-ref (db reset is local unless --linked), Stripe --live (test mode otherwise), git push <remote> <branch>, psql hosts (localhost is Development), fly and Heroku -a/--app, kubectl --context/-n, helm --kube-context/-n, AWS --profile, gcloud --project, plus any flags the server adds. Names follow Passport's rules (prod|production|live|main → Production, stag|qa|uat|preview → Staging, dev|local|test|sandbox|feature|branch → Development), and the workspace's own placement of a name wins. Anything else, or no flag at all (a linked project the hook can't see), counts as Production, and the reason says Environment unknown, treated as Production.

Attended or unattended (src/cliHookMode.ts). With a person at the keyboard, an ask is the agent's own prompt with Passport's reason. With nobody there, the guard asks Passport instead (POST /api/my/cli/decide, inside the hook's budget): the server knows grants already given this session, every rule, and the workspace's environments. An allow from it runs the command (for example, a retry after approval); an ask becomes an approval request on the web and the person's phone, and the agent reads Waiting for approval from Ramish. Run `passport wait <id>` to wait, or continue with other work. If Passport can't answer in time, the local decision stands and the reason adds Passport couldn't be reached, so this computer decided. Signals, strongest first:

| Signal | Means | | ------------------------------------------------------------------------------------------------- | --------------------------------------- | | PASSPORT_HOOK_MODE=attended\|unattended | Override | | Claude Code CLAUDE_CODE_SESSION_ATTENDED (1/0), passed to every hook | Attended / unattended | | Hook input permission_mode dontAsk or bypassPermissions (Claude Code, Codex) | Unattended: prompts are suppressed | | Cursor is_background_agent: true | Unattended: background and cloud agents | | CLAUDE_CODE_ENTRYPOINT sdk-cli (claude -p), sdk-ts, sdk-py, claude-code-github-action | Unattended (older Claude Code builds) | | CI, GITHUB_ACTIONS, GITLAB_CI, BUILDKITE, CIRCLECI, JENKINS_URL, TF_BUILD | Unattended |

Codex has no field for codex exec; its prompt-free modes count as unattended, where an ask blocks for now with the waiting line.

The approval request carries a command summary with secrets removed on this computer (src/cliHookRedact.ts, using passport scan's key formats plus secret-named flags, variables, JSON fields, URL credentials, and long opaque values; heredoc bodies are dropped). It is the only part of a command line that ever leaves the machine, and only approvers see it.

The guard decides from local data inside HOOK_BUDGET_MS (1.5 s) and prints first; only an unattended ask waits for the server, within the same budget. After answering it never waits on the network: its report, the batches, and, at most every ten minutes, a refresh of a policy cache older than an hour go through the detached passport hook flush. Any failure is silent and fails open. Neither hook sends the raw command line, output, folder path, or environment. Both hooks exchange the profile's refresh credential without rotating it and keep the short-lived access token in $PASSPORT_HOME/.<profile>.access.json (src/accessTokenCache.ts, mode 600, atomic replace), so most runs send one request instead of an exchange and then the request. A cached token is used only with more than a minute left; a 401 on it drops it, exchanges once, and retries once.

An ask becomes one Activity row. In Claude Code, with the audit hook registered, the guard parks its report in $PASSPORT_HOME/hook-pending/ (src/cliHookPending.ts, mode 600, claimed by atomic rename). The audit hook for the same tool-use id sends the run with decision: "ask", ran: true ("asked first, then ran"). An ask nobody claims within 30 minutes (no agent fires a hook for a declined prompt) is sent by the next hook run as ran: false ("asked first, not run"). An allow the server gave is parked the same way, so the run's row says Approved earlier this session. Cursor has no tool-use id to match, so its asks are sent at once as their own "asked first" row. Denies are always sent at once; asks and blocks the server decided are recorded by the server.

  • Claude Code: PreToolUse, plus PostToolUse and PostToolUseFailure (failed or interrupted calls, recorded as ran with a non-zero exit), in ~/.claude/settings.json (CLAUDE_CONFIG_DIR honored).
  • Codex: the same events in ~/.codex/hooks.json (CODEX_HOME honored). Codex sends every shell command as tool_name: "Bash" with the model's raw command string in tool_input.command (never shell-wrapped), so it is read exactly like Claude Code's; its apply_patch tool is a file edit, not a shell command, and isn't recorded. Codex supports deny only (an ask fails the hook open, and its allow rewrites commands), so an ask is a deny there that says why (Codex can't ask from a hook, so Passport blocked it. Approve it in Passport or run it yourself.), recorded as a block. With a member profile and Autopilot, it goes to Passport like an unattended ask: an approval request, then the waiting line. Trust the hooks in Codex's /hooks.
  • Cursor: beforeShellExecution/afterShellExecution in ~/.cursor/hooks.json. Cursor also imports Claude Code's hooks by default; that imported copy records nothing when Passport's own Cursor entry exists, so rows aren't doubled.

Guard health (src/cliGuardHealth.ts, states and fixes in src/cliGuardState.ts, a checked copy of shared/guardContract.ts). passport hook status and passport status show each agent's guard in the web's words, On, Off, Outdated, Not trusted, or Locked, and each problem's fix: passport hook install --client <agent> --guard --apply, npx passport-bridge@latest init, or Open Codex → /hooks and trust Passport's hooks. At most once a day ($PASSPORT_HOME/guard/.<profile>.heartbeat) the CLI sends POST /api/my/guard-health with {cliVersion, launcherTarget?, clients: [{client, present, mode, guard, plugin, codexTrust?, locked?, disabledEnv?}]}: flags only, never a command, path, or value. It goes from a guard run with at least 300 ms of its budget left (a hook's first one waits a day), from passport exec, and from passport status. Codex trust (src/cliCodexTrust.ts) compares trusted_hash in ~/.codex/config.toml [hooks.state."<hooks.json>:pre_tool_use:<group>:<handler>"] with Codex's own hash of Passport's guard entry: SHA-256 of the sorted-key JSON of {event_name, matcher?, hooks: [{type, command, timeout (600 when unset), async, statusMessage?, additionalContextLimit? (when not 2500)}]}, as in openai/codex hook_hash/version_for_toml, checked against codex-cli 0.146.0's hooks/list. Missing is Not trusted, a different hash is Not trusted (modified), enabled = false is Off. Locked means managed settings hold Passport's guard: /Library/Application Support/ClaudeCode/managed-settings.json (and managed-settings.d/), /etc/claude-code/…, /etc/codex/requirements.toml, /Library/Application Support/Cursor/hooks.json, or /etc/cursor/hooks.json (PASSPORT_MANAGED_ROOT points tests elsewhere).

Coverage (src/cliSeen.ts). The guard counts every command it saw, per agent per day, one character per command in $PASSPORT_HOME/seen/<profile>.<agent>.<day>.log (an app's action, not checked, or neither), and Cursor's imported copy of Claude Code's hook doesn't count twice. Counts go in their own batches, at most every five minutes, as seen: {batch, day, count, governed, opaque} on POST /api/my/cli-activity, never inside a report that can expire. passport status prints Checked: 94% of commands Passport saw this week.

When Passport can't be reached. When the guard needs Passport (an unattended ask, Codex, or a Production action someone else approves) and it doesn't answer within the budget, the environment's offline setting from cli-policy v2 decides: ask, block, or allow (Production asks, the rest run, by default). Allow never covers an irreversible action, a second-person action is always blocked, Codex's ask is a block, and a policy synced more than 7 days ago uses the defaults. The reason ends with Passport couldn't be reached, so this computer decided.

src/cliPolicy.ts caches the member's CLI policy (GET /api/my/cli-policy) at $PASSPORT_HOME/cli-policy/<profile>.json (mode 600). Servers with Autopilot send version 2 (src/cliPolicyV2.ts: presets, floors, CLI rules, irreversible and delete verbs, environment flags, placed environments); older servers send version 1, and then the guard keeps its earlier shape: ask or deny before destructive verbs, never allow, no server call, and reports without the Autopilot fields. passport login and passport status refresh it and passport logout removes it. A 404 or error keeps the previous copy; with no copy the guard asks before every destructive verb. PASSPORT_HOOK_DISABLED=1 turns both hooks off.

The Passport plugin (rsyed94/passport-plugins) runs the same two hooks from the agent's plugin folder, with PASSPORT_HOOK_VIA=plugin. passport hook status shows those agents as via plugin, and hook install, init, and setup --org skip them (Claude Code: already covered by the Passport plugin.) so nothing is recorded twice. The plugin pins the CLI version in src/pluginPin.ts.

Autopilot from the terminal

passport rules test "railway redeploy -e staging"
passport rules test "rm -rf ~/"
passport wait apr_123
passport sessions
passport sync            # or: passport sync --remove
passport env             # or: passport env set railway qa staging
passport actions add scripts/deploy.py --environment production --class change
passport actions sync    # propose this repository's .passport/actions.toml
  • env lists where commands in this folder land, read the way each vendor's own CLI reads it (src/envResolve.ts; order: flag, the folder's link, the vendor's default, the workspace's saved mapping, the whole-word naming rule, else Production). Link readers: src/envLinksHosting.ts (Railway ~/.railway/config.json, Vercel .vercel/project.json / repo.json, Supabase supabase/.temp/project-ref, Wrangler config, Netlify .netlify/state.json, Firebase .firebaserc + configstore, Convex CONVEX_DEPLOYMENT, Neon .neon), src/envLinksCloud.ts (Fly, Heroku git remotes, kubeconfig, AWS profile, gcloud configuration, Terraform workspace, Stripe key mode), and src/envDatabase.ts (database URLs: only DATABASE_URL, DIRECT_URL, POSTGRES_URL*, DATABASE_URL_UNPOOLED, reduced to a host-derived key; the URL, user, and password never leave the function). Files are read fresh for every command, size-capped, and a link file symlinked out of the project is ignored (src/envLinkFs.ts). For a placed id its mapping decides, and a different name in the link file is treated as a rename (sent as the new label, which the server stores). A name in a link file never loosens anything: an unplaced id is named only by a label the server confirmed through the connected app (resources[].confirmed), which the naming rule then reads (source: "name", shown as Staging (from its name)); without one, or when it says nothing, it is assumed Production. The table keeps its columns when every row fits the terminal and stacks each row otherwise. Findings to a server that sends resources in cli-policy v2 carry {kind, source, resourceKey?, label?, platform?, flag?, via}. env set calls PUT /api/my/environments/:platform/:key; members may only say Production.

  • rules test classifies the command here and sends only its app and verb (and the environment kind) to POST /api/my/rules/test, never the command line. An everyday command goes as a finding ({finding: {vendor, verb, risk, environment}}), because only this computer can place its paths. It prints the decision, its cause, the environment, and the class. Without a server answer it decides from this computer's copy and says how old it is. --environment picks one when the flags don't say.

  • wait long-polls GET /api/my/approvals/<id>/wait. Approved: Approved by Ramish. Run the command again. (exit 0). Declined: the note for the agent, Declined by Ramish: <note> (exit 5, cause approval_denied). Expired: Expired. The agent can ask again. (exit 5, cause approval_expired). Still waiting at --timeout (default 100 seconds, under an agent's 2-minute command limit): exit 4 with the same command as next.

  • sessions lists recent sessions (GET /api/my/agent-sessions): status, agent, folder, start, 42 ran · 2 asked · 1 blocked, environments touched, and Waiting for you when approvals are pending.

  • sync writes the allows Passport is sure of into each agent's own permission settings, so those prompts disappear offline and in Codex (src/nativeSync.ts). Only reads, test runners, builds and type checks, linters that only check, git status, git add, and git commit, checked against the person's rules. Formatters, fixing linters, mv/cp, and package scripts stay with the hook, which sees what they touch; deletes, installs, pushes, and Production never sync. Claude Code: Bash(npm test *) in permissions.allow of ~/.claude/settings.json. Codex: prefix_rule(pattern = ["npm", "test"], decision = "allow") in ~/.codex/rules/passport.rules, a file Passport owns. Cursor CLI: Shell(npm:test*) in permissions.allow of ~/.cursor/cli-config.json. Cursor: npm test in terminalAllowlist of ~/.cursor/permissions.json, only when that list already exists there (defining it replaces Cursor's in-app list). Entries Passport added are listed in $PASSPORT_HOME/native-sync.json; an entry you had before is never claimed or removed, and a file Passport can't parse exactly (JSONC) is left alone. Sync runs on init, setup --org, every rules refresh, and passport sync; hook uninstall, sync --remove, and signing out of the last profile take the entries out.

  • status adds each agent's guard (Guard: Claude Code On · Codex Not trusted), the coverage line, and each fix, and sends the daily guard report when due. It also adds today's speed line, Passport added 0.3 s and asked you twice today. 412 actions ran on their own. (across your computers when the server keeps speed numbers, else (this computer)), and Answered for you: N prompts today (allows the hook gave in Claude Code and Cursor, and Codex commands Passport synced, counted only when the agent would have prompted) and which agents' allowlists are synced. Under the presets, one line per exception that lets something run your presets would ask about, in the web's words: ! 1 exception: deletes run in Production without asking, even without a backup. Counts are appended locally ($PASSPORT_HOME/answered/) and sent in batches at most every five minutes as answered: {batch, count} on POST /api/my/cli-activity.

  • actions tells Passport what a project script does (src/cliActions.ts): actions add <script> [args…] --environment <kind> --class <class> sends the path in the repository (or make, npm, pnpm, yarn with the target), an argument glob, the repository's remote fingerprint and name, and the SHA-256 of what it runs, never its contents, to POST /api/my/project-actions. --id <op_…> resolves a Session row's Tell Passport what this does from this computer's scan cache. actions sync proposes each entry of .passport/actions.toml. A change, delete, or irreversible in Production applies at once; anything looser waits for an admin. The guard then decides a matching run as vendor project with that class and environment; it never makes a line allowed on its own, and a changed script stops matching (projectChanged) until it's set again. .passport/actions.toml entries apply locally only when they ask or block.

  • rules test, sessions, actions, and sync need a member profile. wait also works for a cloud agent's key, on the approval its own passport exec opened.

Keys and passport exec

passport keys move            # confirm each key [y/n], store it in Passport, sign that CLI out here
passport keys                 # what Passport holds, and what's still on this computer
passport keys add railway     # store a key you paste (stdin, never echoed)
passport exec -- railway up -e staging
  • After a move, keys move remembers where each removed key was in $PASSPORT_HOME/keys-moved.json (vendor, ~/… location, file, field names, size, modified time; never a value or hash). With the daily guard report, files that changed are read again; a key that's back is reported as reappeared: [vendor] on POST /api/my/keys/report, so the key reads "Back on " in Apps with the fix passport keys move. Kept keys are never listed. The report's computer is the CLI's own sign-in (its Device); the name it sends stays for older servers.
  • keys move finds CLI sign-ins (Railway, GitHub CLI, Vercel, Wrangler API tokens, Supabase, Stripe test keys, the AWS default profile) and tokens in Claude Code's settings env. For each it asks [y/n] with no default (an empty answer asks again; --yes moves them all), stores the key in Passport (POST /api/my/keys), then signs that CLI out by removing only the credential: the token fields of ~/.railway/config.json (project links stay), token in Vercel's auth.json (not revoked; the current team stays), Wrangler's sign-in keys in default.toml, the Supabase access-token file and its keychain token (saved database passwords stay), the Stripe profile's API keys and its live key in the keychain (account, color, and other profiles stay), the AWS profile's key lines, or the one settings entry. Files are written atomically with their mode kept. Vendor sign-outs don't run because they do more (railway logout unlinks every project, vercel logout revokes the token Passport just stored); only the GitHub CLI's gh auth logout --hostname github.com runs, adding --user <active> when several accounts are signed in, so other hosts and accounts stay. Keychain items are deleted by exact service and account (security delete-generic-password on macOS, secret-tool clear on Linux); where that isn't possible the row says which keychain copy may stay. Wrangler OAuth tokens are removed locally, not revoked. A sign-out that fails leaves the key in Passport and says what's left on disk. .env files are never touched: "Used by your app, so Passport leaves it." Browser-session sign-ins (Railway, Vercel, Wrangler OAuth) can't move; the line says how to make a token instead. A different key than the one already in Passport is replaced only when the person answers yes to Replace the <CLI> key in Passport with the one on this computer? [y/n]; the CLI then names the key it replaces (replace: <keyId> on POST /api/my/keys). The server alerts the owner and admins with an Undo, records it in the audit trail, and keeps the old value encrypted for seven days (POST /api/my/keys/:id/restore from the browser, or the alert). Without a person (--yes, no terminal), both keys stay. The scan report sent afterwards carries provider counts only.
  • exec -- <cli> … runs railway, gh, vercel, wrangler, supabase, stripe, aws, neonctl, or flyctl from PATH. It classifies the command here, asks POST /api/my/exec/authorize, and on allow sets only that vendor's variables (GH_TOKEN, RAILWAY_API_TOKEN, VERCEL_TOKEN, CLOUDFLARE_API_TOKEN, SUPABASE_ACCESS_TOKEN, STRIPE_API_KEY, AWS_*, NEON_API_KEY, FLY_API_TOKEN) in the child's environment, removing Passport's own and any stray vendor tokens. Output and exit code pass through. Afterwards it reports the exit code and duration (POST /api/my/exec/result, best effort, two seconds at most) so the session row reads Ran or Failed. It refuses paths, shells, env, and subcommands that would print the key, sign in again, mint longer-lived tokens, or run another program with the key (railway run, gh auth, gh extensions, aws configure, vercel dev, fly tokens).
  • Asks wait for your approval in Passport (web or push) when you're at the keyboard or with --wait <sec>; otherwise exit 4 with the approval id: run passport wait <id>, then the command again. An approval is used once. Blocked or declined: exit 5. No key, or it stopped working: exit 3 ("Your Railway key in Passport stopped working. Update it in Apps."). Offline: exit 1, nothing runs, and exec never uses a key on this computer instead. A team that requires the guard refuses exec for an agent whose computer hasn't reported its guard On in 48 hours: exit 5 with Blocked by Passport: guard required… and the fix.
  • In Claude Code, the guard rewrites a plain vendor command whose key is in Passport to passport exec (PreToolUse updatedInput with allow; a block stays a deny). When the rules ask, or someone is at the keyboard, the rewrite adds --wait 120, so the agent holds while the person approves: "Approve it on your phone or in Passport." Codex and Cursor get no rewrite: use passport exec there.
  • Cloud agents (Agents → Cloud agents) run exec and MCP with PASSPORT_AGENT_KEY and PASSPORT_ENDPOINT: the owner's rules decide, the owner's keys are used, and approvals go to the owner.
  • Backups: for gh issue close, gh pr close, gh api -X DELETE of a branch, vercel env rm, vercel rollback, and vercel promote, exec also sends the ids Passport needs to take a backup first (repo and number or branch; the linked Vercel project and variable name from .vercel/project.json), never a value. After the run it prints Backup taken. Undo within 7 days: passport undo <action-id>, or No backup: <reason> for a delete it couldn't back up.

Undo

passport undo act_123
passport undo --last --yes
  • Shows what comes back and what won't, how old the backup is, and how long undo stays available, then asks Undo it? [y/N] (and which option, when there are several). Without a terminal it prints the preview and passport undo <id> --yes; --option <id> picks an option.
  • Passport decides an undo from the CLI like a change in that environment. Asks first: exit 4 with the approval id; run passport wait <id>, then the undo again. Blocked or declined: exit 5. Done: exit 0, with each step (✓/✗). Partly done or failed: exit 1. Expired, already undone, or otherwise unavailable: exit 5 with the reason. No backup: exit 6. Needs a member profile.

Persistent sessions

printf '%s\n' \
  '{"id":"find","argv":["search","open issues"]}' \
  '{"id":"call","argv":["run","github_search_issues","{\"query\":\"SSO\"}"]}' \
  | passport session --profile cli@ws_example
  • One {"id"?:string|number,"argv":string[]} request per line, run in order; one JSON response per line with ok, exitCode, and result or error fields. id is echoed.
  • A failed request does not close the session. EOF, SIGINT, or SIGTERM closes it cleanly.
  • Limits: 1 MiB per line, 128 arguments. Set --profile, --caller, and --timeout on passport session, not per request.
  • Allowed commands: status, search, describe, run, connect, resources, prompts, discover.
  • Governance is unchanged; access tokens keep normal expiry and refresh.

Shell completion

passport completion bash > ~/.local/share/bash-completion/completions/passport
passport completion zsh > ~/.zfunc/_passport      # ~/.zfunc must be in fpath before compinit
passport completion fish > ~/.config/fish/completions/passport.fish

Profiles

Credentials live in ~/.passport/credentials.json (mode 0600, atomic updates); PASSPORT_HOME changes the directory. The store is shared with passport-bridge, and refreshes are coordinated across processes. The hooks cache each profile's current access token next to it in .<profile>.access.json (mode 0600): the token, its expiry, the endpoint, and a sha256 fingerprint of the refresh credential (never the credential itself), so a re-login, rotation, or endpoint change can't reuse it. Logging out, signing in again, or rotating a profile deletes its cached token.

Resolution order:

  1. --profile <name>
  2. PASSPORT_PROFILE
  3. Active profile (passport use <name>)
  4. Exactly one CLI-owned profile, which becomes active
  5. The sole stored profile

Otherwise Passport refuses to guess: run passport profiles, then passport use <name>. Names are 1-80 letters, numbers, ., _, @, or -, starting with a letter or number.

A distinct agent identity for policy, selection, devices, and activity, while keeping the CLI credential surface:

passport login --workspace ws_example --agent claude-code --profile claude-code@ws_example

Caller attribution

--caller <id> (1-24 lowercase letters, numbers, hyphens) adds activity detail for the agent behind a call. Without it, the CLI detects Claude Code, Codex, OpenCode, Cursor, or Gemini CLI from environment markers. Attribution is self-reported, not attested, and is accepted only from CLI-surface credentials or CLI agent-key requests.

Headless and CI

export PASSPORT_ENDPOINT=https://passport.example.com/mcp
export PASSPORT_AGENT_KEY="$CI_PASSPORT_AGENT_KEY"
passport ready --json
passport search "deployment status" --json

PASSPORT_ENDPOINT is Passport's one URL (https://<host>/mcp, where the agent key's own prefix says which workspace) or a workspace's own (https://<host>/w/<workspace>/mcp). Set both variables together. Environment auth overrides stored profiles, persists nothing, and cannot be combined with --profile or login/logout. Agent keys skip member approval prompts; use a member profile for work that must pause for a human.

Managed access

Workload credentials that sign private_key_jwt assertions. Requires PostgreSQL and Enterprise workspace controls, with connections and grants set up in Agents & access. Creating new delegations is retired; existing delegation IDs still work.

passport access keygen --algorithm ES256    # or RS256; never overwrites; private PEM is owner-only

Register only the public JWK in Agents & access, then configure the workload:

export PASSPORT_ENDPOINT=https://passport.example.com
export PASSPORT_CLIENT_ID=ws_example~awc_example
export PASSPORT_PRIVATE_KEY_FILE=/run/secrets/passport-access.pem
export PASSPORT_WORKSPACE_ID=ws_example     # optional; defaults to the client ID prefix
export PASSPORT_CALLER_ID=build-job-1       # or --caller; required for invoke

passport access actions --json
passport access run --input run.json --json
passport access invoke --input invocation.json --json
passport access status <invocation-id> --json
passport access cancel <run-id> --json
  • run.json: non-empty capabilities copied from actions; optional delegationId, expiresInSeconds (60 to 86,400), callLimit (1 to 10,000).
  • invocation.json: runId, a stable unique invocationId, appId, tool, resource, connectionId, and args.
  • Approval pending: after a human approves, invoke again with the same file and caller. Timeout or unknown result: check access status before any new write. Provider writes are never retried automatically.
  • Delegated work: passport access actions --delegation-id <id> --json, then include the same delegationId in run.json. The agent and the delegated person both need matching allow grants.
  • Tokens are never printed.

SDK, from the package root or passport-bridge/access:

import { accessClientFromEnv } from "passport-bridge/access";

const client = accessClientFromEnv(process.env, { callerId: "build-job-1" });
const actions = await client.actions(); // or client.actions(delegationId)

AccessGatewayClient exposes actions, createRun, invoke, status, and cancel.

Output contract

With --json, stdout is exactly one JSON value; hints and warnings go to stderr.

| Command | JSON value | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | login | {profile,endpoint,client,surface,active,workspaceId,workspaceName?,next} (workspaceName after browser sign-in) | | profiles | {profiles:[{name,active,client?}],active?} | | use | {profile,active:true} | | logout | {profile,removed,revoked} | | status | {workspace,member,endpoint,auth,profile?,client,mcps,autopilot?:{presets,floors,floorReason?,exceptions?,cache:{version,fetchedAt,ageMs}},next?} | | wait | {approvalId,status:"approved",scope?,message}; declined, expired, and still-waiting are errors with approvalId | | rules | {command,decision,cause?,explanation?,environment?,environmentAssumed?,actionClass?,source} | | sessions | {sessions:[{id,client,folder,status,startedAt,lastActivityAt,counts,environments,waiting}],nextCursor} | | undo | {actionId,status,message,steps:[{label,ok,detail?}],option,undoActionId?}; without --yes off a terminal, {actionId,status:"preview",preview,next}; pending, blocked, and declined are errors | | doctor | {version,ok,auth,profile?,profileCount,endpointOrigin?,server?,checks}, also on failure | | ready | {ready,version,ok,auth,profile?,profileCount,endpointOrigin?,server?,checks}