diff --git a/src/pages/docs/app-store.astro b/src/pages/docs/app-store.astro index a65beff..184bc92 100644 --- a/src/pages/docs/app-store.astro +++ b/src/pages/docs/app-store.astro @@ -10,7 +10,9 @@ const bodyContent = `
Each method entry also carries a warm round-trip estimate the app publishes, so an agent can budget a call end-to-end (agent → daemon → app → backend → back).
+Two surfaces put the right next command in front of an agent at the two moments it is most likely to stall — right after install, and after every call. Both are authored once by the publisher and need no runtime code (see Authoring dynamic context).
+ +When pilotctl appstore install io.pilot.<app> finishes, its last step is a compact, example-first “Full usage demo”: when to reach for the app, the one call to run first, and a handful of worked examples (with per-call cost for metered apps). It turns a bare install into a correct first call. The same demo renders as the Full usage demo section on the app's store page.
After every pilotctl appstore call, pilotctl prints a short next-steps block to stderr — the recommended commands for where the agent now stands, on success and on failure. stdout stays pure JSON, so an agent can still pipe the result into jq; the hints never touch it.
A missing param renders a corrected, runnable call; a needs-signup state names the signup command; a 402 points at the balance check. For example, a sqlite.query with no database:
# stdout is the pure JSON result (here an error envelope) — the hints go to stderr:
+pilotctl appstore call io.pilot.sqlite sqlite.query '{"sql":"SELECT 42"}'
+error: ipc: server error: backend: missing required param(s): database
+next: sqlite needs an explicit database — there is no default
+ 1. pilotctl appstore call io.pilot.sqlite sqlite.query '{"database":":memory:","sql":"SELECT 42 AS a"}'
+ why: pass database (:memory: for a scratch db) — fixes the error above
+
+ Turn it off with PILOT_NEXT_STEPS=off. With --json, the hint arrives as a structured next_steps object instead of prose. A hint is never load-bearing: an absent, malformed, or unmatched graph prints nothing and leaves the call itself unchanged — no hint bug can ever turn a working call into a failed one.
The hints are driven by a small graph the app ships in the catalogue. Agents do not reinstall to get them: pilotctl lazily refreshes each app's cached graph from the catalogue in the background — a bounded check, at most about every 12 hours per app. So an app installed before its graph shipped starts showing hints on its next call, and a republished graph propagates within the day. No user action.
pilotctl appstore restart io.pilot.cosift # respawn (e.g. after writing a config.json)
@@ -91,6 +120,20 @@ const bodyContent = `App Store
Upgrades key on the version. The supervisor respawns an app when its app_version changes. Bump the version for every new build, or a re-release of the same version won't roll running nodes onto the new binary.
+ Keeping apps current
+
+ Apps are versioned in the catalogue. Two commands find and apply new versions:
+
+ pilotctl appstore outdated # installed apps with a newer — or rebuilt — catalogue version
+pilotctl appstore upgrade io.pilot.cosift # re-install one app: verified, then respawned
+pilotctl appstore upgrade --all # bring every installed app up to date
+
+ upgrade re-runs the same verified install (catalogue signature → tarball sha256 → manifest signature → publisher trust anchor), then the supervisor applies the version change and restarts the app, refusing any downgrade. outdated flags an app not only on a version bump but also as rebuilt when a same-version bundle was republished — so a fix shipped without a version change is still surfaced.
+
+
+ Auto-update. pilotctl's own updater — pilotctl update status|enable|disable — keeps the CLI and daemon current (automatic updates are off by default). With auto-update enabled, the updater also keeps installed app adapters current automatically, so you won't need to run upgrade by hand.
+
+
Building an app
An app is a binary that listens on the socket the daemon hands it and speaks the app-store IPC protocol. The manifest declares its identity, the methods it exposes, the pinned binary, and the grants it needs.
@@ -153,6 +196,57 @@ ipc.Serve(ctx, conn, d) // on the --socket the daemon su
Three integrity layers protect every install: the catalogue carries a detached ed25519 signature (a substituted app list fails), the catalogue pins each tarball sha256 (a swapped CDN byte fails), and the manifest pins the binary sha256 under an ed25519 signature (verified at scan time). The binary sha256 is re-checked immediately before every spawn.
+ Authoring dynamic context
+
+ The two runtime surfaces from Dynamic context on every call are authored once in your app's submission and validated at submit time — no runtime code, and both are carried verbatim into the catalogue.
+
+ Product demo. A product_demo block is the example-first usage guide that renders both at install and as the store page's Full usage demo: a one-sentence when_to_use, a quickstart call, 2–6 worked examples, and — for metered apps — a cost table. It is what turns an install into a first successful call.
+
+ Next-steps graph. A next_steps graph is a flat list of edges. Each edge answers one question — the agent just ran from and it went on; what now? — and names 1–3 recommended commands:
+
+ {
+ "schema": 1,
+ "app": "io.pilot.example",
+ "edges": [
+ {
+ "from": "*",
+ "on": "err",
+ "code": 402,
+ "why": "budget exhausted",
+ "then": [
+ { "cmd": "pilotctl appstore call io.pilot.example example.balance '{}'",
+ "why": "check what's left before spending again",
+ "kind": "recovery" }
+ ]
+ }
+ ]
+}
+
+
+ Field Meaning
+
+ fromthe method just called, or "*" for any
+ on"ok" (exit 0) or "err" (exit 1)
+ matchregex over the outcome payload — the error text on err, the JSON result body on ok
+ codeexact backend HTTP status; err edges only
+ whyone line of framing, printed above the steps
+ then1–3 steps, each a full pilotctl command with its own why and an optional kind (gateway / flow / recovery)
+
+
+
+ The most important case is often not an error: an app with a mandatory signup soft-fails with exit 0 and a "needs_signup": true body, so its gateway edge matches on on:"ok". The best-matching edge wins on specificity, not file order, and there is no session state — a recommendation is a pure function of (method, outcome, payload), so the app's own response is the only signal.
+
+ The authoring rules keep a graph small and trustworthy:
+
+ - Recommended, not exhaustive. A graph is not
<app>.help — name the few commands that move an agent toward value, not every method.
+ - Every step needs a
why — it is what turns a bare command into a decision.
+ - Cover the failures that end sessions — needs-signup,
402 budget, 429 quota, missing/invalid param, server-not-started — each with a corrected, runnable command.
+ - Commands must actually run. Every
cmd is copy-pasteable and complete; a recovery step that still fails is worse than silence.
+ - If your app has a mandatory first method, a
from:"*" edge must route a cold agent to it.
+
+
+ Because the graph lives in the catalogue metadata rather than the bundle, rewording or extending it is a metadata republish — no app rebuild — and existing installs pick it up automatically (see above).
+
Catalogue vs sideload
There are two install paths, with different trust:
diff --git a/src/pages/docs/cli-reference.astro b/src/pages/docs/cli-reference.astro
index 52244f4..f7790c1 100644
--- a/src/pages/docs/cli-reference.astro
+++ b/src/pages/docs/cli-reference.astro
@@ -364,7 +364,13 @@ const bodyContent = `CLI Reference
install
pilotctl appstore install <app-id> [--force]
pilotctl appstore install <bundle-dir> --local [--force]
- Install by catalogue ID (fetches, verifies, and extracts), or sideload a local bundle with --local (sandboxed: fs.read/fs.write under $APP + audit.log; no net, no key.sign, no hooks). The daemon auto-spawns the app on install.
+ Install by catalogue ID (fetches, verifies, and extracts), or sideload a local bundle with --local (sandboxed: fs.read/fs.write under $APP + audit.log; no net, no key.sign, no hooks). The daemon auto-spawns the app on install. The last step prints the app's example-first “Full usage demo” — when to use it, the first call, and a few worked examples.
+
+ outdated / upgrade
+ pilotctl appstore outdated
+pilotctl appstore upgrade <id>
+pilotctl appstore upgrade --all
+ outdated lists installed apps with a newer catalogue version, and also flags an app as rebuilt when a same-version bundle was republished. upgrade re-runs the verified install for one app (or --all) and the supervisor respawns it, refusing any downgrade. With auto-update enabled, installed app adapters are kept current automatically.
list
pilotctl appstore list
@@ -372,7 +378,7 @@ const bodyContent = `CLI Reference
call
pilotctl appstore call <id> <method> [json-args] [--timeout <dur>]
- Dispatches an IPC call into an app and prints the JSON result on stdout. Every app exposes <app>.help listing its methods, params, and latency class. Default timeout 120s (also $PILOT_APPSTORE_CALL_TIMEOUT).
+ Dispatches an IPC call into an app and prints the JSON result on stdout. Every app exposes <app>.help listing its methods, params, and latency class. Default timeout 120s (also $PILOT_APPSTORE_CALL_TIMEOUT). After each call, a short next-steps block prints to stderr (stdout stays pure JSON for jq) — the recommended follow-up commands on success or failure, e.g. a corrected call for a missing param. Disable with PILOT_NEXT_STEPS=off; --json returns a structured next_steps object instead. See Dynamic context on every call.
status / caps / audit / actions
pilotctl appstore status <id>
diff --git a/src/pages/plain/docs/app-store.astro b/src/pages/plain/docs/app-store.astro
index 828172f..067c648 100644
--- a/src/pages/plain/docs/app-store.astro
+++ b/src/pages/plain/docs/app-store.astro
@@ -1,7 +1,7 @@
---
// Auto-generated by scripts/regen-plain.mjs. Edit the marketing source and re-run.
// plain-source: src/pages/docs/app-store.astro
-// plain-source-sha256: 08112ca6201f5b87c7d8f7c79588834b2b9b60db3d55ad7c059e78d0a52750cc
+// plain-source-sha256: ee64e44b05204145d581a743c225ddfdee4adfc71ef46ccdb2130db43c90b510
import PlainLayout from '../../../layouts/PlainLayout.astro';
---
@@ -54,6 +54,22 @@ pilotctl appstore call io.pilot.cosift cosift.search '{"q":"raft consensus"
Each method entry also includes a warm round-trip estimate published by the app.
+Dynamic context on every call
+Two surfaces put the right next command in front of an agent at the two moments it is most likely to stall: right after install, and after every call. Both are authored once by the publisher and need no runtime code.
+Product demo at install
+When `pilotctl appstore install io.pilot.<app>` finishes, its last step is a compact, example-first "Full usage demo": when to reach for the app, the one call to run first, and a handful of worked examples (with per-call cost for metered apps). The same demo renders as the Full usage demo section on the app's store page.
+Next-steps after every call
+After every `pilotctl appstore call`, `pilotctl` prints a short next-steps block to stderr — the recommended commands for where the agent now stands, on success and on failure. stdout stays pure JSON, so an agent can still pipe the result into `jq`; the hints never touch it. A missing param renders a corrected, runnable call; a needs-signup state names the signup command; a 402 points at the balance check. For example, a `sqlite.query` with no `database`:
+# stdout is the pure JSON result (here an error envelope) — the hints go to stderr:
+pilotctl appstore call io.pilot.sqlite sqlite.query '{"sql":"SELECT 42"}'
+error: ipc: server error: backend: missing required param(s): database
+next: sqlite needs an explicit database — there is no default
+ 1. pilotctl appstore call io.pilot.sqlite sqlite.query '{"database":":memory:","sql":"SELECT 42 AS a"}'
+ why: pass database (:memory: for a scratch db) — fixes the error above
+Turn it off with `PILOT_NEXT_STEPS=off`. With `--json`, the hint arrives as a structured `next_steps` object instead of prose. A hint is never load-bearing: an absent, malformed, or unmatched graph prints nothing and leaves the call itself unchanged.
+Hints reach installed apps automatically
+The hints are driven by a small graph the app ships in the catalogue. Agents do not reinstall to get them: `pilotctl` lazily refreshes each app's cached graph from the catalogue in the background — a bounded check, at most about every 12 hours per app. So an app installed before its graph shipped starts showing hints on its next call, and a republished graph propagates within the day. No user action.
+
Lifecycle
pilotctl appstore restart io.pilot.cosift # respawn (e.g. after writing a config.json)
pilotctl appstore caps io.pilot.cosift # spend caps + current rolling-window usage
@@ -64,6 +80,14 @@ pilotctl appstore uninstall io.pilot.cosift --yes
`caps` reports manifest spend caps and usage. `audit` tails the supervisor lifecycle log for an app. `actions` is the install/uninstall log, which persists after removal. Both `audit` and `actions` accept `--tail <n>` and `--event <name>`. `audit` also accepts `--since <dur>`.
Upgrades are keyed on the version. The supervisor respawns an app when its `app_version` changes. The version must be bumped for each new build.
+Keeping apps current
+Apps are versioned in the catalogue. Two commands find and apply new versions:
+pilotctl appstore outdated # installed apps with a newer — or rebuilt — catalogue version
+pilotctl appstore upgrade io.pilot.cosift # re-install one app: verified, then respawned
+pilotctl appstore upgrade --all # bring every installed app up to date
+`upgrade` re-runs the same verified install (catalogue signature, tarball sha256, manifest signature, publisher trust anchor), then the supervisor applies the version change and restarts the app, refusing any downgrade. `outdated` flags an app not only on a version bump but also as rebuilt when a same-version bundle was republished, so a fix shipped without a version change is still surfaced.
+Auto-update: `pilotctl`'s own updater — `pilotctl update status|enable|disable` — keeps the CLI and daemon current (automatic updates are off by default). With auto-update enabled, the updater also keeps installed app adapters current automatically, so you won't need to run `upgrade` by hand.
+
Building an app
An app is a binary that listens on a socket provided by the daemon and uses the app-store IPC protocol. The manifest declares its identity, methods, pinned binary, and required grants.
{
@@ -112,6 +136,38 @@ gh release create cosift-v0.1.2 io.pilot.cosift-0.1.2.tar.gz
pilotctl appstore sign-catalogue --key catalog-signing.key catalogue/catalogue.json
This creates a detached `catalogue.json.sig` file. `pilotctl` verifies this signature before trusting any entry. Three integrity layers protect installs: the signed catalogue, the tarball sha256 pinned in the catalogue, and the binary sha256 pinned and signed in the manifest. The binary sha256 is re-checked before every spawn.
+Authoring dynamic context
+The two runtime surfaces above (the install demo and the per-call next-steps) are authored once in your app's submission and validated at submit time — no runtime code, and both are carried verbatim into the catalogue.
+Product demo: a `product_demo` block is the example-first usage guide that renders both at install and as the store page's Full usage demo — a one-sentence `when_to_use`, a `quickstart` call, 2–6 worked `examples`, and, for metered apps, a cost table.
+Next-steps graph: a `next_steps` graph is a flat list of edges. Each edge answers one question — the agent just ran `from` and it went `on`; what now? — and names 1–3 recommended commands.
+{
+ "schema": 1,
+ "app": "io.pilot.example",
+ "edges": [
+ {
+ "from": "*",
+ "on": "err",
+ "code": 402,
+ "why": "budget exhausted",
+ "then": [
+ { "cmd": "pilotctl appstore call io.pilot.example example.balance '{}'",
+ "why": "check what's left before spending again",
+ "kind": "recovery" }
+ ]
+ }
+ ]
+}
+Edge fields:
+
+ - `from`: the method just called, or `"*"` for any.
+ - `on`: `"ok"` (exit 0) or `"err"` (exit 1).
+ - `match`: regex over the outcome payload — the error text on `err`, the JSON result body on `ok`.
+ - `code`: exact backend HTTP status; `err` edges only.
+ - `why`: one line of framing, printed above the steps.
+ - `then`: 1–3 steps, each a full `pilotctl` command with its own `why` and an optional `kind` (gateway / flow / recovery).
+
+The most important case is often not an error: an app with a mandatory signup soft-fails with exit 0 and a `"needs_signup": true` body, so its gateway edge matches on `on:"ok"`. The best-matching edge wins on specificity, not file order. Authoring rules: recommended, not exhaustive (a graph is not `<app>.help`); every step needs a `why`; cover the failures that end sessions (needs-signup, 402 budget, 429 quota, missing/invalid param, server-not-started) each with a corrected, runnable command; every command must actually run; and if your app has a mandatory first method, a `from:"*"` edge must route a cold agent to it. Because the graph lives in the catalogue metadata rather than the bundle, rewording or extending it is a metadata republish — no app rebuild — and existing installs pick it up automatically.
+
Catalogue vs sideload
There are two installation paths with different trust models:
diff --git a/src/pages/plain/docs/cli-reference.astro b/src/pages/plain/docs/cli-reference.astro
index b42921f..526b477 100644
--- a/src/pages/plain/docs/cli-reference.astro
+++ b/src/pages/plain/docs/cli-reference.astro
@@ -1,7 +1,7 @@
---
// Auto-generated by scripts/regen-plain.mjs. Edit the marketing source and re-run.
// plain-source: src/pages/docs/cli-reference.astro
-// plain-source-sha256: e2cdec80884a84c4f360e02bb10e303e5f885a98181f478bb47b77124dee970e
+// plain-source-sha256: a24d4d3cdba6dfc2800359c5da333ce296bbe86e3e78760e2e7c2dcc744e47b3
import PlainLayout from '../../../layouts/PlainLayout.astro';
---
@@ -282,13 +282,18 @@ jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"
install
pilotctl appstore install <app-id> [--force]
pilotctl appstore install <bundle-dir> --local [--force]
-Install by catalogue ID (fetches, verifies, and extracts), or sideload a local bundle with `--local` (sandboxed: `fs.read`/`fs.write` under `$APP` + `audit.log`; no net, no `key.sign`, no hooks). The daemon auto-spawns the app on install.
+Install by catalogue ID (fetches, verifies, and extracts), or sideload a local bundle with `--local` (sandboxed: `fs.read`/`fs.write` under `$APP` + `audit.log`; no net, no `key.sign`, no hooks). The daemon auto-spawns the app on install. The last step prints the app's example-first "Full usage demo" — when to use it, the first call, and a few worked examples.
+outdated / upgrade
+pilotctl appstore outdated
+pilotctl appstore upgrade <id>
+pilotctl appstore upgrade --all
+`outdated` lists installed apps with a newer catalogue version, and also flags an app as rebuilt when a same-version bundle was republished. `upgrade` re-runs the verified install for one app (or `--all`) and the supervisor respawns it, refusing any downgrade. With auto-update enabled, installed app adapters are kept current automatically.
list
pilotctl appstore list
Lists installed apps and the IPC methods each exposes.
call
pilotctl appstore call <id> <method> [json-args] [--timeout <dur>]
-Dispatches an IPC call into an app and prints the JSON result on stdout. Every app exposes `<app>.help` listing its methods, params, and latency class. Default timeout 120s (also `$PILOT_APPSTORE_CALL_TIMEOUT`).
+Dispatches an IPC call into an app and prints the JSON result on stdout. Every app exposes `<app>.help` listing its methods, params, and latency class. Default timeout 120s (also `$PILOT_APPSTORE_CALL_TIMEOUT`). After each call, a short next-steps block prints to stderr (stdout stays pure JSON for `jq`) — the recommended follow-up commands on success or failure, e.g. a corrected call for a missing param. Disable with `PILOT_NEXT_STEPS=off`; `--json` returns a structured `next_steps` object instead.
status / caps / audit / actions
pilotctl appstore status <id>
pilotctl appstore caps <id>