# CLI & one-command setup

**Wiring a project into a new identity system usually means an SDK, a bespoke config file, and an afternoon of docs - `whisper` collapses all of it into one binary and one line.**

`whisper init claude`, and your project has a real, routable, DNSSEC-anchored identity before you've finished reading this sentence.

## The pain this removes

Every coding agent, every automation runner, every CI job that needs a network identity has historically faced the same bad menu: install a Python package with native deps for one stack, an npm package for another, hand-roll a WireGuard config for a third, and hope the proxy environment variables you set actually get inherited by the child process. None of that is hard, exactly - it's just *friction*, repeated once per tool, per language, per environment. And friction is where identity systems die: an agent that's "supposed to" carry its address ends up talking out from whatever IP the host happened to have, and the whole point of a verifiable identity - that the network layer *is* the proof - quietly evaporates.

The `whisper` CLI is the fix: one MIT-licensed, cross-platform Go binary that does registration, connection, and verification without asking you to touch a config file, and a `whisper init <tool>` family that wires that identity into the specific tool you're already using - Claude Code, Gemini, aider, Zed, browser-use, and seven more - with zero manual setup. It is also, deliberately, not the only way in: everything it does is one HTTP call or one `dig` away (see the mappings below), so nothing about it is a black box.

## Install

```sh
curl -fsSL https://get.whisper.online | sh      # reads before it runs; one binary, no daemon required
```

Prefer a package manager? The same signed binary ships on all of them - pick the one your platform already trusts:

```sh
brew install whisper-sec/tap/whisper                                  # Homebrew (macOS/Linux)
scoop bucket add whisper https://github.com/whisper-sec/scoop-bucket && scoop install whisper  # Windows
go install github.com/whisper-sec/whisper-cli/cmd/whisper@latest      # straight from source
mise use -g "github:whisper-sec/whisper-cli[exe=whisper]"             # mise
sudo apt install whisper   # after adding get.whisper.online/deb - see /docs/integrations
```

