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
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:
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. 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:
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
gpgis 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
gpgis installed. The.ascmust be exactly one good signature by the AS219419 release key, primary fingerprintEFF1663D992539682106A5EAD0F70908CF3B7929, 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;--verboseprintsverified 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
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).
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 |
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 and Connect & egress. 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:
# 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.
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:
- Connects, if the named (or default) agent isn't already connected -
whisper.agents({op:'connect', tier:'socks5'})by default, since Tier 1.5 needs no root and no kernel interface. - Verifies the egress before handing off control: it fetches
https://rdap.whisper.online/egress-ipthrough the new proxy and checks the answer equals the agent's own/128- the same checkwhisper ipexposes standalone. A mismatch is a hard error, never a quiet fallback to your host's own address. - Execs your command with
ALL_PROXYandHTTPS_PROXYpointed 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 (or, for--tier wireguard, nothing at all - the interface itself carries the address, so no env var is needed):
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:
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.
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; they use the same two calls, just via a platform-native connector instead of a CLI wrapper.
Uninstalling, cleanly
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 - the three tiers whisper connect and whisper run choose between, and how source-binding actually works · Integrations - every non-CLI way to wire in the same identity, from MCP to Kubernetes.