setup-agent 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +16 -4
  2. package/dist/agents.d.ts.map +1 -1
  3. package/dist/browser.d.ts.map +1 -1
  4. package/dist/cli.d.ts +0 -20
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +90 -84
  7. package/dist/cli.js.map +1 -1
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/guide.d.ts +45 -0
  10. package/dist/guide.d.ts.map +1 -0
  11. package/dist/guide.js +133 -0
  12. package/dist/guide.js.map +1 -0
  13. package/dist/host.d.ts.map +1 -1
  14. package/dist/host.js +1 -1
  15. package/dist/host.js.map +1 -1
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -1
  19. package/dist/needs.d.ts +4 -2
  20. package/dist/needs.d.ts.map +1 -1
  21. package/dist/needs.js +20 -17
  22. package/dist/needs.js.map +1 -1
  23. package/dist/presets.d.ts.map +1 -1
  24. package/dist/protocol.d.ts +1 -1
  25. package/dist/protocol.d.ts.map +1 -1
  26. package/dist/sdk.d.ts.map +1 -1
  27. package/dist/sdk.js +19 -24
  28. package/dist/sdk.js.map +1 -1
  29. package/dist/stream.d.ts +8 -4
  30. package/dist/stream.d.ts.map +1 -1
  31. package/dist/stream.js +28 -17
  32. package/dist/stream.js.map +1 -1
  33. package/package.json +4 -4
  34. package/src/cli.test.ts +129 -116
  35. package/src/cli.ts +145 -132
  36. package/src/guide.test.ts +246 -0
  37. package/src/guide.ts +177 -0
  38. package/src/host.ts +1 -1
  39. package/src/index.ts +2 -0
  40. package/src/needs.test.ts +4 -5
  41. package/src/needs.ts +38 -23
  42. package/src/sdk.test.ts +8 -12
  43. package/src/sdk.ts +21 -31
  44. package/src/stream.test.ts +9 -7
  45. package/src/stream.ts +50 -23
  46. package/src/tests/guide-fixture.ts +39 -0
  47. package/src/tests/setup.ts +15 -0
