# 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 eight 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 the build's commit and the key fingerprint it was signed with.

## Sign in

```sh
whisper login                 # opens the console in a browser, drops a scoped key in your keychain
whisper login --token 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 --label my-agent` | mint an identity: 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:{}})` |
| `whisper connect my-agent [--tier socks5\|wireguard]` | bring up egress sourced from the agent's `/128` | `CALL whisper.agents({op:'connect', args:{agent:'my-agent', tier:'socks5'}})` |
| `whisper run -- <cmd>` | connect (if needed), verify egress, exec `<cmd>` under it | `connect`, then the keyless echo check, then `execve` |
| `whisper ip 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 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 an agent — POST the one control-plane verb; the key travels in the header, never the body
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_..." \
  -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 --label my-agent` and `whisper verify 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4` — send the same bytes to the same endpoints. 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 — `whisper.agents({op:'connect', tier:'socks5'})` by default, since Tier 1.5 needs no root and no kernel interface.
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 (or, for `--tier wireguard`, nothing at all — the interface itself carries the address, so no env var is needed):

```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 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.