Source, issues, and the release signatures live at [github.com/whisper-sec/whisper-cli](https://github.com/whisper-sec/whisper-cli). Every artifact is signed - `whisper --version` prints just the version number, so verify with the detached signature published beside each binary. Check the key's fingerprint against the pin before you trust it:

```sh
curl -fsSL -o whisper-pgp.asc https://get.whisper.online/dl/whisper-pgp.asc
gpg --show-keys whisper-pgp.asc    # must show EFF1663D992539682106A5EAD0F70908CF3B7929
curl -fsSL -o whisper.asc https://get.whisper.online/dl/linux-amd64/whisper.asc
gpg --import whisper-pgp.asc && gpg --verify whisper.asc "$(command -v whisper)"
# Primary key fingerprint: EFF1 663D 9925 3968 2106  A5EA D0F7 0908 CF3B 7929
```

### What the installer verifies

The install script (and `install.ps1` on Windows) always checks the binary against the sha256 published beside it, and a mismatch stops the install. Whether it also checks the signature decides which of two guarantees you get:

- **sha256 only**, when `gpg` is not installed, or no signature or no usable copy of the key can be fetched. The bytes arrived intact and match their checksum. Both come from the same host, so this does not prove who built them. The installer says so in one line:
  `whisper: verified the sha256 checksum only: gpg is not installed, so the PGP signature was not checked`
- **sha256 + PGP**, wherever `gpg` is installed. The `.asc` must be exactly one good signature by the AS219419 release key, primary fingerprint `EFF1663D992539682106A5EAD0F70908CF3B7929`, which is pinned in the script itself. That proves the bytes are the ones the holder of that key signed, wherever you downloaded them from. It does not prove the code is free of bugs or that this is the newest release. A signature by any other key, by an expired or revoked key, or a file carrying a second signature is refused, and the install stops. This is silent on success; `--verbose` prints `verified the sha256 checksum and the PGP signature`.

The public key is fetched from as219419.net, or from `/dl/whisper-pgp.asc` if that is unreachable, and either copy is used only if its fingerprint matches the pin, so the host serving the binary cannot also vouch for it. Replacing the pin with `WHISPER_PGP_FPR` is refused unless `WHISPER_INSTALL_UNSAFE_KEY=1` is set too, and then every run warns that the key is not ours. A key rotation changes the pin and the published key in the same release; an older script then says the key is not its pin and stops at the sha256 level.

## Sign in

```sh
whisper login                 # opens the console in a browser, drops a scoped key in your keychain
whisper login whisper_live_...   # or paste one directly (CI, containers, no browser)
```

A key is only needed for the control-plane half. Everything under `whisper verify` works with **no login at all** - that's the keyless tier, by design (see [Verify an agent](/docs/verify)).

## The commands, and what each one actually calls

Nothing the CLI does is proprietary to the CLI. Every command is a thin, honest wrapper over either a public DNS/HTTP check or the one control-plane verb, `whisper.agents`. Here's the mapping, so you can reimplement any of it yourself or debug it when something looks wrong:

| Command | What it does | Underlying call |
|---|---|---|
| `whisper login` | store a scoped API key | none - local only |
| `whisper create --name my-agent` | claim the caller's own `/128` identity (one key, one identity - `create` reuses it if the key already has one) | `CALL whisper.agents({op:'identity', args:{label:'my-agent'}})` |
| `whisper create --register --name my-agent` | mint a brand-new agent with its own key: address, forward name, PTR, DANE pin | `CALL whisper.agents({op:'register', args:{label:'my-agent'}})` |
| `whisper list` | your agents, their addresses and status | `CALL whisper.agents({op:'list', args:{kind:'agents'}})` |
| `whisper connect --agent my-agent [--tier auto\|wireguard\|socks5]` | bring up egress sourced from the agent's `/128` - `auto` (the default) tries Tier-1 WireGuard first and falls back to Tier-1.5 SOCKS5 on any failure; an explicit tier is forced, no downgrade | `CALL whisper.agents({op:'connect', args:{agent:'my-agent', tier:'wireguard'}})` |
| `whisper run -- <cmd>` | connect (if needed), verify egress, exec `<cmd>` under it | `connect`, then the keyless echo check, then `execve` |
| `whisper ip --agent my-agent` | prove the current egress source address | `curl` through the proxy to `https://rdap.whisper.online/egress-ip` |
| `whisper verify <addr>` | keyless identity check | `GET https://rdap.whisper.online/verify-identity/<addr>` |
| `whisper verify --trustless <addr>` | the same check, re-derived locally from the DNSSEC root - Whisper's own API is never trusted | local DNSSEC chain-of-trust validation, see [Trustless verification](/docs/verifiable-identity) |
| `whisper kill --revoke my-agent` | tear down address, DNS, DANE pin, and egress in one step | `CALL whisper.agents({op:'revoke', args:{agent:'my-agent'}})` |

> Every one of the right-hand-column calls is documented on its own in [Control plane](/docs/control-plane) and [Connect & egress](/docs/connect). The CLI adds nothing to the protocol - it only removes the typing.

### The same thing, with stock tools only

The table isn't aspirational - every call really is one `curl` or one `dig`. Provisioning an agent and proving an identity, with no Whisper software installed anywhere:

```sh
# mint a brand-new agent - POST the one control-plane verb; the key travels in the header, never the body
curl -s https://graph.whisper.online/api/query \
  -H "X-API-Key: whisper_live_..." -H "content-type: application/json" \
  -d '{"query":"CALL whisper.agents({op:'"'"'register'"'"', args:{label:'"'"'my-agent'"'"'}})"}' | jq .

# verify any agent's identity - keyless, no header at all
curl -s https://rdap.whisper.online/verify-identity/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 | jq .
```

The CLI equivalents - `whisper create --register --name my-agent` and `whisper verify 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4` - send the same bytes to the same endpoints. (`--label` still works as a legacy alias for `--name`, but `--name` is the documented spelling; and dropping `--register` sends `op:identity` instead - claiming the caller's own `/128` on the key already in hand rather than minting a new one.) Nothing is hidden behind the binary; it only saves the typing. Full request and response shapes: [Control plane](/docs/control-plane).

## `whisper run` - what actually changes in the child process

`whisper run -- python agent.py` does exactly three things, in order, and fails loudly rather than falling back silently if any of them don't hold:

1. **Connects**, if the named (or default) agent isn't already connected, negotiating exactly as `whisper connect` does: Tier-1 WireGuard first (`whisper.agents({op:'connect', tier:'wireguard'})`), then Tier-1.5 SOCKS5 (`tier:'socks5'`) if that is refused. Both run in userspace, so neither needs root or a kernel interface. `--tier wireguard` or `--tier socks5` forces one, with no downgrade.
2. **Verifies** the egress before handing off control: it fetches `https://rdap.whisper.online/egress-ip` *through* the new proxy and checks the answer equals the agent's own `/128` - the same check `whisper ip` exposes standalone. A mismatch is a hard error, never a quiet fallback to your host's own address.
3. **Execs** your command with `ALL_PROXY` and `HTTPS_PROXY` pointed at a local loopback SOCKS5 endpoint the CLI holds open for the life of the command - source-bound to the agent's `/128`, so no egress credential ever lands in the child's environment. On the WireGuard tier that endpoint is the front of the userspace tunnel, so the variables are the same on both tiers:

```sh
whisper run -- python my_agent.py
# child process env now includes:
#   ALL_PROXY=socks5h://127.0.0.1:1080
#   HTTPS_PROXY=socks5h://127.0.0.1:1080
```

Because `ALL_PROXY`/`HTTPS_PROXY` are a decades-old Unix convention, this needs no cooperation from your code: `curl`, `git`, Python `requests`/`httpx`, Node's `fetch`, and Claude Code's own tool-use network layer all already honor them. There is no SDK to `import`.

## `whisper init <tool>` - wiring an identity into what you already run

`whisper connect` and `whisper run` are generic; `whisper init <tool>` goes one step further and speaks each target's *own* configuration surface, so the identity is there before the tool's first network call - no wrapper script, no env var to remember:

```sh
whisper init claude          # writes the proxy/network entry into ./.claude/settings.local.json
whisper init gemini          # same egress, Gemini CLI's own config surface
whisper init aider           # aider's local config, same proxy underneath
whisper init zed             # Zed's context-server settings
whisper init browser-use     # a browser-context proxy config for headless/agentic browsing
```

Twelve targets ship today - `claude`, `python`, `zed`, `gemini`, `aider`, `ai-sdk`, `browser-use`, `discord`, `telegram`, `notebook`, `compose`, `k8s` - each writing into that tool's native format rather than a Whisper-specific one. Run it with no argument and it detects the project (a `.claude/` directory, a `pyproject.toml`, a `docker-compose.yml`) and picks the right target itself. Every target is provisioned against the *same* control-plane `register` + `connect` pair above; `init` only differs in where it writes the result. Before starting the target process, `init` confirms the local egress endpoint is already listening - so the tool's very first outbound call never races an unready proxy.

```sh
whisper init claude && claude    # the project now has an identity; Claude Code's traffic carries it
```

For everything that isn't a first-class `init` target - n8n, Zapier, Cloudflare Workers, Kubernetes - see the full connector catalog at [Integrations](/docs/integrations); they use the same two calls, just via a platform-native connector instead of a CLI wrapper.

## Uninstalling, cleanly

```sh
whisper kill --revoke my-agent   # first: kill the identity - address, DNS, DANE pin, and status-list entry, provably
rm $(command -v whisper)  # then: remove the binary, no other state left behind
```

`whisper` keeps no daemon running between invocations and no local database beyond the key in your OS keychain - revoking an agent and deleting the binary leaves nothing behind on either side.

## Next

[Connect & egress](/docs/connect) - the three tiers `whisper connect` and `whisper run` choose between, and how source-binding actually works · [Integrations](/docs/integrations) - every non-CLI way to wire in the same identity, from MCP to Kubernetes.
