@aibridge/cli 0.1.1 → 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 CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  ---
17
17
 
18
- Your agent is one model, from one provider. Your machine probably has several more sitting behind CLIs you already use — `grok`, `agy` (Antigravity), `codex`, `claude`. **aibridge** turns them into seats your agent can drive: a planner that studies your repo, an implementer that edits it and runs your real tests, a reviewer from a *different* model family that cross-checks the diff against the plan, concurrent one-shot delegates — and capabilities your agent's own provider may not offer at all, like real image generation (gpt-image-2, Grok Imagine).
18
+ Your agent is one model, from one provider. Your machine probably has several more sitting behind CLIs you already use — `grok`, `agy` (Antigravity), `codex`, `claude`. **aibridge** turns them into seats your agent can drive: a planner that studies your repo, an implementer that edits it and runs your real tests, a reviewer from a *different* model family that cross-checks the diff against the plan, concurrent one-shot delegates — and capabilities your agent's own provider may not offer at all, like real image generation (Codex, Antigravity, or Grok seats).
19
19
 
20
20
  ## Install
21
21
 
@@ -28,7 +28,7 @@ npx skills add ycmjason/aibridge
28
28
  That's it. The skill runs the CLI on demand via `npx -y @aibridge/cli` — nothing else to install. Ask your agent to "use aibridge", or try it yourself:
29
29
 
30
30
  ```bash
31
- npx -y @aibridge/cli subagent "summarize the architecture of this repo"
31
+ npx -y @aibridge/cli subagent --model xai-grok/grok-4.5 "summarize the architecture of this repo"
32
32
  ```
33
33
 
34
34
  <sup>Want the `aibridge` command on your PATH for manual use? `npm i -g @aibridge/cli` (optional).</sup>
@@ -37,21 +37,21 @@ npx -y @aibridge/cli subagent "summarize the architecture of this repo"
37
37
 
38
38
  | Command | Use when |
39
39
  |---|---|
40
- | `aibridge plan "<task>"` | You want a delegate model to study the repo and expand a task into a detailed, reviewable **plan file** before any code is written |
41
- | `aibridge implement <plan.md>` | You have an approved plan file and want it executed in place — with your project's **real typecheck and tests** run until green |
42
- | `aibridge review [--plan <plan.md>]` | You want a **different model** to pressure-test the working-tree diff against the plan contract (over-reach is a finding) — or to review the plan itself before implementing |
43
- | `aibridge subagent "<task>"` | A self-contained task deserves a concurrent delegate, a cross-model second opinion, or a red-team pass |
44
- | `aibridge image-gen "<prompt>"` | You need a real raster image — gpt-image-2 (default) or Grok Imagine, with render verification |
40
+ | `aibridge plan --model xai-grok/grok-4.5 --out plan.md "<task>"` | You want a delegate model to study the repo and expand a task into a detailed, reviewable **plan file** before any code is written |
41
+ | `aibridge implement --model google-antigravity/gemini-3.6-flash <plan.md>` | You have an approved plan file and want it executed in place — with your project's **real typecheck and tests** run until green |
42
+ | `aibridge review --model xai-grok/grok-4.5 --out review.md [--plan <plan.md>]` | You want a **different model** to pressure-test the working-tree diff against the plan contract (over-reach is a finding) — or to review the plan itself before implementing |
43
+ | `aibridge subagent --model xai-grok/grok-4.5 "<task>"` | A self-contained task deserves a concurrent delegate, a cross-model second opinion, or a red-team pass |
44
+ | `aibridge image-gen --model openai-codex/gpt-5.6-sol --out out.png "<prompt>"` | You need a real raster image — on a Codex, Antigravity, or Grok seat, with render verification |
45
45
  | `aibridge quota` | Two-second check of every backend's remaining quota before you pipeline work |
46
46
  | `aibridge runs` | Inspect or watch past delegation runs (`~/.aibridge/runs`) |
47
47
 
48
48
  The three verbs compose into an orchestrator-driven loop your agent stays in charge of:
49
49
 
50
50
  ```
51
- aibridge plan "add rate limiting to the API" # delegate writes plan.md
51
+ aibridge plan --model xai-grok/grok-4.5 --out plan.md "add rate limiting to the API" # delegate writes plan.md
52
52
  # → your agent reads, edits, approves the plan
53
- aibridge implement plan.md # another model executes it, runs your gates
54
- aibridge review --plan plan.md # a third seat cross-checks the diff
53
+ aibridge implement --model google-antigravity/gemini-3.6-flash plan.md # another model executes it, runs your gates
54
+ aibridge review --model xai-grok/grok-4.5 --out review.md --plan plan.md # a third seat cross-checks the diff
55
55
  ```
56
56
 
57
57
  Plan files — not their contents — travel between stages, so the loop is nearly free on your agent's context.
@@ -61,7 +61,7 @@ Plan files — not their contents — travel between stages, so the loop is near
61
61
  - **The skill carries judgment; the CLI owns execution.** The skill teaches your agent prompt-craft, seat selection, and when to gate; the CLI deterministically drives the backing CLIs, captures their output, verifies results (a "generated image" under 100 KB is a code-drawn fake, an empty answer is a quota death), and logs every run.
62
62
  - **Seats stay cross-model by default.** Grok plans and reviews, Gemini implements — a model never reviews its own diff, and independent eyes catch what shared blind spots miss.
63
63
  - **No API keys.** Delegation runs on the backing CLIs' existing logins, each spending its own quota. (The skill treats a backend that shares your agent's own quota pool as a last resort.)
