Flow

CLI Reference

Complete reference for pilotctl. All commands support --json for structured output.

Global flags

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

Use --json with any command for structured output:

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 (start the daemon, discover agents, handshake + query). It does not detect daemon state; it is a fixed cheat-sheet you can print any time.

Step 1 — Start the daemon

pilotctl daemon start

The banner shows the pilotctl daemon start command in its first-time-setup footer. (The steps below expand each line of the banner.)

Step 2 — Discover specialist agents

pilotctl send-message list-agents     --data '/data {"search":"","limit":10}' --wait

Queries the public directory for 400+ specialist agents covering weather, crypto, news, sports, transit, science, and more. Filter by keyword: weather, crypto, news, sports, joke.

Step 3 — Handshake + query a specialist

pilotctl send-message <hostname> --data '/help' --wait
pilotctl handshake <hostname>
pilotctl send-message <hostname> --data '/data {"<filter>":"<value>"}' --wait

The canonical 3-command pattern:

  1. Learn the API: send /help to see the specialist's schema
  2. Establish trust: handshake the specialist
  3. Query it: send /data with a filter → get a structured JSON reply

--json is accepted but quickstart always prints the text banner — it 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>]
  [--admin-token <token>] [--networks <ids>]

Starts as a background process. Blocks until registered, prints status, then exits. Use --foreground to run in the current process.

Note: --email is optional. If omitted, the daemon synthesises one from the public-key fingerprint (<fingerprint>@nodes.pilotprotocol.network); when supplied, it persists to the account file ~/.pilot/account.json and is not needed on subsequent starts. --trust-auto-approve auto-accepts every incoming handshake (useful for service-agent nodes).

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); it rejects names that do not conform.

Returns: hostname, node_id

clear-hostname

pilotctl clear-hostname

Clears the user-set hostname. The node keeps a registry-assigned internal hostname (of the form pilot-XXXXXXXX) until you set a new one.

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 (signs the request). Returns: node_id, visibility

Communication

connect

pilotctl connect <address|hostname> [port] --message "<msg>" [--timeout <dur>]

Dials the target, sends the message, reads one response, 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, 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

dgram

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

Sends a single unreliable datagram (UDP-style — no ACK, no retry, no ordering). Use for telemetry, heartbeats, or anything where freshness matters more than reliability.

listen

pilotctl listen <port> [--count <n>] [--timeout <dur>]

Listens for datagrams. Without --count: 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. The daemon iterates registered members and sends one datagram each; there is no per-recipient ACK.

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: 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: reads from stdin (piped), sends it, 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 a real, attested identity. Bare verify (or verify status) shows your current state; --provider <name> runs a device-flow to become verified; --badge/--badge-sig (or --from <file>) submits a badge you already hold. Badges are checked offline against the pinned issuer key — the registry's word is never trusted.

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 an opaque recovery commitment for the address; new-key rotates to a fresh key; recover reclaims the address using recovery material. 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 your node's Ed25519 identity so a peer can verify who sent it — and verify an envelope a peer produced with sign-request. Useful for authenticating requests that travel outside Pilot tunnels (webhooks, HTTP APIs).

sign-request returns: envelope, signature, address. verify-request returns the verifier reply (valid, …) 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 operator setup, so they live in the extras tier — pilotctl set-tags on its own is rejected.

set-tags

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

Maximum 3 tags. Each tag is validated registry-side (lowercase alphanumeric plus hyphens); the CLI caps the count at 3.

Returns: node_id, tags

clear-tags

pilotctl extras clear-tags

Returns: tags (empty array)

Mailbox

received

pilotctl received [--clear]

Lists files in ~/.pilot/received/. Use --clear to delete all.

Returns: files [{name, bytes, modified, path}], total, dir

inbox

pilotctl inbox [--clear] [--full] [--latest] [--limit <n>] [--since <dur>] [read <id>]

Lists messages in ~/.pilot/inbox/. Use --clear to delete all (returns {cleared: N}); relative age and byte count are shown by default.

