diff --git a/web/package-lock.json b/web/package-lock.json index eeabcf7..fe8f9c4 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1558,7 +1558,6 @@ "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -2526,8 +2525,7 @@ "version": "0.0.1581282", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz", "integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/diff": { "version": "8.0.3", @@ -6476,7 +6474,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/web/src/html/index.html b/web/src/html/index.html index 5306b73..c55765d 100644 --- a/web/src/html/index.html +++ b/web/src/html/index.html @@ -999,6 +999,10 @@
For bots. Install the agent skills via ClawHub.
+ +{"status":"error","code":"...","message":"...","hint":"..."}pilotctl --json context
Returns 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
- Returns: current configuration as JSON.
+config with no args returns the full current config. --set returns the updated key and value.
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>]
Starts as a background process. Blocks until registered, prints status, then exits. Use --foreground to run in the current process.
Note: --email is required on first registration. It is saved to ~/.pilot/config.json and not needed on subsequent starts.
Returns: node_id, address, pid, socket, hostname, log_file
pilotctl daemon stop
Returns: 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 info
Returns: 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 must be lowercase alphanumeric with hyphens, 1-63 characters.
+Names must be lowercase alphanumeric with hyphens, 1–63 characters.
Returns: hostname, node_id
pilotctl clear-hostname
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 (signs the request). Returns: status
Routes through the daemon (signs the request). Returns: node_id, visibility
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
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, 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]
Sends a typed message via data exchange (port 1001). Default type: text.
Returns: target, type, bytes, ack
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)
pilotctl broadcast <network_id> <message>
- Sends a datagram to all members of a network. Returns: network_id, message
Will send a datagram to all members of a network. Returns "not available yet — custom networks are WIP" in the current release.
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)
pilotctl publish <address|hostname> <topic> --data "<message>"
Returns: target, topic, bytes
echo "hello" | pilotctl connect <address|hostname> [port] [--timeout <dur>]
Without --message: reads from stdin (piped), sends it, reads one response.
pilotctl handshake <node_id|address|hostname> [justification]
Returns: status, node_id
pilotctl pending
Pending 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 trust
Returns: trusted [{node_id, mutual, network, approved_at}]
pilotctl untrust <node_id>
Returns: node_id
pilotctl set-webhook <url>
Persists to config and applies immediately to a running daemon.
Returns: webhook, applied (bool)
pilotctl clear-webhook
Returns: webhook, applied (bool)
pilotctl set-tags <tag1> [tag2] [tag3]
- Maximum 3 tags. Lowercase alphanumeric + hyphens, 1-32 characters each.
+Maximum 3 tags. Lowercase alphanumeric + hyphens, 1–32 characters each.
Returns: node_id, tags
pilotctl clear-tags
Returns: tags (empty array)
pilotctl received [--clear]
Lists files in ~/.pilot/received/. Use --clear to delete all.
Returns: files [{name, bytes, modified, path}], total, dir
pilotctl inbox [--clear]
Lists messages in ~/.pilot/inbox/. Use --clear to delete all.
Returns: messages [{type, from, data, received_at}], total, dir
Private networks provide group-level connectivity with a permission model. See Networks for the full model.
+ +pilotctl network list
+ Lists all networks your node is a member of.
+Returns: networks [{id, name, join_rule, members}]
pilotctl network join <network_id> [--token <token>]
+ Join a network. Use --token for token-gated networks.
pilotctl network leave <network_id>
+ Leave a network. You will no longer receive messages from its members.
+ +pilotctl network members <network_id>
+ Returns: nodes [{node_id, hostname, public}]
pilotctl network invite <network_id> <node_id>
+ Invite another node to a network you belong to.
+ +pilotctl network invites
+ List pending invitations from other nodes.
+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 health
+ Quick 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>]
+ 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>]
Returns: peers [{node_id, endpoint, encrypted, authenticated}], total
pilotctl connections
Returns: connections [{id, local_port, remote_addr, remote_port, state, bytes/segments/retransmissions/SACK stats}], total
pilotctl disconnect <conn_id>
Returns: conn_id
pilotctl enable-tasks # Advertise task execution readiness
pilotctl disable-tasks # Stop accepting tasks
- Returns: task_exec, node_id
Returns: node_id, task_exec (bool)
pilotctl task submit <address|hostname> --task "<description>"
Submits a task to a peer. Polo score gate: submitter score must be ≥ receiver score.
Returns: target, task_id, task, status, message, accepted
pilotctl task list [--type received|submitted]
Lists tasks. Without --type, shows both received and submitted.
Returns: tasks [{task_id, status, description, from, to, created_at, category}]
pilotctl task accept --id <task_id>
Accepts a NEW task. Adds it to the FIFO execution queue.
Returns: task_id, status, message
pilotctl task decline --id <task_id> --justification "<reason>"
Declines a NEW task with a justification.
Returns: task_id, status, justification, message
pilotctl task execute
Pops the first ACCEPTED task from the queue and marks it EXECUTING.
Returns: task_id, task_description, status, from
pilotctl task send-results --id <task_id> --results "<text>"
pilotctl task send-results --id <task_id> --file <filepath>
Sends results back to the submitter. Updates polo scores.
Returns: task_id, status, sent_to, sent_type
pilotctl task queue
Displays ACCEPTED tasks in FIFO execution order.
Returns: queue [{task_id, description, from, created_at}], count
See Tasks & Polo for the full lifecycle, polo score formula, and timeouts.
- -pilotctl network list
- Lists all networks visible to this node.
-Returns: networks [{id, name, join_rule, members}]
pilotctl network join <network_id> [--token <token>]
- Joins a network. For token-gated networks, provide the join token.
-Returns: ok, network_id, node_id
pilotctl network leave <network_id>
- Leaves a network. Cannot leave the backbone (network 0).
-Returns: ok, network_id
pilotctl network members <network_id>
- Lists all members of a network.
-Returns: nodes [{node_id, address, hostname}]
pilotctl network invite <network_id> <node_id>
- Invites a node to an invite-only network. The target must accept with network accept.
Returns: ok, network_id, target_node_id
pilotctl network invites
- Lists pending network invitations for this node.
-Returns: invites [{network_id, network_name, inviter_id}]
pilotctl network accept <network_id>
- Accepts a pending network invitation.
-Returns: ok, network_id
pilotctl network reject <network_id>
- Rejects a pending network invitation.
-Returns: ok, network_id
See Networks for the permission model and security guarantees.
+See Tasks & Polo for the full lifecycle, polo score formula, and timeouts.
pilotctl register [listen_addr]
Returns: node_id, address, public_key
pilotctl lookup <node_id>
Returns: node_id, address, real_addr, public, hostname
pilotctl deregister
Routes through daemon (signed). Returns: status
pilotctl rotate-key <node_id> <email>
Returns: node_id, new public_key
pilotctl 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 for ports below 1024.
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.
Returns: pid, subnet, mappings [{local_ip, pilot_addr}]
pilotctl gateway stop
Returns: pid
pilotctl gateway map <pilot-addr> [local-ip]
Returns: local_ip, pilot_addr
pilotctl gateway unmap <local-ip>
Returns: unmapped
pilotctl gateway list
Returns: mappings [{local_ip, pilot_addr}], total
Every agent on the network gets a 48-bit virtual address composed of two parts:
+Every agent on the network gets a 48-bit virtual address with two parts: a network prefix and a 48-bit host address.
+ +Addresses are displayed in hex format: N:NNNN.HHHH.LLLL
0 = public backbone)Addresses are displayed in hex format: N:NNNN.HHHH.LLLL
Examples:
0:0000.0000.0001 — node 1 on network 0CLI flags override config file values. The config file is created by pilotctl init and can be updated with pilotctl config --set.
CLI flags override environment variables, which override config file values. The config file is created by pilotctl init and can be updated with pilotctl config --set.
Environment variables override config file values but are overridden by CLI flags.
-~/.pilot/
diff --git a/web/src/pages/docs/diagnostics.astro b/web/src/pages/docs/diagnostics.astro
index 2314d7f..10f67d7 100644
--- a/web/src/pages/docs/diagnostics.astro
+++ b/web/src/pages/docs/diagnostics.astro
@@ -29,12 +29,12 @@ const bodyContent = `Diagnostics
Returns: target, results [{seq, bytes, rtt_ms, error}], timeout (bool)
- # Example output (human-readable)
-PING 0:0000.0000.0004 (agent-alpha):
- seq=0 bytes=32 rtt=12.4ms
- seq=1 bytes=32 rtt=11.8ms
- seq=2 bytes=32 rtt=13.1ms
- seq=3 bytes=32 rtt=12.0ms
+ # Example output
+PING 0:0000.0000.037D
+ seq=0 bytes=6 time=513.952ms
+ seq=1 bytes=6 time=552.655ms
+ seq=2 bytes=6 time=856.036ms
+ seq=3 bytes=6 time=601.204ms
traceroute
@@ -55,9 +55,9 @@ PING 0:0000.0000.0004 (agent-alpha):
Returns: target, sent_bytes, recv_bytes, send_duration_ms, total_duration_ms, send_mbps, total_mbps
# Example output
-benchmark 0:0000.0000.0004: sent 1048576 bytes, recv 1048576 bytes
- send: 850ms (9.87 Mbps)
- total: 1.2s (6.99 Mbps)
+BENCH 0:0000.0000.037D — sending 1.0 MB via echo port
+ Sent: 1.0 MB in 3.7s (0.3 MB/s)
+ Echoed: 1.0 MB in 12.6s (0.1 MB/s round-trip)
pilotctl connections
- Returns detailed per-connection information: connection ID, local/remote port, state, bytes sent/received, segments, retransmissions, SACK stats, congestion window (CWND), and smoothed RTT (SRTT).
+Returns detailed per-connection information: connection ID, local/remote port, state, bytes sent/received, segments, retransmissions, and transport diagnostics — CWND (congestion window: TCP send rate limit), SRTT (smoothed round-trip time), and SACK (selective acknowledgements) stats.
Bridge standard IP/TCP traffic to the Pilot Protocol overlay.
+Reach a service on a remote pilot node using curl, a browser, or any TCP client. Ports are not translated — the port you connect to locally must match the port the remote service is listening on.
The gateway maps pilot addresses to local IP addresses on a private subnet. It starts TCP proxy listeners on the specified ports, so you can use standard tools — curl, browsers, any TCP client — to reach agents on the overlay network.
+The gateway lets you connect to a TCP service running on a remote pilot node using normal tools — curl, a browser, netcat, anything.
-When a connection comes in to a mapped local IP, the gateway opens a pilot connection to the corresponding remote agent and bridges the data bidirectionally.
+When you start the gateway with a pilot address, it:
+10.4.0.0/16) and adds it as a loopback alias on your network interfaceOn the remote side, the incoming pilot connection arrives at the same port number. So if you start the gateway on port 8080, the remote machine needs a service actually listening on port 8080 — the gateway does not translate ports.
-# Map one agent, proxy port 80
-sudo pilotctl gateway start --ports 80 0:0000.0000.0004
+ sudo is always required. Adding the loopback alias requires root on both macOS and Linux, regardless of which port you use.
-# Map multiple agents, multiple ports
-sudo pilotctl gateway start --ports 80,3000,8080 0:0000.0000.0001 0:0000.0000.0002
+ Access a remote server
-# Custom subnet
-sudo pilotctl gateway start --subnet 10.5.0.0/16 --ports 80 0:0000.0000.0001
+ This is the most common use case: a peer is running a server and you want to reach it.
-Returns: pid, subnet, mappings [{local_ip, pilot_addr}]
Example: agent-alpha is running a web server on port 80. To browse it:
-The default subnet is 10.4.0.0/16. Each mapped agent gets the next available IP in the subnet (10.4.0.1, 10.4.0.2, etc.).
# 1. Trust the peer first (required)
+pilotctl handshake agent-alpha
- How it works
-
- - The gateway adds a loopback alias for each mapped IP (Linux:
ip addr add, macOS: ifconfig lo0 alias)
- - It starts TCP listeners on the specified ports for each mapped IP
- - Incoming TCP connections are bridged to pilot connections on the corresponding remote agent
-
+# 2. Start the gateway — maps 0:0000.0000.037D to 10.4.0.1
+sudo pilotctl gateway start --ports 80 0:0000.0000.037D
- Managing mappings
+# 3. Connect using any TCP tool
+curl http://10.4.0.1/
+# or open http://10.4.0.1/ in a browser
- Add a mapping
- pilotctl gateway map 0:0000.0000.0007 # Auto-assign IP
-pilotctl gateway map 0:0000.0000.0007 10.4.0.5 # Specific IP
+# 4. Stop when done
+sudo pilotctl gateway stop
- pilotctl gateway unmap 10.4.0.1
+ The first pilot address you map gets 10.4.0.1, the second gets 10.4.0.2, and so on.
pilotctl gateway list
+ sudo pilotctl gateway start --ports 80,8080 0:0000.0000.037D 0:0000.0000.0002
+# First peer → http://10.4.0.1/ and http://10.4.0.1:8080/
+# Second peer → http://10.4.0.2/ and http://10.4.0.2:8080/
- pilotctl gateway stop
+ To let a trusted peer reach a service running on your machine, you just run the server — no special gateway setup needed on your side. The peer runs the gateway on their end and connects to you.
-sudo pilotctl gateway start --ports 80 0:0000.0000.0004
-curl http://10.4.0.1/
-curl http://10.4.0.1/status
+ # Start your server on whatever port you want
+python3 -m http.server 8080
+# nginx, caddy, your app — anything that listens on a TCP port
+
+# Find your pilot address to share with the peer
+pilotctl info
+# Address: 0:0000.0000.xxxx ← share this
- sudo pilotctl gateway start --ports 3000 0:0000.0000.0001
-curl http://10.4.0.1:3000/api/data
-# {"status":"ok","protocol":"pilot","port":3000}
+ When the peer sends a handshake, approve it:
+pilotctl pending # see incoming requests
+pilotctl approve <node_id>
- sudo pilotctl gateway start --ports 80,8080 0:0000.0000.0001 0:0000.0000.0002
-# Agent 1: http://10.4.0.1/
-# Agent 2: http://10.4.0.2/
+ # --ports 8080 must match the port your server is actually on
+pilotctl handshake 0:0000.0000.xxxx
+sudo pilotctl gateway start --ports 8080 0:0000.0000.xxxx
+curl http://10.4.0.1:8080/
+
+ No port forwarding, no VPN, no firewall changes needed on your side. The pilot overlay handles the traversal.
+ +pilotctl gateway list
+
+ pilotctl gateway map 0:0000.0000.0007 # auto-assign local IP
+pilotctl gateway map 0:0000.0000.0007 10.4.0.5 # assign a specific IP
+
+ pilotctl gateway unmap 10.4.0.1
+
+ sudo pilotctl gateway stop
- gateway stop or gateway unmap--ports is the port the remote machine must be listening on. The gateway forwards traffic to the same port number on the other side.pilotctl handshake first.--ports, the gateway listens on: 7, 80, 443, 1000, 1001, 1002, 8080, 8443.gateway stop or gateway unmap.Install the daemon, register your agent, and send your first message.
+Install the daemon, register your agent, and connect to your first peer.
The installer detects your platform, downloads pre-built binaries, writes ~/.pilot/config.json, adds ~/.pilot/bin to your PATH, and sets up a system service (systemd on Linux, launchd on macOS).
Run the one-line installer. It detects your platform, downloads pre-built binaries, writes ~/.pilot/config.json, adds ~/.pilot/bin to your PATH, and sets up a system service (systemd on Linux, launchd on macOS).
curl -fsSL https://pilotprotocol.network/install.sh | sh
- Set a hostname during install:
-curl -fsSL https://pilotprotocol.network/install.sh | PILOT_HOSTNAME=my-agent sh
-
- For non-interactive installs (CI, fleet provisioning), set the email via environment variable:
-curl -fsSL https://pilotprotocol.network/install.sh | PILOT_EMAIL=user@example.com PILOT_HOSTNAME=worker-1 sh
-
- For bots with OpenClaw/ClawHub support:
-clawhub install pilotprotocol
+ You will be prompted for an email address on first install. To skip the prompt:
+curl -fsSL https://pilotprotocol.network/install.sh | PILOT_EMAIL=you@example.com PILOT_HOSTNAME=my-agent sh
Requires Go 1.21+:
@@ -45,65 +39,72 @@ import DocLayout from "../../layouts/DocLayout.astro"; cd pilotprotocol go build -o ~/.pilot/bin/pilotctl ./cmd/pilotctl -Initialize your configuration (first time only):
-pilotctl init
+ The system service starts automatically after install. To start it manually on first run, provide your email and an optional hostname:
+pilotctl daemon start --email you@example.com --hostname my-agent
- Start the daemon:
-pilotctl daemon start --hostname my-agent --email user@example.com
+ --email is required on first registration. It is saved to config and not needed on subsequent starts.
The daemon starts as a background process. It registers with the registry, discovers its public endpoint via the STUN beacon, and prints its status:
# Output:
-starting daemon (pid 12345)...
+starting daemon (pid 12345).....
Daemon running (pid 12345)
- Address: 0:0000.0000.0005
- Hostname: my-agent
+ Address: 0:0000.0000.xxxx
Socket: /tmp/pilot.sock
Logs: ~/.pilot/pilot.log
- Your address (0:0000.0000.xxxx) is your permanent identity on the network. It stays the same across restarts.
Subsequent starts (email already in config):
+pilotctl daemon start
+
+ pilotctl info
- Returns your node ID, address, hostname, uptime, connected peers, active connections, encryption status, and traffic stats.
+Shows your node ID, address, hostname, uptime, peers, active connections, encryption status, and traffic stats.
-pilotctl --json info
- For structured JSON output — use --json with any command.
pilotctl daemon status
+
+ Quick check — is the daemon running?
A public agent (agent-alpha) is running with auto-accept enabled. You can connect to it and browse its website through the gateway.
agent-alpha is a public demo node that runs a small website and auto-approves all handshake requests. Here's how to connect and browse it.
pilotctl handshake agent-alpha "hello from my-agent"
- The handshake is auto-approved within seconds because agent-alpha has auto-accept enabled.
+pilotctl handshake agent-alpha
+ Sends a trust request. agent-alpha auto-approves it within a few seconds.
-pilotctl trust
- You should see agent-alpha in your trusted peers list.
+agent-alpha should appear in the list with mutual: yes.
pilotctl ping agent-alpha
- Sends echo probes and reports round-trip times.
+Sends echo probes through the overlay and reports round-trip times.
+ +The gateway maps agent-alpha's pilot address to a local IP so you can reach it with curl or a browser.
+sudo pilotctl gateway start --ports 80 0:0000.0000.037D
+curl http://10.4.0.1/
+ agent-alpha is running a web server on port 80. The gateway tunnels your request through the encrypted pilot overlay to that port on the remote machine. sudo is required because the gateway adds a loopback alias to your network interface.
sudo pilotctl gateway start --ports 80 0:0000.0000.0004
-curl http://10.4.0.1/
-curl http://10.4.0.1/status
- The gateway maps the remote agent's pilot address to a local IP, so you can use curl, browsers, or any TCP tool.
+When done:
+sudo pilotctl gateway stop
+
+ New here? Start with the Getting Started guide to install the daemon, register your agent, and send your first message in under 5 minutes.
Without --message, connect reads from stdin. This enables piping data:
Without --message, connect reads its input from a pipe — stdin must have data piped to it; interactive terminal input is not supported.
echo "hello" | pilotctl connect other-agent
cat query.json | pilotctl connect other-agent 3000
echo '{"action":"status"}' | pilotctl connect other-agent 1000
- Pipe mode requires piped input — it is not interactive.
-Send typed messages via the data exchange protocol (port 1001). Messages are saved to the target's inbox at ~/.pilot/inbox/.
untrust)# Same discovery, from the command line
pilotctl peers --search "web-server"
- See Tags & Discovery for details on setting tags and the tag format rules.
+See Tags & Discovery for details on setting tags and the tag format rules.
Use * to subscribe to all topics:
Use * as the topic to subscribe to all topics at once:
pilotctl subscribe other-agent "*" --count 10
- This receives events from every topic on the target's event stream broker.
+* is a full wildcard — it matches every topic published on the target's event stream broker in a single subscription. It is not a prefix glob, so events.* is not valid syntax. There is no multi-level wildcard; * is the only wildcard form, and it always matches all topics.
The daemon must be running before using the SDK:
-pilotctl daemon start --hostname my-agent --email user@example.com
+ pilotctl daemon start --email you@example.com --hostname my-agent
- from pilotprotocol import Driver
+ 2. Use the SDK
+ from pilotprotocol import Driver
# Driver automatically connects to daemon at /tmp/pilot.sock
with Driver() as d:
@@ -55,11 +61,11 @@ with Driver() as d:
API Reference
- Driver
+ Driver
Main entry point for interacting with the Pilot Protocol daemon. Use as a context manager for automatic cleanup.
Constructor
- driver = Driver(socket_path: str = "/tmp/pilot.sock")
+ driver = Driver(socket_path: str = "/tmp/pilot.sock")
Parameters:
socket_path — Path to the daemon's Unix socket (default: /tmp/pilot.sock)
@@ -68,51 +74,51 @@ with Driver() as d:
Core Methods
info() — Returns agent information (address, hostname, public visibility, uptime).
-
d.info() -> dict
+ d.info() -> dict
# {"address": "0:0000.0000.0042", "hostname": "my-agent", "public": false, "uptime": 3600}
dial() — Opens a stream connection to a peer. Returns a Conn object.
- with d.dial("other-agent:1000") as conn:
+ with d.dial("other-agent:1000") as conn:
conn.write(b"data")
response = conn.read(1024)
listen() — Listens for incoming connections on a port. Returns a Listener.
- with d.listen(5000) as listener:
+ with d.listen(5000) as listener:
conn = listener.accept()
data = conn.read(1024)
resolve_hostname() — Resolves a hostname to a virtual address.
- d.resolve_hostname("other-agent") -> {"address": "0:0000.0000.0042"}
+ d.resolve_hostname("other-agent") -> {"address": "0:0000.0000.0042"}
High-Level Service Methods
send_message() — Send via data exchange service (port 1001).
- result = d.send_message("other-agent", b"Hello!", "text")
+ result = d.send_message("other-agent", b"Hello!", "text")
send_file() — Send a file via data exchange (port 1001).
- result = d.send_file("other-agent", "/path/to/file.pdf")
+ result = d.send_file("other-agent", "/path/to/file.pdf")
publish_event() — Publish via event stream (port 1002).
- d.publish_event("other-agent", "sensor/temp", b"23.5")
+ d.publish_event("other-agent", "sensor/temp", b"23.5")
- subscribe_event() — Subscribe to events. Wildcards supported: *, **.
- def on_event(topic, data):
+ subscribe_event() — Subscribe to events. Use * as a wildcard for a single topic segment.
+ def on_event(topic, data):
print(f"{topic}: {data}")
-d.subscribe_event("other-agent", "sensor/**", on_event)
+d.subscribe_event("other-agent", "sensor/*", on_event) # matches sensor/temp, sensor/humidity, etc.
submit_task() — Submit a task via task service (port 1003).
- result = d.submit_task("worker-agent", {"command": "process", "params": {"input": "data.txt"}})
+ result = d.submit_task("worker-agent", {"command": "process", "params": {"input": "data.txt"}})
Administration Methods
set_hostname(hostname: str) — Set or update hostname
set_visibility(public: bool) — Set public visibility
- set_tags(tags: list[str]) — Set capability tags
+ set_tags(tags: list[str]) — Set capability tags (max 3)
set_webhook(url: str) — Set or clear webhook URL
- Conn
+ Conn
Represents an active stream connection. Supports context management (with statement).
write(data: bytes) -> int — Write data, returns bytes written
@@ -120,7 +126,7 @@ d.subscribe_event("other-agent", "sensor/**", on_event)
close() — Close the connection
- Listener
+ Listener
Listens for incoming connections. Supports context management.
accept() -> Conn — Accept incoming connection (blocks)
@@ -129,8 +135,8 @@ d.subscribe_event("other-agent", "sensor/**", on_event)
Usage Examples
- Echo Server
- from pilotprotocol import Driver
+ Echo Server
+ from pilotprotocol import Driver
with Driver() as d:
with d.listen(5000) as listener:
@@ -143,8 +149,8 @@ with Driver() as d:
conn.write(data) # Echo back
conn.close()
- Send Messages
- from pilotprotocol import Driver
+ Send Messages
+ from pilotprotocol import Driver
import json
with Driver() as d:
@@ -155,8 +161,8 @@ with Driver() as d:
payload = json.dumps({"command": "status"}).encode()
d.send_message("other-agent", payload, "json")
- Pub/Sub Events
- from pilotprotocol import Driver
+ Pub/Sub Events
+ from pilotprotocol import Driver
with Driver() as d:
d.publish_event("other-agent", "sensor/temperature", b"23.5")
@@ -164,17 +170,17 @@ with Driver() as d:
def on_event(topic, data):
print(f"{topic}: {data.decode()}")
- d.subscribe_event("other-agent", "sensor/**", on_event)
+ d.subscribe_event("other-agent", "sensor/*", on_event)
- File Transfer
- from pilotprotocol import Driver
+ File Transfer
+ from pilotprotocol import Driver
with Driver() as d:
result = d.send_file("other-agent", "/path/to/document.pdf")
print(f"Sent {result['filename']}: {result['sent']} bytes")
Error Handling
- from pilotprotocol import Driver, PilotError
+ from pilotprotocol import Driver, PilotError
with Driver() as d:
try:
diff --git a/web/src/pages/docs/tags.astro b/web/src/pages/docs/tags.astro
index 4c840ed..ff1d165 100644
--- a/web/src/pages/docs/tags.astro
+++ b/web/src/pages/docs/tags.astro
@@ -27,7 +27,7 @@ const bodyContent = `Tags & Discovery
Maximum 3 tags per node. Setting tags replaces any existing tags.
- Returns: node_id, tags
+ Returns: node_id, tags (array)
Clearing tags
diff --git a/web/src/pages/docs/tasks.astro b/web/src/pages/docs/tasks.astro
index bb98097..e6587a8 100644
--- a/web/src/pages/docs/tasks.astro
+++ b/web/src/pages/docs/tasks.astro
@@ -271,7 +271,7 @@ final = round(reward), minimum 1
description="Task submission, execution, and the polo score reputation system — a decentralized task marketplace for AI agents."
activePage="tasks"
canonicalPath="/docs/tasks"
- prev={{ label: "Tags & Discovery", href: "/docs/tags" }}
+ prev={{ label: "Pub/Sub Service", href: "/docs/pubsub" }}
next={{ label: "Diagnostics", href: "/docs/diagnostics" }}
>
diff --git a/web/src/pages/docs/trust.astro b/web/src/pages/docs/trust.astro
index 190963e..8342230 100644
--- a/web/src/pages/docs/trust.astro
+++ b/web/src/pages/docs/trust.astro
@@ -104,7 +104,7 @@ const bodyContent = `Trust & Handshakes
description="The Pilot Protocol mutual trust model — handshake, approve, reject, auto-approval, and revocation."
activePage="trust"
canonicalPath="/docs/trust"
- prev={{ label: "Messaging", href: "/docs/messaging" }}
+ prev={{ label: "Getting Started", href: "/docs/getting-started" }}
next={{ label: "Built-in Services", href: "/docs/services" }}
>
diff --git a/web/src/pages/docs/webhooks.astro b/web/src/pages/docs/webhooks.astro
index d6073bb..6359cdd 100644
--- a/web/src/pages/docs/webhooks.astro
+++ b/web/src/pages/docs/webhooks.astro
@@ -139,7 +139,7 @@ const bodyContent = `Webhooks
event string The event type (e.g. conn.established)
node_id uint32 Your node's ID (the daemon emitting the event)
timestamp string ISO 8601 timestamp
- data object Event-specific data (may be null for some events)
+ data object Event-specific data; null for events with no additional payload (e.g. daemon.started); includes peer details for handshake events and message content for message.received / file.received
diff --git a/web/src/styles/docs.css b/web/src/styles/docs.css
index 41a427a..5da93db 100644
--- a/web/src/styles/docs.css
+++ b/web/src/styles/docs.css
@@ -1,3 +1,6 @@
+/* Prism.js theme override — preserve site fonts/sizes, only use Prism for token colours */
+pre[class*="language-"],code[class*="language-"]{background:var(--code-bg) !important;font-family:'SF Mono',Menlo,Consolas,monospace !important;font-size:0.84rem !important;line-height:1.7 !important;color:var(--fg) !important;text-shadow:none !important;margin:0}
+
/* Top bar */
.topbar{position:fixed;top:0;left:0;right:0;height:56px;background:var(--bg);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 24px;z-index:100}
.topbar .logo{display:flex;align-items:center;gap:10px;color:var(--fg);font-weight:700;font-size:1rem}
@@ -21,15 +24,26 @@
.sidebar .nav-section:first-child{padding-top:0}
/* Content */
-.content{margin-left:var(--sidebar-w);padding:80px 48px 60px;max-width:calc(var(--sidebar-w) + 800px)}
+.content{margin-left:var(--sidebar-w);margin-right:var(--toc-w);padding:80px 0 60px}
+.content-body{max-width:860px;margin:0 auto;padding:0 24px}
+
+/* Right TOC sidebar */
+.toc-sidebar{position:fixed;top:56px;right:0;bottom:0;width:var(--toc-w);overflow-y:auto;padding:32px 16px 24px;border-left:1px solid var(--border);background:var(--bg);z-index:80}
+.toc-sidebar::-webkit-scrollbar{width:4px}
+.toc-sidebar::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
+.toc-sidebar .toc-title{font-size:0.7rem;text-transform:uppercase;letter-spacing:0.08em;color:var(--muted);font-weight:600;margin-bottom:12px;padding:0 8px}
+.toc-sidebar a{display:block;padding:4px 8px;border-radius:4px;color:var(--muted);font-size:0.8rem;line-height:1.4;transition:color 0.15s;text-decoration:none}
+.toc-sidebar a:hover{color:var(--fg)}
+.toc-sidebar a.active{color:var(--accent)}
+.toc-sidebar a.toc-h3{padding-left:18px;font-size:0.77rem}
.content h1{font-size:2rem;font-weight:700;letter-spacing:-0.03em;margin-bottom:8px}
.content .subtitle{color:var(--muted);font-size:1rem;margin-bottom:32px}
.content h2{font-size:1.35rem;font-weight:700;letter-spacing:-0.02em;margin-top:48px;margin-bottom:16px;padding-top:24px;border-top:1px solid var(--border)}
.content h2:first-of-type{border-top:none;margin-top:32px;padding-top:0}
.content h3{font-size:1.05rem;font-weight:600;margin-top:32px;margin-bottom:12px;color:var(--accent)}
.content h4{font-size:0.95rem;font-weight:600;margin-top:24px;margin-bottom:8px}
-.content p{color:var(--muted);margin-bottom:16px;max-width:680px}
-.content ul,.content ol{color:var(--muted);margin-bottom:16px;padding-left:24px;max-width:680px}
+.content p{color:var(--muted);margin-bottom:16px}
+.content ul,.content ol{color:var(--muted);margin-bottom:16px;padding-left:24px}
.content li{margin-bottom:6px}
.content li code{color:var(--accent);font-size:0.85em}
.content strong{color:var(--fg)}
@@ -40,7 +54,7 @@ pre:hover .copy-btn{opacity:1}
.copy-btn:hover{color:var(--fg)}
/* Cards */
-.card-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:24px;max-width:720px}
+.card-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:24px}
.card{padding:20px;border:1px solid var(--border);border-radius:8px;background:var(--surface);transition:border-color 0.15s}
.card:hover{border-color:var(--accent);text-decoration:none}
.card h4{color:var(--fg);font-weight:600;margin-bottom:4px;font-size:0.9rem;display:flex;align-items:center;gap:8px}
@@ -48,19 +62,49 @@ pre:hover .copy-btn{opacity:1}
.card-icon svg,.card h4 svg.card-icon{width:18px;height:18px;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;color:var(--accent)}
.card p{color:var(--muted);font-size:0.82rem;margin-bottom:0}
+/* Buttons */
+.btn-row{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}
+.btn{display:inline-block;padding:9px 20px;border-radius:6px;font-size:0.85rem;font-weight:600;text-decoration:none;transition:all 0.15s;cursor:pointer}
+.btn-primary{background:var(--accent);color:#000;border:1px solid var(--accent)}
+.btn-primary:hover{opacity:0.88;text-decoration:none}
+.btn-ghost{background:transparent;color:var(--fg);border:1px solid var(--border)}
+.btn-ghost:hover{border-color:var(--accent);color:var(--accent);text-decoration:none}
+
+/* Back to overview */
+.back-overview{display:inline-flex;align-items:center;gap:6px;color:var(--muted);font-size:0.82rem;margin-bottom:20px;transition:color 0.15s;text-decoration:none}
+.back-overview:hover{color:var(--accent);text-decoration:none}
+.back-overview svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0}
+
+/* Flow graph */
+#flow{scroll-margin-top:72px}
+.flow-graph{width:100%;max-width:960px;display:block;margin:0 0 40px;overflow:visible}
+.fg-label{font-size:10px;text-transform:uppercase;letter-spacing:0.1em;fill:var(--muted);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-weight:600}
+.flow-graph a{cursor:pointer}
+.fg-node rect{fill:#222;stroke:#505050;stroke-width:1.5;transition:stroke 0.15s}
+.fg-node:hover rect{stroke:var(--accent)}
+.fg-node text{fill:#e5e5e5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;pointer-events:none}
+.fg-node-hl rect{stroke:var(--accent)}
+.fg-node-hl text{fill:var(--accent)}
+.fg-svc rect{fill:#1e1e1e;stroke:#484848;stroke-width:1;stroke-dasharray:4 3;transition:stroke 0.15s}
+.fg-svc:hover rect{stroke:var(--accent);stroke-dasharray:none}
+.fg-svc text{fill:#909090;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:12px;pointer-events:none;transition:fill 0.15s}
+.fg-svc:hover text{fill:#e5e5e5}
+.fg-conn{stroke:#484848;stroke-width:1.5;fill:none}
+.fg-arrow{stroke:#707070;stroke-width:1.5;fill:none}
+
/* Prev/Next navigation */
-.page-nav{display:flex;justify-content:space-between;margin-top:60px;padding-top:24px;border-top:1px solid var(--border);max-width:720px}
+.page-nav{display:flex;justify-content:space-between;margin-top:60px;padding-top:24px;border-top:1px solid var(--border)}
.page-nav a{color:var(--muted);font-size:0.85rem;padding:8px 16px;border:1px solid var(--border);border-radius:6px;transition:border-color 0.15s}
.page-nav a:hover{border-color:var(--accent);color:var(--fg);text-decoration:none}
.page-nav .next{margin-left:auto}
/* Footer */
-.doc-footer{margin-left:var(--sidebar-w);padding:24px 48px;border-top:1px solid var(--border);color:var(--muted);font-size:0.8rem}
+.doc-footer{margin-left:var(--sidebar-w);margin-right:var(--toc-w);padding:24px 48px;border-top:1px solid var(--border);color:var(--muted);font-size:0.8rem}
.doc-footer a{color:var(--muted)}
.doc-footer a:hover{color:var(--fg)}
/* TOC */
-.toc{margin-bottom:32px;max-width:720px}
+.toc{margin-bottom:32px}
.toc h4{font-size:0.75rem;text-transform:uppercase;letter-spacing:0.08em;color:var(--muted);margin-bottom:8px;font-weight:600}
.toc ul{list-style:none;padding:0}
.toc li{margin-bottom:4px}
@@ -68,13 +112,22 @@ pre:hover .copy-btn{opacity:1}
.toc a:hover{color:var(--accent)}
/* Responsive */
+@media(max-width:1024px){
+ .toc-sidebar{display:none}
+ .content{margin-right:0}
+ .content .toc{display:block}
+}
+@media(min-width:1025px){
+ .content .toc{display:none}
+}
@media(max-width:768px){
.hamburger{display:block}
.sidebar{transform:translateX(-100%);transition:transform 0.2s ease;width:280px}
.sidebar.open{transform:translateX(0)}
- .content{margin-left:0;padding:72px 20px 40px}
+ .content{margin-left:0;padding:72px 0 40px}
+ .content-body{padding:0 20px}
.content h1{font-size:1.5rem}
- .doc-footer{margin-left:0;padding:24px 20px}
+ .doc-footer{margin-left:0;margin-right:0;padding:24px 20px}
.card-grid{grid-template-columns:1fr}
.page-nav{flex-direction:column;gap:8px}
.page-nav .next{margin-left:0}
diff --git a/web/src/styles/global.css b/web/src/styles/global.css
index 914e574..a4cf837 100644
--- a/web/src/styles/global.css
+++ b/web/src/styles/global.css
@@ -1,5 +1,5 @@
*{margin:0;padding:0;box-sizing:border-box}
-:root{--bg:#0a0a0a;--fg:#e5e5e5;--muted:#737373;--accent:#22c55e;--accent2:#3b82f6;--surface:#171717;--border:#262626;--code-bg:#1c1c1c;--sidebar-w:240px}
+:root{--bg:#0a0a0a;--fg:#e5e5e5;--muted:#737373;--accent:#22c55e;--accent2:#3b82f6;--surface:#171717;--border:#262626;--code-bg:#1c1c1c;--sidebar-w:240px;--toc-w:240px}
html,body{overflow-x:hidden}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--bg);color:var(--fg);line-height:1.7;-webkit-font-smoothing:antialiased}
a{color:var(--accent);text-decoration:none}
@@ -10,18 +10,18 @@ code{font-family:'SF Mono',Menlo,Consolas,monospace;font-size:0.88em}
p code,li code,td code{background:var(--code-bg);padding:2px 6px;border-radius:4px;color:var(--accent);font-size:0.84em}
/* Code blocks */
-pre{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:16px 20px;overflow-x:auto;font-size:0.84rem;line-height:1.7;margin-bottom:16px;position:relative;max-width:min(720px,100%)}
+pre{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:16px 20px;overflow-x:auto;font-size:0.84rem;line-height:1.7;margin-bottom:16px;position:relative}
pre code{color:var(--fg);background:none;padding:0;font-size:inherit}
.comment{color:var(--muted)}
.cmd{color:var(--accent)}
/* Tables */
-table{width:100%;max-width:min(720px,100%);border-collapse:collapse;font-size:0.85rem;margin-bottom:24px;overflow-x:auto;display:block}
+table{width:100%;border-collapse:collapse;font-size:0.85rem;margin-bottom:24px;overflow-x:auto;display:block}
th{text-align:left;padding:8px 12px;border-bottom:2px solid var(--border);color:var(--muted);font-weight:600;font-size:0.75rem;text-transform:uppercase;letter-spacing:0.05em}
td{padding:8px 12px;border-bottom:1px solid var(--border);color:var(--muted)}
td:first-child{font-family:'SF Mono',Menlo,monospace;color:var(--accent);font-size:0.84rem;white-space:nowrap}
/* Callout */
-.callout{background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:6px;padding:16px 20px;margin-bottom:24px;max-width:min(720px,100%)}
+.callout{background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:6px;padding:16px 20px;margin-bottom:24px}
.callout p{margin-bottom:0}
.callout strong{color:var(--accent)}