Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 140 additions & 17 deletions public/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ set -e
# Legacy env vars (still honored, lower precedence than flags):
# PILOT_RELEASE_TAG=vX.Y.Z Same as --version.
# PILOT_RC=1 Same as --channel edge.
# PILOT_EMAIL=you@host Account-recovery email. Provide it inline for
# non-interactive/headless installs (no TTY prompt).
# If omitted headless, the daemon auto-synthesizes a
# <fingerprint>@nodes.pilotprotocol.network identity.
#
# WHAT THIS SCRIPT DOES (read before piping to sh):
# 1. Detects OS/arch (Linux/Darwin × amd64/arm64)
Expand Down Expand Up @@ -288,11 +292,21 @@ if [ -z "$EMAIL" ] && [ ! -x "$BIN_DIR/pilotctl" ]; then
EMAIL=$(grep '"email"' "$PILOT_DIR/account.json" 2>/dev/null | head -1 | cut -d'"' -f4 || true)
fi
if [ -z "$EMAIL" ]; then
printf " Email (for account recovery): "
read EMAIL < /dev/tty
# Interactive (TTY): prompt. Non-interactive (piped into a headless
# agent, no controlling terminal): do NOT block on /dev/tty — the
# daemon auto-synthesizes a <fingerprint>@nodes.pilotprotocol.network
# identity when email is empty, so a missing email must not abort.
if [ -t 0 ]; then
printf " Email (for account recovery): "
read EMAIL < /dev/tty
fi
if [ -z "$EMAIL" ]; then
echo " Error: email is required. Set PILOT_EMAIL or enter when prompted."
exit 1
if [ -t 0 ]; then
echo " Error: email is required. Set PILOT_EMAIL or enter when prompted."
exit 1
else
echo " Note: no email provided (non-interactive). Set PILOT_EMAIL= for account recovery."
fi
fi
fi
fi
Expand Down Expand Up @@ -528,7 +542,7 @@ echo "Config written to ${PILOT_DIR}/config.json"

# --- Set up system service ---

if [ "$OS" = "linux" ] && command -v systemctl >/dev/null 2>&1; then
if [ "$OS" = "linux" ] && command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
CAN_SUDO=false
if [ "$(id -u)" = "0" ] || sudo -n true 2>/dev/null; then
CAN_SUDO=true
Expand Down Expand Up @@ -587,14 +601,23 @@ WantedBy=multi-user.target
USVC
fi

sudo systemctl daemon-reload
# daemon-reload can fail on hosts where systemctl exists but systemd is
# not PID 1 (older containers, chroots). Never let it abort the install
# under `set -e` — the binaries and skill injection still matter.
sudo systemctl daemon-reload || true
echo " Service: pilot-daemon.service"
echo " Service: pilot-updater.service (auto-updates)"
echo " Start: sudo systemctl start pilot-daemon pilot-updater"
echo " Enable: sudo systemctl enable pilot-daemon pilot-updater"
else
echo " Skipped systemd setup (run as root or with passwordless sudo to enable)"
fi
elif [ "$OS" = "linux" ]; then
# systemd is not the init system here (container / WSL / CI runner).
# There is no service to install — tell the agent the portable start path
# instead of silently leaving it with no daemon.
echo "No systemd detected (container / WSL / CI) — start the daemon manually:"
echo " pilotctl daemon start"
fi

if [ "$OS" = "darwin" ]; then
Expand Down Expand Up @@ -727,12 +750,110 @@ echo " Socket: /tmp/pilot.sock"
echo " Identity: ${PILOT_DIR}/identity.json"
echo " Email: ${EMAIL}"
echo ""
echo "Get started:"
echo "============================================"
echo " GET STARTED"
echo ""
echo " export PATH=\"${BIN_DIR}:\$PATH\" # if not restarting your shell"
echo " pilotctl daemon start --hostname my-agent # email already saved"
echo " pilotctl info"
echo " pilotctl ping <other-agent>"
echo " 0) Put pilotctl on your PATH and bring the node online."
echo " ------------------------------------------------------------------"
echo " export PATH=\"${BIN_DIR}:\$PATH\" # if not restarting your shell"
cat <<'PILOT_GET_STARTED'
pilotctl daemon start --hostname my-agent # blocks until registered; email already saved
pilotctl daemon status # confirm it's running
pilotctl info # node ID, address, peer count, uptime

Reading any reply is always the same two-step idiom — send with
--wait, then read the newest inbox file's .data field:
------------------------------------------------------------------
pilotctl send-message <agent> --data '<cmd>' --wait
jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"


1) PILOT DIRECTOR — your default for anything LIVE or fresh.
Describe the whole task in plain English; it picks the specialist
agents + filters and returns a validated, ready-to-run plan.
------------------------------------------------------------------
pilotctl send-message pilot-director --data 'current weather and air quality for Berlin' --wait
jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"

# More examples — hand it the whole sentence, don't hunt for agents yourself:
pilotctl send-message pilot-director --data 'summarize this season F1 drivers' --wait
pilotctl send-message pilot-director --data 'latest CVEs for nginx' --wait
pilotctl send-message pilot-director --data "what's BTC at right now in USD" --wait


