@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 +15 -11
- package/dist/cli.mjs +1 -1
- package/dist/{context-8JjDzZBG.mjs → context-B6I4QI9z.mjs} +92 -134
- package/dist/index.d.mts +9 -14
- package/dist/index.mjs +2 -2
- package/package.json +6 -6
- package/src/app.ts +1 -1
- package/src/commands/image-gen/command.ts +9 -17
- package/src/commands/image-gen/impl.ts +46 -87
- package/src/commands/implement/command.ts +2 -3
- package/src/commands/implement/impl.ts +3 -3
- package/src/commands/plan/command.ts +3 -5
- package/src/commands/plan/impl.ts +5 -9
- package/src/commands/review/command.ts +3 -5
- package/src/commands/review/impl.ts +5 -9
- package/src/commands/subagent/command.ts +3 -4
- package/src/commands/subagent/impl.ts +4 -10
- package/src/delegate.ts +1 -1
- package/src/driver.ts +3 -4
- package/src/drivers.ts +1 -0
- package/src/flagMapping.test.ts +27 -4
- package/src/index.ts +0 -3
- package/src/models.test.ts +28 -14
- package/src/models.ts +35 -21
- package/src/quotaPreflight.ts +1 -2
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 (
|
|
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 —
|
|
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
|
|
54
|
-
aibridge review --plan plan.md
|
|
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
|
|
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
|
@@ -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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
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 && !
|
|
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
|
|
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
|
|
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 (
|
|
227
|
-
const
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
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 :
|
|
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
|
|
271
|
+
backendModel: backendModelId(model),
|
|
263
272
|
effort: model.effort,
|
|
264
|
-
|
|
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
|
|
282
|
+
backendModel: backendModelId(model),
|
|
275
283
|
effort: model.effort,
|
|
276
|
-
|
|
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
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
400
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
394
|
+
aspectRatio: {
|
|
423
395
|
kind: "parsed",
|
|
424
396
|
parse: String,
|
|
425
397
|
optional: true,
|
|
426
|
-
brief: "
|
|
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)
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
84
|
+
readonly backendModel: string;
|
|
86
85
|
readonly effort: Effort | undefined;
|
|
87
|
-
readonly
|
|
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,
|
|
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 };
|