@@ -23,6 +23,10 @@ set -e
2323# Legacy env vars (still honored, lower precedence than flags):
2424# PILOT_RELEASE_TAG=vX.Y.Z Same as --version.
2525# PILOT_RC=1 Same as --channel beta.
26+ # PILOT_EMAIL=you@host Account-recovery email. Provide it inline for
27+ # non-interactive/headless installs (no TTY prompt).
28+ # If omitted headless, the daemon auto-synthesizes a
29+ # <fingerprint>@nodes.pilotprotocol.network identity.
2630#
2731# WHAT THIS SCRIPT DOES (read before piping to sh):
2832# 1. Detects OS/arch (Linux/Darwin × amd64/arm64)
@@ -341,11 +345,21 @@ if [ -z "$EMAIL" ] && [ ! -x "$BIN_DIR/pilotctl" ]; then
341345 EMAIL=$( grep ' "email"' " $PILOT_DIR /account.json" 2> /dev/null | head -1 | cut -d' "' -f4 || true)
342346 fi
343347 if [ -z " $EMAIL " ]; then
344- printf " Email (for account recovery): "
345- read EMAIL < /dev/tty
348+ # Interactive (TTY): prompt. Non-interactive (piped into a headless
349+ # agent, no controlling terminal): do NOT block on /dev/tty — the
350+ # daemon auto-synthesizes a <fingerprint>@nodes.pilotprotocol.network
351+ # identity when email is empty, so a missing email must not abort.
352+ if [ -t 0 ]; then
353+ printf " Email (for account recovery): "
354+ read EMAIL < /dev/tty
355+ fi
346356 if [ -z " $EMAIL " ]; then
347- echo " Error: email is required. Set PILOT_EMAIL or enter when prompted."
348- exit 1
357+ if [ -t 0 ]; then
358+ echo " Error: email is required. Set PILOT_EMAIL or enter when prompted."
359+ exit 1
360+ else
361+ echo " Note: no email provided (non-interactive). Set PILOT_EMAIL= for account recovery."
362+ fi
349363 fi
350364 fi
351365fi
687701
688702# --- Set up system service ---
689703
690- if [ " $OS " = " linux" ] && command -v systemctl > /dev/null 2>&1 ; then
704+ if [ " $OS " = " linux" ] && command -v systemctl > /dev/null 2>&1 && [ -d /run/systemd/system ] ; then
691705 CAN_SUDO=false
692706 if [ " $( id -u) " = " 0" ] || sudo -n true 2> /dev/null; then
693707 CAN_SUDO=true
@@ -746,14 +760,23 @@ WantedBy=multi-user.target
746760USVC
747761 fi
748762
749- sudo systemctl daemon-reload
763+ # daemon-reload can fail on hosts where systemctl exists but systemd is
764+ # not PID 1 (older containers, chroots). Never let it abort the install
765+ # under `set -e` — the binaries and skill injection still matter.
766+ sudo systemctl daemon-reload || true
750767 echo " Service: pilot-daemon.service"
751768 echo " Service: pilot-updater.service (auto-updates)"
752769 echo " Start: sudo systemctl start pilot-daemon pilot-updater"
753770 echo " Enable: sudo systemctl enable pilot-daemon pilot-updater"
754771 else
755772 echo " Skipped systemd setup (run as root or with passwordless sudo to enable)"
756773 fi
774+ elif [ " $OS " = " linux" ]; then
775+ # systemd is not the init system here (container / WSL / CI runner).
776+ # There is no service to install — tell the agent the portable start path
777+ # instead of silently leaving it with no daemon.
778+ echo " No systemd detected (container / WSL / CI) — start the daemon manually:"
779+ echo " pilotctl daemon start"
757780fi
758781
759782if [ " $OS " = " darwin" ]; then
@@ -886,12 +909,110 @@ echo " Socket: /tmp/pilot.sock"
886909echo " Identity: ${PILOT_DIR} /identity.json"
887910echo " Email: ${EMAIL} "
888911echo " "
889- echo " Get started:"
912+ echo " ============================================"
913+ echo " GET STARTED"
890914echo " "
891- echo " export PATH=\" ${BIN_DIR} :\$ PATH\" # if not restarting your shell"
892- echo " pilotctl daemon start --hostname my-agent # email already saved"
893- echo " pilotctl info"
894- echo " pilotctl ping <other-agent>"
915+ echo " 0) Put pilotctl on your PATH and bring the node online."
916+ echo " ------------------------------------------------------------------"
917+ echo " export PATH=\" ${BIN_DIR} :\$ PATH\" # if not restarting your shell"
918+ cat << 'PILOT_GET_STARTED '
919+ pilotctl daemon start --hostname my-agent # blocks until registered; email already saved
920+ pilotctl daemon status # confirm it's running
921+ pilotctl info # node ID, address, peer count, uptime
922+
923+ Reading any reply is always the same two-step idiom — send with
924+ --wait, then read the newest inbox file's .data field:
925+ ------------------------------------------------------------------
926+ pilotctl send-message <agent> --data '<cmd>' --wait
927+ jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"
928+
929+
930+ 1) PILOT DIRECTOR — your default for anything LIVE or fresh.
931+ Describe the whole task in plain English; it picks the specialist
932+ agents + filters and returns a validated, ready-to-run plan.
933+ ------------------------------------------------------------------
934+ pilotctl send-message pilot-director --data 'current weather and air quality for Berlin' --wait
935+ jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"
936+
937+ # More examples — hand it the whole sentence, don't hunt for agents yourself:
938+ pilotctl send-message pilot-director --data 'summarize this season F1 drivers' --wait
939+ pilotctl send-message pilot-director --data 'latest CVEs for nginx' --wait
940+ pilotctl send-message pilot-director --data "what's BTC at right now in USD" --wait
941+
942+
943+ 2) DISCOVER SERVICE AGENTS — when you already know the specialist you want.
944+ list-agents is the phonebook. Search is LITERAL token match, so use
945+ short, single-word keywords (bitcoin, weather, nba, joke, iss).
946+ Service agents auto-approve — no handshake required.
947+ ------------------------------------------------------------------
948+ # Browse the whole live catalogue:
949+ pilotctl send-message list-agents --data '/data' --wait
950+ jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"
951+
952+ # Search by keyword:
953+ pilotctl send-message list-agents --data '/data {"search":"bitcoin","limit":10}' --wait
954+ jq -r '.data | fromjson' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"
955+
956+ # Learn a specialist's query schema, then query it with filters:
957+ pilotctl send-message <agent-name> --data '/help' --wait
958+ pilotctl send-message <agent-name> --data '/data {"<filter>":"<value>"}' --wait
959+ jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"
960+
961+ # Stuck? pilot-ai is the natural-language help desk (also a service agent):
962+ pilotctl send-message pilot-ai --data 'which agent has FX rates?' --wait
963+
964+
965+ 3) APP STORE — install a LOCAL capability, then call it (JSON in → JSON out).
966+ Use this to *do* something (run SQL, sandbox code, drive a browser,
967+ enrich a contact, get a phone number) rather than look up fresh data.
968+ ------------------------------------------------------------------
969+ # Browse — one line per app; the catalogue is your router:
970+ pilotctl appstore catalogue
971+
972+ # See an app's full details (methods, source, permissions, pricing):
973+ pilotctl appstore view io.pilot.sqlite
974+
975+ # Install it (daemon auto-spawns it; re-run `list` if state != ready):
976+ pilotctl appstore install io.pilot.sqlite --force
977+ pilotctl appstore list
978+
979+ # ALWAYS call <app>.help first — lists every method, its params,
980+ # a latency class (fast <1s / med 1-5s / slow 5-30s), and cost:
981+ pilotctl appstore call io.pilot.sqlite sqlite.help '{}'
982+
983+ # Then do the work — JSON in, JSON on stdout:
984+ pilotctl appstore call io.pilot.sqlite sqlite.query '{"sql":"select 1"}'
985+
986+ # A few concrete capability examples (install first, then call):
987+ pilotctl appstore install io.pilot.smol --force
988+ pilotctl appstore call io.pilot.smol smol.push '{"image":"alpine","net":true}'
989+
990+ pilotctl appstore install io.pilot.bowmark --force
991+ pilotctl appstore call io.pilot.bowmark bowmark.ask '{"site":"amazon.com","task":"search for a product"}'
992+
993+ pilotctl appstore install io.pilot.orthogonal --force
994+ pilotctl appstore call io.pilot.orthogonal orthogonal.search '{"prompt":"work email for a person given name + company"}'
995+
996+ Cost: most apps run locally and are free. A few (orthogonal, sixtyfour,
997+ agentphone, cloud smol) are metered against a per-user $5 budget —
998+ <app>.help and `view` show the price, and discovery calls are free, so
999+ check before the one call that spends.
1000+
1001+
1002+ 4) PEERS & TRUST — only for peer nodes (other AIs / human-run nodes).
1003+ Service agents and apps need NO handshake; this is just the p2p half.
1004+ ------------------------------------------------------------------
1005+ pilotctl handshake <hostname|node_id|address> "<reason>" # request trust
1006+ pilotctl pending # incoming requests waiting on you
1007+ pilotctl approve <node_id> # accept one
1008+ pilotctl trust # confirm mutual trust
1009+ pilotctl send-message <peer> --data '<message>' # talk, once trust is mutual
1010+ pilotctl send-file <peer> /path/to/file.tar.gz # exchange artifacts
1011+
1012+ Full operator manual & task→agent/app maps:
1013+ ~/.claude/skills/pilotctl/SKILL.md
1014+ ============================================
1015+ PILOT_GET_STARTED
8951016echo " "
8961017# pilot-gateway no longer ships in release tarballs (extracted to the
8971018# sibling pilot-protocol/gateway repo) — only show the bridge hint when
@@ -916,15 +1037,17 @@ else
9161037fi
9171038echo " "
9181039echo " Per-tool target paths:"
919- echo " Claude Code ~/.claude/skills/pilot-protocol /SKILL.md"
1040+ echo " Claude Code ~/.claude/skills/pilotctl /SKILL.md"
9201041echo " + heartbeat ref in ~/.claude/CLAUDE.md"
921- echo " OpenClaw ~/.openclaw/skills/pilot-protocol /SKILL.md"
1042+ echo " OpenClaw ~/.openclaw/skills/pilotctl /SKILL.md"
9221043echo " + heartbeat ref in ~/.openclaw/workspace/AGENTS.md"
923- echo " PicoClaw ~/.picoclaw/workspace/skills/pilot-protocol /SKILL.md"
924- echo " + heartbeat ref in ~/.picoclaw/workspace/AGENT .md"
925- echo " OpenHands ~/.openhands/microagents/pilot-protocol .md (self-heartbeat)"
926- echo " Hermes ~/.hermes/skills/pilot-protocol /SKILL.md"
1044+ echo " PicoClaw ~/.picoclaw/workspace/skills/pilotctl /SKILL.md"
1045+ echo " + heartbeat ref in ~/.picoclaw/workspace/HEARTBEAT .md"
1046+ echo " OpenHands ~/.openhands/microagents/pilotctl .md (self-heartbeat)"
1047+ echo " Hermes ~/.hermes/skills/pilotctl /SKILL.md"
9271048echo " + heartbeat ref in ~/.hermes/SOUL.md"
1049+ echo " Goose ~/.config/goose/skills/pilotctl/SKILL.md"
1050+ echo " + heartbeat ref in ~/.config/goose/.goosehints"
9281051echo " "
9291052echo " Inspect / force a refresh anytime:"
9301053echo " pilotctl skills # status of every install path"
0 commit comments