Returns: messages [{id, from, type, bytes, received_at, preview}], total, shown, dir. The full plain-text payload (data) replaces preview when you pass --full or --latest.

Networks

Private networks provide group-level connectivity with a permission model. See Networks for the full model.

network list

pilotctl network list

Lists all networks your node is a member of.

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

network join

pilotctl network join <network_id> [--token <token>]

Join a network. Use --token for token-gated networks.

network leave

pilotctl network leave <network_id>

Leave a network. Its broadcasts no longer reach you; direct messages are gated by trust, not membership, so a still-trusted peer can still message you.

network members

pilotctl network members <network_id>

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

network invite

pilotctl network invite <network_id> <node_id>

Invite another node to a network you belong to.

network invites

pilotctl network invites

List pending invitations from other nodes.

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]
pilotctl network delete <network_id>
pilotctl network rename <network_id> <new_name>

Create, delete, or rename a network you administer. --enterprise enables the enterprise control suite at creation time.

network promote / demote / kick / role

pilotctl network promote <network_id> <node_id>
pilotctl network demote <network_id> <node_id>
pilotctl network kick <network_id> <node_id>
pilotctl 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. The CLI path authenticates with the registry admin token (--admin-token or PILOT_ADMIN_TOKEN), not per-network RBAC roles; role needs no auth. For the RBAC role model see RBAC.

network policy

pilotctl network policy <network_id> [--max-members <n>] [--description <text>] [--allowed-ports <list>]

Get the network policy, or set it with the key flags above (membership cap, description, port whitelist). See Policies.

Service Agents

Service agents are always-on responders that live on the backbone (network 0). You discover them via list-agents, handshake the one you want, then talk to it using the same send-message primitive — the agent treats the --data payload as a typed command (e.g. /help, /data <json>, /summary) and replies into your inbox.

# 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 noaa-weather
pilotctl send-message noaa-weather --data '/help' --wait
pilotctl send-message noaa-weather --data '/data {"airport":"KSFO"}' --wait

# 3. Read the reply that --wait blocked for
jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"

The --wait [<dur>] flag (default 30s) makes send-message block until the reply lands in ~/.pilot/inbox/, which avoids racing with the file system poll. See Service Agents for the full pattern.

App Store

The app store installs local capability apps that run on your daemon as typed IPC services — JSON in, JSON out, auto-spawned on install. All subcommands are invoked as pilotctl appstore <subcommand>. The install root is $PILOT_APPSTORE_ROOT or ~/.pilot/apps. See the full App Store guide for the discover → install → call loop. Run pilotctl appstore help for the complete subcommand list.

catalogue

pilotctl appstore catalogue

Lists apps available for one-command install (alias: catalog).

view

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

App detail page — description, vendor, changelog, download/installed size, source URL, license, exposed methods, and granted permissions. Works whether or not the app is installed.

install

pilotctl appstore install <app-id> [--force]
pilotctl appstore install <bundle-dir> --local [--force]

Install by catalogue ID (fetches, verifies, and extracts), or sideload a local bundle with --local (sandboxed: fs.read/fs.write under $APP + audit.log; no net, no key.sign, no hooks). The daemon auto-spawns the app on install.

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 listing its methods, params, and latency class. Default timeout 120s (also $PILOT_APPSTORE_CALL_TIMEOUT).

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 deep-dives one app's pinned state; caps shows the manifest's spend caps and current rolling-window usage; audit shows the supervisor lifecycle log (spawn/exit/suspend/verify-fail); actions shows the pilotctl-side install/uninstall log, which survives app removal.

restart / uninstall / verify

pilotctl appstore restart <id>
pilotctl appstore uninstall <id> --yes
pilotctl appstore verify <bundle-dir>

restart clears crash-loop suspension and respawns the app; uninstall removes it from the install root; 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 an ed25519 publisher keypair, sign a manifest's store.signature so the supervisor accepts it, and sign a catalogue (writes a detached .sig pilotctl verifies on load). 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>]

