setup-agent 0.0.7 → 0.0.9

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.
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A coding agent a person can hand a setup to. `launch` is set when the
3
+ * agent has a terminal command that opens an interactive session with a
4
+ * first prompt; the others are handed the prompt to paste.
5
+ */
6
+ export type CodingAgent = {
7
+ kind: string;
8
+ name: string;
9
+ launch?: {
10
+ /** Command names to try in order; the first one on PATH runs. */
11
+ commands: readonly string[];
12
+ args: (prompt: string) => string[];
13
+ install: string;
14
+ };
15
+ };
16
+ /** The coding agents people use most, in the order the picker lists them. */
17
+ export declare const CODING_AGENTS: readonly CodingAgent[];
18
+ /** The coding agent this process runs under, from the variables each one exports to its shell. */
19
+ export declare const detectAgentKind: (env?: NodeJS.ProcessEnv) => string | undefined;
20
+ /** True when a person typed the command into a terminal rather than an agent running it from its shell. */
21
+ export declare const isDirectInvocation: (env?: NodeJS.ProcessEnv, stdin?: {
22
+ isTTY?: boolean | undefined;
23
+ }, stdout?: {
24
+ isTTY?: boolean | undefined;
25
+ }) => boolean;
26
+ /** The first of `commands` found on PATH, or `undefined`. */
27
+ export declare const findCommand: (commands: readonly string[], env?: NodeJS.ProcessEnv) => string | undefined;
28
+ /**
29
+ * Starts `agent` in this terminal with `prompt` as its first message and
30
+ * resolves with its exit code; `undefined` when its command is not installed.
31
+ */
32
+ export declare const runCodingAgent: (agent: CodingAgent, prompt: string) => Promise<number | undefined>;
33
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +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"}
package/dist/agents.js ADDED
@@ -0,0 +1,221 @@
1
+ import spawn from "cross-spawn";
2
+ import { accessSync, constants } from "node:fs";
3
+ import { delimiter, join } from "node:path";
4
+ // src/agents.ts
5
+ /** The coding agents people use most, in the order the picker lists them. */
6
+ const CODING_AGENTS = [
7
+ {
8
+ kind: "claude",
9
+ name: "Claude Code",
10
+ launch: {
11
+ commands: ["claude"],
12
+ args: (prompt) => [prompt],
13
+ install: "npm install -g @anthropic-ai/claude-code"
14
+ }
15
+ },
16
+ {
17
+ kind: "codex",
18
+ name: "Codex",
19
+ launch: {
20
+ commands: ["codex"],
21
+ args: (prompt) => [prompt],
22
+ install: "npm install -g @openai/codex"
23
+ }
24
+ },
25
+ {
26
+ kind: "cursor",
27
+ name: "Cursor",
28
+ launch: {
29
+ commands: ["cursor-agent", "agent"],
30
+ args: (prompt) => [prompt],
31
+ install: "curl https://cursor.com/install -fsS | bash"
32
+ }
33
+ },
34
+ {
35
+ kind: "gemini",
36
+ name: "Gemini CLI",
37
+ launch: {
38
+ commands: ["gemini"],
39
+ args: (prompt) => ["-i", prompt],
40
+ install: "npm install -g @google/gemini-cli"
41
+ }
42
+ },
43
+ {
44
+ kind: "copilot",
45
+ name: "Copilot CLI",
46
+ launch: {
47
+ commands: ["copilot"],
48
+ args: (prompt) => ["-i", prompt],
49
+ install: "npm install -g @github/copilot"
50
+ }
51
+ },
52
+ {
53
+ kind: "amp",
54
+ name: "Amp"
55
+ },
56
+ {
57
+ kind: "opencode",
58
+ name: "OpenCode",
59
+ launch: {
60
+ commands: ["opencode"],
61
+ args: (prompt) => ["--prompt", prompt],
62
+ install: "npm install -g opencode-ai"
63
+ }
64
+ },
65
+ {
66
+ kind: "cline",
67
+ name: "Cline"
68
+ },
69
+ {
70
+ kind: "aider",
71
+ name: "Aider"
72
+ },
73
+ {
74
+ kind: "goose",
75
+ name: "Goose"
76
+ },
77
+ {
78
+ kind: "warp",
79
+ name: "Warp"
80
+ },
81
+ {
82
+ kind: "windsurf",
83
+ name: "Windsurf"
84
+ },
85
+ {
86
+ kind: "kiro",
87
+ name: "Kiro",
88
+ launch: {
89
+ commands: ["kiro-cli"],
90
+ args: (prompt) => ["chat", prompt],
91
+ install: "curl -fsSL https://cli.kiro.dev/install | bash"
92
+ }
93
+ },
94
+ {
95
+ kind: "junie",
96
+ name: "Junie"
97
+ },
98
+ {
99
+ kind: "qwen",
100
+ name: "Qwen Code",
101
+ launch: {
102
+ commands: ["qwen"],
103
+ args: (prompt) => ["-i", prompt],
104
+ install: "npm install -g @qwen-code/qwen-code"
105
+ }
106
+ },
107
+ {
108
+ kind: "roo",
109
+ name: "Roo Code"
110
+ },
111
+ {
112
+ kind: "zed",
113
+ name: "Zed"
114
+ },
115
+ {
116
+ kind: "augment",
117
+ name: "Augment",
118
+ launch: {
119
+ commands: ["auggie"],
120
+ args: (prompt) => [prompt],
121
+ install: "npm install -g @augmentcode/auggie"
122
+ }
123
+ },
124
+ {
125
+ kind: "droid",
126
+ name: "Factory Droid",
127
+ launch: {
128
+ commands: ["droid"],
129
+ args: (prompt) => [prompt],
130
+ install: "curl -fsSL https://app.factory.ai/cli | sh"
131
+ }
132
+ },
133
+ {
134
+ kind: "crush",
135
+ name: "Crush"
136
+ }
137
+ ];
138
+ /** The variables each agent exports to the shells it runs commands in. */
139
+ const AGENT_ENV = [
140
+ ["claude", "CLAUDECODE"],
141
+ ["claude", "CLAUDE_CODE"],
142
+ ["codex", "CODEX_SANDBOX"],
143
+ ["codex", "CODEX_SANDBOX_NETWORK_DISABLED"],
144
+ ["codex", "CODEX_CI"],
145
+ ["codex", "CODEX_THREAD_ID"],
146
+ ["cursor", "CURSOR_AGENT"],
147
+ ["gemini", "GEMINI_CLI"],
148
+ ["qwen", "QWEN_CODE"],
149
+ ["copilot", "COPILOT_AGENT"],
150
+ ["copilot", "COPILOT_CLI"],
151
+ ["opencode", "OPENCODE_CLIENT"],
152
+ ["augment", "AUGMENT_AGENT"],
153
+ ["junie", "JUNIE_DATA"]
154
+ ];
155
+ /** `AI_AGENT` values name the agent first, such as `claude-code_2-1-3_agent` or `github-copilot`. */
156
+ const AI_AGENT_PREFIXES = [
157
+ ["claude", "claude"],
158
+ ["codex", "codex"],
159
+ ["cursor", "cursor"],
160
+ ["gemini", "gemini"],
161
+ ["github-copilot", "copilot"],
162
+ ["github_copilot", "copilot"],
163
+ ["copilot", "copilot"],
164
+ ["opencode", "opencode"],
165
+ ["augment", "augment"],
166
+ ["auggie", "augment"],
167
+ ["qwen", "qwen"],
168
+ ["amp", "amp"],
169
+ ["goose", "goose"],
170
+ ["junie", "junie"],
171
+ ["droid", "droid"],
172
+ ["factory", "droid"],
173
+ ["kiro", "kiro"],
174
+ ["cline", "cline"],
175
+ ["crush", "crush"]
176
+ ];
177
+ /** The coding agent this process runs under, from the variables each one exports to its shell. */
178
+ const detectAgentKind = (env = process.env) => {
179
+ const named = env.AI_AGENT?.trim().toLowerCase();
180
+ if (named) return AI_AGENT_PREFIXES.find(([prefix]) => named.startsWith(prefix) && (named.length === prefix.length || /[-_ ./0-9]/.test(named[prefix.length])))?.[1] ?? "other";
181
+ return AGENT_ENV.find(([, variable]) => env[variable])?.[0];
182
+ };
183
+ /** True when a person typed the command into a terminal rather than an agent running it from its shell. */
184
+ const isDirectInvocation = (env = process.env, stdin = process.stdin, stdout = process.stdout) => detectAgentKind(env) === void 0 && stdin.isTTY === true && stdout.isTTY === true;
185
+ /** The first of `commands` found on PATH, or `undefined`. */
186
+ const findCommand = (commands, env = process.env) => {
187
+ const directories = (env.PATH ?? "").split(delimiter).filter(Boolean);
188
+ const extensions = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
189
+ return commands.find((command) => directories.some((directory) => extensions.some((extension) => {
190
+ try {
191
+ accessSync(join(directory, command + extension), constants.X_OK);
192
+ return true;
193
+ } catch {
194
+ return false;
195
+ }
196
+ })));
197
+ };
198
+ /**
199
+ * Starts `agent` in this terminal with `prompt` as its first message and
200
+ * resolves with its exit code; `undefined` when its command is not installed.
201
+ */
202
+ const runCodingAgent = (agent, prompt) => {
203
+ const launch = agent.launch;
204
+ if (!launch) return Promise.resolve(void 0);
205
+ const command = findCommand(launch.commands);
206
+ if (command === void 0) return Promise.resolve(void 0);
207
+ const child = spawn(command, launch.args(prompt), { stdio: "inherit" });
208
+ return new Promise((resolve) => {
209
+ child.on("error", (error) => {
210
+ if (error.code === "ENOENT") resolve(void 0);
211
+ else {
212
+ process.stderr.write(`${error.message}\n`);
213
+ resolve(1);
214
+ }
215
+ });
216
+ child.on("exit", (status) => resolve(status ?? 1));
217
+ });
218
+ };
219
+ export { CODING_AGENTS, detectAgentKind, findCommand, isDirectInvocation, runCodingAgent };
220
+
221
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","names":[],"sources":["../src/agents.ts"],"sourcesContent":["import spawn from \"cross-spawn\";\nimport { accessSync, constants } from \"node:fs\";\nimport { delimiter, join } from \"node:path\";\n\n/**\n * A coding agent a person can hand a setup to. `launch` is set when the\n * agent has a terminal command that opens an interactive session with a\n * first prompt; the others are handed the prompt to paste.\n */\nexport type CodingAgent = {\n kind: string;\n name: string;\n launch?: {\n /** Command names to try in order; the first one on PATH runs. */\n commands: readonly string[];\n args: (prompt: string) => string[];\n install: string;\n };\n};\n\n/** The coding agents people use most, in the order the picker lists them. */\nexport const CODING_AGENTS: readonly CodingAgent[] = [\n {\n kind: \"claude\",\n name: \"Claude Code\",\n launch: {\n commands: [\"claude\"],\n args: (prompt) => [prompt],\n install: \"npm install -g @anthropic-ai/claude-code\",\n },\n },\n {\n kind: \"codex\",\n name: \"Codex\",\n launch: {\n commands: [\"codex\"],\n args: (prompt) => [prompt],\n install: \"npm install -g @openai/codex\",\n },\n },\n {\n kind: \"cursor\",\n name: \"Cursor\",\n launch: {\n commands: [\"cursor-agent\", \"agent\"],\n args: (prompt) => [prompt],\n install: \"curl https://cursor.com/install -fsS | bash\",\n },\n },\n {\n kind: \"gemini\",\n name: \"Gemini CLI\",\n launch: {\n commands: [\"gemini\"],\n args: (prompt) => [\"-i\", prompt],\n install: \"npm install -g @google/gemini-cli\",\n },\n },\n {\n kind: \"copilot\",\n name: \"Copilot CLI\",\n launch: {\n commands: [\"copilot\"],\n args: (prompt) => [\"-i\", prompt],\n install: \"npm install -g @github/copilot\",\n },\n },\n { kind: \"amp\", name: \"Amp\" },\n {\n kind: \"opencode\",\n name: \"OpenCode\",\n launch: {\n commands: [\"opencode\"],\n args: (prompt) => [\"--prompt\", prompt],\n install: \"npm install -g opencode-ai\",\n },\n },\n { kind: \"cline\", name: \"Cline\" },\n { kind: \"aider\", name: \"Aider\" },\n { kind: \"goose\", name: \"Goose\" },\n { kind: \"warp\", name: \"Warp\" },\n { kind: \"windsurf\", name: \"Windsurf\" },\n {\n kind: \"kiro\",\n name: \"Kiro\",\n launch: {\n commands: [\"kiro-cli\"],\n args: (prompt) => [\"chat\", prompt],\n install: \"curl -fsSL https://cli.kiro.dev/install | bash\",\n },\n },\n { kind: \"junie\", name: \"Junie\" },\n {\n kind: \"qwen\",\n name: \"Qwen Code\",\n launch: {\n commands: [\"qwen\"],\n args: (prompt) => [\"-i\", prompt],\n install: \"npm install -g @qwen-code/qwen-code\",\n },\n },\n { kind: \"roo\", name: \"Roo Code\" },\n { kind: \"zed\", name: \"Zed\" },\n {\n kind: \"augment\",\n name: \"Augment\",\n launch: {\n commands: [\"auggie\"],\n args: (prompt) => [prompt],\n install: \"npm install -g @augmentcode/auggie\",\n },\n },\n {\n kind: \"droid\",\n name: \"Factory Droid\",\n launch: {\n commands: [\"droid\"],\n args: (prompt) => [prompt],\n install: \"curl -fsSL https://app.factory.ai/cli | sh\",\n },\n },\n { kind: \"crush\", name: \"Crush\" },\n];\n\n/** The variables each agent exports to the shells it runs commands in. */\nconst AGENT_ENV: readonly [kind: string, variable: string][] = [\n [\"claude\", \"CLAUDECODE\"],\n [\"claude\", \"CLAUDE_CODE\"],\n [\"codex\", \"CODEX_SANDBOX\"],\n [\"codex\", \"CODEX_SANDBOX_NETWORK_DISABLED\"],\n [\"codex\", \"CODEX_CI\"],\n [\"codex\", \"CODEX_THREAD_ID\"],\n [\"cursor\", \"CURSOR_AGENT\"],\n [\"gemini\", \"GEMINI_CLI\"],\n [\"qwen\", \"QWEN_CODE\"],\n [\"copilot\", \"COPILOT_AGENT\"],\n [\"copilot\", \"COPILOT_CLI\"],\n [\"opencode\", \"OPENCODE_CLIENT\"],\n [\"augment\", \"AUGMENT_AGENT\"],\n [\"junie\", \"JUNIE_DATA\"],\n];\n\n/** `AI_AGENT` values name the agent first, such as `claude-code_2-1-3_agent` or `github-copilot`. */\nconst AI_AGENT_PREFIXES: readonly [prefix: string, kind: string][] = [\n [\"claude\", \"claude\"],\n [\"codex\", \"codex\"],\n [\"cursor\", \"cursor\"],\n [\"gemini\", \"gemini\"],\n [\"github-copilot\", \"copilot\"],\n [\"github_copilot\", \"copilot\"],\n [\"copilot\", \"copilot\"],\n [\"opencode\", \"opencode\"],\n [\"augment\", \"augment\"],\n [\"auggie\", \"augment\"],\n [\"qwen\", \"qwen\"],\n [\"amp\", \"amp\"],\n [\"goose\", \"goose\"],\n [\"junie\", \"junie\"],\n [\"droid\", \"droid\"],\n [\"factory\", \"droid\"],\n [\"kiro\", \"kiro\"],\n [\"cline\", \"cline\"],\n [\"crush\", \"crush\"],\n];\n\n/** The coding agent this process runs under, from the variables each one exports to its shell. */\nexport const detectAgentKind = (\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined => {\n const named = env.AI_AGENT?.trim().toLowerCase();\n if (named) {\n // The name, or the name followed by a separator and a version or role.\n const match = AI_AGENT_PREFIXES.find(\n ([prefix]) =>\n named.startsWith(prefix) &&\n (named.length === prefix.length ||\n /[-_ ./0-9]/.test(named[prefix.length]!)),\n );\n return match?.[1] ?? \"other\";\n }\n return AGENT_ENV.find(([, variable]) => env[variable])?.[0];\n};\n\n/** True when a person typed the command into a terminal rather than an agent running it from its shell. */\nexport const isDirectInvocation = (\n env: NodeJS.ProcessEnv = process.env,\n stdin: { isTTY?: boolean | undefined } = process.stdin,\n stdout: { isTTY?: boolean | undefined } = process.stdout,\n) =>\n detectAgentKind(env) === undefined &&\n stdin.isTTY === true &&\n stdout.isTTY === true;\n\n/** The first of `commands` found on PATH, or `undefined`. */\nexport const findCommand = (\n commands: readonly string[],\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined => {\n // Empty entries (the working directory) are skipped on purpose: a project\n // must not be able to plant its own `claude` next to the user.\n const directories = (env.PATH ?? \"\").split(delimiter).filter(Boolean);\n const extensions =\n process.platform === \"win32\"\n ? (env.PATHEXT ?? \".EXE;.CMD;.BAT\").split(\";\")\n : [\"\"];\n return commands.find((command) =>\n directories.some((directory) =>\n extensions.some((extension) => {\n try {\n accessSync(join(directory, command + extension), constants.X_OK);\n return true;\n } catch {\n return false;\n }\n }),\n ),\n );\n};\n\n/**\n * Starts `agent` in this terminal with `prompt` as its first message and\n * resolves with its exit code; `undefined` when its command is not installed.\n */\nexport const runCodingAgent = (\n agent: CodingAgent,\n prompt: string,\n): Promise<number | undefined> => {\n const launch = agent.launch;\n if (!launch) return Promise.resolve(undefined);\n const command = findCommand(launch.commands);\n if (command === undefined) return Promise.resolve(undefined);\n const child = spawn(command, launch.args(prompt), { stdio: \"inherit\" });\n return new Promise((resolve) => {\n child.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") resolve(undefined);\n else {\n process.stderr.write(`${error.message}\\n`);\n resolve(1);\n }\n });\n child.on(\"exit\", (status) => resolve(status ?? 1));\n });\n};\n"],"mappings":"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"}
package/dist/cli.d.ts CHANGED
@@ -1,20 +1,33 @@
1
1
  import type { CheckoutClient } from "./client.js";