2) DISCOVER SERVICE AGENTS — when you already know the specialist you want.
list-agents is the phonebook. Search is LITERAL token match, so use
short, single-word keywords (bitcoin, weather, nba, joke, iss).
Service agents auto-approve — no handshake required.
------------------------------------------------------------------
# Browse the whole live catalogue:
pilotctl send-message list-agents --data '/data' --wait
jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"

# Search by keyword:
pilotctl send-message list-agents --data '/data {"search":"bitcoin","limit":10}' --wait
jq -r '.data | fromjson' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"

# Learn a specialist's query schema, then query it with filters:
pilotctl send-message <agent-name> --data '/help' --wait
pilotctl send-message <agent-name> --data '/data {"<filter>":"<value>"}' --wait
jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"

# Stuck? pilot-ai is the natural-language help desk (also a service agent):
pilotctl send-message pilot-ai --data 'which agent has FX rates?' --wait


3) APP STORE — install a LOCAL capability, then call it (JSON in → JSON out).
Use this to *do* something (run SQL, sandbox code, drive a browser,
enrich a contact, get a phone number) rather than look up fresh data.
------------------------------------------------------------------
# Browse — one line per app; the catalogue is your router:
pilotctl appstore catalogue

# See an app's full details (methods, source, permissions, pricing):
pilotctl appstore view io.pilot.sqlite

# Install it (daemon auto-spawns it; re-run `list` if state != ready):
pilotctl appstore install io.pilot.sqlite --force
pilotctl appstore list

# ALWAYS call <app>.help first — lists every method, its params,
# a latency class (fast <1s / med 1-5s / slow 5-30s), and cost:
pilotctl appstore call io.pilot.sqlite sqlite.help '{}'

# Then do the work — JSON in, JSON on stdout:
pilotctl appstore call io.pilot.sqlite sqlite.query '{"sql":"select 1"}'

# A few concrete capability examples (install first, then call):
pilotctl appstore install io.pilot.smol --force
pilotctl appstore call io.pilot.smol smol.push '{"image":"alpine","net":true}'

pilotctl appstore install io.pilot.bowmark --force
pilotctl appstore call io.pilot.bowmark bowmark.ask '{"site":"amazon.com","task":"search for a product"}'

pilotctl appstore install io.pilot.orthogonal --force
pilotctl appstore call io.pilot.orthogonal orthogonal.search '{"prompt":"work email for a person given name + company"}'

Cost: most apps run locally and are free. A few (orthogonal, sixtyfour,
agentphone, cloud smol) are metered against a per-user $5 budget —
<app>.help and `view` show the price, and discovery calls are free, so
check before the one call that spends.


4) PEERS & TRUST — only for peer nodes (other AIs / human-run nodes).
Service agents and apps need NO handshake; this is just the p2p half.
------------------------------------------------------------------
pilotctl handshake <hostname|node_id|address> "<reason>" # request trust
pilotctl pending # incoming requests waiting on you
pilotctl approve <node_id> # accept one
pilotctl trust # confirm mutual trust
pilotctl send-message <peer> --data '<message>' # talk, once trust is mutual
pilotctl send-file <peer> /path/to/file.tar.gz # exchange artifacts

Full operator manual & task→agent/app maps:
~/.claude/skills/pilotctl/SKILL.md
============================================
PILOT_GET_STARTED
echo ""
# pilot-gateway no longer ships in release tarballs (extracted to the
# sibling pilot-protocol/gateway repo) — only show the bridge hint when
Expand All @@ -757,15 +878,17 @@ else
fi
echo ""
echo " Per-tool target paths:"
echo " Claude Code ~/.claude/skills/pilot-protocol/SKILL.md"
echo " Claude Code ~/.claude/skills/pilotctl/SKILL.md"
echo " + heartbeat ref in ~/.claude/CLAUDE.md"
echo " OpenClaw ~/.openclaw/skills/pilot-protocol/SKILL.md"
echo " OpenClaw ~/.openclaw/skills/pilotctl/SKILL.md"
echo " + heartbeat ref in ~/.openclaw/workspace/AGENTS.md"
echo " PicoClaw ~/.picoclaw/workspace/skills/pilot-protocol/SKILL.md"
echo " + heartbeat ref in ~/.picoclaw/workspace/AGENT.md"
echo " OpenHands ~/.openhands/microagents/pilot-protocol.md (self-heartbeat)"
echo " Hermes ~/.hermes/skills/pilot-protocol/SKILL.md"
echo " PicoClaw ~/.picoclaw/workspace/skills/pilotctl/SKILL.md"
echo " + heartbeat ref in ~/.picoclaw/workspace/HEARTBEAT.md"
echo " OpenHands ~/.openhands/microagents/pilotctl.md (self-heartbeat)"
echo " Hermes ~/.hermes/skills/pilotctl/SKILL.md"
echo " + heartbeat ref in ~/.hermes/SOUL.md"
echo " Goose ~/.config/goose/skills/pilotctl/SKILL.md"
echo " + heartbeat ref in ~/.config/goose/.goosehints"
echo ""
echo " Inspect / force a refresh anytime:"
echo " pilotctl skills # status of every install path"
Expand Down
Loading