package/README.md CHANGED
@@ -5,13 +5,13 @@ One setup, shared live between the browser that opened it and the coding agent i
5
5
  ```sh
6
6
  npx setup-agent <url> # instructions plus the current setup
7
7
  npx setup-agent <url> stream # one JSON array of events per line; run it under a Monitor
8
- npx setup-agent <url> wait start # wait for the user to click Begin plan in their browser
8
+ npx setup-agent <url> wait start # wait for the setup page to start the plan
9
9
  npx setup-agent <url> log "Sure, I will keep the existing chat route" # a reply, or something no question or step conveys
10
10
  npx setup-agent <url> ask --preset llm-provider --only openai,anthropic --default openai
11
11
  npx setup-agent <url> ask "Where should assistant-ui live?" --optional # keep working; the answer arrives on the stream
12
12
  npx setup-agent <url> ask "Which integrations?" --choices slack,email --icons "slack=message,email=mail" # multi-select; answers ["slack","email"]
13
13
  npx setup-agent <url> ask "Which package manager?" --choices pnpm,npm --icons "pnpm=package,npm=package" --single # the options exclude each other; answers "pnpm"
14
- npx setup-agent <url> wait q1 # block only when nothing else can proceed
14
+ npx setup-agent <url> wait q1 # block for the answer; after 100s it prints {"ok":false,"status":"waiting","next":...} to run again
15
15
  npx setup-agent <url> plan --file plan.md --wait # {"decision":"approve"} or {"decision":"revise","feedback":"..."}
16
16
  npx setup-agent <url> add-step "Install the packages" --detail "pnpm add ..." --active # prints {"stepId":"s1"}
17
17
  npx setup-agent <url> step s1 done --note "installed"
@@ -23,12 +23,16 @@ npx setup-agent <url> stream --setup-app-url https://setup.assistant-ui.com #
23
23
 
24
24
  ## Phases
25
25
 
26
- A setup opens `waiting` with its products and nothing else: no steps, no questions. After connecting, the agent tells the user to return to their browser and click **Begin plan**. It keeps the stream running and blocks on `wait start`, which prints `{"ok":true,"status":"planning"}` once planning begins, or `{"ok":false,"status":"cancelled"}` when the user ends the setup first. The browser sends `checkout/begin-plan`, moving the setup to `planning` and emitting `planning.started` on the stream. Questions and plan submissions are refused while waiting. Products are the starting list of names and setup guide references. The agent explores every starting guide and may discover other relevant products or dependencies without adding them to the cart. It includes those additions in the plan for approval. The legacy `ask --product` and `checkout/add-product` protocol remains supported for older agents. In `planning`, the agent investigates the project, asks what the code does not decide, and proposes a markdown plan with `plan` carrying its findings. `log` is for replying to the user and for what no question or step conveys. The user approves the plan or asks for changes with feedback; every revision is kept. Approval moves the setup to `installing`, where the agent declares its own steps with `add-step` and works through them with `step`. `add-step` and `step` are refused before approval. `done` marks the remaining steps done and proposes closing, with an optional `--summary` and an optional `--preview <url>` naming a dev server the agent left running; a preview must be an http(s) URL on localhost, and the browser offers to open it. `done` does not close. The browser shows the proposal with a close action that sends `checkout/finish`, and the stream prints `finished`. Until then the agent stays idle on the stream, because the user may send a follow-up message; after handling one it runs `done` again to renew the proposal.
26
+ A setup opens `waiting` with its products and nothing else: no steps, no questions. After connecting, the agent keeps the stream running and blocks on `wait start`, which prints `{"ok":true,"status":"planning"}` once planning begins, or `{"ok":false,"status":"cancelled"}` when the user ends the setup first. The setup page has no Begin plan button: it sends `checkout/begin-plan` by itself once it joins a waiting setup, moving the setup to `planning` and emitting `planning.started` on the stream. Questions and plan submissions are refused while waiting. Products are the starting list of names and setup guide references. The agent explores every starting guide and may discover other relevant products or dependencies without adding them to the cart. It includes those additions in the plan for approval. The legacy `ask --product` and `checkout/add-product` protocol remains supported for older agents. In `planning`, the agent investigates the project, asks what the code does not decide, and proposes a markdown plan with `plan` carrying its findings. `log` is for replying to the user and for what no question or step conveys. The user approves the plan or asks for changes with feedback; every revision is kept. Approval moves the setup to `installing`, where the agent declares its own steps with `add-step` and works through them with `step`. `add-step` and `step` are refused before approval. `done` marks the remaining steps done and proposes closing, with an optional `--summary` and an optional `--preview <url>` naming a dev server the agent left running; a preview must be an http(s) URL on localhost, and the browser offers to open it. `done` does not close. The browser shows the proposal with a close action that sends `checkout/finish`, and the stream prints `finished`. Until then the agent stays idle on the stream, because the user may send a follow-up message; after handling one it runs `done` again to renew the proposal.
27
27
 
28
- The URL is the browser's link to its agent, not one setup. A finished or cancelled setup stays on the link, and the browser's next setup replaces it with a new `checkout/create` carrying a new `id`: the plans, steps, inputs and log start over, the agent's presence carries across when it is still connected, and the stream prints `setup.created` with the new products. The agent keeps the stream running after `finished` or `cancelled` and blocks on `wait start`, which returns once the next setup begins planning; a setup already closed when the wait begins is skipped rather than reported.
28
+ The URL is the browser's link to its agent, not one setup. A finished or cancelled setup stays on the link until the agent replaces it with a new `checkout/create` carrying a new `id` (only the agent creates setups; the browser never sends it): the plans, steps, inputs and log start over, the agent's presence carries across when it is still connected, and the stream prints `setup.created` with the new products. The agent keeps the stream running after `finished` or `cancelled` and blocks on `wait start`, which returns once the next setup begins planning; a setup already closed when the wait begins is skipped rather than reported.
29
29
 
30
30
  The browser sends the setup `id` and the displayed plan `revision` with each `checkout/plan` decision. The host rejects a mismatched setup or revision with `stale-plan`, so a queued decision cannot approve or revise a newer plan. Older clients may omit these fields and retain their existing behavior. The guard requires a host running this version of `setup-agent`.
31
31
 
32
+ ## Waiting
33
+
34
+ Nothing wakes a coding agent that ends its turn: the stream prints to a background process the agent only reads between commands. So the briefing tells the agent to run the setup to the end in one turn and to block on every answer with `wait <input-id>`, `plan --wait` or `ask --wait`. Those waits return after 100 seconds, under the two minutes Claude Code gives a shell command by default, with `{"ok":false,"status":"waiting","waitingFor":"q1","next":"<command>"}`; the agent runs `next` again. `--timeout <seconds>` sets another limit, and `0` waits as long as it takes. `wait start` has no limit by default, since waiting for the user's next setup is idle time. The agent ends its turn only to ask the user in its own conversation, and after `done`.
35
+
32
36
  ## Inputs
33
37
 
34
38
  Inputs include `text`, `choice`, `model` and `entry-point`, plus the legacy `product` kind. A choice carries `options` (`id`, `label`, `description`, `icon`, `variants`), an optional `default` and a `help` note the browser renders as a "help me choose" disclosure. Option ids are unique, and neither option nor variant ids may contain `:`. The answer is the option id, `<option>:<variant>` when the option has variants (`langgraph:python`), or the user's own text when none of the options fit (including any text with more than one `:`); the stream marks that answer `custom`. An agent's own choice is multi-select unless asked with `--single`: the user checks any number of options (none may have variants), and the answer is a JSON array of those entries, which `wait` and the stream print as an array and `status` holds as text. `--single` is for options that exclude each other (one framework, one model, yes or no) and answers one id. Every one of the agent's own options must carry an `icon` from the pack `OPTION_ICONS` exports (`--icons "slack=message,email=mail"`); the CLI refuses the question otherwise. A preset option carries a brand mark instead. Every answer may carry a `note` from the user. A model input lists providers as its options; the browser collects the provider, its API key, the model and an optional reasoning effort, and the answer is that object as JSON (`{"provider","model","reasoningEffort"?}`); the API key is deposited separately and only ever reaches the env file the agent names through `env`. Only the CLI that asked can take it: `ask` keeps a redeem key in a private file under the user's state directory, or a per-user directory under the temp directory, and refuses a model question when neither can hold it, so `env` runs as the same user on the same machine.
@@ -72,6 +76,14 @@ The browser opens only when the setup waits on the user (an open input, a propos
72
76
 
73
77
  Each `input.needed` carries the question, its options and the `answer` command. On a worker without browser presence, setup opens once and then leaves the rest to the agent.
74
78
 
79
+ ## Guide
80
+
81
+ Setup is online-only. Every text the CLI shows the agent lives on the setup server (`apps/checkout-worker/src/guide.ts`), so a wording or flow fix ships with a worker deploy and reaches every CLI from this version on. That covers the briefing, the stream's messages, the `waiting` and `done` hints, and the questions `input.needed` relays. Commands that print guidance (`<url>` itself, `stream`, `ask`, `plan`, `wait`, `done`, and `runAgentSetup`) fetch `GET <setup-url>/guide?protocol=1&started=<user|agent|agent-stream>&opened=<0|1>`. Without it they fail with `Setup needs internet access to <url>`.
82
+
83
+ The guide is data. The CLI strips control characters and bidirectional overrides from every text, and fills `{{cmd}}`, `{{url}}` and the other placeholders with values it computes itself. It runs the intro only from three local actions: `print` (a text by key), `print-status` and `announce-agent`. It refuses any other step, a guide over 256 KB, and redirects. Commands for the CLI to run, files it writes and URLs it opens never come from the guide. The server's text can still steer the agent, as its questions and user messages already can, so the guide is as trusted as the setup server.
84
+
85
+ `GUIDE_PROTOCOL` is the format version. The server answers another version with 426, and the CLI tells the user to update. Bump it only for a change an older CLI cannot read. A new command, flag or local action needs an npm release.
86
+
75
87
  ## Workspace
76
88
 
77
89
  The package lives in `packages/setup-agent`; its Cloudflare worker lives in `apps/checkout-worker`. Build the worker dependencies with `pnpm turbo build --filter="checkout-worker^..."`, then run `pnpm --filter checkout-worker dev`. The worker consumes the published `@statewire/cloudflare` adapter.
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE;QACP,iEAAiE;QACjE,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;QACnC,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAqG/C,CAAC;AA2CF,kGAAkG;AAClG,eAAO,MAAM,eAAe,GAC1B,MAAK,MAAM,CAAC,UAAwB,KACnC,MAAM,GAAG,SAaX,CAAC;AAEF,2GAA2G;AAC3G,eAAO,MAAM,kBAAkB,GAC7B,MAAK,MAAM,CAAC,UAAwB,EACpC,QAAO;IAAE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAAkB,EACtD,SAAQ;IAAE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAAmB,YAInC,CAAC;AAExB,6DAA6D;AAC7D,eAAO,MAAM,WAAW,GACtB,UAAU,SAAS,MAAM,EAAE,EAC3B,MAAK,MAAM,CAAC,UAAwB,KACnC,MAAM,GAAG,SAoBX,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,OAAO,WAAW,EAClB,QAAQ,MAAM,KACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAgB5B,CAAC"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE;QACP,iEAAiE;QACjE,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;QACnC,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAqG/C,CAAC;AA2CF,kGAAkG;AAClG,eAAO,MAAM,eAAe,SACrB,MAAM,CAAC,UAAU,KACrB,MAAM,GAAG,SAaX,CAAC;AAEF,2GAA2G;AAC3G,eAAO,MAAM,kBAAkB,SACxB,MAAM,CAAC,UAAU,UACf;IAAE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE,WAC9B;IAAE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE,YAIlB,CAAC;AAExB,6DAA6D;AAC7D,eAAO,MAAM,WAAW,aACZ,SAAS,MAAM,EAAE,QACtB,MAAM,CAAC,UAAU,KACrB,MAAM,GAAG,SAoBX,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,UAClB,WAAW,UACV,MAAM,KACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAgB5B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAgB;AAEzC,OAAO,KAAK,EAAE,QAAQ,EAAE,sBAAmB;AAE3C,oEAAoE;AACpE,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAC9C,sGAAsG;AACtG,eAAO,MAAM,sBAAsB,QAA+B,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,YAAY,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,wBAI/B;IACD,0EAA0E;IAC1E,GAAG,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,KAAK,MAAM,GAAG,SAAS,CAAC;IACnD,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;oBAwCmB,QAAQ,CAAC,KAAK,KAAG,iBAAiB,EAAE;CAkEvD,CAAC"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAS,CAAC;AAEzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAY,CAAC;AAE3C,oEAAoE;AACpE,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAC9C,sGAAsG;AACtG,eAAO,MAAM,sBAAsB,QAA+B,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,YAAY,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,2BAI3B;IACD,0EAA0E;IAC1E,GAAG,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,KAAK,MAAM,GAAG,SAAS,CAAC;IACnD,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;IAwCG,MAAM,UAAU,QAAQ,CAAC,KAAK,KAAG,iBAAiB,EAAE;CAkEvD,CAAC"}
package/dist/cli.d.ts CHANGED
@@ -9,26 +9,6 @@ export declare const HELP: string;
9
9
  * no credentials, query or fragment, and hold only address characters.
10
10
  */
11
11
  export declare const parseSetupUrl: (value: string) => string;
12
- export type AgentInstructionsOptions = {
13
- /** How the agent invokes the setup commands, before the URL; `npx setup-agent` by default. */
14
- command?: string;
15
- /**
16
- * The agent started this setup with `assistant-ui setup`, which already
17
- * began planning; there is no Begin plan click to wait for.
18
- */
19
- startedByAgent?: {
20
- browserUrl: string;
21
- /** False when the browser was not opened for the user; the agent then hands them the link. */
22
- opened?: boolean;
23
- /**
24
- * The command that printed this briefing is the stream: it keeps running
25
- * and opens the browser only when the setup waits on the user.
26
- */
27
- streaming?: boolean;
28
- };
29
- };
30
- export declare const agentInstructions: (setupUrl: string, options?: AgentInstructionsOptions) => string;
31
- export declare const INSTRUCTIONS: (setupUrl: string, options?: AgentInstructionsOptions) => string;
32
12
  type Parsed = {
33
13
  positional: string[];
34
14
  flags: Map<string, string | true>;
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAiB;AA2B/C,OAAO,KAAK,EAAE,QAAQ,EAAE,sBAAmB;AAI3C,OAAO,EAAE,UAAU,EAAE,oBAAiB;AAItC,eAAO,MAAM,IAAI,QAoChB,CAAC;AAMF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,MA6B7C,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,cAAc,CAAC,EAAE;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,8FAA8F;QAC9F,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB;;;WAGG;QACH,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,CAAC;CACH,CAAC;AAuCF,eAAO,MAAM,iBAAiB,GAC5B,UAAU,MAAM,EAChB,UAAS,wBAA6B,WA6CvC,CAAC;AAEF,eAAO,MAAM,YAAY,aAhDb,MAAM,YACP,wBAAwB,WA+CU,CAAC;AAE9C,KAAK,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;CAAE,CAAC;AAuB1E,OAAO,EACL,aAAa,EACb,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,cAAc,GACf,oBAAiB;AAClB,YAAY,EAAE,WAAW,EAAE,oBAAiB;AAuG5C,2LAA2L;AAC3L,eAAO,MAAM,YAAY,GACvB,QAAQ,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,WAAW,CAAC,6BAiBjD,CAAC;AAwDL,eAAO,MAAM,OAAO,GAClB,MAAM,SAAS,MAAM,EAAE,EACvB,OAAO,MAAM,CAAC,OAAO,CAAC,KACrB,QAAQ,CAAC,SAAS,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CA+HxC,CAAC;AAQF,+EAA+E;AAC/E,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,EAAE,KAAK,MAAM,EAAE,OAAO,MAAM,WAqBxE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EACZ,KAAK,MAAM,EACX,MAAM,MAAM,OAAO,CAAC,MAAM,CAAC,EAC3B,oBAA2B,kBAsD5B,CAAC;AAgHF,MAAM,MAAM,sBAAsB,GAAG;IACnC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qGAAqG;IACrG,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACtD,CAAC;AAkCF,eAAO,MAAM,IAAI,GAAI,MAAM,SAAS,MAAM,EAAE,kBAA0B,CAAC;AAEvE;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,MAAM,SAAS,MAAM,EAAE,EACvB,UAAS,sBAA2B,kBAwRrC,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAU,CAAC;AA2B/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAY,CAAC;AAM3C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAU,CAAC;AAatC,eAAO,MAAM,IAAI,QAuChB,CAAC;AAMF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,UAAW,MAAM,KAAG,MA6B7C,CAAC;AASF,KAAK,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;CAAE,CAAC;AAuB1E,OAAO,EACL,aAAa,EACb,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,cAAc,GACf,MAAM,aAAU,CAAC;AAClB,YAAY,EAAE,WAAW,EAAE,MAAM,aAAU,CAAC;AAuG5C,2LAA2L;AAC3L,eAAO,MAAM,YAAY,WACf,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,WAAW,CAAC,6BAiBjD,CAAC;AAiGL,eAAO,MAAM,OAAO,SACZ,SAAS,MAAM,EAAE,SAChB,MAAM,CAAC,OAAO,CAAC,KACrB,QAAQ,CAAC,SAAS,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CA+HxC,CAAC;AAQF,+EAA+E;AAC/E,eAAO,MAAM,aAAa,YAAa,MAAM,OAAO,MAAM,SAAS,MAAM,WAqBxE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,SACnB,MAAM,OACP,MAAM,QACL,MAAM,OAAO,CAAC,MAAM,CAAC,wCAuD5B,CAAC;AAgHF,MAAM,MAAM,sBAAsB,GAAG;IACnC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qGAAqG;IACrG,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACtD,CAAC;AAoCF,eAAO,MAAM,IAAI,SAAU,SAAS,MAAM,EAAE,kBAA0B,CAAC;AAEvE;;;GAGG;AACH,eAAO,MAAM,eAAe,SACpB,SAAS,MAAM,EAAE,YACd,sBAAsB,kBAgVhC,CAAC"}
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { CODING_AGENTS, detectAgentKind, findCommand, isDirectInvocation, runCodingAgent } from "./agents.js";
2
2
  import { OPTION_ICONS, currentPlan, isAgentPresent, isClosed, isLoopbackHostname, isOptionIcon, isValidModelAnswer, parseEntryPointOptions, stepProgress } from "./protocol.js";
3
+ import { loadGuide, renderText } from "./guide.js";
3
4
  import { answerNeed } from "./needs.js";
4
5
  import { connectCheckout } from "./client.js";
5
6
  import { INPUT_PRESETS, isPresetId, presetInput } from "./presets.js";
@@ -12,6 +13,14 @@ import { homedir, tmpdir, userInfo } from "node:os";
12
13
  import { createInterface } from "node:readline/promises";
13
14
  // src/cli.ts
14
15
  const DEFAULT_COMMAND = "npx setup-agent";
16
+ /**
17
+ * How long "wait <input-id>", "wait plan" and "--wait" block before handing
18
+ * control back with a "waiting" result, in seconds. Agent shells kill a
19
+ * command after a limit of their own (two minutes by default in Claude
20
+ * Code); a wait that returns first lets the agent run it again instead of
21
+ * reading the kill as the end of the setup.
22
+ */
23
+ const DEFAULT_WAIT_SECONDS = 100;
15
24
  const HELP = `setup-agent <url> [command]
