setup-agent 0.0.3 → 0.0.5
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.
- package/LICENSE +21 -0
- package/README.md +11 -5
- package/dist/cli.d.ts +51 -46
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +81 -22
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +3 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +12 -4
- package/dist/client.js.map +1 -1
- package/dist/host.d.ts +7 -9
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +82 -75
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/presets.d.ts +23 -25
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +4 -3
- package/dist/presets.js.map +1 -1
- package/dist/protocol.d.ts +279 -262
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +35 -4
- package/dist/protocol.js.map +1 -1
- package/package.json +20 -13
- package/src/cli.test.ts +615 -0
- package/src/cli.ts +118 -29
- package/src/client.test.ts +54 -0
- package/src/client.ts +12 -4
- package/src/host.test.ts +1030 -0
- package/src/host.ts +52 -9
- package/src/index.ts +3 -0
- package/src/presets.ts +4 -3
- package/src/protocol.test.ts +128 -0
- package/src/protocol.ts +103 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentbaseAI Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -21,18 +21,24 @@ npx setup-agent <url> done --summary "installed the thread and a chat route" --p
|
|
|
21
21
|
|
|
22
22
|
## Phases
|
|
23
23
|
|
|
24
|
-
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
|
|
24
|
+
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.
|
|
25
25
|
|
|
26
26
|
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.
|
|
27
27
|
|
|
28
28
|
## Inputs
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
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`.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
When the setup puts an assistant in the app, the agent asks for its entry point after discovering the app, with `ask "<prompt>" --entry-points '<JSON array>' [--default <id>]`. Each of 1–3 options includes a stable `id`, `label`, contextual `description`, and `entryPoint: { formFactor: "modal" | "sidebar" | "full-page", placement, trigger, recommended? }`. At most one option is recommended, and the agent marks it with `recommended` rather than `--default`, which would preselect it. Two alternatives can share a form factor when their placements differ. Entry-point questions accept exactly one option id, with an optional user note; variants, custom ids, a help note and multi-selection are refused. The agent describes the actual app's workflow, placement and opening action, then incorporates the user's selection into the approved plan.
|
|
33
|
+
|
|
34
|
+
Two standard choices and a legacy project choice ship as presets (`setup-agent` exports `INPUT_PRESETS` and `presetInput`), so the browser and the CLI agree on their options and brand marks:
|
|
33
35
|
|
|
34
36
|
- `framework`: Vercel AI SDK, Mastra, LangGraph, each with its language.
|
|
35
37
|
- `llm-provider`: OpenAI, Anthropic, Google Gemini, OpenRouter, xAI, Mistral, DeepSeek, Groq, Fireworks AI; defaults to OpenAI. Rendered as the model picker (provider, key, model, reasoning effort).
|
|
36
|
-
- `project
|
|
38
|
+
- `project` (legacy decoder compatibility): the React projects the agent found, passed as `--choices "apps/web=Next.js,apps/admin"`, ahead of a fixed "New project" option whose second pick is the meta framework. Answers a path, or `new:<next|vite|react-router|tanstack-start|expo>`.
|
|
39
|
+
|
|
40
|
+
`--only` restricts a preset to some of its options and leaves out `--choices` (a single id locks the choice in) and `--default` preselects one.
|
|
41
|
+
|
|
42
|
+
## Workspace
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
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.
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,66 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
import { Checkout } from "./protocol.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
declare const
|
|
6
|
-
declare const INSTRUCTIONS: (url: string) => string;
|
|
1
|
+
import type { CheckoutClient } from "./client.js";
|
|
2
|
+
import type { Checkout } from "./protocol.js";
|
|
3
|
+
export declare const HELP: string;
|
|
4
|
+
export declare const agentInstructions: (url: string) => string;
|
|
5
|
+
export declare const INSTRUCTIONS: (url: string) => string;
|
|
7
6
|
type Parsed = {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
positional: string[];
|
|
8
|
+
flags: Map<string, string | true>;
|
|
10
9
|
};
|
|
11
10
|
/** The coding agent this process runs under, from the variables each one exports to its shell. */
|
|
12
|
-
declare const detectAgentKind: (env?: NodeJS.ProcessEnv) => string | undefined;
|
|
11
|
+
export declare const detectAgentKind: (env?: NodeJS.ProcessEnv) => string | undefined;
|
|
13
12
|
/** True when a person typed the command into a terminal rather than an agent running it from its shell. */
|
|
14
|
-
declare const isDirectInvocation: (env?: NodeJS.ProcessEnv, stdin?: {
|
|
15
|
-
|
|
13
|
+
export declare const isDirectInvocation: (env?: NodeJS.ProcessEnv, stdin?: {
|
|
14
|
+
isTTY?: boolean | undefined;
|
|
16
15
|
}, stdout?: {
|
|
17
|
-
|
|
16
|
+
isTTY?: boolean | undefined;
|
|
18
17
|
}) => boolean;
|
|
19
18
|
type StreamEvent = {
|
|
20
|
-
|
|
19
|
+
event: "planning.started";
|
|
21
20
|
} | {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
event: "message.created";
|
|
22
|
+
messageId: string;
|
|
23
|
+
text: string;
|
|
25
24
|
} | {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
event: "input.answered";
|
|
26
|
+
inputId: string;
|
|
27
|
+
prompt: string;
|
|
28
|
+
/** The entries of a multiple choice answer, else the answer text. */
|
|
29
|
+
answer: string | string[];
|
|
30
|
+
note?: string;
|
|
31
|
+
/** True when a choice answer is, or a multiple one contains, the user's own text rather than an option. */
|
|
32
|
+
custom?: true;
|
|
34
33
|
} | {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
event: "input.dismissed";
|
|
35
|
+
inputId: string;
|
|
36
|
+
prompt: string;
|
|
38
37
|
} | {
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
event: "plan.approved";
|
|
39
|
+
revision: number;
|
|
41
40
|
} | {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
event: "plan.changes-requested";
|
|
42
|
+
revision: number;
|
|
43
|
+
feedback: string;
|
|
45
44
|
} | {
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
event: "finished";
|
|
46
|
+
next: string;
|
|
48
47
|
} | {
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
event: "cancelled";
|
|
49
|
+
next: string;
|
|
51
50
|
} | {
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
event: "setup.created";
|
|
52
|
+
products: string[];
|
|
54
53
|
};
|
|
55
|
-
declare const diffEvents: (before: Checkout.State, after: Checkout.State) => StreamEvent[];
|
|
54
|
+
export declare const diffEvents: (before: Checkout.State, after: Checkout.State) => StreamEvent[];
|
|
56
55
|
/** Resolves once a setup is planning or installing, or once the one being waited on is cancelled; a setup already closed when the wait begins is over, so the wait is for the next one. */
|
|
57
|
-
declare const waitForStart: (client: Pick<CheckoutClient, "state" | "subscribe">) => Promise<Checkout.Status>;
|
|
58
|
-
declare const askSeed: (rest: readonly string[], flags: Parsed["flags"]) => Checkout.InputSeed & {
|
|
59
|
-
|
|
56
|
+
export declare const waitForStart: (client: Pick<CheckoutClient, "state" | "subscribe">) => Promise<Checkout.Status>;
|
|
57
|
+
export declare const askSeed: (rest: readonly string[], flags: Parsed["flags"]) => Checkout.InputSeed & {
|
|
58
|
+
stepId?: string;
|
|
60
59
|
};
|
|
61
60
|
/** Sets `key=value` in dotenv text, replacing an existing line for the key. */
|
|
62
|
-
declare const upsertEnvLine: (content: string, key: string, value: string) => string;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
export declare const upsertEnvLine: (content: string, key: string, value: string) => string;
|
|
62
|
+
/**
|
|
63
|
+
* Write the secret `take` returns to `file` under `key`, leaving the file
|
|
64
|
+
* owner-only. The file is opened before `take` runs, because taking the
|
|
65
|
+
* secret consumes it and an unwritable destination must fail while it is
|
|
66
|
+
* still deposited; it is read after, so lines written meanwhile survive.
|
|
67
|
+
*/
|
|
68
|
+
export declare const writeEnvSecret: (file: string, key: string, take: () => Promise<string>) => Promise<void>;
|
|
69
|
+
export declare const main: (argv: readonly string[]) => Promise<undefined>;
|
|
70
|
+
export {};
|
|
66
71
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAiB;AAmB/C,OAAO,KAAK,EAAE,QAAQ,EAAE,sBAAmB;AAE3C,eAAO,MAAM,IAAI,QAgChB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,KAAK,MAAM,WA2CZ,CAAC;AAEF,eAAO,MAAM,YAAY,QA7ClB,MAAM,WA6CgC,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;AA6C1E,kGAAkG;AAClG,eAAO,MAAM,eAAe,GAAI,MAAK,MAAM,CAAC,UAAwB,uBACd,CAAC;AAyCvD,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;AA0FxB,KAAK,WAAW,GACZ;IAAE,KAAK,EAAE,kBAAkB,CAAA;CAAE,GAC7B;IAAE,KAAK,EAAE,iBAAiB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC7D;IACE,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2GAA2G;IAC3G,MAAM,CAAC,EAAE,IAAI,CAAC;CACf,GACD;IAAE,KAAK,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,KAAK,EAAE,wBAAwB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAKnD,eAAO,MAAM,UAAU,GACrB,QAAQ,QAAQ,CAAC,KAAK,EACtB,OAAO,QAAQ,CAAC,KAAK,KACpB,WAAW,EAqEb,CAAC;AA4CF,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,WAWxE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EACZ,KAAK,MAAM,EACX,MAAM,MAAM,OAAO,CAAC,MAAM,CAAC,kBAa5B,CAAC;AAkBF,eAAO,MAAM,IAAI,GAAU,MAAM,SAAS,MAAM,EAAE,uBA+LjD,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { connectCheckout } from "./client.js";
|
|
2
2
|
import { INPUT_PRESETS, isPresetId, presetInput } from "./presets.js";
|
|
3
|
-
import { AGENT_HEARTBEAT_MS, OPTION_ICONS, classifyChoiceAnswer, currentPlan, isAgentPresent, isClosed, isOptionIcon,
|
|
3
|
+
import { AGENT_HEARTBEAT_MS, OPTION_ICONS, classifyChoiceAnswer, currentPlan, isAgentPresent, isClosed, isOptionIcon, isValidModelAnswer, parseEntryPointOptions, parseMultipleAnswer, stepProgress } from "./protocol.js";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import {
|
|
5
|
+
import { open, readFile } from "node:fs/promises";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
//#region src/cli.ts
|
|
@@ -15,17 +15,16 @@ Commands (the url always comes first):
|
|
|
15
15
|
ack <message-id> Acknowledge a user message.
|
|
16
16
|
ask <prompt> [--placeholder <text>] [--optional] [--step <step-id>] [--wait]
|
|
17
17
|
Ask the user for a line of text.
|
|
18
|
-
ask [prompt] --preset <framework|llm-provider
|
|
19
|
-
Ask a standard question. --only restricts the options (one id locks it in); --default preselects one.
|
|
18
|
+
ask [prompt] --preset <framework|llm-provider> [--only <id,id>] [--choices <id=description,...>] [--default <id>] [--optional] [--step <step-id>] [--wait]
|
|
19
|
+
Ask a standard question. --only restricts the options and leaves out --choices (one id locks it in); --default preselects one.
|
|
20
20
|
"framework" answers "<framework>:<language>"; "llm-provider" answers JSON with provider, model and reasoningEffort; the key itself arrives through "env".
|
|
21
|
-
"project" lists the React projects you found as --choices "<path>=<what it is>,..." ahead of "New project"; it answers a path, or "new:<next|vite|react-router|tanstack-start|expo>".
|
|
22
21
|
ask <prompt> --choices <id,id,...> --icons <id=icon,...> [--single] [--default <id>] [--optional] [--step <step-id>] [--wait]
|
|
23
22
|
Ask the user to pick from your own options; they may also type their own answer.
|
|
24
23
|
The user checks any number of options and the answer is a JSON array of option ids (their own text is one entry).
|
|
25
24
|
--single is for options that exclude each other (one framework, one model, yes or no); the answer is then one id.
|
|
26
25
|
--icons is required and names an icon for every option, from: ${OPTION_ICONS.join(", ")}.
|
|
27
|
-
ask <prompt> --
|
|
28
|
-
|
|
26
|
+
ask <prompt> --entry-points '<JSON array>' [--default <id>] [--optional] [--step <step-id>] [--wait]
|
|
27
|
+
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.
|
|
29
28
|
wait start Block until the user clicks Begin plan in their browser; after a finished or cancelled setup, until the next one begins.
|
|
30
29
|
wait <input-id> Block until the user answers or dismisses an input. A multi-select answer prints as an array.
|
|
31
30
|
plan (<markdown> | --file <path>) [--wait]
|
|
@@ -40,7 +39,7 @@ Commands (the url always comes first):
|
|
|
40
39
|
done [--summary <text>] [--preview <url>]
|
|
41
40
|
Mark every remaining step done and propose closing; the user closes it.
|
|
42
41
|
`;
|
|
43
|
-
const
|
|
42
|
+
const agentInstructions = (url) => `You are the coding agent for a setup 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.
|
|
44
43
|
|
|
45
44
|
First, start a persistent Monitor on
|
|
46
45
|
npx setup-agent ${url} stream
|
|
@@ -57,13 +56,14 @@ Keep the stream running while you wait. Do not inspect the project, fetch guides
|
|
|
57
56
|
The remaining phases are plan, then install.
|
|
58
57
|
|
|
59
58
|
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:
|
|
60
|
-
npx setup-agent ${url} ask "Which project should I install into?" --placeholder "An absolute path
|
|
59
|
+
npx setup-agent ${url} ask "Which project should I install into?" --placeholder "An absolute path to the app" --wait
|
|
61
60
|
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:
|
|
62
61
|
npx setup-agent ${url} ask --preset framework
|
|
63
62
|
npx setup-agent ${url} ask --preset llm-provider
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
The setup's products are starting names and guide references. 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.
|
|
64
|
+
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:
|
|
65
|
+
npx setup-agent ${url} ask "Where should users access the assistant?" --entry-points '<JSON array>' --wait
|
|
66
|
+
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.
|
|
67
67
|
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.
|
|
68
68
|
Then write the plan as markdown and submit it:
|
|
69
69
|
npx setup-agent ${url} plan --file <path> --wait
|
|
@@ -82,6 +82,7 @@ Before that, start the project's dev server in the background when the project h
|
|
|
82
82
|
npx setup-agent ${url} wait start
|
|
83
83
|
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.
|
|
84
84
|
`;
|
|
85
|
+
const INSTRUCTIONS = agentInstructions;
|
|
85
86
|
const parseArgs = (argv) => {
|
|
86
87
|
const positional = [];
|
|
87
88
|
const flags = /* @__PURE__ */ new Map();
|
|
@@ -376,6 +377,38 @@ const askSeed = (rest, flags) => {
|
|
|
376
377
|
const stepId = flagText(flags, "step");
|
|
377
378
|
const optional = flags.has("optional");
|
|
378
379
|
const fallback = flagText(flags, "default");
|
|
380
|
+
if (flags.has("entry-points")) {
|
|
381
|
+
if ([
|
|
382
|
+
"preset",
|
|
383
|
+
"product",
|
|
384
|
+
"choices",
|
|
385
|
+
"icons",
|
|
386
|
+
"multiple",
|
|
387
|
+
"single",
|
|
388
|
+
"only",
|
|
389
|
+
"placeholder"
|
|
390
|
+
].some((flag) => flags.has(flag))) return fail("--entry-points cannot be combined with other input kinds, --multiple, --single or --only");
|
|
391
|
+
const json = flagText(flags, "entry-points");
|
|
392
|
+
let value;
|
|
393
|
+
try {
|
|
394
|
+
value = JSON.parse(json ?? "");
|
|
395
|
+
} catch {
|
|
396
|
+
return fail("--entry-points needs a JSON array of options");
|
|
397
|
+
}
|
|
398
|
+
const options = parseEntryPointOptions(value);
|
|
399
|
+
if (!options) return fail("--entry-points needs 1–3 unique options with id, label, description and entryPoint {formFactor, placement, trigger, recommended?}; at most one is recommended");
|
|
400
|
+
if (fallback !== void 0 && !options.some((option) => option.id === fallback)) return fail(`no option "${fallback}" to default to`);
|
|
401
|
+
const prompt = rest[0] ?? fail("usage: ask <prompt> --entry-points '<JSON array>'");
|
|
402
|
+
if (prompt.trim() === "") return fail("an entry-point prompt cannot be empty");
|
|
403
|
+
return {
|
|
404
|
+
kind: "entry-point",
|
|
405
|
+
prompt,
|
|
406
|
+
options,
|
|
407
|
+
...fallback !== void 0 && { default: fallback },
|
|
408
|
+
optional,
|
|
409
|
+
...stepId !== void 0 && { stepId }
|
|
410
|
+
};
|
|
411
|
+
}
|
|
379
412
|
const preset = flagText(flags, "preset");
|
|
380
413
|
if (preset !== void 0) {
|
|
381
414
|
if (!isPresetId(preset)) return fail(`unknown preset "${preset}"; use ${Object.keys(INPUT_PRESETS).join(" or ")}`);
|
|
@@ -446,15 +479,38 @@ const askSeed = (rest, flags) => {
|
|
|
446
479
|
...stepId !== void 0 && { stepId }
|
|
447
480
|
};
|
|
448
481
|
};
|
|
482
|
+
const assertEnvKey = (key) => {
|
|
483
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`"${key}" is not an environment variable name`);
|
|
484
|
+
};
|
|
449
485
|
/** Sets `key=value` in dotenv text, replacing an existing line for the key. */
|
|
450
486
|
const upsertEnvLine = (content, key, value) => {
|
|
487
|
+
assertEnvKey(key);
|
|
451
488
|
const line = `${key}=${value}`;
|
|
452
489
|
const lines = content === "" ? [] : content.replace(/\n$/, "").split("\n");
|
|
453
|
-
const index = lines.findIndex((entry) =>
|
|
490
|
+
const index = lines.findIndex((entry) => /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(entry)?.[1] === key);
|
|
454
491
|
if (index === -1) lines.push(line);
|
|
455
492
|
else lines[index] = line;
|
|
456
493
|
return `${lines.join("\n")}\n`;
|
|
457
494
|
};
|
|
495
|
+
/**
|
|
496
|
+
* Write the secret `take` returns to `file` under `key`, leaving the file
|
|
497
|
+
* owner-only. The file is opened before `take` runs, because taking the
|
|
498
|
+
* secret consumes it and an unwritable destination must fail while it is
|
|
499
|
+
* still deposited; it is read after, so lines written meanwhile survive.
|
|
500
|
+
*/
|
|
501
|
+
const writeEnvSecret = async (file, key, take) => {
|
|
502
|
+
assertEnvKey(key);
|
|
503
|
+
const handle = await open(file, "a+", 384);
|
|
504
|
+
try {
|
|
505
|
+
await handle.chmod(384);
|
|
506
|
+
const value = await take();
|
|
507
|
+
const current = await handle.readFile("utf8");
|
|
508
|
+
await handle.truncate(0);
|
|
509
|
+
await handle.writeFile(upsertEnvLine(current, key, value));
|
|
510
|
+
} finally {
|
|
511
|
+
await handle.close();
|
|
512
|
+
}
|
|
513
|
+
};
|
|
458
514
|
const answerFields = (input, answer) => ({
|
|
459
515
|
answer: input.multiple && parseMultipleAnswer(answer) || answer,
|
|
460
516
|
...input.note !== void 0 && { note: input.note },
|
|
@@ -579,16 +635,19 @@ const main = async (argv) => {
|
|
|
579
635
|
const inputId = rest[0] ?? fail(usage);
|
|
580
636
|
const envKey = rest[1] ?? fail(usage);
|
|
581
637
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(envKey)) fail(`"${envKey}" is not an environment variable name`);
|
|
582
|
-
const
|
|
638
|
+
const state = requireState(client);
|
|
639
|
+
const input = state.inputs.find((candidate) => candidate.id === inputId) ?? fail(`no input "${inputId}"`);
|
|
640
|
+
const setupId = state.id ?? fail("the setup has not been created yet");
|
|
641
|
+
if (isClosed(state)) fail("the setup is not open");
|
|
583
642
|
if (input.status !== "answered") fail(`input "${inputId}" is ${input.status}`);
|
|
584
|
-
if (
|
|
643
|
+
if (input.kind !== "model" || !isValidModelAnswer(input, input.answer ?? "")) fail(`input "${inputId}" is not an llm-provider answer`);
|
|
585
644
|
const file = resolve(flagText(flags, "file") ?? ".env.local");
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
645
|
+
await writeEnvSecret(file, envKey, async () => {
|
|
646
|
+
const response = await fetch(`${url.replace(/\/$/, "")}/secret/${encodeURIComponent(inputId)}?setup=${encodeURIComponent(setupId)}`);
|
|
647
|
+
if (response.status === 404) fail(`the key for "${inputId}" was already taken or never deposited`);
|
|
648
|
+
if (!response.ok) fail(`could not fetch the key: ${response.status}`);
|
|
649
|
+
return response.text();
|
|
650
|
+
});
|
|
592
651
|
print({
|
|
593
652
|
ok: true,
|
|
594
653
|
envKey,
|
|
@@ -631,6 +690,6 @@ const main = async (argv) => {
|
|
|
631
690
|
}
|
|
632
691
|
};
|
|
633
692
|
//#endregion
|
|
634
|
-
export { HELP, INSTRUCTIONS, askSeed, detectAgentKind, diffEvents, isDirectInvocation, main, upsertEnvLine, waitForStart };
|
|
693
|
+
export { HELP, INSTRUCTIONS, agentInstructions, askSeed, detectAgentKind, diffEvents, isDirectInvocation, main, upsertEnvLine, waitForStart, writeEnvSecret };
|
|
635
694
|
|
|
636
695
|
//# sourceMappingURL=cli.js.map
|