2
2
  import type { Checkout } from "./protocol.js";
3
3
  export declare const HELP: string;
4
- export declare const agentInstructions: (url: string) => string;
5
- export declare const INSTRUCTIONS: (url: string) => string;
4
+ /**
5
+ * The normalized setup URL, or an error saying why it is refused. A setup URL
6
+ * is printed into shell commands and the coding agent's prompt, and the API
7
+ * key travels over it, so it must be https (http only on this machine), carry
8
+ * no credentials, query or fragment, and hold only address characters.
9
+ */
10
+ export declare const parseSetupUrl: (value: string) => string;
11
+ export type AgentInstructionsOptions = {
12
+ /**
13
+ * The agent started this setup with `assistant-ui setup`, which already
14
+ * began planning and opened the setup page in the user's browser; there is
15
+ * no Begin plan click to wait for.
16
+ */
17
+ startedByAgent?: {
18
+ browserUrl: string;
19
+ /** False when the browser was not opened for the user; the agent then hands them the link. */
20
+ opened?: boolean;
21
+ };
22
+ };
23
+ export declare const agentInstructions: (setupUrl: string, options?: AgentInstructionsOptions) => string;
24
+ export declare const INSTRUCTIONS: (setupUrl: string, options?: AgentInstructionsOptions) => string;
6
25
  type Parsed = {
7
26
  positional: string[];
8
27
  flags: Map<string, string | true>;
9
28
  };
