From 417333b3db7c85730200a9cf281b2e80a038b38b Mon Sep 17 00:00:00 2001 From: Alexgodoroja Date: Thu, 16 Jul 2026 16:35:03 -0700 Subject: [PATCH 1/2] install.sh: make injection reach non-systemd hosts + headless installs Served copy of the installer fix. On hosts with systemctl but no booted systemd (containers/WSL/CI), 'sudo systemctl daemon-reload' aborted the script under set -e before the skill-injection first-pass; headless installs also died at the /dev/tty email prompt. - Gate systemd on [ -d /run/systemd/system ]; guard daemon-reload with || true; add a non-systemd fallthrough printing 'pilotctl daemon start'. - Prompt for email only when a TTY is present; otherwise continue and let the daemon auto-synthesize a @nodes.pilotprotocol.network identity. Document PILOT_EMAIL for headless installs. - Correct per-tool skill paths (skills/pilotctl), fix PicoClaw/OpenClaw heartbeat files, and list Goose. --- public/install.sh | 49 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/public/install.sh b/public/install.sh index 144c46fc..e10b114d 100755 --- a/public/install.sh +++ b/public/install.sh @@ -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 +# @nodes.pilotprotocol.network identity. # # WHAT THIS SCRIPT DOES (read before piping to sh): # 1. Detects OS/arch (Linux/Darwin × amd64/arm64) @@ -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 @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 @@ -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 @@ -587,7 +601,10 @@ 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" @@ -595,6 +612,12 @@ USVC 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 @@ -757,15 +780,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" From b3e0984bb9441551b7cad1efa1c74aa87a751f88 Mon Sep 17 00:00:00 2001 From: Alexgodoroja Date: Thu, 16 Jul 2026 17:19:26 -0700 Subject: [PATCH 2/2] install.sh: replace terse get-started with a full GET STARTED walkthrough The post-install hint was four lines (PATH, daemon start, info, ping) and never showed an agent the two things it actually needs on a fresh node: the send-with --wait / read-newest-inbox idiom, and the three ways to get value (pilot-director for live data, list-agents for known specialists, appstore for local capabilities), plus the peers/trust half. Replace it with a numbered GET STARTED block covering all of it, with copy-paste examples. Ends by pointing at the full operator manual (the injected SKILL.md). --- public/install.sh | 108 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 5 deletions(-) diff --git a/public/install.sh b/public/install.sh index e10b114d..95345c0c 100755 --- a/public/install.sh +++ b/public/install.sh @@ -750,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 " +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 --data '' --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 --data '/help' --wait + pilotctl send-message --data '/data {"":""}' --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 .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 — + .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 "" # request trust + pilotctl pending # incoming requests waiting on you + pilotctl approve # accept one + pilotctl trust # confirm mutual trust + pilotctl send-message --data '' # talk, once trust is mutual + pilotctl send-file /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