[ Switch to styled version → ]


← Docs index

CLI Reference

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

Global flags

pilotctl --json <command> [args...]

Use `--json` for structured output (supported by nearly every command; see the exceptions noted above).

Self-discovery

pilotctl --json context

Returns the full command schema. Use this to discover capabilities at runtime.

Bootstrap

init

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`

config

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.

Quickstart

quickstart

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.

Daemon lifecycle

daemon start

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`

daemon stop

pilotctl daemon stop

Returns: `pid`. Includes `forced` (bool) if the daemon required SIGKILL.

daemon status

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`

Identity & Discovery

info

pilotctl info

Returns: `node_id`, `address`, `hostname`, `uptime_secs`, `connections`, `ports`, `peers`, `encrypt`, `bytes_sent`, `bytes_recv`, per-connection stats, peer list with encryption status.

set-hostname

pilotctl set-hostname <name>

Names are validated by the registry (lowercase alphanumeric plus hyphens).

Returns: `hostname`, `node_id`

clear-hostname

pilotctl clear-hostname

Clears the user-set hostname. The node then has no hostname and is reachable by address only until a new one is set.

Returns: `hostname`

find

pilotctl find <hostname>

Discovers a node by hostname. Requires mutual trust.

Returns: `hostname`, `node_id`, `address`, `public`

set-public / set-private

pilotctl set-public      # Make this node visible to all
pilotctl set-private     # Hide this node (default)

Routes through the daemon. Returns: `node_id`, `visibility`

Communication

connect

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`

send

pilotctl send <address|hostname> <port> --data "<msg>" [--timeout <dur>]

Returns: `target`, `port`, `sent`, `response`

recv

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)

send-file

pilotctl send-file <address|hostname> <filepath>

Sends via data exchange (port 1001). Saved to `~/.pilot/received/` on the target.

Returns: `filename`, `bytes`, `destination`, `ack`

send-message

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

dgram

pilotctl dgram <address|hostname> <port> --data "<msg>"

Sends a single unreliable datagram.

listen

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)

broadcast

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`

subscribe

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)

publish

pilotctl publish <address|hostname> <topic> --data "<message>"

Returns: `target`, `topic`, `bytes`

Pipe mode

echo "hello" | pilotctl connect <address|hostname> [port] [--timeout <dur>]

Without `--message`, `connect` reads from stdin, sends it, and reads one response.

Trust management

handshake

pilotctl handshake <node_id|address|hostname> [justification]

Returns: `status`, `node_id`

pending

pilotctl pending

Pending requests persist across daemon restarts.

Returns: `pending` [{`node_id`, `justification`, `received_at`}]

approve

pilotctl approve <node_id>

Returns: `status`, `node_id`

reject

pilotctl reject <node_id> [reason]

Returns: `status`, `node_id`

trust

pilotctl trust

Returns: `trusted` [{`node_id`, `mutual`, `network`, `approved_at`}]

untrust

pilotctl untrust <node_id>

Returns: `node_id`

Verification & Recovery

verify

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`

recovery

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.

sign-request / verify-request

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.

Webhooks

set-webhook

pilotctl set-webhook <url>

Persists to config and applies immediately to a running daemon.

Returns: `webhook`, `applied` (bool)

clear-webhook

pilotctl clear-webhook

Returns: `webhook`, `applied` (bool)

Tags

Discovery tags are in the `extras` tier.

set-tags

pilotctl extras set-tags <tag1> [tag2] [tag3]

Maximum 3 tags. Each tag is validated by the registry.

Returns: `node_id`, `tags`

clear-tags

pilotctl extras clear-tags

Returns: `tags` (empty array)

Mailbox

received

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`

inbox

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.

Networks

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.

network list

pilotctl network list

Lists all networks the node is a member of.

Returns: `networks` [{`id`, `name`, `join_rule`, `members`}]

network join

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.

network leave

pilotctl network leave <network_id>

Leave a network.

network members

pilotctl network members <network_id>

Returns: `nodes` [{`node_id`, `hostname`, `public`}]

network invite

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.

network invites

pilotctl network invites

List pending invitations addressed to this node.

Returns: `invites` [{`network_id`, `inviter_id`, `timestamp`}]

network accept

pilotctl network accept <network_id>

Accept a pending invite and join the network.

network reject

pilotctl network reject <network_id>

Decline a pending invite.

network create / delete / rename

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.

network promote / demote / kick / role

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.

network policy

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

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

App Store

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.

catalogue

pilotctl appstore catalogue

Lists apps available for install. Alias: `catalog`.

view

pilotctl appstore view <id> [--all-changelog]

Shows app details, including description, vendor, changelog, size, source URL, license, methods, and permissions.

install

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

outdated / 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.

list

pilotctl appstore list

Lists installed apps and the IPC methods each exposes.

call

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

status / caps / audit / actions

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.

restart / uninstall / verify

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.

gen-key / sign / sign-catalogue

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

Diagnostics

health

pilotctl health

Quick daemon health check.

Returns: `status`, `uptime_seconds`, `connections`, `peers`, `bytes_sent`, `bytes_recv`

ping

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)

traceroute

pilotctl traceroute <address> [--timeout <dur>]

Returns: `target`, `setup_ms`, `rtt_samples` [{`rtt_ms`, `bytes`}]

bench

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`

