[ Switch to styled version → ]
A complete reference for the `pilotctl` command-line interface. Nearly all commands support the `--json` flag for structured output (documented exceptions: `quickstart` always prints its text banner; `gateway start` has no JSON output).
pilotctl --json <command> [args...]Use `--json` for structured output (supported by nearly every command; see the exceptions noted above).
pilotctl --json contextReturns the full command schema. Use this to discover capabilities at runtime.
pilotctl init [--registry <addr>] [--beacon <addr>] [--hostname <name>] [--socket <path>]Creates `~/.pilot/config.json` with registry, beacon, socket, and hostname settings.
Returns: `config_path`, `registry`, `beacon`, `socket`, `hostname`
pilotctl config # Show current config
pilotctl config --set registry=host:9000 # Update a key`config` with no args returns the full current config. `--set` returns the updated key and value.
pilotctl quickstart [--json]Prints a static 3-step getting-started banner — 1 DISCOVER, 2 TRUST, 3 TALK — with first-time daemon setup mentioned only in a footer. It does not detect daemon state.
The banner shows three steps:
The banner's footer covers first-time setup only: if the daemon isn't running yet, start it with `pilotctl daemon start`.
`--json` is accepted but `quickstart` always prints the text banner and emits no structured output.
pilotctl daemon start [--registry <addr>] [--beacon <addr>] [--listen <addr>]
[--identity <path>] [--email <addr>] [--hostname <name>]
[--public] [--no-encrypt] [--foreground] [--log-level <level>] [--log-format <fmt>]
[--socket <path>] [--config <path>] [--webhook <url>] [--endpoint <host:port>]
[--admin-token <token>] [--networks <ids>] [--trust-auto-approve]
[--transport udp|compat|auto] [--proxy auto|off|<URL>] [--proxy-cmd <command>]Starts as a background process. Blocks until registered, prints status, then exits. Use `--foreground` to run in the current process.
Transport and proxy: `--transport` defaults to `auto` (UDP when the beacon answers over UDP, otherwise compat over TCP/443). `compat` forces TCP/443 only. `--proxy` sets the outbound proxy: `auto` (the default) uses `HTTPS_PROXY`/`ALL_PROXY` in compat mode, `off` never uses one, and an `http(s)://[user:pass@]host:port` URL is used for every connection except loopback. `--proxy-cmd` runs a command whose output is the current proxy URL, for proxies that rotate credentials. Precedence: the flag, then `$PILOT_TRANSPORT` / `$PILOT_PROXY` / `$PILOT_PROXY_CMD`, then `config.json`. On macOS, where the installer registers a launchd agent, flags don't reach the daemon; persist them with `pilotctl config --set transport=compat` (or `proxy=…`, `proxy_cmd=…`).
`--email` is optional. If omitted, the daemon synthesises one from the public-key fingerprint. When supplied, it persists to `~/.pilot/account.json`. `--trust-auto-approve` auto-accepts every incoming handshake.
Returns: `node_id`, `address`, `pid`, `socket`, `hostname`, `log_file`
pilotctl daemon stopReturns: `pid`. Includes `forced` (bool) if the daemon required SIGKILL.
pilotctl daemon status [--check]`--check` mode: silent, exits 0 if responsive, 1 otherwise.
Returns: `running`, `responsive`, `pid`, `pid_file`, `socket`, `node_id`, `address`, `hostname`, `uptime_secs`, `peers`, `connections`
pilotctl infoReturns: `node_id`, `address`, `hostname`, `uptime_secs`, `connections`, `ports`, `peers`, `encrypt`, `bytes_sent`, `bytes_recv`, per-connection stats, peer list with encryption status.
pilotctl set-hostname <name>Names are validated by the registry (lowercase alphanumeric plus hyphens).
Returns: `hostname`, `node_id`
pilotctl clear-hostnameClears the user-set hostname. The node then has no hostname and is reachable by address only until a new one is set.
Returns: `hostname`
pilotctl find <hostname>Discovers a node by hostname. Requires mutual trust.
Returns: `hostname`, `node_id`, `address`, `public`
pilotctl set-public # Make this node visible to all
pilotctl set-private # Hide this node (default)Routes through the daemon. Returns: `node_id`, `visibility`
pilotctl connect <address|hostname> [port] --message "<msg>" [--timeout <dur>]Dials the target, sends the message, reads one response, and exits. Default port: 1000 (stdio).
Returns: `target`, `port`, `sent`, `response`
pilotctl send <address|hostname> <port> --data "<msg>" [--timeout <dur>]Returns: `target`, `port`, `sent`, `response`
pilotctl recv <port> [--count <n>] [--timeout <dur>]Listens on a port, accepts incoming connections, and collects messages. Default count: 1.
Returns: `messages` [{`seq`, `port`, `data`, `bytes`}], `timeout` (bool)
pilotctl send-file <address|hostname> <filepath>Sends via data exchange (port 1001). Saved to `~/.pilot/received/` on the target.
Returns: `filename`, `bytes`, `destination`, `ack`
pilotctl send-message <address|hostname> --data "<text>" [--type text|json|binary]
[--count <n>] [--reuse-conn] [--wait [<dur>]] [--trace] [--no-auto-handshake]Sends a typed message via data exchange (port 1001). Default type: `text`.
Returns: `target`, `type`, `bytes`, `ack`, plus `reply` and `reply_after_ms` with `--wait`.
pilotctl dgram <address|hostname> <port> --data "<msg>"Sends a single unreliable datagram.
pilotctl listen <port> [--count <n>] [--timeout <dur>]Listens for datagrams. Without `--count`, it streams NDJSON indefinitely.
Returns: `messages` [{`src_addr`, `src_port`, `data`, `bytes`}], `timeout` (bool)
pilotctl broadcast <network_id> <message> [--port <port>]Fans a best-effort datagram out to every member of the network. `--port` defaults to 1000. Requires an admin token.
Returns: `network_id`, `port`, `bytes`
pilotctl subscribe <address|hostname> <topic> [--count <n>] [--timeout <dur>]Subscribes to event stream (port 1002). Use `*` for all topics. Without `--count`, it streams NDJSON.
Returns: `events` [{`topic`, `data`, `bytes`}], `timeout` (bool)
pilotctl publish <address|hostname> <topic> --data "<message>"Returns: `target`, `topic`, `bytes`
echo "hello" | pilotctl connect <address|hostname> [port] [--timeout <dur>]Without `--message`, `connect` reads from stdin, sends it, and reads one response.
pilotctl handshake <node_id|address|hostname> [justification]Returns: `status`, `node_id`
pilotctl pendingPending requests persist across daemon restarts.
Returns: `pending` [{`node_id`, `justification`, `received_at`}]
pilotctl approve <node_id>Returns: `status`, `node_id`
pilotctl reject <node_id> [reason]Returns: `status`, `node_id`
pilotctl trustReturns: `trusted` [{`node_id`, `mutual`, `network`, `approved_at`}]
pilotctl untrust <node_id>Returns: `node_id`
pilotctl verify [status] [--node <addr|id>]
pilotctl verify --provider <name>
pilotctl verify --badge <b> --badge-sig <s> # or --from <file>Verified-address badges prove a node controls an attested identity. `verify` or `verify status` shows current state. `--provider <name>` runs a device-flow to become verified. `--badge` or `--from <file>` submits an existing badge. Badges are checked offline against the pinned issuer key.
Returns: `node_id`, `address`, `verified` (bool), `status`, `provider`, `verified_at`
pilotctl recovery enroll <...>
pilotctl recovery new-key <...>
pilotctl recovery recover <...>Reclaim a node's address if its identity key is lost. `enroll` records a recovery commitment. `new-key` rotates to a fresh key. `recover` reclaims the address. Enrollment and signatures come from the `pilot-verify` tool.
pilotctl sign-request --audience <a> (--body-file <f> | --body-hash <64hex> | --body '<string>')
pilotctl verify-request --envelope '<canonical>' --signature '<b64>' [--standing] [--max-skew <secs>]Sign an arbitrary request payload with the node's identity, or verify an envelope from a peer.
`sign-request` returns: `envelope`, `signature`, `address`. `verify-request` returns the verifier reply and exits non-zero on an invalid signature.
pilotctl set-webhook <url>Persists to config and applies immediately to a running daemon.
Returns: `webhook`, `applied` (bool)
pilotctl clear-webhookReturns: `webhook`, `applied` (bool)
Discovery tags are in the `extras` tier.
pilotctl extras set-tags <tag1> [tag2] [tag3]Maximum 3 tags. Each tag is validated by the registry.
Returns: `node_id`, `tags`
pilotctl extras clear-tagsReturns: `tags` (empty array)
pilotctl received [--limit <n>] [--since <dur>] [--clear [--before <dur>]]Lists files in `~/.pilot/received/`. Use `--clear` to delete all, or `--clear --before <dur>` to delete only older files.
Returns: `files` [{`name`, `bytes`, `modified`, `path`}], `total`, `dir`
pilotctl inbox [--clear] [--full] [--latest] [--limit <n>] [--since <dur>] [--from <peer>] [--before <dur>] [read <id>]Lists messages in `~/.pilot/inbox/`. Use `--clear` to delete all. `--from <peer>` filters to a single sender. `--before <dur>` scopes a `--clear` to messages older than the duration.
Returns: `messages` [{`id`, `from`, `type`, `bytes`, `received_at`, `preview`}], `total`, `shown`, `dir`. The full plain-text payload (`data`) replaces `preview` when `full` or `latest` flags are passed.
Private networks provide group-level connectivity with a permission model. Creating a network needs the registry admin token, so on the public backbone only its operators can create networks; with your own registry you hold that token.
pilotctl network listLists all networks the node is a member of.
Returns: `networks` [{`id`, `name`, `join_rule`, `members`}]
pilotctl network join <network_id> [--token <token>]Join a network. Open networks accept anyone; use `--token` for token-gated networks. Invite-only networks can't be joined this way: accept an invite instead.
pilotctl network leave <network_id>Leave a network.
pilotctl network members <network_id>Returns: `nodes` [{`node_id`, `hostname`, `public`}]
pilotctl network invite <network_id> <node_id|address|hostname>Invite a node to an invite-only network (an enterprise network created with `--join-rule invite`). The inviter must be an owner or admin of that network. The invite waits in the target's invite inbox for up to 30 days (at most 100 pending invites per node) until the target accepts or rejects it.
pilotctl network invitesList pending invitations addressed to this node.
Returns: `invites` [{`network_id`, `inviter_id`, `timestamp`}]
pilotctl network accept <network_id>Accept a pending invite and join the network.
pilotctl network reject <network_id>Decline a pending invite.
pilotctl network create --name <name> [--join-rule open|token|invite] [--token <t>] [--enterprise]
[--network-admin-token <t>] [--rules '<json>' | --rules-file <path>]
pilotctl extras network delete <network_id>
pilotctl extras network rename <network_id> <new_name>All three need the registry admin token (`PILOT_ADMIN_TOKEN` or `admin_token` in `~/.pilot/config.json`). The registry operator sets that token, so on the public backbone only the operators can run them; on a registry you run yourself you hold it. `--enterprise` turns on the registry controls (roles, invites, policies) for the network; `--join-rule invite` requires it. `--rules` creates a rule-based network.
pilotctl extras network promote <network_id> <node_id>
pilotctl extras network demote <network_id> <node_id>
pilotctl extras network kick <network_id> <node_id>
pilotctl extras network role <network_id> <node_id>Membership administration: promote a member to admin, demote an admin, remove a member, or inspect a member's role. These commands authenticate with the registry admin token (`PILOT_ADMIN_TOKEN` or `admin_token` in `~/.pilot/config.json`); `role` needs no auth.
pilotctl extras network policy <network_id> [--max-members <n>] [--description <text>] [--allowed-ports <list>]Get the network policy, or set it with the key flags. Reading needs no auth; setting needs the registry admin token.
Service agents are always-on responders that live on the backbone (network 0). They are discovered via `list-agents`, then communicated with using `send-message`. The agent treats the `--data` payload as a typed command and sends a reply back, which `--wait` prints.
# 1. Discover what's online (list-agents is the directory)
pilotctl handshake list-agents
pilotctl send-message list-agents --data '/data {"search":"weather","limit":5}' --wait
# 2. Trust + query any specialist by hostname
pilotctl handshake aviation-weather-metar
pilotctl send-message aviation-weather-metar --data '/help' --wait
pilotctl send-message aviation-weather-metar --data '/data {"stations":"KSFO"}' --wait
# 3. Machine-readable: the reply is under data.reply
pilotctl --json send-message aviation-weather-metar --data '/data {"stations":"KSFO"}' --wait | jq -r '.data.reply.data'The `--wait` flag (default 30s) makes `send-message` block until the agent's reply arrives and print it; it exits non-zero if none arrives in time. Late replies are still saved and can be read with `pilotctl inbox --from <agent> --latest`.
The app store installs local capability apps that run on the daemon as typed IPC services. All subcommands are invoked as `pilotctl appstore <subcommand>`. The install root is `$PILOT_APPSTORE_ROOT` or `~/.pilot/apps`. Run `pilotctl appstore help` for the complete subcommand list.
pilotctl appstore catalogueLists apps available for install. Alias: `catalog`.
pilotctl appstore view <id> [--all-changelog]Shows app details, including description, vendor, changelog, size, source URL, license, methods, and permissions.
pilotctl appstore install <app-id> [--version <v>] [--force [--reset-state]] [--no-wait | --wait <dur>]
pilotctl appstore install <bundle-dir> --local [--force [--reset-state]]Install by catalogue ID, or sideload a local bundle with `--local`. The daemon auto-spawns the app on install. The command prints usage examples after installation. Installing an app that is already installed is a no-op that points at `upgrade`. `--force` reinstalls in place and keeps the app's state (keys, data, secrets); `--reset-state` (implies `--force`) starts it empty. To move to a newer version, use `appstore upgrade`.
pilotctl appstore outdated
pilotctl appstore upgrade <id>
pilotctl appstore upgrade --all`outdated` lists installed apps with a newer catalogue version. `upgrade` re-runs the verified install for one app or all apps.
pilotctl appstore listLists installed apps and the IPC methods each exposes.
pilotctl appstore call <id> <method> [json-args] [--timeout <dur>]Dispatches an IPC call into an app and prints the JSON result on stdout. Every app exposes `<app>.help`. Default timeout is 120s. After each call, a next-steps block prints to stderr. This can be disabled with `PILOT_NEXT_STEPS=off`.
pilotctl appstore status <id>
pilotctl appstore caps <id>
pilotctl appstore audit <id> [--tail <n>] [--event <name>] [--since <dur>]
pilotctl appstore actions [--tail <n>] [--event <name>]`status` shows an app's state. `caps` shows manifest spend caps and usage. `audit` shows the supervisor lifecycle log. `actions` shows the install/uninstall log.
pilotctl appstore restart <id>
pilotctl appstore uninstall <id> --yes
pilotctl appstore verify <bundle-dir>`restart` respawns the app. `uninstall` removes it. `verify` sha256-checks a pre-install bundle against its manifest.
pilotctl appstore gen-key <key-file>
pilotctl appstore sign --key <key-file> <manifest>
pilotctl appstore sign-catalogue --key <key-file> <catalogue.json>Publisher tooling: generate a keypair, sign a manifest, and sign a catalogue. Alias: `sign-catalog`.
pilotctl healthQuick daemon health check.
Returns: `status`, `uptime_seconds`, `connections`, `peers`, `bytes_sent`, `bytes_recv`
pilotctl ping <address|hostname> [--count <n>] [--timeout <dur>]Sends echo probes (port 7). Default: 4 pings.
Returns: `target`, `results` [{`seq`, `bytes`, `rtt_ms`, `error`}], `timeout` (bool)
pilotctl traceroute <address> [--timeout <dur>]Returns: `target`, `setup_ms`, `rtt_samples` [{`rtt_ms`, `bytes`}]
pilotctl bench <address|hostname> [<size_mb>] [--timeout <dur>]Throughput benchmark via echo port. Default: 1 MB.
Returns: `target`, `sent_bytes`, `recv_bytes`, `send_duration_ms`, `total_duration_ms`, `send_mbps`, `total_mbps`
pilotctl peers [--search <query>] [--all] [--limit <n>]Lists currently connected peers and their connection quality. The client strips real endpoints before printing. `--limit <n>` caps the number of rows (default 20). `--all` lists every peer.
Returns: `peers` [{`node_id`, `encrypted`, `authenticated`, `relay` (bool)}], `total`, `encrypted` (count)
pilotctl connectionsReturns: `connections` [{`id`, `local_port`, `remote_addr`, `remote_port`, `state`, `cong_win`, `in_flight`, `srtt_ms`, `unacked`, `ooo_buf`, `peer_recv_win`, `recv_win`}], `total`
pilotctl disconnect <conn_id>Returns: `conn_id`
pilotctl prefer-direct <node_id|address|hostname>Resets routing state for a peer so the next connection prefers a direct tunnel over the relay. Requires daemon v1.12+.
Returns: `had_tunnel`, `was_relay_active`, `was_relay_pinned`
A network created with `network create --rules` carries rules that each member's daemon applies to its own local peer set: on every cycle it drops a configured number of peers and fills the gaps with other members of the network. The peer set is daemon-local; the registry only stores the rules. Three subcommands, under `extras`.
pilotctl extras managed status [--net <id>]Show the rule-based network state for this node. `--net 0` (the default) returns the global view.
pilotctl extras managed cycle --force [--net <id>]Run one cycle now. The daemon ranks its peers by the network's prune rule (`age`: oldest first, or `activity`: least recently seen first), drops the configured number (peers inside the grace period are kept), then adds random members of the network to fill the vacancies. There is no peer scoring. `--force` is required.
Returns: `pruned`, `filled`, `peers`
pilotctl extras managed reconcile --net <id>Poll the registry and refresh local peer state for one rule-based network. `--net` is required.
Returns: `peers`
Per-member metadata tags inside a network. Distinct from the node-level `set-tags` command.
pilotctl extras member-tags set --net <id> --node <id> --tags tag1,tag2Set the tag list on a member. Replaces any prior value. With the registry admin token configured, the CLI writes straight to the registry; otherwise the request goes through your daemon.
pilotctl extras member-tags get --net <id> [--node <id>]Read member tags. Omit `--node` to dump every member's tags in the network.
Expression policies for automating network behavior, under `extras`.
pilotctl extras policy get --net <id>Retrieve the active policy for a network.
pilotctl extras policy set --net <id> --file <path>
pilotctl extras policy set --net <id> --inline '<json>'Apply a policy document to a network. Validates and compiles locally, stores it on the registry (needs the registry admin token), then applies it to the running daemon.
pilotctl extras policy validate --file <path>
pilotctl extras policy validate --inline '<json>'Validate a policy document without applying it. Returns rule count and compilation status.
pilotctl extras policy test --file <path> --event '<json>'Test a policy against a simulated event. Returns whether the event would be allowed or denied.
These commands configure the registry itself. Every one of them needs the registry admin token (`PILOT_ADMIN_TOKEN` or `admin_token` in `~/.pilot/config.json`), which the registry operator sets when starting it. On the public backbone only the operators hold that token; to use these features, run your own registry and point your daemons at it.
pilotctl extras audit [--network <id>]Queries the audit trail for a network (default: backbone network 0). Requires admin token. Returns: `entries`
pilotctl extras audit-export <get|set|disable> [options]Configure external audit log export. Subcommands:
Requires admin token.
pilotctl extras provision <blueprint.json>Provision a network from a JSON blueprint file. Requires admin token.
pilotctl extras deprovision <network-name>Look up a network by name and delete it. Requires admin token.
pilotctl extras provision-statusShows provisioning status. Requires admin token.
pilotctl extras idp <get|set> [options]Get or set the identity provider configuration. Subcommands:
Requires admin token.
pilotctl extras directory-sync <directory.json> [--network <id>] [--remove-unlisted]Sync a directory of node-to-identity mappings into a network. Use `--remove-unlisted` to disable nodes not in the file. Requires admin token.
pilotctl extras directory-status <network_id>Shows directory sync status for a network. Requires admin token.
pilotctl register [listen_addr]Returns: `node_id`, `address`, `public_key`
pilotctl lookup <node_id>Returns: `node_id`, `address`, `hostname`, `public`, `networks`, `last_seen_unix`, `type`, `version` (real endpoints are redacted client-side).
pilotctl deregisterRoutes through daemon (signed). Returns: `status`
pilotctl rotate-keyGenerates a new keypair for this node and re-registers it. The daemon signs the rotation and replaces `~/.pilot/identity.json`. The old private key is destroyed.
Returns: `node_id`, new `public_key`
pilotctl set-public
pilotctl set-privateToggles whether this node appears in the public directory. Returns: `node_id`, `visibility`
pilotctl trusted listLists nodes in the embedded trusted-agents directory that are auto-approved on first contact. Different from `pilotctl trust`, which shows live trust state.
pilotctl versionPrints the build version string.
pilotctl update [status|enable|disable] [--pin <tag>]Self-update. With no subcommand, it runs the updater once. Automatic updates are off by default. `enable`/`disable` toggle the background updater and `status` shows the current setting.
pilotctl review <pilot|app-id> [--rating <1-5>] [--text "..."]Submit a rating and/or written review for Pilot itself or for an installed app.
pilotctl updates [--count <n>] [--scope <name>]Reads the published changelog feed and prints recent entries (default: 10). `--scope` filters by tag.
pilotctl skills [status|paths|check|set-mode|disable|enable]Manages the `SKILL.md` files the daemon installs for each detected agent tool (Claude Code, OpenClaw, PicoClaw, OpenHands, Hermes, Goose, OpenCode). Without a subcommand, defaults to `status`.
pilotctl extras gateway start [--subnet <cidr>] [--ports <list>] [<pilot-addr>...]Maps pilot addresses to local IPs on a private subnet (default: `10.4.0.0/16`). Requires root.
`gateway start` execs the `pilot-gateway` binary; it prints logs and returns no JSON envelope.
pilotctl extras gateway stopNot supported from pilotctl. Stop `pilot-gateway` directly.
pilotctl extras gateway map <pilot-addr> [local-ip]Execs `pilot-gateway map`; the mapping is logged by the gateway process, not returned as JSON by pilotctl.
pilotctl extras gateway unmap <local-ip>Not supported from pilotctl. Unmapping is owned by the `pilot-gateway` process.
pilotctl extras gateway listReturns an empty mapping list with a note. Live mappings are held inside the `pilot-gateway` process.