@gigaai/newton 0.3.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.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # Newton
2
+
3
+ Newton runs deterministic multi-agent workflows. A workflow is a plain
4
+ JavaScript file; the control flow (loops, fan-out, conditionals) is your code,
5
+ and the non-deterministic parts are delegated to coding agents over the
6
+ [Agent Client Protocol](https://agentclientprotocol.com) — Codex, Claude Code,
7
+ Gemini CLI, or any other ACP-speaking agent.
8
+
9
+ No config files, no server, no accounts. `newton run workflow.js` in a
10
+ workspace and it works.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g @gigaai/newton # or: npx @gigaai/newton
16
+ ```
17
+
18
+ Or grab a standalone binary from
19
+ [Releases](https://github.com/GigaML/newton/releases) — no Node required.
20
+
21
+ Agents authenticate themselves: `openai` uses your existing `codex` login (or
22
+ `OPENAI_API_KEY` / `CODEX_API_KEY`). Check your environment with `newton doctor`.
23
+
24
+ ## Quick start
25
+
26
+ ```js
27
+ // hello.js
28
+ export const meta = {
29
+ name: 'hello',
30
+ description: 'Two scouts in parallel, then a structured synthesis.',
31
+ }
32
+
33
+ phase('Scout')
34
+ const notes = await parallel([
35
+ () => agent('Describe the layout of this repository.', { provider: 'openai', model: 'gpt-5.5', access: 'read' }),
36
+ () => agent('Find the entry points of this repository.', { provider: 'openai', model: 'gpt-5.5', access: 'read' }),
37
+ ])
38
+
39
+ phase('Synthesize')
40
+ const report = await agent(`Merge these notes:\n\n${notes.filter(Boolean).join('\n\n')}`, {
41
+ provider: 'openai',
42
+ model: 'gpt-5.5',
43
+ access: 'read',
44
+ schema: { type: 'object', required: ['summary'], properties: { summary: { type: 'string' } } },
45
+ })
46
+
47
+ return { report }
48
+ ```
49
+
50
+ ```bash
51
+ newton run hello.js
52
+ ```
53
+
54
+ Progress streams to stderr; the result JSON prints to stdout. Pass `--json`
55
+ for NDJSON events instead — built for driving Newton from another agent.
56
+
57
+ ## Workflow API
58
+
59
+ A workflow file is `export const meta = { name, description }` followed by a
60
+ script body. The body runs as an async function with these in scope:
61
+
62
+ | Primitive | What it does |
63
+ |---|---|
64
+ | `agent(prompt, opts?)` | Run one agent turn; returns its final text, or a schema-validated object when `opts.schema` is set. Returns `null` on agent failure — filter with `.filter(Boolean)`. |
65
+ | `parallel(thunks)` | Run `() => …` tasks concurrently; a failed task resolves to `null`. Barrier: waits for all. |
66
+ | `pipeline(items, ...stages)` | Each item flows through the stages independently — no barrier between stages. Stage signature: `(prev, item, index)`. |
67
+ | `phase(title)` | Group subsequent agents in progress output. |
68
+ | `log(message)` | Narrator line in progress output and the journal. |
69
+ | `args` | Whatever you passed via `--args` (JSON-parsed when possible). |
70
+ | `budget` | `{ total, spent(), remaining() }` for the `--budget` output-token ceiling. |
71
+
72
+ `agent()` options: `provider` (see below), `model`, `effort` (`low`–`xhigh`),
73
+ `access` (`read` | `write` | `full` — sandbox posture), `schema` (JSON Schema
74
+ for structured output, validated with automatic retries), `label`, `phase`,
75
+ `cwd`, `timeoutMs`, and `command` — an escape hatch to run any ACP agent,
76
+ e.g. `command: ['my-agent', '--acp']`.
77
+
78
+ ## Providers
79
+
80
+ | Provider | Aliases | Model override | Runs via |
81
+ |---|---|---|---|
82
+ | `codex` | `openai` | yes | bundled codex-acp adapter |
83
+ | `claude` | `anthropic` | yes | claude-agent-acp adapter |
84
+ | `gemini` | `google` | yes | `gemini --experimental-acp` |
85
+ | `qwen` | `qwen-code` | yes | `qwen --acp` |
86
+ | `cursor` | `cursor-agent` | no | cursor-agent-acp adapter |
87
+ | `opencode` | | no | `opencode acp` |
88
+ | `goose` | | no | `goose acp` |
89
+ | `copilot` | `github-copilot` | no | copilot-language-server `--acp` |
90
+
91
+ Resolution order per provider: newton's own node_modules → PATH → `npx -y`.
92
+ Agents authenticate themselves with their normal logins; `newton agents`
93
+ detects which ones are installed and signed in on this machine.
94
+
95
+ Model selection works two ways: providers with a CLI/env knob get it passed
96
+ directly, and for everything else Newton matches `model:` against the models
97
+ the agent advertises over ACP (session config options) and selects it
98
+ protocol-side. `newton models <provider>` shows what an agent exposes;
99
+ requesting a model an agent doesn't offer fails with the available list.
100
+
101
+ `Date.now()`, argless `new Date()`, and `Math.random()` throw inside
102
+ workflows: nondeterminism would silently break `--resume` replay. Pass
103
+ timestamps in via `--args`.
104
+
105
+ ## Runs, traces, resume
106
+
107
+ Every run writes durable artifacts to `.newton/runs/<runId>/` in the
108
+ workspace:
109
+
110
+ - `journal.jsonl` — every lifecycle event, including each agent's prompt and
111
+ full result.
112
+ - `agents/<n>.jsonl` — the complete ACP wire trace per agent: every message
113
+ chunk, tool call, and permission decision.
114
+
115
+ `newton run workflow.js --resume <runId>` replays successful agent results
116
+ from a previous run's journal — identical calls return instantly and only new
117
+ or changed calls hit real agents.
118
+
119
+ ## CLI
120
+
121
+ ```
122
+ newton run <workflow.js> --args <json> --cwd <dir> --resume <runId> --json
123
+ --budget <tokens> --max-agents <n> --timeout <seconds>
124
+ --concurrency <n>
125
+ newton validate <workflow.js> parse + compile without spawning agents
126
+ newton agents [--json] supported agents: installed? signed in? (alias: doctor)
127
+ newton models <provider> ask an agent which models/modes it exposes
128
+ ```
129
+
130
+ Exit codes: `0` success, `1` workflow error, `2` usage error.
131
+
132
+ ## Development
133
+
134
+ ```bash
135
+ npm install
136
+ npm run check # build + validate the example
137
+ node dist/cli.js run examples/hello.js
138
+ ```
139
+
140
+ Standalone binaries are built with `bun build --compile` (see
141
+ `.github/workflows/release.yml`); pushing a `v*` tag publishes to npm and
142
+ attaches binaries to a GitHub Release.
package/dist/acp.js ADDED
@@ -0,0 +1,257 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ import { Readable, Writable } from "node:stream";
5
+ import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION, } from "@agentclientprotocol/sdk";
6
+ import { resolveAgentCommand } from "./providers.js";
7
+ import { VERSION } from "./version.js";
8
+ const READ_ONLY_TOOL_KINDS = new Set(["read", "search", "fetch", "think", "other"]);
9
+ function pickPermissionOption(params, access) {
10
+ const readOnly = access === "read";
11
+ const toolKind = params.toolCall.kind ?? "other";
12
+ const wantAllow = !readOnly || READ_ONLY_TOOL_KINDS.has(toolKind);
13
+ const preference = wantAllow
14
+ ? ["allow_always", "allow_once"]
15
+ : ["reject_once", "reject_always"];
16
+ for (const kind of preference) {
17
+ const option = params.options.find((o) => o.kind === kind);
18
+ if (option)
19
+ return { outcome: { outcome: "selected", optionId: option.optionId } };
20
+ }
21
+ const fallback = params.options[0];
22
+ if (fallback)
23
+ return { outcome: { outcome: "selected", optionId: fallback.optionId } };
24
+ return { outcome: { outcome: "cancelled" } };
25
+ }
26
+ const liveChildren = new Set();
27
+ function killAll() {
28
+ for (const child of liveChildren)
29
+ child.kill("SIGKILL");
30
+ }
31
+ process.once("exit", killAll);
32
+ for (const signal of ["SIGINT", "SIGTERM"]) {
33
+ process.once(signal, () => {
34
+ killAll();
35
+ process.exit(130);
36
+ });
37
+ }
38
+ /**
39
+ * Run one agent turn over ACP: spawn the agent, initialize, open a session,
40
+ * send the prompt, and collect streamed output until the turn stops.
41
+ * `promptAgain` allows follow-up turns in the same session (structured-output retries).
42
+ */
43
+ export class AcpAgentSession {
44
+ child;
45
+ connection;
46
+ sessionId;
47
+ sessionInfo;
48
+ request;
49
+ text = "";
50
+ toolCalls = 0;
51
+ usage = null;
52
+ stderrTail = [];
53
+ constructor(request) {
54
+ this.request = request;
55
+ }
56
+ async prompt() {
57
+ try {
58
+ await this.ensureSession();
59
+ return await this.promptAgain(this.request.prompt);
60
+ }
61
+ catch (error) {
62
+ return this.failure(error);
63
+ }
64
+ }
65
+ /** Open a session and report what the agent exposes, without prompting. */
66
+ async inspect() {
67
+ await this.ensureSession();
68
+ return {
69
+ modes: this.sessionInfo?.modes ?? null,
70
+ configOptions: this.sessionInfo?.configOptions ?? [],
71
+ };
72
+ }
73
+ async promptAgain(prompt) {
74
+ try {
75
+ const connection = this.connection;
76
+ const sessionId = this.sessionId;
77
+ this.text = "";
78
+ this.request.trace({ type: "prompt", prompt });
79
+ let timer;
80
+ const timedOut = new Promise((resolveTimeout) => {
81
+ timer = setTimeout(() => resolveTimeout("timeout"), this.request.timeoutMs);
82
+ });
83
+ const turn = connection.prompt({
84
+ sessionId,
85
+ prompt: [{ type: "text", text: prompt }],
86
+ });
87
+ const outcome = await Promise.race([turn, timedOut]);
88
+ clearTimeout(timer);
89
+ if (outcome === "timeout") {
90
+ await connection.cancel({ sessionId }).catch(() => { });
91
+ // Give the agent a moment to flush final updates, then hard-stop.
92
+ await Promise.race([turn.catch(() => { }), new Promise((r) => setTimeout(r, 3000))]);
93
+ this.close();
94
+ return {
95
+ text: this.text,
96
+ stopReason: "timeout",
97
+ usage: this.usage,
98
+ toolCalls: this.toolCalls,
99
+ error: `agent timed out after ${this.request.timeoutMs}ms`,
100
+ };
101
+ }
102
+ this.usage = outcome.usage ?? this.usage;
103
+ return {
104
+ text: this.text,
105
+ stopReason: outcome.stopReason,
106
+ usage: this.usage,
107
+ toolCalls: this.toolCalls,
108
+ };
109
+ }
110
+ catch (error) {
111
+ return this.failure(error);
112
+ }
113
+ }
114
+ close() {
115
+ if (this.child && !this.child.killed)
116
+ this.child.kill("SIGTERM");
117
+ if (this.child)
118
+ liveChildren.delete(this.child);
119
+ this.child = undefined;
120
+ }
121
+ failure(error) {
122
+ this.close();
123
+ const message = error instanceof Error ? error.message : String(error);
124
+ const stderr = this.stderrTail.join("").trim();
125
+ this.request.trace({ type: "error", message, stderr });
126
+ return {
127
+ text: this.text,
128
+ stopReason: "error",
129
+ usage: this.usage,
130
+ toolCalls: this.toolCalls,
131
+ error: stderr ? `${message}\n${stderr.slice(-2000)}` : message,
132
+ };
133
+ }
134
+ async ensureSession() {
135
+ if (this.sessionId)
136
+ return;
137
+ const { spec, cwd, trace } = this.request;
138
+ const resolved = resolveAgentCommand(spec);
139
+ trace({ type: "spawn", command: resolved.command, args: resolved.args });
140
+ const child = spawn(resolved.command, resolved.args, {
141
+ cwd,
142
+ env: { ...process.env, ...resolved.env },
143
+ stdio: ["pipe", "pipe", "pipe"],
144
+ });
145
+ this.child = child;
146
+ liveChildren.add(child);
147
+ child.once("exit", () => liveChildren.delete(child));
148
+ child.stderr.setEncoding("utf8");
149
+ child.stderr.on("data", (data) => {
150
+ this.stderrTail.push(data);
151
+ if (this.stderrTail.length > 50)
152
+ this.stderrTail.shift();
153
+ trace({ type: "stderr", data });
154
+ });
155
+ const spawned = new Promise((resolveSpawn, rejectSpawn) => {
156
+ child.once("spawn", () => resolveSpawn());
157
+ child.once("error", (error) => rejectSpawn(error));
158
+ });
159
+ await spawned;
160
+ const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
161
+ const clientHandler = {
162
+ requestPermission: (params) => {
163
+ const response = pickPermissionOption(params, this.request.spec.access);
164
+ trace({ type: "permission", request: params, response });
165
+ return response;
166
+ },
167
+ sessionUpdate: (params) => this.handleUpdate(params),
168
+ readTextFile: async (params) => {
169
+ const content = await readFile(resolve(cwd, params.path), "utf8");
170
+ return { content };
171
+ },
172
+ writeTextFile: async (params) => {
173
+ if (this.request.spec.access === "read") {
174
+ throw new Error("write rejected: agent has read-only access");
175
+ }
176
+ await writeFile(resolve(cwd, params.path), params.content, "utf8");
177
+ return {};
178
+ },
179
+ };
180
+ const connection = new ClientSideConnection(() => clientHandler, stream);
181
+ this.connection = connection;
182
+ await connection.initialize({
183
+ protocolVersion: PROTOCOL_VERSION,
184
+ clientInfo: { name: "newton", version: VERSION },
185
+ clientCapabilities: {
186
+ fs: { readTextFile: true, writeTextFile: true },
187
+ session: { configOptions: {} },
188
+ },
189
+ });
190
+ const session = await connection.newSession({ cwd: resolve(cwd), mcpServers: [] });
191
+ this.sessionId = session.sessionId;
192
+ this.sessionInfo = session;
193
+ trace({ type: "session", sessionId: session.sessionId, configOptions: session.configOptions ?? null });
194
+ if (this.request.spec.model && !resolved.modelHandled) {
195
+ await this.selectModelViaProtocol(session, this.request.spec.model);
196
+ }
197
+ }
198
+ /**
199
+ * Providers without a CLI/env model knob may still expose model choice as an
200
+ * ACP session config option — match the requested model against it.
201
+ */
202
+ async selectModelViaProtocol(session, model) {
203
+ const flatten = (options) => {
204
+ const values = options.options;
205
+ if (values.length > 0 && "group" in values[0]) {
206
+ return values.flatMap((g) => g.options);
207
+ }
208
+ return values;
209
+ };
210
+ const selects = (session.configOptions ?? []).filter((o) => o.type === "select" && (o.category === "model" || (!o.category && /model/i.test(o.id))));
211
+ const wanted = model.toLowerCase();
212
+ for (const option of selects) {
213
+ const values = flatten(option);
214
+ const match = values.find((v) => v.value === model) ??
215
+ values.find((v) => v.name.toLowerCase() === wanted) ??
216
+ values.find((v) => v.value.toLowerCase().includes(wanted) || v.name.toLowerCase().includes(wanted));
217
+ if (match) {
218
+ if (option.currentValue !== match.value) {
219
+ await this.connection.setSessionConfigOption({
220
+ sessionId: this.sessionId,
221
+ configId: option.id,
222
+ value: match.value,
223
+ });
224
+ }
225
+ this.request.trace({ type: "model_selected", configId: option.id, value: match.value });
226
+ return;
227
+ }
228
+ }
229
+ const available = selects.flatMap((o) => flatten(o).map((v) => v.value));
230
+ throw new Error(available.length
231
+ ? `model "${model}" not available for this agent; available: ${available.join(", ")}`
232
+ : `model "${model}" requested but this agent exposes no model selection (drop the model option); check \`newton models ${this.request.spec.provider ?? ""}\``);
233
+ }
234
+ handleUpdate(params) {
235
+ this.request.trace({ type: "update", update: params.update });
236
+ const update = params.update;
237
+ switch (update.sessionUpdate) {
238
+ case "agent_message_chunk":
239
+ if (update.content.type === "text") {
240
+ this.text += update.content.text;
241
+ this.request.onActivity?.("message", update.content.text);
242
+ }
243
+ break;
244
+ case "agent_thought_chunk":
245
+ if (update.content.type === "text") {
246
+ this.request.onActivity?.("thought", update.content.text);
247
+ }
248
+ break;
249
+ case "tool_call":
250
+ this.toolCalls += 1;
251
+ this.request.onActivity?.("tool", update.title ?? update.kind ?? "tool");
252
+ break;
253
+ default:
254
+ break;
255
+ }
256
+ }
257
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ import { accessSync, constants } from "node:fs";
3
+ import { AcpAgentSession } from "./acp.js";
4
+ import { compileWorkflow, loadWorkflow, runWorkflow } from "./executor.js";
5
+ import { HumanReporter, JsonReporter } from "./progress.js";
6
+ import { describeProviders } from "./providers.js";
7
+ import { VERSION } from "./version.js";
8
+ const USAGE = `newton — deterministic multi-agent workflows over ACP
9
+
10
+ Usage:
11
+ newton run <workflow.js> [options] Run a workflow in the current directory
12
+ newton validate <workflow.js> Parse + compile a workflow without running agents
13
+ newton agents [--json] Supported agents: installed? signed in? (alias: doctor)
14
+ newton models <provider> [--json] Ask an agent which models it exposes
15
+
16
+ Run options:
17
+ --args <json> Value exposed to the script as \`args\`
18
+ --cwd <dir> Workspace agents operate in (default: current directory)
19
+ --resume <runId> Replay cached agent results from a previous run
20
+ --json NDJSON events on stdout instead of human output
21
+ --budget <tokens> Output-token ceiling; agent() throws once exhausted
22
+ --max-agents <n> Agent-call cap (default 200)
23
+ --timeout <seconds> Per-agent timeout (default 1200)
24
+ --concurrency <n> Concurrent agents (default min(16, cores-2))
25
+
26
+ Workflows are plain JavaScript: \`export const meta = { name, description }\`
27
+ followed by a script body using agent()/parallel()/pipeline()/phase()/log(),
28
+ with \`args\` and \`budget\` in scope. agent() opts: provider (codex | claude |
29
+ gemini | qwen | cursor | opencode | goose | copilot), model, effort, access
30
+ (read | write | full), schema, label, phase, cwd, command (any ACP agent,
31
+ e.g. ["my-agent", "--acp"]).
32
+
33
+ Runs are recorded under .newton/runs/<runId>/ — journal.jsonl plus a full
34
+ per-agent ACP trace.`;
35
+ function parseArgs(argv) {
36
+ const positional = [];
37
+ const flags = {};
38
+ for (let i = 0; i < argv.length; i++) {
39
+ const token = argv[i];
40
+ if (!token.startsWith("--")) {
41
+ positional.push(token);
42
+ continue;
43
+ }
44
+ const name = token.slice(2);
45
+ const next = argv[i + 1];
46
+ if (next !== undefined && !next.startsWith("--")) {
47
+ flags[name] = next;
48
+ i++;
49
+ }
50
+ else {
51
+ flags[name] = true;
52
+ }
53
+ }
54
+ return { positional, flags };
55
+ }
56
+ function fail(message) {
57
+ process.stderr.write(`error: ${message}\n`);
58
+ process.exit(2);
59
+ }
60
+ function intFlag(flags, name) {
61
+ const raw = flags[name];
62
+ if (raw === undefined)
63
+ return undefined;
64
+ const value = Number(raw);
65
+ if (!Number.isInteger(value) || value <= 0)
66
+ fail(`--${name} requires a positive integer`);
67
+ return value;
68
+ }
69
+ async function commandRun(positional, flags) {
70
+ const file = positional[0];
71
+ if (!file)
72
+ fail("newton run requires a workflow file");
73
+ let args;
74
+ if (typeof flags.args === "string") {
75
+ try {
76
+ args = JSON.parse(flags.args);
77
+ }
78
+ catch {
79
+ args = flags.args; // plain-string args are fine; don't force JSON quoting
80
+ }
81
+ }
82
+ const json = flags.json === true;
83
+ const reporter = json ? new JsonReporter() : new HumanReporter();
84
+ const timeoutSeconds = intFlag(flags, "timeout");
85
+ const result = await runWorkflow({
86
+ file,
87
+ args,
88
+ cwd: typeof flags.cwd === "string" ? flags.cwd : process.cwd(),
89
+ resumeRunId: typeof flags.resume === "string" ? flags.resume : undefined,
90
+ budgetTokens: intFlag(flags, "budget"),
91
+ maxAgents: intFlag(flags, "max-agents"),
92
+ agentTimeoutMs: timeoutSeconds ? timeoutSeconds * 1000 : undefined,
93
+ concurrency: intFlag(flags, "concurrency"),
94
+ reporter,
95
+ });
96
+ reporter.done(result);
97
+ if (!json) {
98
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
99
+ }
100
+ process.exit(result.status === "ok" ? 0 : 1);
101
+ }
102
+ function commandValidate(positional) {
103
+ const file = positional[0];
104
+ if (!file)
105
+ fail("newton validate requires a workflow file");
106
+ const workflow = loadWorkflow(file);
107
+ compileWorkflow(workflow);
108
+ process.stdout.write(`${JSON.stringify({ ok: true, name: workflow.meta.name, description: workflow.meta.description, phases: workflow.meta.phases ?? [] }, null, 2)}\n`);
109
+ }
110
+ function commandDoctor(flags) {
111
+ const providers = describeProviders();
112
+ let workspaceWritable = true;
113
+ try {
114
+ accessSync(process.cwd(), constants.W_OK);
115
+ }
116
+ catch {
117
+ workspaceWritable = false;
118
+ }
119
+ if (flags.json === true) {
120
+ const report = { newton: VERSION, node: process.version, workspace: process.cwd(), workspaceWritable, providers };
121
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
122
+ return;
123
+ }
124
+ process.stdout.write(`newton ${VERSION} · node ${process.version}\n`);
125
+ process.stdout.write(`workspace: ${process.cwd()}${workspaceWritable ? "" : " (NOT writable)"}\n\n`);
126
+ const rank = { ok: 0, unknown: 1, none: 2 };
127
+ const mark = { ok: "✔", unknown: "?", none: "✖" };
128
+ const width = Math.max(...providers.map((p) => p.provider.length));
129
+ for (const p of [...providers].sort((a, b) => rank[a.auth.state] - rank[b.auth.state])) {
130
+ const aliases = p.aliases.length ? ` (aka ${p.aliases.join(", ")})` : "";
131
+ process.stdout.write(`${mark[p.auth.state]} ${p.provider.padEnd(width)} ${p.auth.detail}${aliases}\n` +
132
+ ` ${" ".repeat(width)} via ${p.resolvesVia}\n`);
133
+ }
134
+ process.stdout.write(`\n✔ signed in · ? undetectable · ✖ needs setup\n`);
135
+ }
136
+ async function commandModels(positional, flags) {
137
+ const provider = positional[0];
138
+ if (!provider)
139
+ fail("newton models requires a provider (see `newton agents`)");
140
+ const session = new AcpAgentSession({
141
+ spec: { provider },
142
+ prompt: "",
143
+ cwd: process.cwd(),
144
+ timeoutMs: 60_000,
145
+ trace: () => { },
146
+ });
147
+ try {
148
+ const info = await session.inspect();
149
+ if (flags.json === true) {
150
+ process.stdout.write(`${JSON.stringify(info, null, 2)}\n`);
151
+ return;
152
+ }
153
+ const selects = info.configOptions.filter((o) => o.type === "select");
154
+ if (selects.length === 0 && !info.modes) {
155
+ process.stdout.write(`${provider}: exposes no session options; it uses its own configured model\n`);
156
+ return;
157
+ }
158
+ for (const option of selects) {
159
+ process.stdout.write(`${option.name} [${option.id}]${option.category ? ` (${option.category})` : ""}\n`);
160
+ const values = option.options.flatMap((v) => ("group" in v ? v.options : [v]));
161
+ for (const value of values) {
162
+ const current = value.value === option.currentValue ? " (current)" : "";
163
+ process.stdout.write(` ${value.value}${value.name !== value.value ? ` — ${value.name}` : ""}${current}\n`);
164
+ }
165
+ }
166
+ if (info.modes) {
167
+ process.stdout.write(`Modes [current: ${info.modes.currentModeId}]\n`);
168
+ for (const mode of info.modes.availableModes) {
169
+ process.stdout.write(` ${mode.id}${mode.name !== mode.id ? ` — ${mode.name}` : ""}\n`);
170
+ }
171
+ }
172
+ }
173
+ catch (error) {
174
+ fail(error.message);
175
+ }
176
+ finally {
177
+ session.close();
178
+ }
179
+ }
180
+ async function main() {
181
+ const [command, ...rest] = process.argv.slice(2);
182
+ const { positional, flags } = parseArgs(rest);
183
+ switch (command) {
184
+ case "run":
185
+ await commandRun(positional, flags);
186
+ break;
187
+ case "validate":
188
+ try {
189
+ commandValidate(positional);
190
+ }
191
+ catch (error) {
192
+ fail(error.message);
193
+ }
194
+ break;
195
+ case "models":
196
+ await commandModels(positional, flags);
197
+ break;
198
+ case "agents":
199
+ case "doctor":
200
+ commandDoctor(flags);
201
+ break;
202
+ case "version":
203
+ case "--version":
204
+ case "-v":
205
+ process.stdout.write(`${VERSION}\n`);
206
+ break;
207
+ case undefined:
208
+ case "help":
209
+ case "--help":
210
+ case "-h":
211
+ process.stdout.write(`${USAGE}\n`);
212
+ break;
213
+ default:
214
+ fail(`unknown command "${command}"\n\n${USAGE}`);
215
+ }
216
+ }
217
+ main().catch((error) => {
218
+ process.stderr.write(`error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
219
+ process.exit(1);
220
+ });