16
25
 
17
26
  Commands (the url always comes first):
@@ -35,11 +44,14 @@ Commands (the url always comes first):
35
44
  --icons is required and names an icon for every option, from: ${OPTION_ICONS.join(", ")}.
36
45
  ask <prompt> --entry-points '<JSON array>' [--default <id>] [--optional] [--step <step-id>] [--wait]
37
46
  Ask where users should access the assistant. Each of 1–3 options has id, label, description and entryPoint {formFactor: modal|sidebar|full-page, placement, trigger, recommended?}. Answers one option id.
38
- wait start Block until the user clicks Begin plan in their browser; after a finished or cancelled setup, until the next one begins.
39
- wait <input-id> Block until the user answers or dismisses an input. A multi-select answer prints as an array.
40
- plan (<markdown> | --file <path>) [--wait]
47
+ wait start Block until the setup page starts the plan; prints {"ok":false,"status":"cancelled"} if the user cancels first. After a finished or cancelled setup, waits for the next one to begin.
48
+ wait <input-id> [--timeout <seconds>]
49
+ Block until the user answers or dismisses an input. A multi-select answer prints as an array.
50
+ plan (<markdown> | --file <path>) [--wait] [--timeout <seconds>]
41
51
  Propose the plan for the user to approve. --wait blocks until they decide.