Lists currently connected peers and their connection quality. The pilotctl client strips real endpoints before printing, so the output carries the relay-vs-direct breakdown rather than IP:port pairs.

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 (hole-punched) tunnel over the relay — useful after a relay path got pinned. Requires daemon v1.12+.

Returns: had_tunnel, was_relay_active, was_relay_pinned

Managed Networks

Operator commands for networks that run an automated evaluation cycle (membership pruning + vacancy filling). Three subcommands: status, cycle, reconcile.

managed status

pilotctl managed status [--net <id>]

Show managed-network status for this node. --net 0 (the default) returns the global view.

managed cycle

pilotctl managed cycle --force [--net <id>]

Force a managed-network evaluation cycle. Prunes low-scoring peers and fills vacancies. --force is required; the daemon refuses to run a cycle without it.

Returns: pruned, filled, peers

managed reconcile

pilotctl managed reconcile --net <id>

Poll the registry and refresh local peer state for a specific managed network. --net is required (must be non-zero).

Returns: peers

Member Tags

Per-member metadata tags inside a managed network. Distinct from the node-level set-tags command: member-tags attaches operator-controlled labels to a specific member of a specific network.

member-tags set

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

Set the tag list on a member. Replaces any prior value.

member-tags get

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

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

Network Policies

Local policy engine for automating network behavior.

policy get

pilotctl policy get --net <id>

Retrieve the active policy for a network.

policy set

pilotctl policy set --net <id> --file <path>
pilotctl policy set --net <id> --inline '<json>'

Apply a policy document to a network. Validates and compiles locally before applying.

policy validate

pilotctl policy validate --file <path>
pilotctl policy validate --inline '<json>'

Validate a policy document without applying it. Returns rule count and compilation status.

policy test

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

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

Enterprise Admin

audit

pilotctl audit [--network <id>]

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

audit-export

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

Configure external audit log export. Subcommands:

Requires admin token.

provision

pilotctl provision <blueprint.json>

Provision a network from a JSON blueprint file. Creates the network, sets RBAC roles and policies. Requires admin token.

deprovision

pilotctl deprovision <network-name>

Look up a network by name and delete it. Combines list-networks + delete-network in one step. Requires admin token.

provision-status

pilotctl provision-status

Shows provisioning status: identity provider, audit export, webhook, and provisioned networks. Requires admin token.

idp

pilotctl idp <get|set> [options]

Get or set the identity provider configuration. Subcommands:

Requires admin token.

directory-sync

pilotctl 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 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 Ed25519 keypair for this node and re-registers it. Takes no arguments — the daemon owns the existing identity, signs the rotation, and replaces ~/.pilot/identity.json in place. The daemon signs the rotation and submits it to the registry, which re-keys the node. The old private key is destroyed; there is no rollback.

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. Peers you are already connected to are unaffected. Returns: node_id, visibility

trusted

pilotctl trusted list

Lists nodes in the embedded trusted-agents directory — well-known service agents (e.g. list-agents) that are auto-approved on first contact without requiring manual approve. 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, runs the updater once — checks for and installs the latest release. Automatic updates are off by default; enable/disable toggle the background updater and status shows the current setting and version. Distinct from updates (below), which just reads the changelog feed.

review

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

Submit a rating and/or written review for Pilot itself (pilot) or for an installed app (e.g. io.pilot.cosift).

updates

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

Reads the published Pilot Protocol changelog feed and prints recent entries (default: 10). --scope filters by tag (e.g. protocol, cli, networks).

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). 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). Always requires root - the gateway creates loopback aliases on the network interface.

gateway start execs the pilot-gateway binary (replacing the pilotctl process); it prints the gateway's own logs and returns no JSON envelope.

gateway stop

pilotctl extras gateway stop

Not supported from pilotctl — it no longer tracks the gateway process. 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, not pilotctl.