Skip to content

Commit 5ab963f

Browse files
AlexgodorojaAlex Godorojateovl
authored
fix(pilotctl): surface app store + missing commands in help and context (#332)
* fix(pilotctl): surface app store + missing commands in help and context pilotctl help (usage), the JSON `context` catalog, and per-command --help had drifted from the dispatch table: - usage() listed set-tags/clear-tags/gateway as core commands, but they are extras-gated and error out when invoked that way. - usage() and context omitted the entire `appstore` family plus review, verify, recovery, prefer-direct, trusted, and the extras tier. - verify/recovery/prefer-direct/quickstart had no per-command help. Realign all three surfaces with the actual commands: - usage(): drop the extras-gated entries from core; add App Store, Updates, and Identity & recovery sections, trusted/prefer-direct, and an operator/admin pointer to `pilotctl extras` / `pilotctl context`. - context: bump to v1.4, add an `app_store` section (15 subcommands) and core entries for quickstart/update/updates/skills/review/verify/ recovery/prefer-direct/trusted. - commandHelp: add entries for verify/recovery/prefer-direct/quickstart. go build, go vet, and go test ./cmd/pilotctl all pass. * docs: regenerate cli-reference.md from updated pilotctl --help * fix(pilotctl): move diagnostic commands to the bottom of help * fix(daemon): bind IPC socket via private dir to end umask test race IPCServer.Start flipped the process-global syscall.Umask around net.Listen to create the socket at 0600. umask is process-wide rather than goroutine-scoped, so under parallel tests a concurrent t.TempDir() inherited the restrictive 0177 mask and its nested mkdir failed with "permission denied", flaking the pkg/daemon suite on both runners. Bind the socket inside a freshly-created private 0700 staging dir, chmod it 0600, then atomically rename it onto the published path. This closes the same TOCTOU window without ever touching the global umask. --------- Co-authored-by: Alex Godoroja <alex@vulturelabs.io> Co-authored-by: Teodor Calin <teodor@vulturelabs.io>
1 parent 4f97fb4 commit 5ab963f

3 files changed

Lines changed: 209 additions & 49 deletions

File tree

cmd/pilotctl/main.go

Lines changed: 147 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,49 @@ Reliability caveats (current implementation):
13521352
"appstore": AppStoreHelpText,
13531353

13541354
"review": reviewHelpText,
1355+
1356+
"quickstart": `Usage: pilotctl quickstart
1357+
1358+
Guided 3-step getting-started walkthrough. Re-run after each step — it detects
1359+
whether the daemon is already running and points you to the next action:
1360+
1. start the daemon pilotctl daemon start
1361+
2. discover specialists pilotctl send-message list-agents --data '...' --wait
1362+
3. handshake + query one pilotctl handshake <hostname> ; pilotctl send-message ...
1363+
`,
1364+
1365+
"verify": `Usage: pilotctl verify [status] [flags]
1366+
1367+
Verified-address badges — prove a node controls a real, attested identity.
1368+
1369+
pilotctl verify show this node's verification state (alias: verify status)
1370+
pilotctl verify status [--node <id>] check a specific node's badge
1371+
pilotctl verify --provider <name> run a device-flow to become verified
1372+
pilotctl verify --badge <b> --badge-sig <s> submit a badge you already hold
1373+
pilotctl verify --from <file> submit a badge from a JSON credential file
1374+
1375+
Badges are verified OFFLINE against the pinned issuer key — the registry's word
1376+
is never trusted.
1377+
`,
1378+
1379+
"recovery": `Usage: pilotctl recovery <enroll|new-key|recover> ...
1380+
1381+
Recover a node's address if its identity key is lost.
1382+
1383+
pilotctl recovery enroll record an opaque recovery commitment for this address
1384+
pilotctl recovery new-key rotate to a fresh identity key
1385+
pilotctl recovery recover reclaim the address using recovery material
1386+
1387+
Enrollment + signatures come from the 'pilot-verify' tool.
1388+
`,
1389+
1390+
"prefer-direct": `Usage: pilotctl prefer-direct <node_id|address|hostname>
1391+
1392+
Reset routing state for a peer so the next connection prefers a direct
1393+
(hole-punched) tunnel over the relay. Useful after a relay path got pinned.
1394+
Requires daemon v1.12+.
1395+
1396+
Returns: had_tunnel, was_relay_active, was_relay_pinned
1397+
`,
13551398
}
13561399