10
- /** The coding agent this process runs under, from the variables each one exports to its shell. */
11
- export declare const detectAgentKind: (env?: NodeJS.ProcessEnv) => string | undefined;
12
- /** True when a person typed the command into a terminal rather than an agent running it from its shell. */
13
- export declare const isDirectInvocation: (env?: NodeJS.ProcessEnv, stdin?: {
14
- isTTY?: boolean | undefined;
15
- }, stdout?: {
16
- isTTY?: boolean | undefined;
17
- }) => boolean;
29
+ export { CODING_AGENTS, detectAgentKind, findCommand, isDirectInvocation, runCodingAgent, } from "./agents.js";
30
+ export type { CodingAgent } from "./agents.js";
18
31
  type StreamEvent = {
19
32
  event: "planning.started";
20
33
  } | {
@@ -66,6 +79,5 @@ export declare const upsertEnvLine: (content: string, key: string, value: string
66
79
  * still deposited; it is read after, so lines written meanwhile survive.
67
80
  */
68
81
  export declare const writeEnvSecret: (file: string, key: string, take: () => Promise<string>, projectRoot?: string) => Promise<void>;
69
- export declare const main: (argv: readonly string[]) => Promise<undefined>;
70
- export {};
82
+ export declare const main: (argv: readonly string[]) => Promise<void>;
71
83
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAiB;AA8B/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,EAC3B,oBAA2B,kBAoD5B,CAAC;AAyHF,eAAO,MAAM,IAAI,GAAU,MAAM,SAAS,MAAM,EAAE,uBA8OjD,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,13 +1,13 @@
1
+ import { CODING_AGENTS, detectAgentKind, findCommand, isDirectInvocation, runCodingAgent } from "./agents.js";
1
2
  import { connectCheckout } from "./client.js";
2
3
  import { INPUT_PRESETS, isPresetId, presetInput } from "./presets.js";
3
- import { AGENT_HEARTBEAT_MS, OPTION_ICONS, classifyChoiceAnswer, currentPlan, isAgentPresent, isClosed, isOptionIcon, isValidModelAnswer, parseEntryPointOptions, parseMultipleAnswer, stepProgress } from "./protocol.js";
4
- import { spawn } from "node:child_process";
5
- import { createHash, randomBytes } from "node:crypto";
4
+ import { AGENT_HEARTBEAT_MS, OPTION_ICONS, classifyChoiceAnswer, currentPlan, isAgentPresent, isClosed, isLoopbackHostname, isOptionIcon, isValidModelAnswer, parseEntryPointOptions, parseMultipleAnswer, stepProgress } from "./protocol.js";
6
5
  import { constants } from "node:fs";
6
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
+ import { createHash, randomBytes } from "node:crypto";
7
8
  import { chmod, lstat, mkdir, open, readFile, realpath, unlink } from "node:fs/promises";
8
9
  import { homedir, tmpdir, userInfo } from "node:os";
9
10
  import { createInterface } from "node:readline/promises";
10
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
11
11
  // src/cli.ts
12
12
  const HELP = `setup-agent <url> [command]
13
13
 
@@ -42,7 +42,38 @@ Commands (the url always comes first):
42
42
  done [--summary <text>] [--preview <url>]
43
43
  Mark every remaining step done and propose closing; the user closes it.
44
44
  `;
45
- 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.
45
+ /** Characters a setup URL may hold: an origin and a path, nothing a shell or a reader treats as more than an address. */
46
+ const SETUP_URL_PATTERN = /^https?:\/\/[A-Za-z0-9.-]+(?::[0-9]+)?(?:\/[A-Za-z0-9._~%/-]*)?$|^http:\/\/\[::1\](?::[0-9]+)?(?:\/[A-Za-z0-9._~%/-]*)?$/;
47
+ /**
48
+ * The normalized setup URL, or an error saying why it is refused. A setup URL
49
+ * is printed into shell commands and the coding agent's prompt, and the API
50
+ * key travels over it, so it must be https (http only on this machine), carry
51
+ * no credentials, query or fragment, and hold only address characters.
52
+ */
53
+ const parseSetupUrl = (value) => {
54
+ let url;
55
+ try {
56
+ url = new URL(value);
57
+ } catch {
58
+ throw new Error(`"${value}" is not a URL`);
59
+ }
60
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHostname(url.hostname))) throw new Error("the setup URL must use https (http only on localhost)");
61
+ if (url.username !== "" || url.password !== "") throw new Error("the setup URL must not carry credentials");
62
+ if (url.search !== "" || url.hash !== "" || value.includes("?") || value.includes("#")) throw new Error("the setup URL must not have a query or fragment");
63
+ const href = url.href.replace(/\/$/, "");
64
+ if (!SETUP_URL_PATTERN.test(href)) throw new Error("the setup URL has characters a setup URL does not use");
65
+ return href;
66
+ };
67
+ /** `value` as one shell word, quoted only when it holds more than plain URL characters. */
68
+ const shellWord = (value) => /^[A-Za-z0-9._~%/:@=+,-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
69
+ const beginPlanHandoff = (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:
70
+ npx setup-agent ${url} wait start
71
+ 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.`;
72
+ const agentStartedHandoff = ({ browserUrl, opened = true }) => `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.`;
73
+ const agentInstructions = (setupUrl, options = {}) => {
74
+ const url = shellWord(setupUrl);
75
+ const started = options.startedByAgent;
76
+ return `You are the coding agent for a setup ${started ? "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.
46
77
 
47
78
  First, start a persistent Monitor on
48
79
  npx setup-agent ${url} stream
@@ -52,9 +83,7 @@ The user can message you at any time, including while a question is waiting. Rea
52
83
 
53
84
  "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.
54
85
 
55
- 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:
56
- npx setup-agent ${url} wait start
57
- 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}. 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.
86
+ ${started ? agentStartedHandoff(started) : beginPlanHandoff(url)}
58
87
 
59
88
  The remaining phases are plan, then install.
60
89
 
@@ -63,7 +92,7 @@ The remaining phases are plan, then install.
63
92
  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:
64
93
  npx setup-agent ${url} ask --preset framework
65
94
  npx setup-agent ${url} ask --preset llm-provider
66
- 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.
95
+ 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.
67
96
  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:
68
97
  npx setup-agent ${url} ask "Where should users access the assistant?" --entry-points '<JSON array>' --wait
69
98
  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.
@@ -85,6 +114,7 @@ Before that, start the project's dev server in the background when the project h
85
114
  npx setup-agent ${url} wait start
86
115
  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.
87
116
  `;
117
+ };
88
118
  const INSTRUCTIONS = agentInstructions;
89
119
  const parseArgs = (argv) => {
90
120
  const positional = [];
@@ -118,47 +148,8 @@ const fail = (message) => {
118
148
  const print = (value) => {
119
149
  process.stdout.write(`${JSON.stringify(value)}\n`);
120
150
  };
121
- const AGENT_ENV = [
122
- ["claude", "CLAUDECODE"],
123
- ["codex", "CODEX_SANDBOX"],
124
- ["codex", "CODEX_SANDBOX_NETWORK_DISABLED"],
125
- ["cursor", "CURSOR_AGENT"],
126
- ["gemini", "GEMINI_CLI"]
127
- ];
128
- /** The coding agent this process runs under, from the variables each one exports to its shell. */
129
- const detectAgentKind = (env = process.env) => AGENT_ENV.find(([, variable]) => env[variable])?.[0];
130
- const LAUNCHERS = [
131
- {
132
- kind: "claude",
133
- name: "Claude Code",
134
- command: "claude",
135
- args: (prompt) => [prompt],
136
- install: "npm install -g @anthropic-ai/claude-code"
137
- },
138
- {
139
- kind: "codex",
140
- name: "Codex",
141
- command: "codex",
142
- args: (prompt) => [prompt],
143
- install: "npm install -g @openai/codex"
144
- },
145
- {
146
- kind: "cursor",
147
- name: "Cursor",
148
- command: "agent",
149
- args: (prompt) => [prompt],
150
- install: "curl https://cursor.com/install -fsS | bash"
151
- },
152
- {
153
- kind: "gemini",
154
- name: "Gemini CLI",
155
- command: "gemini",
156
- args: (prompt) => ["-i", prompt],
157
- install: "npm install -g @google/gemini-cli"
158
- }
159
- ];
160
- /** True when a person typed the command into a terminal rather than an agent running it from its shell. */
161
- const isDirectInvocation = (env = process.env, stdin = process.stdin, stdout = process.stdout) => detectAgentKind(env) === void 0 && stdin.isTTY === true && stdout.isTTY === true;
151
+ /** The agents `setup-agent <url>` can start itself, for a person who ran it in a terminal. */
152
+ const LAUNCHERS = CODING_AGENTS.filter((agent) => agent.launch !== void 0);
162
153
  const chooseLauncher = async () => {
163
154
  const rl = createInterface({
164
155
  input: process.stdin,
@@ -184,18 +175,11 @@ const chooseLauncher = async () => {
184
175
  };
185
176
  const launchAgent = async (url) => {
186
177
  const launcher = await chooseLauncher();
187
- const prompt = `Run \`npx setup-agent ${url}\` and follow the instructions it prints.`;
178
+ const prompt = `Run \`npx setup-agent ${shellWord(url)}\` and follow the instructions it prints.`;
188
179
  process.stdout.write(`\nStarting ${launcher.name}...\n\n`);
189
- const child = spawn(launcher.command, launcher.args(prompt), { stdio: "inherit" });
190
- const code = await new Promise((resolve) => {
191
- child.on("error", (error) => {
192
- if (error.code === "ENOENT") process.stderr.write(`${launcher.name} is not installed. Install it with:\n ${launcher.install}\nthen run this command again.\n`);
193
- else process.stderr.write(`${error.message}\n`);
194
- resolve(1);
195
- });
196
- child.on("exit", (status) => resolve(status ?? 1));
197
- });
198
- process.exit(code);
180
+ const code = await runCodingAgent(launcher, prompt);
181
+ if (code === void 0) process.stderr.write(`${launcher.name} is not installed. Install it with:\n ${launcher.launch.install}\nthen run this command again.\n`);
182
+ process.exitCode = code ?? 1;
199
183
  };
200
184
  const requireState = (client) => client.state ?? fail("the setup has no state");
201
185
  const summary = (state) => {
@@ -488,6 +472,8 @@ const assertEnvKey = (key) => {
488
472
  /** Sets `key=value` in dotenv text, replacing an existing line for the key. */
489
473
  const upsertEnvLine = (content, key, value) => {
490
474
  assertEnvKey(key);
475
+ const isLineBreaking = (code) => code < 32 || code >= 127 && code <= 159 || code === 8232 || code === 8233;
476
+ if ([...value].some((char) => isLineBreaking(char.charCodeAt(0)))) throw new Error(`the value for ${key} must be a single line`);
491
477
  const line = `${key}=${value}`;
492
478
  const lines = content === "" ? [] : content.replace(/\n$/, "").split("\n");
493
479
  const index = lines.findIndex((entry) => /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(entry)?.[1] === key);
@@ -523,8 +509,9 @@ const writeEnvSecret = async (file, key, take, projectRoot = process.cwd()) => {
523
509
  await handle.chmod(384);
524
510
  const value = await take();
525
511
  const current = await handle.readFile("utf8");
512
+ const next = upsertEnvLine(current, key, value);
526
513
  await handle.truncate(0);
527
- await handle.writeFile(upsertEnvLine(current, key, value));
514
+ await handle.writeFile(next);
528
515
  } finally {
529
516
  await handle.close();
530
517
  }
@@ -610,7 +597,12 @@ const main = async (argv) => {
610
597
  process.stdout.write(HELP);
611
598
  return process.exit(first === void 0 ? 1 : 0);
612
599
  }
613
- const url = first;
600
+ let url;
601
+ try {
602
+ url = parseSetupUrl(first);
603
+ } catch (error) {
604
+ return fail(error instanceof Error ? error.message : String(error));
605
+ }
614
606
  if (command === "intro" && isDirectInvocation()) return launchAgent(url);
615
607
  const client = await connectCheckout(url).catch((error) => fail(error instanceof Error ? error.message : String(error)));
616
608
  const commands = client.commands;
@@ -739,11 +731,15 @@ const main = async (argv) => {
739
731
  if (input.redeemKeyHash !== void 0 && storedKey === void 0) fail(`no local redeem key for "${inputId}"`);
740
732
  const file = resolve(flagText(flags, "file") ?? ".env.local");
741
733
  await writeEnvSecret(file, envKey, async () => {
734
+ const secretUrl = new URL(url);
735
+ if (secretUrl.protocol !== "https:" && !(secretUrl.protocol === "http:" && isLoopbackHostname(secretUrl.hostname))) fail("the key is only fetched over https");
742
736
  const response = await fetch(`${url.replace(/\/$/, "")}/secret/${encodeURIComponent(inputId)}?setup=${encodeURIComponent(setupId)}`, storedKey === void 0 ? void 0 : { headers: { "Setup-Agent-Redeem-Key": storedKey.key } });
743
737
  if (response.status === 403) fail(`redeem key rejected for "${inputId}"`);
744
738
  if (response.status === 404) fail(`the key for "${inputId}" was already taken or never deposited`);
745
739
  if (!response.ok) fail(`could not fetch the key: ${response.status}`);
746
- return response.text();
740
+ const key = await response.text();
741
+ if (!/^[A-Za-z0-9._~+/=:@%,-]+$/.test(key)) fail("the deposited key is not a valid API key");
742
+ return key;
747
743
  });
748
744
  if (storedKey !== void 0) await unlink(storedKey.file).catch((error) => {
749
745
  if (error.code !== "ENOENT") process.stderr.write(`warning: could not remove a redeem key: ${error.message}\n`);
@@ -789,6 +785,6 @@ const main = async (argv) => {
789
785
  client.dispose();
790
786
  }
791
787
  };
792
- export { HELP, INSTRUCTIONS, agentInstructions, askSeed, detectAgentKind, diffEvents, isDirectInvocation, main, upsertEnvLine, waitForStart, writeEnvSecret };
788
+ export { CODING_AGENTS, HELP, INSTRUCTIONS, agentInstructions, askSeed, detectAgentKind, diffEvents, findCommand, isDirectInvocation, main, parseSetupUrl, runCodingAgent, upsertEnvLine, waitForStart, writeEnvSecret };
793
789
 
794
790
  //# sourceMappingURL=cli.js.map