42
- wait plan Block until the user approves the plan or asks for changes.
52
+ wait plan [--timeout <seconds>] Block until the user approves the plan or asks for changes.
53
+ wait <input-id>, wait plan and every --wait give up after ${DEFAULT_WAIT_SECONDS}s (--timeout, 0 for no limit) and print
54
+ {"ok":false,"status":"waiting","next":"<command>"}: run "next" again to keep waiting.
43
55
  env <input-id> <ENV_VAR> [--file <path>]
44
56
  Write the API key behind an answered llm-provider input to an env file (default .env.local) as ENV_VAR=<key>, without printing it.
45
57
  add-step <title> [--detail <text>] [--product <slug>] [--active]
@@ -71,65 +83,12 @@ const parseSetupUrl = (value) => {
71
83
  if (!SETUP_URL_PATTERN.test(href)) throw new Error("the setup URL has characters a setup URL does not use");
72
84
  return href;
73
85
  };
74
- const EVENT_LIST = `"planning.started", "input.answered" ({inputId, answer, note?}; answer is an array for a choice question not asked with --single), "input.dismissed", "plan.approved", "plan.changes-requested" ({feedback}), "message.created" ({messageId, text}), "finished", "cancelled", "setup.created" ({products})`;
75
- const beginPlanHandoff = (cmd, url) => `Before planning, check the current setup status. If it is "waiting", tell the user in your own conversation: "I'm connected. Go back to your browser and click Begin plan. You can watch my progress, answer questions, and steer me there." Then run:
76
- ${cmd} ${url} wait start
77
- Keep the stream running while you wait. Do not inspect the project, fetch guides, ask setup questions, or propose a plan until this command returns {"ok":true}. If it returns {"ok":false,"status":"cancelled"}, the user ended this setup before planning: do not plan, tell the user you are connected and waiting for their next setup, and run "wait start" again. Only the user in the browser should start planning; never invoke the browser's start command yourself. "planning.started" also arrives on the stream. If the status is already "planning" or "installing", resume that phase without asking the user to begin again. If it is "done" or "cancelled", that setup is over: tell the user you are connected and waiting for their next setup, and run "wait start", which returns once the next one begins.`;
78
- const agentStartedHandoff = ({ browserUrl, opened = true, streaming = false }) => streaming ? `You started this setup, and it is already planning: there is no Begin plan click to wait for. Check "status" before planning: if it is "planning" or "installing", go on with that phase. If the status is "done" or "cancelled", that setup is over: tell the user you are connected and waiting for their next setup, and run "wait start", which returns once the next one begins.` : `You started this setup, and it is already planning: there is no Begin plan click to wait for. The browser asks the user to connect to you first; it shows your working directory once your stream is running. Check "status" before planning: if it is "planning" or "installing", go on with that phase. Tell the user in your own conversation, once: ${opened ? `"I opened the assistant-ui setup in your browser. I'll ask my questions there, and you can watch my progress and steer me from that page. If it didn't open, go to ${browserUrl} ."` : `"Open ${browserUrl} to follow the assistant-ui setup. I'll ask my questions there, and you can watch my progress and steer me from that page."`} If the status is "done" or "cancelled", that setup is over: tell the user you are connected and waiting for their next setup, and run "wait start", which returns once the next one begins.`;
79
- const streamSection = (cmd, url, streaming, noOpen) => streaming ? `The command that printed this is the stream. Keep it running in the background until the setup ends: the browser shows you as connected only while it runs, and it is how answers reach you. Each new line of its output is a JSON array of events: ${EVENT_LIST}, plus:
80
- - "browser.opened" ({id, message}): setup opened the setup page in the user's browser for a question. Tell the user once, in your own conversation, that it waits there.
81
- - "input.needed" ({id, message, question, options?, answer}): the user may not see the browser right now. Do what its message says: wait for "input.answered", or ask the user in your own conversation with that question and options, then run its "answer" command with their reply. Answered either way, the setup goes on.
82
- If this command stops, start it again in the background with
83
- ${cmd} ${url} stream${noOpen ? " --no-open" : ""}
84
- The URL is the user's link to you from their browser, not one setup: it outlives this one, and their next setup arrives on the same stream.` : `First, start a persistent Monitor on
85
- ${cmd} ${url} stream
86
- and keep it running: the browser shows you as connected only while it runs, and it is how answers reach you. The URL is the user's link to you from their browser, not one setup: it outlives this one, and their next setup arrives on the same stream. Each line is a JSON array of events: ${EVENT_LIST}.`;
87
- const agentInstructions = (setupUrl, options = {}) => {
88
- const url = shellWord(setupUrl);
89
- const cmd = options.command ?? DEFAULT_COMMAND;
90
- const started = options.startedByAgent;
91
- const streaming = started?.streaming === true;
92
- return `You are the coding agent for a setup ${started ? streaming ? started.opened === false ? "you started; setup opens no browser (--no-open), so the user follows it on the setup page if they open it, or answers you in your own conversation" : "you started; the user follows it in their browser, which setup opens when you ask the user something" : "you opened in the user's browser" : "the user opened in their browser"}. The browser shows this setup live; every command below updates it. The user watches the browser, not this terminal: once setup begins, send questions through "ask" so the user can answer in their browser.
93
-
94
- ${streamSection(cmd, url, streaming, started?.opened === false)}
95
-
96
- The user can message you at any time, including while a question is waiting. Read "message.created" events, acknowledge each with "${cmd} ${url} ack <message-id>", and reply using "log" with what you will do. Pending messages are replayed when the stream starts and listed in "status". A message is not an answer or plan approval; keep those decisions explicit.
97
-
98
- "log" is a message to the user, and the user reads every one: send it only to reply to their message, to tell them something no question or step conveys (a blocker, a deviation from the plan, a decision you made for them), or once as a wrap-up when the install is finished. Findings go in the plan, progress in step notes, questions in "ask"; do not narrate.
99
-
100
- ${started ? agentStartedHandoff(started) : beginPlanHandoff(cmd, url)}
101
-
102
- The remaining phases are plan, then install.
103
-
104
- 1. Plan. Investigate only; change nothing yet. If the current directory is not an app to install into (a monorepo root, an empty folder, a library), ask where to install first:
105
- ${cmd} ${url} ask "Which project should I install into?" --placeholder "An absolute path to the app" --wait
106
- Read the project: its app framework and package manager, the agent framework and model provider it already uses, where components live, any existing chat route. Fetch each product's "guide" URL from the setup below; it is the install reference. Ask for what the project does not decide, using the standard questions so the browser can render them well:
107
- ${cmd} ${url} ask --preset framework
108
- ${cmd} ${url} ask --preset llm-provider
109
- The setup's products are starting names and guide references. If the setup lists no products, first ask the user what to set up with a choice question whose options are slugs from https://www.assistant-ui.com/catalog.md, then fetch https://www.assistant-ui.com/install.md?items=<slugs> as the guide. Explore each product's setup instructions, then discover and plan any other relevant products or dependencies the app needs. Read /catalog.md on the guide URL’s origin to find related products and fetch their setup guides. The starting list does not restrict the implementation or require adding another product to the list. Include relevant additions in the plan for approval.
110
- When the setup puts an assistant in the app, ask how users should access it after discovering the actual app and before proposing the plan. Skip this question when no product's guide installs an assistant (a standalone library or service setup, for example). Use an entry-point question with only 1–3 sensible alternatives. Describe the actual app's pages, workflow and component placement, not generic placeholder copy. Recommend one clear fit when there is one; two options may use the same form factor in different places. Each option needs a stable id, label, contextual description and entryPoint {"formFactor":"modal"|"sidebar"|"full-page","placement":"where it appears","trigger":"how users open it or navigate to it","recommended":true?}. Write the JSON array for this app and send it with:
111
- ${cmd} ${url} ask "Where should users access the assistant?" --entry-points '<JSON array>' --wait
112
- Mark a clear fit with "recommended":true rather than --default, so the user makes the pick. When you ask it, do not submit the plan before the user picks an entry point. Respect the selected placement and trigger in the plan and implementation. Do not ask for a new-versus-existing app setting; discover the app from its files and ask only for an install path when needed.
113
- Skip a preset the code already answers (a chat route on the AI SDK, a provider key in .env.local) and log why. Ask everything at once, keep investigating, and block with "wait <input-id>" only when nothing else can proceed. A framework answer is "<framework>:<language>", for example "ai-sdk:typescript". A model answer is JSON {"provider","model","reasoningEffort"?}. An answer may carry a "note" from the user; follow it. A choice answer that matches none of the options is the user's own text.
114
- Then write the plan as markdown and submit it:
115
- ${cmd} ${url} plan --file <path> --wait
116
- Use these headings, each with bullets: "## What I found" (facts as \`- **Label:** value\`: app framework, package manager, agent framework, model provider and model, where components live), "## What I will install" (packages and files, one per bullet), "## Steps" (a numbered list, one line each), and "## Open questions" only if any remain. Keep it under 30 lines; no preamble. "plan --wait" prints {"decision":"approve"} or {"decision":"revise","feedback":"..."}. On revise, address the feedback (ask again if you need to) and submit a new plan; repeat until approved. Do not change the project before approval: the setup refuses steps until then.
117
-
118
- 2. Install. Declare the steps from your plan up front, in order:
119
- ${cmd} ${url} add-step "<title>" --detail "<one line>"
120
- which prints the step id. Then work through them: "step <id> active" before, "step <id> done --note '<one line on what you did>'" after; "skipped" when the step is already satisfied, "blocked" when you cannot continue, saying why in --note. Add steps as the work reveals them. Ask when you need another decision or value (--choices for a pick, plain for text; --optional when you can proceed without an answer; --wait only when the answer is the very next thing you need). A choice question is multi-select: the user checks any number of your options, and the answer is an array of the ids they checked, plus their own text as an entry when they typed one. Pass --single only when the options exclude each other (one framework, one model, yes or no); the answer is then one id. Every option must carry an icon, given as --icons "<id>=<icon>,..." with the closest name from this pack: ${OPTION_ICONS.join(", ")}. A question without an icon for each option is refused. Open inputs the user has not answered are listed under "inputs" in "status"; check them before asking again.
121
- The API key behind a model answer is not in the answer and you never see it: pick the variable name the provider's SDK reads (OPENAI_API_KEY, ANTHROPIC_API_KEY, ...) and run
122
- ${cmd} ${url} env <input-id> <ENV_VAR> --file .env.local
123
- which writes ENV_VAR=<key> into that file. Use the answer's model in the chat route and map reasoningEffort onto the provider's setting when it is set. If "env" says the key was never deposited (the model question was answered from your conversation), ask the user to add the key to that file themselves. Never invent a key or a model name.
124
-
125
- When every product is installed and verified, run
126
- ${cmd} ${url} done --summary "<one or two lines on what you installed and how to try it>" --preview http://localhost:<port>
127
- Before that, start the project's dev server in the background when the project has one, check that the page you changed loads, and pass the address as --preview (the page itself when it is not the root); leave the server running so the user can try the result. Omit --preview when there is nothing to open. This proposes closing the setup; only the user closes it. Do not stop the stream or exit: stay idle on it, because the user may follow up with a "message.created" event. Handle it, then run "done" again. The stream prints "finished" once the user closes the setup, and "cancelled" if they end it early. Keep the stream running after either: the user's next setup starts on this same URL and arrives as "setup.created" with its products. Run
128
- ${cmd} ${url} wait start
129
- which returns once that setup begins, then run "status" for its products, guides and instructions and go through plan and install again. A "setup.created" event while you are still working means the user replaced this setup: drop what you were doing and start over the same way.
130
- `;
131
- };
132
- const INSTRUCTIONS = agentInstructions;
86
+ /** The values the CLI fills into guide texts itself; never the server's to choose. */
87
+ const localValues = (setupUrl, command = DEFAULT_COMMAND) => ({
88
+ cmd: command,
89
+ url: shellWord(setupUrl),
90
+ icons: OPTION_ICONS.join(", ")
91
+ });
133
92
  const parseArgs = (argv) => {
134
93
  const positional = [];
135
94
  const flags = /* @__PURE__ */ new Map();
@@ -263,6 +222,35 @@ const waitForPlan = (client, revision) => new Promise((resolve) => {
263
222
  const unsubscribe = client.subscribe(check);
264
223
  check();
265
224
  });
225
+ /** The seconds `--timeout` asks a wait to block for; 0 is no limit. */
226
+ const waitSeconds = (flags, fallback) => {
227
+ const value = flagText(flags, "timeout");
228
+ if (value === void 0) return fallback;
229
+ const seconds = Number(value);
230
+ if (!/^\d+(?:\.\d+)?$/.test(value) || !Number.isFinite(seconds)) return fail("--timeout takes a number of seconds, 0 for no limit");
231
+ return seconds;
232
+ };
233
+ const TIMED_OUT = Symbol("timed out");
234
+ /** `promise`, or `TIMED_OUT` once `seconds` pass first; 0 waits as long as it takes. */
235
+ const within = async (promise, seconds) => {
236
+ if (seconds === 0) return promise;
237
+ let timer;
238
+ try {
239
+ return await Promise.race([promise, new Promise((resolve) => {
240
+ timer = setTimeout(() => resolve(TIMED_OUT), seconds * 1e3);
241
+ })]);
242
+ } finally {
243
+ clearTimeout(timer);
244
+ }
245
+ };
246
+ /** What a wait prints when the user has not decided yet: the command to run again, now. */
247
+ const stillWaiting = (target, next, guide) => ({
248
+ ok: false,
249
+ status: "waiting",
250
+ waitingFor: target,
251
+ next,
252
+ message: renderText(guide, "wait.waiting")
253
+ });
266
254
  const decided = (plan, state) => {
267
255
  if (state.status === "cancelled") return {
268
256
  ok: false,
@@ -506,15 +494,18 @@ const answered = (input) => ({
506
494
  status: input.status,
507
495
  ...input.answer !== void 0 && answerFields(input, input.answer)
508
496
  });
509
- const runStream = async (client, url, flags, options) => {
497
+ const runStream = async (client, url, flags, options, guide) => {
510
498
  const setupAppUrl = options.browser?.setupAppUrl ?? flagText(flags, "setup-app-url");
511
499
  const linkId = new URL(url).pathname.split("/").filter(Boolean).at(-1);
512
- const stream = await followSetup(client, { ...setupAppUrl !== void 0 && linkId !== void 0 && { browser: {
513
- setupAppUrl: parseSetupUrl(setupAppUrl),
514
- linkId,
515
- answerCommand: `${options.command ?? DEFAULT_COMMAND} ${shellWord(url)} answer`,
516
- canOpen: (options.browser?.canOpen ?? true) && !flags.has("no-open")
517
- } } });
500
+ const stream = await followSetup(client, {
501
+ guide,
502
+ ...setupAppUrl !== void 0 && linkId !== void 0 && { browser: {
503
+ setupAppUrl: parseSetupUrl(setupAppUrl),
504
+ linkId,
505
+ answerCommand: `${options.command ?? DEFAULT_COMMAND} ${shellWord(url)} answer`,
506
+ canOpen: (options.browser?.canOpen ?? true) && !flags.has("no-open")
507
+ } }
508
+ });
518
509
  const stop = () => {
519
510
  stream.stop().finally(() => {
520
511
  client.dispose();
@@ -547,21 +538,28 @@ const runSetupCommand = async (argv, options = {}) => {
547
538
  if (command === "intro" && isDirectInvocation()) return launchAgent(url);
548
539
  const client = await connectCheckout(url).catch((error) => fail(error instanceof Error ? error.message : String(error)));
549
540
  const commands = client.commands;
541
+ let guideLoad;
542
+ const guide = () => guideLoad ??= loadGuide(url, {
543
+ started: "user",
544
+ opened: command !== "stream" || (options.browser?.setupAppUrl ?? flagText(flags, "setup-app-url")) !== void 0 && (options.browser?.canOpen ?? true) && !flags.has("no-open")
545
+ });
546
+ const waitCommand = (target) => `${options.command ?? DEFAULT_COMMAND} ${shellWord(url)} wait ${target}`;
550
547
  try {
551
548
  switch (command) {
552
549
  case "intro": {
553
- const kind = detectAgentKind();
554
- await commands["agent/intro"]({ ...kind !== void 0 && { kind } }).catch(() => {});
555
- process.stdout.write(agentInstructions(url, { ...options.command !== void 0 && { command: options.command } }));
556
- process.stdout.write("\nCurrent setup:\n");
557
- print(summary(requireState(client)));
550
+ const intro = await guide();
551
+ for (const step of intro.intro) if (step.action === "announce-agent") {
552
+ const kind = detectAgentKind();
553
+ await commands["agent/intro"]({ ...kind !== void 0 && { kind } }).catch(() => {});
554
+ } else if (step.action === "print") process.stdout.write(renderText(intro, step.text, localValues(url, options.command)));
555
+ else print(summary(requireState(client)));
558
556
  break;
559
557
  }
560
558
  case "status":
561
559
  print(summary(requireState(client)));
562
560
  break;
563
561
  case "stream":
564
- await runStream(client, url, flags, options);
562
+ await runStream(client, url, flags, options, await guide());
565
563
  break;
566
564
  case "answer": {
567
565
  const id = rest[0] ?? fail("usage: answer <input-id|plan|finish> <answer> [--note <text>]");
@@ -619,8 +617,8 @@ const runSetupCommand = async (argv, options = {}) => {
619
617
  });
620
618
  break;
621
619
  }
622
- const plan = await waitForPlan(client, revision);
623
- print(decided(plan, requireState(client)));
620
+ const plan = await within(waitForPlan(client, revision), waitSeconds(flags, DEFAULT_WAIT_SECONDS));
621
+ print(plan === TIMED_OUT ? stillWaiting("plan", waitCommand("plan"), await guide()) : decided(plan, requireState(client)));
624
622
  break;
625
623
  }
626
624
  case "ask": {
@@ -645,14 +643,19 @@ const runSetupCommand = async (argv, options = {}) => {
645
643
  });
646
644
  break;
647
645
  }
648
- print(await waitForInput(client, inputId).then(answered));
646
+ const input = await within(waitForInput(client, inputId), waitSeconds(flags, DEFAULT_WAIT_SECONDS));
647
+ print(input === TIMED_OUT ? stillWaiting(inputId, waitCommand(inputId), await guide()) : answered(input));
649
648
  break;
650
649
  }
651
650
  case "wait": {
652
651
  const target = rest[0] ?? fail("usage: wait start | wait <input-id> | wait plan");
653
652
  const state = requireState(client);
654
653
  if (target === "start") {
655
- const status = await waitForStart(client);
654
+ const status = await within(waitForStart(client), waitSeconds(flags, 0));
655
+ if (status === TIMED_OUT) {
656
+ print(stillWaiting("start", waitCommand("start"), await guide()));
657
+ break;
658
+ }
656
659
  print({
657
660
  ok: status === "planning" || status === "installing",
658
661
  status
@@ -661,11 +664,13 @@ const runSetupCommand = async (argv, options = {}) => {
661
664
  }
662
665
  if (target === "plan") {
663
666
  const plan = currentPlan(state) ?? fail("no plan has been proposed");
664
- print(decided(await waitForPlan(client, plan.revision), requireState(client)));
667
+ const decision = await within(waitForPlan(client, plan.revision), waitSeconds(flags, DEFAULT_WAIT_SECONDS));
668
+ print(decision === TIMED_OUT ? stillWaiting("plan", waitCommand("plan"), await guide()) : decided(decision, requireState(client)));
665
669
  break;
666
670
  }
667
671
  if (!state.inputs.some((input) => input.id === target)) fail(`no input "${target}"`);
668
- print(await waitForInput(client, target).then(answered));
672
+ const input = await within(waitForInput(client, target), waitSeconds(flags, DEFAULT_WAIT_SECONDS));
673
+ print(input === TIMED_OUT ? stillWaiting(target, waitCommand(target), await guide()) : answered(input));
669
674
  break;
670
675
  }
671
676
  case "env": {
@@ -716,6 +721,7 @@ const runSetupCommand = async (argv, options = {}) => {
716
721
  break;
717
722
  }
718
723
  case "done": {
724
+ const hint = renderText(await guide(), "done.next");
719
725
  const summary = flagText(flags, "summary");
720
726
  const preview = flagText(flags, "preview");
721
727
  await commands["agent/done"]({
@@ -725,7 +731,7 @@ const runSetupCommand = async (argv, options = {}) => {
725
731
  print({
726
732
  ok: true,
727
733
  status: "proposed",
728
- next: "Keep the stream running and stay idle. The user closes the setup or sends a follow-up message."
734
+ next: hint
729
735
  });
730
736
  break;
731
737
  }
@@ -737,6 +743,6 @@ const runSetupCommand = async (argv, options = {}) => {
737
743
  client.dispose();
738
744
  }
739
745
  };
740
- export { CODING_AGENTS, HELP, INSTRUCTIONS, agentInstructions, askSeed, detectAgentKind, diffEvents, findCommand, isDirectInvocation, main, parseSetupUrl, runCodingAgent, runSetupCommand, upsertEnvLine, waitForStart, writeEnvSecret };
746
+ export { CODING_AGENTS, HELP, askSeed, detectAgentKind, diffEvents, findCommand, isDirectInvocation, main, parseSetupUrl, runCodingAgent, runSetupCommand, upsertEnvLine, waitForStart, writeEnvSecret };
741
747
 
742
748
  //# sourceMappingURL=cli.js.map