64
- - **Models are canonical slugs**: `<vendor>-<cli>/<model>[-<effort>]` — e.g. `xai-grok/grok-4.5`, `google-antigravity/gemini-3.6-flash`, `openai-codex/gpt-5.6-sol-high`, `anthropic-claude/opus`. No aliases; `aibridge <command> --help` lists every seat.
64
+ - **Models are canonical slugs**: `<vendor>-<cli>/<model>[-<effort>]` — e.g. `xai-grok/grok-4.5`, `google-antigravity/gemini-3.6-flash`, `openai-codex/gpt-5.6-sol-high`, `anthropic-claude/opus-5`. No aliases — not short ones, and not moving vendor aliases like `opus`: every seat pins an exact model version. `aibridge <command> --help` lists every seat.
65
65
 
66
66
  ## Requirements
67
67
 
@@ -72,6 +72,10 @@ Plan files — not their contents — travel between stages, so the loop is near
72
72
 
73
73
  Everything is published under the [`@aibridge`](https://www.npmjs.com/org/aibridge) scope: [`@aibridge/cli`](https://www.npmjs.com/package/@aibridge/cli) (the command), `@aibridge/proc` (spawn/capture), and one driver per backing CLI — `@aibridge/driver-agy`, `@aibridge/driver-grok`, `@aibridge/driver-codex`, `@aibridge/driver-claude` — reusable if you want to drive a single CLI from your own code.
74
74
 
75
+ ## Security
76
+
77
+ aibridge executes real delegation — that's the product, and security scanners rightly notice: backing CLIs read/write files and run shell in tools mode, **at the same trust level as the agent you already run**. Nothing gains more access than you granted your agent and those CLIs when you installed them. Task content goes to the delegate's provider (use `--no-tools` for untrusted input — reasoning only, no file/shell access). All packages publish from this public repo via OIDC with [SLSA provenance](https://www.npmjs.com/package/@aibridge/cli), with no install-time scripts.
78
+
75
79
  ## Contributing & development
76
80
 
77
81
  Dev docs, architecture, and the working agreements for coding agents live in [`AGENTS.md`](AGENTS.md); design history in [`docs/`](docs/). Quick loop:
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as runCli, t as buildContext } from "./context-8JjDzZBG.mjs";
2
+ import { r as runCli, t as buildContext } from "./context-B6I4QI9z.mjs";
3
3
  //#region src/cli.ts
4
4
  await runCli(buildContext(process), process.argv.slice(2));
5
5
  //#endregion
@@ -3,7 +3,6 @@ import { ExitCode, buildApplication, buildCommand, buildRouteMap, run } from "@s
3
3
  import { appendFileSync, closeSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { homedir, tmpdir } from "node:os";
5
5
  import { isAbsolute, join, resolve } from "node:path";
6
- import { isNotFound, runCaptured } from "@aibridge/proc";
7
6
  import * as agy from "@aibridge/driver-agy";
8
7
  import { fetchAgyQuota, findModelQuota } from "@aibridge/driver-agy";
9
8
  import * as claude from "@aibridge/driver-claude";
@@ -11,6 +10,7 @@ import { fetchClaudeQuota } from "@aibridge/driver-claude";
11
10
  import * as codex from "@aibridge/driver-codex";
12
11
  import { fetchCodexQuota } from "@aibridge/driver-codex";
13
12
  import * as grok from "@aibridge/driver-grok";
13
+ import { runCaptured } from "@aibridge/proc";
14
14
  import { randomBytes } from "node:crypto";
15
15
  //#region src/models.ts
16
16
  const MODELS = {
@@ -35,7 +35,7 @@ const MODELS = {
35
35
  "high"
36
36
  ],
37
37
  defaultEffort: "high",
38
- brief: "Google Gemini 3.6 Flash via agy — default for implement; own Antigravity login"
38
+ brief: "Google Gemini 3.6 Flash via agy — default for implement, also image-gen; own Antigravity login"
39
39
  },
40
40
  "google-antigravity/claude-sonnet-4-6": {
41
41
  slug: "google-antigravity/claude-sonnet-4-6",
@@ -70,10 +70,10 @@ const MODELS = {
70
70
  ],
71
71
  brief: "OpenAI Codex gpt-5.6-sol via codex CLI"
72
72
  },
73
- "anthropic-claude/sonnet": {
74
- slug: "anthropic-claude/sonnet",
73
+ "anthropic-claude/sonnet-5": {
74
+ slug: "anthropic-claude/sonnet-5",
75
75
  backend: "claude",
76
- backendModel: "sonnet",
76
+ backendModel: "claude-sonnet-5",
77
77
  efforts: [
78
78
  "low",
79
79
  "medium",
@@ -81,12 +81,12 @@ const MODELS = {
81
81
  "xhigh",
82
82
  "max"
83
83
  ],
84
- brief: "Claude Sonnet via claude CLI — bills your Claude subscription"
84
+ brief: "Claude Sonnet 5 via claude CLI — bills your Claude subscription"
85
85
  },
86
- "anthropic-claude/opus": {
87
- slug: "anthropic-claude/opus",
86
+ "anthropic-claude/opus-5": {
87
+ slug: "anthropic-claude/opus-5",
88
88
  backend: "claude",
89
- backendModel: "opus",
89
+ backendModel: "claude-opus-5",
90
90
  efforts: [
91
91
  "low",
92
92
  "medium",
@@ -95,15 +95,33 @@ const MODELS = {
95
95
  "max"
96
96
  ],
97
97
  defaultEffort: "high",
98
- brief: "Claude Opus via claude CLI (default effort: high) — bills subscription"
98
+ brief: "Claude Opus 5 via claude CLI (default effort: high) — bills subscription"
99
+ },
100
+ "anthropic-claude/opus-5-1m": {
101
+ slug: "anthropic-claude/opus-5-1m",
102
+ backend: "claude",
103
+ backendModel: "claude-opus-5[1m]",
104
+ efforts: [
105
+ "low",
106
+ "medium",
107
+ "high",
108
+ "xhigh",
109
+ "max"
110
+ ],
111
+ defaultEffort: "high",
112
+ brief: "Claude Opus 5, 1M context via claude CLI — long-context work; bills subscription"
99
113
  }
100
114
  };
101
- const DEFAULT_MODEL = "xai-grok/grok-4.5";
102
- const DEFAULT_IMPLEMENTER = "google-antigravity/gemini-3.6-flash";
103
- const DEFAULT_IMAGE_GEN = "openai-codex/gpt-5.6-sol";
104
- const IMAGE_GEN_BACKENDS = /* @__PURE__ */ new Set(["codex", "grok"]);
115
+ const IMAGE_GEN_FORMATS = /* @__PURE__ */ new Map([
116
+ ["agy", "jpg"],
117
+ ["codex", "png"],
118
+ ["grok", "jpg"]
119
+ ]);
105
120
  function supportsImageGen(resolved) {
106
- return IMAGE_GEN_BACKENDS.has(resolved.spec.backend);
121
+ return IMAGE_GEN_FORMATS.has(resolved.spec.backend);
122
+ }
123
+ function imageFormatFor(resolved) {
124
+ return IMAGE_GEN_FORMATS.get(resolved.spec.backend);
107
125
  }
108
126
  const EFFORTS_SET = /* @__PURE__ */ new Set([
109
127
  "low",
@@ -132,7 +150,6 @@ function resolveModel(input) {
132
150
  }
133
151
  }
134
152
  function backendModelId(resolved) {
135
- if (!resolved.spec.backendModel) return;
136
153
  if (resolved.spec.backend === "agy") {
137
154
  const effort = resolved.effort ?? resolved.spec.defaultEffort;
138
155
  if (effort) return `${resolved.spec.backendModel}-${effort}`;
@@ -142,7 +159,7 @@ function backendModelId(resolved) {
142
159
  function listModelHelpLines(opts = {}) {
143
160
  const lines = [];
144
161
  for (const [slug, spec] of Object.entries(MODELS)) {
145
- if (opts.imageOnly && !IMAGE_GEN_BACKENDS.has(spec.backend)) continue;
162
+ if (opts.imageOnly && !IMAGE_GEN_FORMATS.has(spec.backend)) continue;
146
163
  lines.push(` ${slug}`);
147
164
  lines.push(` ${spec.brief}`);
148
165
  }
@@ -186,7 +203,8 @@ const DRIVERS = {
186
203
  agy: {
187
204
  probe: () => agy.probe(),
188
205
  run: (task) => agy.run(task),
189
- quota: () => agy.fetchAgyQuota()
206
+ quota: () => agy.fetchAgyQuota(),
207
+ generateImage: (req) => agy.generateImage(req)
190
208
  },
191
209
  grok: {
192
210
  probe: () => grok.probe(),
@@ -213,38 +231,29 @@ function getDriver(backend) {
213
231
  //#endregion
214
232
  //#region src/commands/image-gen/impl.ts
215
233
  const MIN_REAL_BYTES_CODEX = 1e5;
216
- const MIN_REAL_BYTES_GROK = 1e4;
234
+ const MIN_REAL_BYTES_TOOL = 1e4;
217
235
  async function imageGen$1(flags, prompt) {
218
236
  const fail = (msg) => {
219
237
  this.process.stderr.write(`aibridge image-gen: ${msg}\n`);
220
238
  this.process.exitCode = 1;
221
239
  };
222
- const inputSlug = flags.model ?? "openai-codex/gpt-5.6-sol";
240
+ const inputSlug = flags.model;
223
241
  const model = resolveModel(inputSlug);
224
242
  if (!model) return fail(formatUnknownModelError(inputSlug));
225
243
  if (!supportsImageGen(model)) return fail(formatImageGenModelError(inputSlug, model));
226
- if (model.spec.backend === "codex" && model.effort) return fail(`effort "-${model.effort}" has no effect on image-gen (gpt-image-2 renders, not the seat model); use ${DEFAULT_IMAGE_GEN}.`);
227
- const quality = (flags.quality ?? "high").toLowerCase();
228
- if (![
229
- "low",
230
- "medium",
231
- "high"
232
- ].includes(quality)) return fail(`invalid --quality "${flags.quality}" (use low | medium | high)`);
233
- let size;
234
- if (flags.size !== void 0) {
235
- const m = flags.size.match(/^(\d+)\s*x\s*(\d+)$/i);
236
- if (!m) return fail(`invalid --size "${flags.size}" (expected e.g. 1024x1024)`);
237
- size = {
238
- w: Number(m[1]),
239
- h: Number(m[2])
240
- };
241
- if (model.spec.backend === "codex") {
242
- const constraint = sizeConstraintError(size.w, size.h);
243
- if (constraint) return fail(`invalid --size ${size.w}x${size.h}: ${constraint}`);
244
- } else if (size.w < 1 || size.h < 1) return fail(`invalid --size ${size.w}x${size.h}: dimensions must be positive`);
244
+ if (model.spec.backend === "codex" && model.effort) return fail(`effort "-${model.effort}" has no effect on image-gen (the image tool renders, not the seat model); pass the un-suffixed slug "${model.spec.slug}" instead.`);
245
+ const expected = imageFormatFor(model);
246
+ if (expected === void 0) return fail(formatImageGenModelError(inputSlug, model));
247
+ const label = expected === "png" ? "PNG" : "JPEG";
248
+ if (!(expected === "png" ? /\.png$/i.test(flags.out) : /\.jpe?g$/i.test(flags.out))) return fail(`--out "${flags.out}" must end in ${expected === "png" ? ".png" : ".jpg or .jpeg"} — the ${model.spec.slug} seat renders ${label} and aibridge does not convert.`);
249
+ let aspectRatio;
250
+ if (flags.aspectRatio !== void 0) {
251
+ const m = flags.aspectRatio.match(/^(\d+)\s*:\s*(\d+)$/);
252
+ if (!m || Number(m[1]) < 1 || Number(m[2]) < 1) return fail(`invalid --aspect-ratio "${flags.aspectRatio}" (expected e.g. 16:9)`);
253
+ aspectRatio = `${Number(m[1])}:${Number(m[2])}`;
245
254
  }
246
255
  const timeoutSec = flags.timeout ?? 600;
247
- const outPath = resolve(this.process.cwd(), flags.out ?? "./aibridge-image.png");
256
+ const outPath = resolve(this.process.cwd(), flags.out);
248
257
  const imagePaths = [];
249
258
  if (flags.image !== void 0) for (const raw of flags.image.split(",").map((s) => s.trim()).filter(Boolean)) {
250
259
  const abs = resolve(this.process.cwd(), raw);
@@ -253,16 +262,15 @@ async function imageGen$1(flags, prompt) {
253
262
  }
254
263
  const driver = getDriver(model.spec.backend);
255
264
  if (!driver.generateImage) return fail(formatImageGenModelError(inputSlug, model));
256
- const minBytes = model.spec.backend === "codex" ? MIN_REAL_BYTES_CODEX : MIN_REAL_BYTES_GROK;
265
+ const minBytes = model.spec.backend === "codex" ? MIN_REAL_BYTES_CODEX : MIN_REAL_BYTES_TOOL;
257
266
  const work = mkdtempSync(join(tmpdir(), "aibridge-imagegen-"));
258
267
  try {
259
268
  let outcome = await driver.generateImage({
260
269
  prompt,
261
270
  workDir: work,
262
- backendModel: model.spec.backendModel,
271
+ backendModel: backendModelId(model),
263
272
  effort: model.effort,
264
- quality,
265
- size,
273
+ aspectRatio,
266
274
  imagePaths,
267
275
  timeoutSec,
268
276
  forceful: false,
@@ -271,10 +279,9 @@ async function imageGen$1(flags, prompt) {
271
279
  if (model.spec.backend === "codex" && outcome.kind === "suspect") outcome = await driver.generateImage({
272
280
  prompt,
273
281
  workDir: work,
274
- backendModel: model.spec.backendModel,
282
+ backendModel: backendModelId(model),
275
283
  effort: model.effort,
276
- quality,
277
- size,
284
+ aspectRatio,
278
285
  imagePaths,
279
286
  timeoutSec,
280
287
  forceful: true,
@@ -282,34 +289,20 @@ async function imageGen$1(flags, prompt) {
282
289
  });
283
290
  if (outcome.kind === "ok" && outcome.bytes < minBytes) outcome = { kind: "suspect" };
284
291
  if (outcome.kind === "error") return fail(outcome.reason);
285
- if (outcome.kind === "suspect") return fail(model.spec.backend === "grok" ? "grok produced no usable image. Check SuperGrok image access and re-run with a simpler prompt." : "codex produced only a tiny/code-drawn image, not a real gpt-image-2 render. Try --quality high or a clearer, simpler prompt.");
292
+ if (outcome.kind === "suspect") return fail(model.spec.backend === "agy" ? "agy produced no usable image. Re-run with a simpler prompt, or check Antigravity image access." : model.spec.backend === "grok" ? "grok produced no usable image. Check SuperGrok image access and re-run with a simpler prompt." : "codex produced only a tiny/code-drawn image, not a real render. Try a clearer, simpler prompt.");
286
293
  const local = join(work, "result.bin");
287
294
  copyFileSync(outcome.path, local);
288
- let dims = imageSize(local);
289
- if (size && dims && (dims.width !== size.w || dims.height !== size.h)) if (await magick([
290
- local,
291
- "-resize",
292
- `${size.w}x${size.h}!`,
293
- local
294
- ])) dims = imageSize(local) ?? dims;
295
- else this.process.stderr.write(`aibridge image-gen: rendered ${dims.width}x${dims.height}, wanted ${size.w}x${size.h}, and ImageMagick (magick/convert) is unavailable to resize.
296
- `);
297
- const outExt = /\.png$/i.test(outPath) ? "png" : /\.jpe?g$/i.test(outPath) ? "jpg" : null;
298
- const actualFmt = pngSize(local) ? "png" : jpegSize(local) ? "jpg" : null;
299
- const needsConvert = outExt !== null && actualFmt !== null && outExt !== actualFmt;
300
- if (!needsConvert || !await magick([local, outPath])) {
301
- if (needsConvert) this.process.stderr.write(`aibridge image-gen: render is ${actualFmt.toUpperCase()} but out path wants ${outExt.toUpperCase()}, and ImageMagick (magick/convert) is unavailable to convert; writing the raw bytes as-is.
302
- `);
303
- copyFileSync(local, outPath);
304
- }
295
+ const dims = imageSize(local);
296
+ const actual = pngSize(local) ? "png" : jpegSize(local) ? "jpg" : null;
297
+ if (actual !== null && actual !== expected) this.process.stderr.write(`aibridge image-gen: expected a ${label} render from this seat but got ${actual === "png" ? "PNG" : "JPEG"}; wrote the raw bytes to ${outPath} anyway — the extension does not match the contents.\n`);
298
+ copyFileSync(local, outPath);
305
299
  const bytes = statSync(outPath).size;
306
300
  if (flags.json) this.process.stdout.write(`${JSON.stringify({
307
301
  out: outPath,
308
302
  bytes,
309
303
  width: dims?.width ?? null,
310
304
  height: dims?.height ?? null,
311
- sizeRequested: flags.size ?? null,
312
- quality: model.spec.backend === "codex" ? quality : null,
305
+ aspectRatio: flags.aspectRatio ?? null,
313
306
  model: model.spec.slug,
314
307
  backend: model.spec.backend,
315
308
  real: true
@@ -317,8 +310,7 @@ async function imageGen$1(flags, prompt) {
317
310
  else {
318
311
  const kb = Math.round(bytes / 1024);
319
312
  const dimStr = dims ? `${dims.width}x${dims.height}, ` : "";
320
- const qualityStr = model.spec.backend === "codex" ? `, ${quality} quality` : "";
321
- this.process.stdout.write(`✓ Wrote ${outPath} (${dimStr}${kb} KB${qualityStr}, ${model.spec.slug})\n`);
313
+ this.process.stdout.write(`✓ Wrote ${outPath} (${dimStr}${kb} KB, ${model.spec.slug})\n`);
322
314
  }
323
315
  } finally {
324
316
  rmSync(work, {
@@ -327,15 +319,6 @@ async function imageGen$1(flags, prompt) {
327
319
  });
328
320
  }
329
321
  }
330
- function sizeConstraintError(w, h) {
331
- if (w % 16 !== 0 || h % 16 !== 0) return "each edge must be divisible by 16";
332
- const long = Math.max(w, h);
333
- if (long / Math.min(w, h) > 3) return "aspect ratio must be within 1:3–3:1";
334
- if (long > 3840) return "longest edge must be <= 3840px";
335
- const px = w * h;
336
- if (px < 655360 || px > 8294400) return "total pixels must be 655,360–8,294,400";
337
- return null;
338
- }
339
322
  function imageSize(path) {
340
323
  return pngSize(path) ?? jpegSize(path);
341
324
  }
@@ -384,24 +367,15 @@ function jpegSize(path) {
384
367
  return null;
385
368
  }
386
369
  }
387
- async function magick(args) {
388
- for (const tool of ["magick", "convert"]) try {
389
- const r = await runCaptured(tool, [...args], { timeoutMs: 6e4 });
390
- if (!r.timedOut && r.code === 0) return true;
391
- } catch (err) {
392
- if (!isNotFound(err)) throw err;
393
- }
394
- return false;
395
- }
396
370
  //#endregion
397
371
  //#region src/commands/image-gen/command.ts
398
372
  const fullDescription$6 = [
399
- "Renders an image by driving the seat's CLI (codex gpt-image-2, grok ",
400
- "Imagine), then verifies the result is a real render before returning it.",
373
+ "Renders an image by driving the seat's own CLI, then verifies the result is",
374
+ "a real render before returning it.",
401
375
  "",
402
376
  "Image-gen seats (canonical slug):",
403
377
  ...listModelHelpLines({ imageOnly: true }),
404
- `Default: ${DEFAULT_IMAGE_GEN} (gpt-image-2 via codex; historical default).`
378
+ "Recommended seat: openai-codex/gpt-5.6-sol."
405
379
  ].join("\n");
406
380
  const imageGen = buildCommand({
407
381
  func: imageGen$1,
@@ -410,20 +384,18 @@ const imageGen = buildCommand({
410
384
  model: {
411
385
  kind: "parsed",
412
386
  parse: String,
413
- optional: true,
414
- brief: `Model slug (default: ${DEFAULT_IMAGE_GEN})`
387
+ brief: "Model slug (required) — see the seat list above"
415
388
  },
416
389
  out: {
417
390
  kind: "parsed",
418
391
  parse: String,
419
- optional: true,
420
- brief: "Path to write the image (default: ./aibridge-image.png)"
392
+ brief: "Path to write the image — extension must match the seat format (.png for codex, .jpg for agy/grok)"
421
393
  },
422
- size: {
394
+ aspectRatio: {
423
395
  kind: "parsed",
424
396
  parse: String,
425
397
  optional: true,
426
- brief: "WIDTHxHEIGHT (codex: each edge ÷16; grok: mapped to aspect_ratio, then optionally resized)"
398
+ brief: "Aspect ratio N:M, e.g. 16:9 (agy/grok: real tool param; codex: prompt hint)"
427
399
  },
428
400
  image: {
429
401
  kind: "parsed",
@@ -431,12 +403,6 @@ const imageGen = buildCommand({
431
403
  optional: true,
432
404
  brief: "Reference image path(s), comma-separated — visual reference"
433
405
  },
434
- quality: {
435
- kind: "parsed",
436
- parse: String,
437
- optional: true,
438
- brief: "low | medium | high (codex/gpt-image-2; default high)"
439
- },
440
406
  timeout: {
441
407
  kind: "parsed",
442
408
  parse: positiveIntSeconds,
@@ -473,7 +439,7 @@ async function delegate(opts, driver = getDriver(opts.model.spec.backend)) {
473
439
  tools: opts.tools,
474
440
  timeoutSec: opts.timeoutSec,
475
441
  cwd: opts.cwd,
476
- backendModel: backendModelId(opts.model) ?? opts.model.spec.backendModel,
442
+ backendModel: backendModelId(opts.model),
477
443
  effort: opts.model.effort,
478
444
  onStdout: (c) => opts.run.stdout(c),
479
445
  onStderr: (c) => opts.run.stderr(c),
@@ -526,7 +492,7 @@ async function preflightModel(resolved) {
526
492
  if (resolved.spec.backend === "codex") return preflightCodex();
527
493
  if (resolved.spec.backend !== "agy") return { ok: true };
528
494
  try {
529
- return evaluateAgyPreflight(await fetchAgyQuota(), backendModelId(resolved) ?? "");
495
+ return evaluateAgyPreflight(await fetchAgyQuota(), backendModelId(resolved));
530
496
  } catch (err) {
531
497
  return {
532
498
  ok: true,
@@ -679,7 +645,7 @@ function readRunLogs(id) {
679
645
  //#endregion
680
646
  //#region src/commands/implement/impl.ts
681
647
  async function implement$1(flags, planFile) {
682
- const inputSlug = flags.model ?? "google-antigravity/gemini-3.6-flash";
648
+ const inputSlug = flags.model;
683
649
  const model = resolveModel(inputSlug);
684
650
  if (!model) {
685
651
  this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
@@ -749,8 +715,7 @@ const implement = buildCommand({
749
715
  model: {
750
716
  kind: "parsed",
751
717
  parse: String,
752
- optional: true,
753
- brief: `Model slug (default: ${DEFAULT_IMPLEMENTER})`
718
+ brief: "Model slug (required) — see the seat list above"
754
719
  },
755
720
  timeout: {
756
721
  kind: "parsed",
@@ -811,7 +776,7 @@ function extractPathFromPorcelainLine(line) {
811
776
  return content;
812
777
  }
813
778
  async function plan$1(flags, taskPrompt) {
814
- const inputSlug = flags.model ?? "xai-grok/grok-4.5";
779
+ const inputSlug = flags.model;
815
780
  const model = resolveModel(inputSlug);
816
781
  if (!model) {
817
782
  this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
@@ -831,7 +796,7 @@ async function plan$1(flags, taskPrompt) {
831
796
  const cwd = this.process.cwd();
832
797
  const promptSnippet = taskPrompt.replace(/\r?\n/g, " ").slice(0, 80);
833
798
  const run = startRun("plan", `${model.spec.slug}: ${promptSnippet}`);
834
- const absOutPath = flags.out ? isAbsolute(flags.out) ? flags.out : resolve(cwd, flags.out) : resolve(run.dir, "plan.md");
799
+ const absOutPath = isAbsolute(flags.out) ? flags.out : resolve(cwd, flags.out);
835
800
  const beforePorcelain = await getPorcelainStatus(cwd);
836
801
  const outcome = await delegate({
837
802
  model,
@@ -896,14 +861,12 @@ const plan = buildCommand({
896
861
  model: {
897
862
  kind: "parsed",
898
863
  parse: String,
899
- optional: true,
900
- brief: `Model slug (default: ${DEFAULT_MODEL})`
864
+ brief: "Model slug (required) — see the seat list above"
901
865
  },
902
866
  out: {
903
867
  kind: "parsed",
904
868
  parse: String,
905
- optional: true,
906
- brief: "Where to write the plan (default: <run.dir>/plan.md)"
869
+ brief: "Where to write the plan file (required)"
907
870
  },
908
871
  timeout: {
909
872
  kind: "parsed",
@@ -1062,7 +1025,7 @@ function parseReviewVerdict(response) {
1062
1025
  };
1063
1026
  }
1064
1027
  async function review$1(flags) {
1065
- const inputSlug = flags.model ?? "xai-grok/grok-4.5";
1028
+ const inputSlug = flags.model;
1066
1029
  const model = resolveModel(inputSlug);
1067
1030
  if (!model) {
1068
1031
  this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
@@ -1112,7 +1075,7 @@ async function review$1(flags) {
1112
1075
  const timeoutSec = flags.timeout ?? 1200;
1113
1076
  const modeDetail = isDirty ? absPlanPath ? `diff + plan (${absPlanPath})` : `diff (${baseRef})` : `plan-only (${absPlanPath})`;
1114
1077
  const run = startRun("review", `${model.spec.slug}: ${modeDetail}`);
1115
- const absOutPath = flags.out ? isAbsolute(flags.out) ? flags.out : resolve(cwd, flags.out) : resolve(run.dir, "review.md");
1078
+ const absOutPath = isAbsolute(flags.out) ? flags.out : resolve(cwd, flags.out);
1116
1079
  let reviewPrompt;
1117
1080
  if (isDirty) reviewPrompt = `You are an expert code reviewer. Inspect the working tree diff against base '${baseRef}' and untracked files at ${cwd}.\n` + (absPlanPath ? `Compare the implementation against the plan contract at ${absPlanPath}. Any file modified or feature added outside the plan contract counts as over-reach (severity: major unless harmful, then critical).\n` : "") + `Write your detailed review report to the file ${absOutPath}. For each finding, include file:line, severity (critical|major|minor), and rationale.\nYour final answer (last message) must consist of EXACTLY ONE VERDICT LINE:\nEither: "PASS"\nOr: "FINDINGS: <c> critical, <m> major, <n> minor"`;
1118
1081
  else reviewPrompt = `You are an expert architecture reviewer. Inspect the plan contract file at ${absPlanPath}.\nReview the plan for soundness, missing edge cases, safety, and feasibility.\nWrite your detailed review report to the file ${absOutPath}. For each finding, include severity (critical|major|minor) and rationale.\nYour final answer (last message) must consist of EXACTLY ONE VERDICT LINE:\nEither: "PASS"\nOr: "FINDINGS: <c> critical, <m> major, <n> minor"`;
@@ -1164,8 +1127,7 @@ const review = buildCommand({
1164
1127
  model: {
1165
1128
  kind: "parsed",
1166
1129
  parse: String,
1167
- optional: true,
1168
- brief: `Model slug (default: ${DEFAULT_MODEL})`
1130
+ brief: "Model slug (required) — see the seat list above"
1169
1131
  },
1170
1132
  plan: {
1171
1133
  kind: "parsed",
@@ -1182,8 +1144,7 @@ const review = buildCommand({
1182
1144
  out: {
1183
1145
  kind: "parsed",
1184
1146
  parse: String,
1185
- optional: true,
1186
- brief: "Where to write the review report (default: <run.dir>/review.md)"
1147
+ brief: "Where to write the review report (required)"
1187
1148
  },
1188
1149
  timeout: {
1189
1150
  kind: "parsed",
@@ -1375,7 +1336,7 @@ const runs = buildCommand({
1375
1336
  //#endregion
1376
1337
  //#region src/commands/subagent/impl.ts
1377
1338
  async function subagent$1(flags, prompt) {
1378
- const inputSlug = flags.model ?? "xai-grok/grok-4.5";
1339
+ const inputSlug = flags.model;
1379
1340
  const model = resolveModel(inputSlug);
1380
1341
  if (!model) {
1381
1342
  this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
@@ -1414,15 +1375,13 @@ async function subagent$1(flags, prompt) {
1414
1375
  this.process.exitCode = 1;
1415
1376
  return;
1416
1377
  }
1417
- if (flags.json) {
1418
- const modelId = backendModelId(model) ?? null;
1419
- this.process.stdout.write(`${JSON.stringify({
1420
- model: modelId,
1421
- slug: model.spec.slug,
1422
- response: outcome.response,
1423
- exitCode: outcome.exitCode
1424
- })}\n`);
1425
- } else this.process.stdout.write(`${outcome.response}\n`);
1378
+ if (flags.json) this.process.stdout.write(`${JSON.stringify({
1379
+ model: backendModelId(model),
1380
+ slug: model.spec.slug,
1381
+ response: outcome.response,
1382
+ exitCode: outcome.exitCode
1383
+ })}\n`);
1384
+ else this.process.stdout.write(`${outcome.response}\n`);
1426
1385
  }
1427
1386
  //#endregion
1428
1387
  //#region src/commands/subagent/command.ts
@@ -1431,7 +1390,7 @@ const fullDescription = [
1431
1390
  "",
1432
1391
  "Available models (canonical slug):",
1433
1392
  ...listModelHelpLines(),
1434
- `Default: ${DEFAULT_MODEL} (off-budget). The claude-backend slugs are FALLBACKS for`,
1393
+ "Recommended first choice: xai-grok/grok-4.5 (off-budget). The claude-backend slugs are FALLBACKS for",
1435
1394
  "when the off-budget CLIs are quota-exhausted — they bill your Claude subscription."
1436
1395
  ].join("\n");
1437
1396
  const subagent = buildCommand({
@@ -1441,8 +1400,7 @@ const subagent = buildCommand({
1441
1400
  model: {
1442
1401
  kind: "parsed",
1443
1402
  parse: String,
1444
- optional: true,
1445
- brief: `Model slug to delegate to (default: ${DEFAULT_MODEL})`
1403
+ brief: "Model slug (required) — see the seat list above"
1446
1404
  },
1447
1405
  timeout: {
1448
1406
  kind: "parsed",
@@ -1509,7 +1467,7 @@ const app = buildApplication(buildRouteMap({
1509
1467
  runs,
1510
1468
  quota
1511
1469
  },
1512
- docs: { brief: "Bridge tasks to non-Claude AI CLIs — a plan → implement → review workflow, task delegation, and image generation (codex gpt-image-2 / grok Imagine)." }
1470
+ docs: { brief: "Bridge tasks to non-Claude AI CLIs — a plan → implement → review workflow, task delegation, and image generation (codex / agy / grok seats)." }
1513
1471
  }), {
1514
1472
  name: "aibridge",
1515
1473
  versionInfo: { currentVersion: version },
@@ -1526,4 +1484,4 @@ function buildContext(process) {
1526
1484
  return { process };
1527
1485
  }
1528
1486
  //#endregion
1529
- export { listModelHelpLines as C, formatUnknownModelError as S, supportsImageGen as T, DEFAULT_IMPLEMENTER as _, readRunLogs as a, backendModelId as b, evaluateCodexPreflight as c, renderPreflightRefusal as d, delegate as f, DEFAULT_IMAGE_GEN as g, positiveIntSeconds as h, listRuns as i, preflightCodex as l, nonEmptyPrompt as m, app as n, startRun as o, getDriver as p, runCli as r, evaluateAgyPreflight as s, buildContext as t, preflightModel as u, DEFAULT_MODEL as v, resolveModel as w, formatImageGenModelError as x, MODELS as y };
1487
+ export { supportsImageGen as S, backendModelId as _, readRunLogs as a, listModelHelpLines as b, evaluateCodexPreflight as c, renderPreflightRefusal as d, delegate as f, MODELS as g, positiveIntSeconds as h, listRuns as i, preflightCodex as l, nonEmptyPrompt as m, app as n, startRun as o, getDriver as p, runCli as r, evaluateAgyPreflight as s, buildContext as t, preflightModel as u, formatImageGenModelError as v, resolveModel as x, formatUnknownModelError as y };
package/dist/index.d.mts CHANGED
@@ -20,14 +20,16 @@ declare function runCli(ctx: LocalContext, argv: readonly string[]): Promise<voi
20
20
  *
21
21
  * Models are registered by canonical, provider-qualified slug —
22
22
  * `<vendor>-<cli>/<model>[-<effort>]`, e.g. `openai-codex/gpt-5.6-sol-high`.
23
- * Canonical slugs only — no short aliases, by design.
23
+ * Canonical slugs only — no short aliases, by design. That holds on both sides:
24
+ * `backendModel` is a pinned model id (`claude-opus-5`), never a moving vendor
25
+ * alias (`opus`), so a seat never silently changes model under you.
24
26
  */
25
27
  type Backend = 'agy' | 'claude' | 'codex' | 'grok';
26
28
  type Effort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
27
29
  interface ModelSpec {
28
30
  readonly slug: string;
29
31
  readonly backend: Backend;
30
- readonly backendModel: string | undefined;
32
+ readonly backendModel: string;
31
33
  readonly efforts: readonly Effort[] | null;
32
34
  readonly defaultEffort?: Effort;
33
35
  readonly brief: string;
@@ -37,12 +39,9 @@ interface ResolvedModel {
37
39
  readonly effort: Effort | undefined;
38
40
  }
39
41
  declare const MODELS: Record<string, ModelSpec>;
40
- declare const DEFAULT_MODEL = "xai-grok/grok-4.5";
41
- declare const DEFAULT_IMPLEMENTER = "google-antigravity/gemini-3.6-flash";
42
- declare const DEFAULT_IMAGE_GEN = "openai-codex/gpt-5.6-sol";
43
42
  declare function supportsImageGen(resolved: ResolvedModel): boolean;
44
43
  declare function resolveModel(input: string): ResolvedModel | undefined;
45
- declare function backendModelId(resolved: ResolvedModel): string | undefined;
44
+ declare function backendModelId(resolved: ResolvedModel): string;
46
45
  declare function listModelHelpLines(opts?: {
47
46
  readonly imageOnly?: boolean;
48
47
  }): string[];
@@ -62,7 +61,7 @@ interface DelegationTask {
62
61
  readonly tools: boolean;
63
62
  readonly timeoutSec: number;
64
63
  readonly cwd: string;
65
- readonly backendModel: string | undefined;
64
+ readonly backendModel: string;
66
65
  readonly effort: Effort | undefined;
67
66
  readonly onStdout?: (chunk: string) => void;
68
67
  readonly onStderr?: (chunk: string) => void;
@@ -82,13 +81,9 @@ type QuotaSnapshot = AgyQuotaSnapshot | CodexQuotaSnapshot | ClaudeQuotaSnapshot
82
81
  interface ImageGenRequest {
83
82
  readonly prompt: string;
84
83
  readonly workDir: string;
85
- readonly backendModel: string | undefined;
84
+ readonly backendModel: string;
86
85
  readonly effort: Effort | undefined;
87
- readonly quality: string;
88
- readonly size: {
89
- readonly w: number;
90
- readonly h: number;
91
- } | undefined;
86
+ readonly aspectRatio: string | undefined;
92
87
  readonly imagePaths: readonly string[];
93
88
  readonly timeoutSec: number;
94
89
  readonly forceful: boolean;
@@ -181,4 +176,4 @@ declare function renderPreflightRefusal(cmd: string, verdict: {
181
176
  resetAt: string | undefined;
182
177
  }): string;
183
178
  //#endregion
184
- export { type AgentCliDriver, type Backend, DEFAULT_IMAGE_GEN, DEFAULT_IMPLEMENTER, DEFAULT_MODEL, type DelegateOptions, type DelegateOutcome, type DelegationResult, type DelegationTask, type Effort, type LocalContext, MODELS, type ModelSpec, type PreflightVerdict, type ResolvedModel, type RunLog, type RunMeta, app, backendModelId, buildContext, delegate, evaluateAgyPreflight, evaluateCodexPreflight, formatImageGenModelError, formatUnknownModelError, getDriver, listModelHelpLines, listRuns, nonEmptyPrompt, positiveIntSeconds, preflightCodex, preflightModel, readRunLogs, renderPreflightRefusal, resolveModel, runCli, startRun, supportsImageGen };
179
+ export { type AgentCliDriver, type Backend, type DelegateOptions, type DelegateOutcome, type DelegationResult, type DelegationTask, type Effort, type LocalContext, MODELS, type ModelSpec, type PreflightVerdict, type ResolvedModel, type RunLog, type RunMeta, app, backendModelId, buildContext, delegate, evaluateAgyPreflight, evaluateCodexPreflight, formatImageGenModelError, formatUnknownModelError, getDriver, listModelHelpLines, listRuns, nonEmptyPrompt, positiveIntSeconds, preflightCodex, preflightModel, readRunLogs, renderPreflightRefusal, resolveModel, runCli, startRun, supportsImageGen };