peers

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)

connections

pilotctl connections

Returns: `connections` [{`id`, `local_port`, `remote_addr`, `remote_port`, `state`, `cong_win`, `in_flight`, `srtt_ms`, `unacked`, `ooo_buf`, `peer_recv_win`, `recv_win`}], `total`

disconnect

pilotctl disconnect <conn_id>

Returns: `conn_id`

prefer-direct

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`

Rule-based Networks

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

managed status

pilotctl extras managed status [--net <id>]

Show the rule-based network state for this node. `--net 0` (the default) returns the global view.

managed cycle

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`

managed reconcile

pilotctl extras managed reconcile --net <id>

Poll the registry and refresh local peer state for one rule-based network. `--net` is required.

Returns: `peers`

Member Tags

Per-member metadata tags inside a network. Distinct from the node-level `set-tags` command.

member-tags set

pilotctl extras member-tags set --net <id> --node <id> --tags tag1,tag2

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

member-tags get

pilotctl extras member-tags get --net <id> [--node <id>]

Read member tags. Omit `--node` to dump every member's tags in the network.

Network Policies

Expression policies for automating network behavior, under `extras`.

policy get

pilotctl extras policy get --net <id>

Retrieve the active policy for a network.

policy set

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.

policy validate

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.

policy test

pilotctl extras policy test --file <path> --event '<json>'

Test a policy against a simulated event. Returns whether the event would be allowed or denied.

Registry admin (self-hosted)

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.

audit

pilotctl extras audit [--network <id>]

Queries the audit trail for a network (default: backbone network 0). Requires admin token. Returns: `entries`

audit-export

pilotctl extras audit-export <get|set|disable> [options]

Configure external audit log export. Subcommands:

Requires admin token.

provision

pilotctl extras provision <blueprint.json>

Provision a network from a JSON blueprint file. Requires admin token.

deprovision

pilotctl extras deprovision <network-name>

Look up a network by name and delete it. Requires admin token.

provision-status

pilotctl extras provision-status

Shows provisioning status. Requires admin token.

idp

pilotctl extras idp <get|set> [options]

Get or set the identity provider configuration. Subcommands:

Requires admin token.

directory-sync

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.

directory-status

pilotctl extras directory-status <network_id>

Shows directory sync status for a network. Requires admin token.

Registry

register

pilotctl register [listen_addr]

Returns: `node_id`, `address`, `public_key`

lookup

pilotctl lookup <node_id>

Returns: `node_id`, `address`, `hostname`, `public`, `networks`, `last_seen_unix`, `type`, `version` (real endpoints are redacted client-side).

deregister

pilotctl deregister

Routes through daemon (signed). Returns: `status`

rotate-key

pilotctl rotate-key

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

set-public / set-private

pilotctl set-public
pilotctl set-private

Toggles whether this node appears in the public directory. Returns: `node_id`, `visibility`

trusted

pilotctl trusted list

Lists nodes in the embedded trusted-agents directory that are auto-approved on first contact. Different from `pilotctl trust`, which shows live trust state.

Meta

version

pilotctl version

Prints the build version string.

update

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.

review

pilotctl review <pilot|app-id> [--rating <1-5>] [--text "..."]

Submit a rating and/or written review for Pilot itself or for an installed app.

updates

pilotctl updates [--count <n>] [--scope <name>]

Reads the published changelog feed and prints recent entries (default: 10). `--scope` filters by tag.

skills

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

Gateway

gateway start

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.

gateway stop

pilotctl extras gateway stop

Not supported from pilotctl. Stop `pilot-gateway` directly.

gateway map

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.

gateway unmap

pilotctl extras gateway unmap <local-ip>

Not supported from pilotctl. Unmapping is owned by the `pilot-gateway` process.

gateway list

pilotctl extras gateway list

Returns an empty mapping list with a note. Live mappings are held inside the `pilot-gateway` process.

Related