CLI reference

One command does everything: pinchpass request <secret-name>... [flags]. It starts a one-time server, prints a link, blocks until the value arrives or the TTL expires, writes .env, and exits.

7 flags -json for agents blocks until submit exits non-zero on timeout

Invocation forms

Three ways to reach the same command.
FormWhat happens
pinchpass request API_KEY The explicit form. Use this in scripts and documentation.
pinchpass API_KEY The subcommand is auto-detected, so a bare secret name is treated as request API_KEY. Convenient interactively.
pinchpass --help Prints usage, the flag list, and examples. --help exists only at the top level, not on the subcommand.
pinchpass version, -version, --version All three print the version, for example pinchpass v1.2.0 (abc1234).

At least one secret name is required. With no arguments, or an unknown command, PinchPass prints usage and exits.

Flags

All flags, with their defaults.
Flag Default Description
-tunnel false Open a bore.pub tunnel and print a public URL instead of a loopback one. Use it when the human is not on your machine.
-note — Human-readable description shown on the form, so the person knows what they are being asked for.
-out .env Output .env path. Parent directories are created if missing.
-ttl 30 Minutes until the link expires. When it lapses the server shuts down and nothing is written.
-port 0 (random) Local port to bind. Zero means pick any free port.
-listen-addr 127.0.0.1 Address to listen on. Changing it exposes the form beyond loopback, which you almost never want without -tunnel.
-json false Machine-readable output for agents and scripts. Two JSON objects are printed, one before blocking and one after.
Flags are single-dash

Write -out, not --out. There is also no -gen-only flag: an unused field with that name exists inside the code, but it is not exposed on the command line, so do not rely on it.

Examples

shell
# local link, usable only from this machine
pinchpass request API_KEY

# public link through bore.pub
pinchpass request API_KEY -tunnel

# explain what is being asked for, and write somewhere else
pinchpass request API_KEY -note "Production API key" -out config/secrets.env

# machine-readable, for an agent to parse
pinchpass request API_KEY -json

# short-lived link
pinchpass request API_KEY -ttl 5

# collect several values into one .env
pinchpass request DB_HOST DB_PORT DB_NAME -out .env

# bare form, public link, JSON output
pinchpass WEBHOOK_SECRET -tunnel -json

Output

Human mode

Before blocking, the CLI prints the link, the names, and the expiry:

shell
  🔗  Secret request link (one-time, E2E encrypted via bore.pub)

     http://bore.pub:38448/claim/gemini_api_key#k=…

     Name:   GEMINI_API_KEY
     Note:   Production API key
     Expiry: 30 minutes

     Waiting for user to submit...

When the value arrives:

shell
  ✅  Saved to .env
       GEMINI_API_KEY

If nobody submits before the TTL:

shell
  ❌  Request timed out. No secret was saved.
A timeout is a failure exit

The process exits non-zero, so a shell pipeline or an agent can tell the difference between "collected" and "gave up" without parsing output.

JSON mode

-json prints two objects. The first is emitted immediately, before the command blocks, so a caller can read the URL and present it to a human while the process keeps waiting:

json — before blocking
{
  "success": true,
  "names": ["GEMINI_API_KEY"],
  "message": "Secret request link (one-time, expires in 30 min)",
  "url": "http://bore.pub:38448/claim/gemini_api_key#k=…",
  "port": 38448
}

The second is emitted once the wait is over:

json — after the wait
{
  "success": true,
  "names": ["GEMINI_API_KEY"],
  "message": "Secrets provisioned successfully.",
  "port": 38448
}

On timeout the second object reports the failure:

json — timed out
{
  "success": false,
  "names": ["GEMINI_API_KEY"],
  "message": "Request timed out or failed.",
  "port": 38448
}
Read the first object for the URL

The second object has no url field — take the URL from the first one. Also note the first object's success field means "the request started", not "a secret was collected": the only reliable signal of collection is success in the second object, or the exit code.

What it writes to .env

Each collected value is written as KEY="value". The value is always double-quoted and shell-escaped, so a key containing quotes, dollars, backticks or backslashes survives being sourced.

Escaping applied to every value.
CharacterWritten as
\\\
"\"
$\$
`\`

An existing key is replaced in place, and every other line in the file is left alone. The file is written with 0600 permissions, parent directories are created with 0700, and the result ends in a newline.

Collecting DB_HOST twice, with a changed value:

.env — before and after
# before
APP_ENV="staging"
DB_HOST="localhost"

# after
APP_ENV="staging"
DB_HOST="db.internal"

The tunnel

-tunnel dials bore.pub:7835 directly. The client is written against the Go standard library and shells out to nothing, so there is no bore binary to install. The server publishes a random remote port and the CLI prints:

shell
http://bore.pub:<remote-port>/claim/<path>#k=<key>
Share the whole URL, fragment included

Everything after # is the decryption key. Browsers never transmit a fragment, which is exactly why the key can travel this way — but it also means the fragment is the one part of the URL that cannot be rebuilt. Paste the URL verbatim; if a chat client or shortener drops the fragment, the form disables submit and the human needs a new link.

Without -tunnel the link points at http://127.0.0.1:<port>/claim/<path>#k=<key>, which only works for someone sitting at that machine. -listen-addr changes the bind address if you need to reach it over a LAN, though -tunnel is usually the better answer.

The path segment is derived from the secret name — lowercased, with anything outside a–z, 0–9 and _ removed — so GEMINI_API_KEY becomes /claim/gemini_api_key. Treat the name as public and the value as protected; the security model explains why that is still safe.

Lifetime of a request

  1. The CLI generates a fresh 32-byte encryption key and starts an HTTP server on a random loopback port.

  2. It prints the link and blocks. The link is now live, whether or not anyone has opened it.

  3. The human opens the form, types the value, and submits. The browser encrypts it with the key from the fragment.

  4. The ciphertext reaches the server, which stores it and unblocks the CLI. The CLI decrypts it locally, writes .env, and exits.

  5. The request is spent. Every later request to that URL returns 404 with a link-expired page, so a leaked link is worthless once used.

  6. Or the TTL lapses first: the server shuts itself down, the CLI exits non-zero, and nothing is written. The default is 30 minutes.

For the crypto and the threat model, see the security model. For the agent-facing view of the same flow, see agent integration.