13571400
// printCommandHelp prints the help text for a command and exits.
@@ -1406,8 +1449,7 @@ Discovery commands:
14061449
pilotctl find <hostname>
14071450
pilotctl set-hostname <hostname>
14081451
pilotctl clear-hostname
1409-
pilotctl set-tags <tag1> [tag2] ...
1410-
pilotctl clear-tags
1452+
(discovery tags are operator setup: pilotctl extras set-tags / clear-tags)
14111453
14121454
Communication commands:
14131455
pilotctl connect <address|hostname> [port] [--message <msg>] [--timeout <dur>]
@@ -1424,7 +1466,15 @@ Trust commands:
14241466
pilotctl reject <node_id> [reason]
14251467
pilotctl untrust <node_id>
14261468
pilotctl pending
1427-
pilotctl trust
1469+
pilotctl trust [--search <substr>] live trust state (peers you trust)
1470+
pilotctl trusted embedded directory of auto-approved service agents
1471+
pilotctl prefer-direct <node_id|address|hostname> prefer a direct tunnel over the relay (daemon v1.12+)
1472+
1473+
Identity & recovery:
1474+
pilotctl verify [status] show this node's verified-address badge state
1475+
pilotctl verify --provider <name> run a device-flow to get a verified-address badge
1476+
pilotctl recovery <enroll|new-key|recover> ... enroll / rotate / reclaim the address if the key is lost
1477+
pilotctl review <pilot|app-id> [--rating <1-5>] [--text "..."] rate Pilot or an installed app
14281478
14291479
Management commands:
14301480
pilotctl connections
@@ -1437,6 +1487,28 @@ Mailbox:
14371487
Service Agents:
14381488
pilotctl send-message list-agents --data "list all agents"
14391489
1490+
Agent tool discovery:
1491+
pilotctl context
1492+
pilotctl skills [status] show where the daemon installs SKILL.md per detected agent tool
1493+
pilotctl skills paths print only the install paths (shell-friendly)
1494+
pilotctl skills check run one reconcile pass right now
1495+
1496+
App store (install + call local capability apps; full help: pilotctl appstore help):
1497+
pilotctl appstore catalogue list apps available for one-command install
1498+
pilotctl appstore view <id> [--all-changelog] app detail page (description, methods, permissions)
1499+
pilotctl appstore install <app-id> [--force] install by catalogue ID (fetch + verify + extract)
1500+
pilotctl appstore list list installed apps + their IPC methods
1501+
pilotctl appstore call <id> <method> [json-args] dispatch an IPC call into an app
1502+
pilotctl appstore status|caps|audit|restart|uninstall <id>
1503+
1504+
Updates:
1505+
pilotctl update [status|enable|disable] [--pin <tag>] self-update (auto-update OFF by default)
1506+
pilotctl updates [--count <n>] [--scope <scope>] read the Pilot changelog feed
1507+
1508+
Operator / admin (run 'pilotctl extras' or 'pilotctl context' for the full list):
1509+
pilotctl extras <cmd> network / managed / policy / member-tags / enterprise / low-level plumbing
1510+
pilotctl extras gateway start|stop|map|unmap|list IP gateway (requires root for ports <1024)
1511+
14401512
Diagnostic commands:
14411513
pilotctl info
14421514
pilotctl health
@@ -1446,21 +1518,6 @@ Diagnostic commands:
14461518
pilotctl bench <address|hostname> [size_mb] [--timeout <dur>]
14471519
pilotctl listen <port> [--count <n>] [--timeout <dur>]
14481520
pilotctl broadcast <network_id> <message>
1449-
pilotctl update [--pin <tag>] run the updater once — check and install new release
1450-
pilotctl updates [--count <n>] [--scope <scope>] read https://pilot-protocol.github.io/pilot-changelog/feed.xml
1451-
1452-
Agent tool discovery:
1453-
pilotctl context
1454-
pilotctl skills [status] show where the daemon installs SKILL.md per detected agent tool
1455-
pilotctl skills paths print only the install paths (shell-friendly)
1456-
pilotctl skills check run one reconcile pass right now
1457-
1458-
Gateway (requires root for ports <1024):
1459-
pilotctl gateway start [--subnet <cidr>] [--ports <list>] [<pilot-addr>...]
1460-
pilotctl gateway stop
1461-
pilotctl gateway map <pilot-addr> [local-ip]
1462-
pilotctl gateway unmap <local-ip>
1463-
pilotctl gateway list
14641521
14651522
Environment:
14661523
PILOT_REGISTRY Registry address (default: 34.71.57.205:9000)
@@ -1996,8 +2053,8 @@ func cmdConfig(args []string) {
19962053
// serve single-command lookups without holding the map at package scope.
19972054
func contextCatalog() map[string]interface{} {
19982055
return map[string]interface{}{
1999-
"version": "1.3",
2000-
"note": "Core commands cover everything an agent needs. Use 'pilotctl extras <cmd>' for operator/admin operations. 'pilot-gateway' is a separate installed binary.",
2056+
"version": "1.4",
2057+
"note": "Core commands cover everything an agent needs. 'app_store' lists the 'pilotctl appstore <sub>' command family (install + call local capability apps). Use 'pilotctl extras <cmd>' for operator/admin operations. 'pilot-gateway' is a separate installed binary.",
20012058

20022059
// ── Core agent commands ──────────────────────────────────────────────
20032060
"commands": map[string]interface{}{
@@ -2017,6 +2074,31 @@ func contextCatalog() map[string]interface{} {
20172074
"description": "Print the installed binary version",
20182075
"returns": "version string",
20192076
},
2077+
"quickstart": map[string]interface{}{
2078+
"args": []string{},
2079+
"description": "Guided 3-step getting-started walkthrough (start daemon → discover agents → handshake + query). Re-run after each step; detects daemon state",
2080+
"returns": "quickstart [{step, title, command, description, done}]",
2081+
},
2082+
"update": map[string]interface{}{
2083+
"args": []string{"[status|enable|disable]", "[--pin <tag>]"},
2084+
"description": "Self-update. Bare: run the updater once (check + install latest). Subcommands: status, enable, disable (auto-update is OFF by default)",
2085+
"returns": "updated (bool), from, to | enabled (bool)",
2086+
},
2087+
"updates": map[string]interface{}{
2088+
"args": []string{"[--count <n>]", "[--scope <scope>]"},
2089+
"description": "Read the Pilot changelog feed (release notes / important updates)",
2090+
"returns": "entries [{title, scope, published, summary}]",
2091+
},
2092+
"skills": map[string]interface{}{
2093+
"args": []string{"[status|paths|check|enable|disable|set-mode <mode>]"},
2094+
"description": "Manage SKILL.md install across detected agent tools. status (default), paths (shell-friendly), check (reconcile once), enable/disable, set-mode",
2095+
"returns": "tools [{tool, path, installed}] | paths",
2096+
},
2097+
"review": map[string]interface{}{
2098+
"args": []string{"<pilot|app-id>", "[--rating <1-5>]", "[--text <\"...\">]"},
2099+
"description": "Submit a rating/review for Pilot itself ('pilot') or for an installed app (e.g. io.pilot.cosift)",
2100+
"returns": "ok, target, rating",
2101+
},
20202102

20212103
// Daemon lifecycle
20222104
"daemon start": map[string]interface{}{
@@ -2108,6 +2190,28 @@ func contextCatalog() map[string]interface{} {
21082190
"description": "Revoke trust for a peer",
21092191
"returns": "node_id",
21102192
},
2193+
"trusted": map[string]interface{}{
2194+
"args": []string{},
2195+
"description": "List the embedded trusted-agents directory — well-known service agents (list-agents, weather, etc.) auto-approved on first contact. For live trust state use 'trust'",
2196+
"returns": "agents [{hostname, node_id}], total",
2197+
},
2198+
"prefer-direct": map[string]interface{}{
2199+
"args": []string{"<node_id|address|hostname>"},
2200+
"description": "Reset routing state for a peer so the next connection prefers a direct (hole-punched) tunnel over the relay. Requires daemon v1.12+",
2201+
"returns": "had_tunnel, was_relay_active, was_relay_pinned",
2202+
},
2203+
2204+
// Identity verification & key recovery
2205+
"verify": map[string]interface{}{
2206+
"args": []string{"[status]", "[--node <addr|id>]", "[--provider <name>]", "[--badge <b> --badge-sig <s>]", "[--from <file>]"},
2207+
"description": "Verified-address badges. Bare 'verify' (or 'verify status') shows your verification state; '--provider <name>' runs a device-flow to get verified; '--badge/--badge-sig' (or '--from <file>') submits a badge",
2208+
"returns": "verified (bool), node_id, address, issuer | submitted",
2209+
},
2210+
"recovery": map[string]interface{}{
2211+
"args": []string{"<enroll|new-key|recover>", "..."},
2212+
"description": "Address recovery if the identity key is lost. enroll: record a recovery commitment; new-key: rotate to a fresh key; recover: reclaim the address with recovery material",
2213+
"returns": "status, node_id, address",
2214+
},
21112215

21122216
// Networks (agent self-management)
21132217
"network list": map[string]interface{}{
@@ -2236,6 +2340,29 @@ func contextCatalog() map[string]interface{} {
22362340
},
22372341
},
22382342

2343+
// ── App store ────────────────────────────────────────────────────────
2344+
// Invoke as: pilotctl appstore <subcommand> [args...]
2345+
"app_store": map[string]interface{}{
2346+
"description": "Install and call local capability apps that run on the daemon as typed IPC services. All invoked as: pilotctl appstore <subcommand>. Install root: $PILOT_APPSTORE_ROOT or ~/.pilot/apps.",
2347+
"subcommands": map[string]interface{}{
2348+
"catalogue": map[string]interface{}{"args": []string{}, "description": "List apps available for one-command install (alias: catalog)"},
2349+
"view": map[string]interface{}{"args": []string{"<id>", "[--all-changelog]"}, "description": "Detail page: description, vendor, changelog, size, source, methods, permissions (installed or not)"},
2350+
"install": map[string]interface{}{"args": []string{"<app-id> [--force]", "| <bundle-dir> --local [--force]"}, "description": "Install by catalogue ID (fetch + verify + extract), or sideload a local bundle with --local"},
2351+
"list": map[string]interface{}{"args": []string{}, "description": "List installed apps and the IPC methods each exposes"},
2352+
"call": map[string]interface{}{"args": []string{"<id>", "<method>", "[json-args]", "[--timeout <dur>]"}, "description": "Dispatch an IPC call into an app (default timeout 120s; $PILOT_APPSTORE_CALL_TIMEOUT)"},
2353+
"status": map[string]interface{}{"args": []string{"<id>"}, "description": "Deep-dive on one app's pinned state"},
2354+
"caps": map[string]interface{}{"args": []string{"<id>"}, "description": "Show the manifest's spend caps and current rolling-window usage"},
2355+
"audit": map[string]interface{}{"args": []string{"<id>", "[--tail <n>]", "[--event <name>]", "[--since <dur>]"}, "description": "Show the supervisor lifecycle log (spawn/exit/suspend/verify-fail)"},
2356+
"actions": map[string]interface{}{"args": []string{"[--tail <n>]", "[--event <name>]"}, "description": "Show the pilotctl-side install/uninstall action log (survives app removal)"},
2357+
"restart": map[string]interface{}{"args": []string{"<id>"}, "description": "Clear crash-loop suspension and respawn the app"},
2358+
"uninstall": map[string]interface{}{"args": []string{"<id>", "--yes"}, "description": "Remove an installed app from the install root"},
2359+
"verify": map[string]interface{}{"args": []string{"<bundle-dir>"}, "description": "sha256-check a pre-install bundle against its manifest"},
2360+
"gen-key": map[string]interface{}{"args": []string{"<key-file>"}, "description": "Generate a fresh ed25519 publisher keypair (publisher tooling)"},
2361+
"sign": map[string]interface{}{"args": []string{"--key <key-file>", "<manifest>"}, "description": "Sign (or re-sign) a manifest's store.signature (publisher tooling)"},
2362+
"sign-catalogue": map[string]interface{}{"args": []string{"--key <key-file>", "<catalogue.json>"}, "description": "Sign the catalogue, writing a detached .sig (alias: sign-catalog; publisher tooling)"},
2363+
},
2364+
},
2365+
22392366
// ── Extras (operator / admin) ────────────────────────────────────────
22402367
// Invoke as: pilotctl extras <command> [args...]
22412368
"extras": map[string]interface{}{

docs/cli-reference.md

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ Discovery commands:
3636
pilotctl find <hostname>
3737
pilotctl set-hostname <hostname>
3838
pilotctl clear-hostname
39-
pilotctl set-tags <tag1> [tag2] ...
40-
pilotctl clear-tags
39+
(discovery tags are operator setup: pilotctl extras set-tags / clear-tags)
4140
4241
Communication commands:
4342
pilotctl connect <address|hostname> [port] [--message <msg>] [--timeout <dur>]
@@ -54,7 +53,15 @@ Trust commands:
5453
pilotctl reject <node_id> [reason]
5554
pilotctl untrust <node_id>
5655
pilotctl pending
57-
pilotctl trust
56+
pilotctl trust [--search <substr>] live trust state (peers you trust)
57+
pilotctl trusted embedded directory of auto-approved service agents
58+
pilotctl prefer-direct <node_id|address|hostname> prefer a direct tunnel over the relay (daemon v1.12+)
59+
60+
Identity & recovery:
61+
pilotctl verify [status] show this node's verified-address badge state
62+
pilotctl verify --provider <name> run a device-flow to get a verified-address badge
63+
pilotctl recovery <enroll|new-key|recover> ... enroll / rotate / reclaim the address if the key is lost
64+
pilotctl review <pilot|app-id> [--rating <1-5>] [--text "..."] rate Pilot or an installed app
5865
5966
Management commands:
6067
pilotctl connections
@@ -67,6 +74,28 @@ Mailbox:
6774
Service Agents:
6875
pilotctl send-message list-agents --data "list all agents"
6976
77+
Agent tool discovery:
78+
pilotctl context
79+
pilotctl skills [status] show where the daemon installs SKILL.md per detected agent tool
80+
pilotctl skills paths print only the install paths (shell-friendly)
81+
pilotctl skills check run one reconcile pass right now
82+
83+
App store (install + call local capability apps; full help: pilotctl appstore help):
84+
pilotctl appstore catalogue list apps available for one-command install
85+
pilotctl appstore view <id> [--all-changelog] app detail page (description, methods, permissions)
86+
pilotctl appstore install <app-id> [--force] install by catalogue ID (fetch + verify + extract)
87+
pilotctl appstore list list installed apps + their IPC methods
88+
pilotctl appstore call <id> <method> [json-args] dispatch an IPC call into an app
89+
pilotctl appstore status|caps|audit|restart|uninstall <id>
90+
91+
Updates:
92+
pilotctl update [status|enable|disable] [--pin <tag>] self-update (auto-update OFF by default)
93+
pilotctl updates [--count <n>] [--scope <scope>] read the Pilot changelog feed
94+
95+
Operator / admin (run 'pilotctl extras' or 'pilotctl context' for the full list):
96+
pilotctl extras <cmd> network / managed / policy / member-tags / enterprise / low-level plumbing
97+
pilotctl extras gateway start|stop|map|unmap|list IP gateway (requires root for ports <1024)
98+
7099
Diagnostic commands:
71100
pilotctl info
72101
pilotctl health
@@ -76,21 +105,6 @@ Diagnostic commands:
76105
pilotctl bench <address|hostname> [size_mb] [--timeout <dur>]
77106
pilotctl listen <port> [--count <n>] [--timeout <dur>]
78107
pilotctl broadcast <network_id> <message>
79-
pilotctl update [--pin <tag>] run the updater once — check and install new release
80-
pilotctl updates [--count <n>] [--scope <scope>] read https://pilot-protocol.github.io/pilot-changelog/feed.xml
81-
82-
Agent tool discovery:
83-
pilotctl context
84-
pilotctl skills [status] show where the daemon installs SKILL.md per detected agent tool
85-
pilotctl skills paths print only the install paths (shell-friendly)
86-
pilotctl skills check run one reconcile pass right now
87-
88-
Gateway (requires root for ports <1024):
89-
pilotctl gateway start [--subnet <cidr>] [--ports <list>] [<pilot-addr>...]
90-
pilotctl gateway stop
91-
pilotctl gateway map <pilot-addr> [local-ip]
92-
pilotctl gateway unmap <local-ip>
93-
pilotctl gateway list
94108
95109
Environment:
96110
PILOT_REGISTRY Registry address (default: 34.71.57.205:9000)

0 commit comments

Comments
 (0)