@aibridge/cli 0.8.0 → 0.10.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 +48 -28
- package/dist/cli.mjs +1 -1
- package/dist/{context-DJtEcg6f.mjs → context-DeJmEXLe.mjs} +194 -125
- package/dist/index.mjs +1 -1
- package/instructions/SKILL.md +115 -0
- package/instructions/reference/image-gen.md +107 -0
- package/instructions/reference/implement.md +51 -0
- package/instructions/reference/plan.md +69 -0
- package/instructions/reference/review.md +75 -0
- package/instructions/reference/subagent.md +48 -0
- package/instructions/reference/why.md +54 -0
- package/package.json +8 -7
- package/src/app.exit-code.test.ts +10 -1
- package/src/app.ts +4 -5
- package/src/commands/image-gen/command.ts +5 -0
- package/src/commands/image-gen/impl.test.ts +1 -0
- package/src/commands/image-gen/impl.ts +14 -0
- package/src/commands/review/command.ts +2 -2
- package/src/commands/review/impl.ts +1 -1
- package/src/commands/skill/command.ts +28 -0
- package/src/commands/skill/impl.test.ts +59 -0
- package/src/commands/skill/impl.ts +60 -0
- package/src/package.ts +5 -0
- package/src/quotaPreflight.test.ts +13 -1
- package/src/quotaPreflight.ts +10 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# aibridge
|
|
5
5
|
|
|
6
|
-
**Let your coding agent
|
|
6
|
+
**Let your coding agent use the other AI CLIs on your machine.** Plan, implement, review, red-team, and generate images with Grok, Gemini, Codex, and Claude. No API keys.
|
|
7
7
|
|
|
8
8
|
[](https://skills.sh/ycmjason/aibridge)
|
|
9
9
|
[](https://www.npmjs.com/package/@aibridge/cli)
|
|
@@ -15,38 +15,42 @@
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
Your agent
|
|
18
|
+
Your coding agent uses one model. Your machine may already have others available
|
|
19
|
+
through `grok`, `agy` (Antigravity), `codex`, or `claude`. **aibridge** lets your
|
|
20
|
+
agent use those models as planners, implementers, reviewers, one-shot delegates,
|
|
21
|
+
and image generators. Each model runs through its existing CLI login.
|
|
19
22
|
|
|
20
23
|
## Install
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
Install the skill into your agent:
|
|
23
26
|
|
|
24
27
|
```bash
|
|
25
28
|
npx skills add ycmjason/aibridge
|
|
26
29
|
```
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
The skill runs the CLI on demand with `npx -y @aibridge/cli`. Ask your agent to
|
|
32
|
+
"use aibridge", or run a command directly:
|
|
29
33
|
|
|
30
34
|
```bash
|
|
31
35
|
npx -y @aibridge/cli subagent --model xai-grok/grok-4.6 "summarize the architecture of this repo"
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
<sup>
|
|
38
|
+
<sup>Optional: install `aibridge` on your PATH with `npm i -g @aibridge/cli`.</sup>
|
|
35
39
|
|
|
36
40
|
## Commands
|
|
37
41
|
|
|
38
42
|
| Command | Use when |
|
|
39
43
|
|---|---|
|
|
40
|
-
| `aibridge plan --model xai-grok/grok-4.6 --out plan.md "<task>"` |
|
|
41
|
-
| `aibridge implement --model google-antigravity/gemini-3.7-flash <plan.md>` |
|
|
42
|
-
| `aibridge review --model xai-grok/grok-4.6 --out review.md [--plan <plan.md>]` |
|
|
43
|
-
| `aibridge subagent --model xai-grok/grok-4.6 "<task>"` |
|
|
44
|
-
| `aibridge image-gen --model openai-codex/gpt-5.6-sol --out out.png "<prompt>"` |
|
|
45
|
-
| `aibridge models [--json]` |
|
|
46
|
-
| `aibridge quota` |
|
|
47
|
-
| `aibridge runs` | Inspect or watch
|
|
44
|
+
| `aibridge plan --model xai-grok/grok-4.6 --out plan.md "<task>"` | Study the repo and write a detailed plan file |
|
|
45
|
+
| `aibridge implement --model google-antigravity/gemini-3.7-flash <plan.md>` | Execute an approved plan and run the project's checks |
|
|
46
|
+
| `aibridge review --model xai-grok/grok-4.6 --out review.md [--plan <plan.md>] [--base <ref>]` | Review a diff or, on a clean tree, a plan |
|
|
47
|
+
| `aibridge subagent --model xai-grok/grok-4.6 "<task>"` | Delegate a self-contained task or request a second opinion |
|
|
48
|
+
| `aibridge image-gen --model openai-codex/gpt-5.6-sol --out out.png "<prompt>"` | Generate and verify a raster image |
|
|
49
|
+
| `aibridge models [--json]` | List registered models and their capabilities |
|
|
50
|
+
| `aibridge quota` | Show quota remaining for every backend |
|
|
51
|
+
| `aibridge runs` | Inspect or watch run logs in `~/.aibridge/runs` |
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
Use `plan`, `implement`, and `review` as one controlled workflow:
|
|
50
54
|
|
|
51
55
|
```
|
|
52
56
|
aibridge plan --model xai-grok/grok-4.6 --out plan.md "add rate limiting to the API" # delegate writes plan.md
|
|
@@ -55,21 +59,26 @@ aibridge implement --model google-antigravity/gemini-3.7-flash plan.md
|
|
|
55
59
|
aibridge review --model xai-grok/grok-4.6 --out review.md --plan plan.md # a third seat cross-checks the diff
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
Only the plan path passes between stages, which keeps the plan out of the
|
|
63
|
+
orchestrator's conversation context.
|
|
59
64
|
|
|
60
65
|
## How it works
|
|
61
66
|
|
|
62
|
-
- **The skill
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
- **
|
|
67
|
+
- **The skill decides; the CLI executes.** The skill covers routing and prompt
|
|
68
|
+
design. The CLI starts the backend, captures output, validates known failure
|
|
69
|
+
modes, and logs the run.
|
|
70
|
+
- **Review stays cross-model.** The recommended workflow uses Grok to plan and
|
|
71
|
+
review, and Gemini to implement.
|
|
72
|
+
- **Existing logins, no API keys.** Each backend uses its CLI login and quota.
|
|
73
|
+
- **Every model has a canonical slug:**
|
|
74
|
+
`<vendor>-<cli>/<model>[-<effort>]`, such as `xai-grok/grok-4.6` or
|
|
75
|
+
`openai-codex/gpt-5.6-sol-high`. There are no aliases. Run
|
|
76
|
+
`aibridge <command> --help` for the current list.
|
|
66
77
|
|
|
67
78
|
## Tell your agent when to reach for it
|
|
68
79
|
|
|
69
|
-
aibridge
|
|
70
|
-
|
|
71
|
-
your agent already reads at the start of every session (`AGENTS.md`, `CLAUDE.md`,
|
|
72
|
-
`.cursorrules`, …). Something like:
|
|
80
|
+
aibridge does not decide when to delegate. Put a routing rule in the instructions
|
|
81
|
+
file your agent reads (`AGENTS.md`, `CLAUDE.md`, `.cursorrules`, and so on):
|
|
73
82
|
|
|
74
83
|
```markdown
|
|
75
84
|
## Delegation gate — decide before you implement
|
|
@@ -91,21 +100,32 @@ Delegated work is yours to verify: re-run the real gates before trusting a diff.
|
|
|
91
100
|
Prefer a reviewer from a different model family than whoever implemented.
|
|
92
101
|
```
|
|
93
102
|
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
Adjust the models and thresholds for your quotas. Make the routing decision before
|
|
104
|
+
implementation starts.
|
|
96
105
|
|
|
97
106
|
## Requirements
|
|
98
107
|
|
|
99
108
|
- **Node ≥ 24.11**
|
|
100
|
-
-
|
|
109
|
+
- At least one authenticated backend CLI on `PATH`:
|
|
110
|
+
[`grok`](https://github.com/superagent-ai/grok-cli), `agy` (Antigravity),
|
|
111
|
+
[`codex`](https://github.com/openai/codex), or
|
|
112
|
+
[`claude`](https://claude.com/claude-code). Missing CLIs produce install hints.
|
|
101
113
|
|
|
102
114
|
## Packages
|
|
103
115
|
|
|
104
|
-
|
|
116
|
+
Packages use the [`@aibridge`](https://www.npmjs.com/org/aibridge) scope:
|
|
117
|
+
[`@aibridge/cli`](https://www.npmjs.com/package/@aibridge/cli),
|
|
118
|
+
`@aibridge/proc`, and one reusable driver for each backend:
|
|
119
|
+
`driver-agy`, `driver-grok`, `driver-codex`, and `driver-claude`.
|
|
105
120
|
|
|
106
121
|
## Security
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+
In tools mode, delegates can read files, write files, and run shell commands with
|
|
124
|
+
the same access as the invoking agent. Task content is sent to the selected
|
|
125
|
+
provider. Use `--no-tools` for untrusted input. Packages are published from this
|
|
126
|
+
public repository through OIDC with
|
|
127
|
+
[SLSA provenance](https://www.npmjs.com/package/@aibridge/cli) and no install-time
|
|
128
|
+
scripts.
|
|
109
129
|
|
|
110
130
|
## Contributing & development
|
|
111
131
|
|
package/dist/cli.mjs
CHANGED
|
@@ -312,6 +312,123 @@ function getDriver(backend) {
|
|
|
312
312
|
return driver;
|
|
313
313
|
}
|
|
314
314
|
//#endregion
|
|
315
|
+
//#region src/quotaPreflight.ts
|
|
316
|
+
function evaluateAgyPreflight(snapshot, backendModel) {
|
|
317
|
+
const quota = findModelQuota(snapshot, backendModel);
|
|
318
|
+
if (!quota) return {
|
|
319
|
+
ok: true,
|
|
320
|
+
warning: `model "${backendModel}" not in quota snapshot; proceeding`
|
|
321
|
+
};
|
|
322
|
+
if (quota.exhausted) {
|
|
323
|
+
let resetAt = quota.resetTime;
|
|
324
|
+
if (!resetAt) {
|
|
325
|
+
for (const group of snapshot.groups) if (group.displayName.includes("Gemini")) {
|
|
326
|
+
for (const bucket of group.buckets) if (bucket.resetTime) {
|
|
327
|
+
if (!resetAt || new Date(bucket.resetTime).getTime() < new Date(resetAt).getTime()) resetAt = bucket.resetTime;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
ok: false,
|
|
333
|
+
kind: "quota",
|
|
334
|
+
message: `agy model "${backendModel}" is quota-exhausted`,
|
|
335
|
+
resetAt
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
return { ok: true };
|
|
339
|
+
}
|
|
340
|
+
function evaluateCodexPreflight(snapshot) {
|
|
341
|
+
if (snapshot.limitReached) return {
|
|
342
|
+
ok: false,
|
|
343
|
+
kind: "quota",
|
|
344
|
+
message: "codex quota limit reached",
|
|
345
|
+
resetAt: snapshot.windows.find((w) => w.resetAt)?.resetAt
|
|
346
|
+
};
|
|
347
|
+
const exhaustedWindow = snapshot.windows.find((w) => w.usedPercent >= 100);
|
|
348
|
+
if (exhaustedWindow) return {
|
|
349
|
+
ok: false,
|
|
350
|
+
kind: "quota",
|
|
351
|
+
message: "codex quota limit reached",
|
|
352
|
+
resetAt: exhaustedWindow.resetAt
|
|
353
|
+
};
|
|
354
|
+
return { ok: true };
|
|
355
|
+
}
|
|
356
|
+
function evaluateGrokPreflight(snapshot) {
|
|
357
|
+
if (snapshot.usedPercent !== void 0 && snapshot.usedPercent >= 100) return {
|
|
358
|
+
ok: false,
|
|
359
|
+
kind: "quota",
|
|
360
|
+
message: "grok credit quota exhausted",
|
|
361
|
+
resetAt: snapshot.periodEnd
|
|
362
|
+
};
|
|
363
|
+
return { ok: true };
|
|
364
|
+
}
|
|
365
|
+
async function preflightModel(resolved) {
|
|
366
|
+
if (resolved.spec.backend === "codex") return preflightCodex();
|
|
367
|
+
if (resolved.spec.backend === "grok") return preflightGrok();
|
|
368
|
+
if (resolved.spec.backend !== "agy") return { ok: true };
|
|
369
|
+
try {
|
|
370
|
+
return evaluateAgyPreflight(await fetchAgyQuota(), backendModelId(resolved));
|
|
371
|
+
} catch (err) {
|
|
372
|
+
if (isAuthExpired(err)) return {
|
|
373
|
+
ok: false,
|
|
374
|
+
kind: "auth",
|
|
375
|
+
message: err.message,
|
|
376
|
+
resetAt: void 0
|
|
377
|
+
};
|
|
378
|
+
return {
|
|
379
|
+
ok: true,
|
|
380
|
+
warning: `quota preflight failed (${err.message}); proceeding`
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async function preflightCodex() {
|
|
385
|
+
try {
|
|
386
|
+
return evaluateCodexPreflight(await fetchCodexQuota());
|
|
387
|
+
} catch (err) {
|
|
388
|
+
if (isAuthExpired(err)) return {
|
|
389
|
+
ok: false,
|
|
390
|
+
kind: "auth",
|
|
391
|
+
message: err.message,
|
|
392
|
+
resetAt: void 0
|
|
393
|
+
};
|
|
394
|
+
return {
|
|
395
|
+
ok: true,
|
|
396
|
+
warning: `quota preflight failed (${err.message}); proceeding`
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function preflightGrok() {
|
|
401
|
+
try {
|
|
402
|
+
return evaluateGrokPreflight(await fetchGrokQuota());
|
|
403
|
+
} catch (err) {
|
|
404
|
+
if (isAuthExpired(err)) return {
|
|
405
|
+
ok: false,
|
|
406
|
+
kind: "auth",
|
|
407
|
+
message: err.message,
|
|
408
|
+
resetAt: void 0
|
|
409
|
+
};
|
|
410
|
+
return {
|
|
411
|
+
ok: true,
|
|
412
|
+
warning: `quota preflight failed (${err.message}); proceeding`
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function formatReset$1(resetTime) {
|
|
417
|
+
if (!resetTime) return "-";
|
|
418
|
+
const ms = new Date(resetTime).getTime() - Date.now();
|
|
419
|
+
if (Number.isNaN(ms)) return resetTime;
|
|
420
|
+
if (ms <= 0) return "now";
|
|
421
|
+
const mins = Math.round(ms / 6e4);
|
|
422
|
+
const rel = mins < 60 ? `${mins}m` : `${Math.floor(mins / 60)}h${mins % 60}m`;
|
|
423
|
+
return `${new Date(resetTime).toLocaleTimeString()} (in ${rel})`;
|
|
424
|
+
}
|
|
425
|
+
function renderPreflightRefusal(cmd, verdict) {
|
|
426
|
+
if (verdict.kind === "auth") return `aibridge ${cmd}: refusing — ${verdict.message}. Running with --no-preflight would only fail unauthenticated later. Or use a different --model.`;
|
|
427
|
+
const resetClause = verdict.resetAt ? ` Resets ${formatReset$1(verdict.resetAt)}.` : "";
|
|
428
|
+
const fallback = cmd === "image-gen" ? "Use --no-preflight to override, or another image seat (--model openai-codex/gpt-5.6-sol | google-antigravity/gemini-3.7-flash | xai-grok/grok-4.6)." : "Use --no-preflight to override, or a claude-backend fallback (subagent --model sonnet|opus — bills the Claude subscription).";
|
|
429
|
+
return `aibridge ${cmd}: refusing — ${verdict.message}.${resetClause} ${fallback}`;
|
|
430
|
+
}
|
|
431
|
+
//#endregion
|
|
315
432
|
//#region src/transparency.ts
|
|
316
433
|
const CHROMA_CLAUSE = "The entire background must be a perfectly flat solid #00ff00 chroma-key green. The background must be one uniform colour with no shadows, gradients, texture, reflections, or lighting variation. Keep the subject fully separated from the background with crisp edges. Do not use #00ff00 or any similar green anywhere on the subject. No cast shadow, no contact shadow, no reflection.";
|
|
317
434
|
const NATIVE_ALPHA_CLAUSE = "Render the subject on a fully transparent background — PNG with a real alpha channel, no backdrop, no canvas colour, no cast shadow.";
|
|
@@ -419,6 +536,15 @@ async function imageGen$1(flags, prompt) {
|
|
|
419
536
|
}
|
|
420
537
|
const driver = getDriver(model.spec.backend);
|
|
421
538
|
if (!driver.generateImage) return fail(formatImageGenModelError(inputSlug, model));
|
|
539
|
+
if (flags.preflight) {
|
|
540
|
+
const verdict = await preflightModel(model);
|
|
541
|
+
if (!verdict.ok) {
|
|
542
|
+
this.process.stderr.write(`${renderPreflightRefusal("image-gen", verdict)}\n`);
|
|
543
|
+
this.process.exitCode = 3;
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (verdict.warning) this.process.stderr.write(`aibridge image-gen: ${verdict.warning}\n`);
|
|
547
|
+
}
|
|
422
548
|
const minBytes = model.spec.backend === "codex" ? MIN_REAL_BYTES_CODEX : MIN_REAL_BYTES_TOOL;
|
|
423
549
|
const work = mkdtempSync(join(tmpdir(), "aibridge-imagegen-"));
|
|
424
550
|
const effectivePrompt = flags.transparent ? `${prompt} ${alpha === "chroma" ? CHROMA_CLAUSE : NATIVE_ALPHA_CLAUSE}` : prompt;
|
|
@@ -585,6 +711,11 @@ const imageGen = buildCommand({
|
|
|
585
711
|
optional: true,
|
|
586
712
|
brief: "Max seconds to wait for the render (default: 600)"
|
|
587
713
|
},
|
|
714
|
+
preflight: {
|
|
715
|
+
kind: "boolean",
|
|
716
|
+
default: true,
|
|
717
|
+
brief: "Check model quota before rendering (use --no-preflight to skip)"
|
|
718
|
+
},
|
|
588
719
|
json: {
|
|
589
720
|
kind: "boolean",
|
|
590
721
|
withNegated: false,
|
|
@@ -626,122 +757,6 @@ async function delegate(opts, driver = getDriver(opts.model.spec.backend)) {
|
|
|
626
757
|
return result;
|
|
627
758
|
}
|
|
628
759
|
//#endregion
|
|
629
|
-
//#region src/quotaPreflight.ts
|
|
630
|
-
function evaluateAgyPreflight(snapshot, backendModel) {
|
|
631
|
-
const quota = findModelQuota(snapshot, backendModel);
|
|
632
|
-
if (!quota) return {
|
|
633
|
-
ok: true,
|
|
634
|
-
warning: `model "${backendModel}" not in quota snapshot; proceeding`
|
|
635
|
-
};
|
|
636
|
-
if (quota.exhausted) {
|
|
637
|
-
let resetAt = quota.resetTime;
|
|
638
|
-
if (!resetAt) {
|
|
639
|
-
for (const group of snapshot.groups) if (group.displayName.includes("Gemini")) {
|
|
640
|
-
for (const bucket of group.buckets) if (bucket.resetTime) {
|
|
641
|
-
if (!resetAt || new Date(bucket.resetTime).getTime() < new Date(resetAt).getTime()) resetAt = bucket.resetTime;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
return {
|
|
646
|
-
ok: false,
|
|
647
|
-
kind: "quota",
|
|
648
|
-
message: `agy model "${backendModel}" is quota-exhausted`,
|
|
649
|
-
resetAt
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
return { ok: true };
|
|
653
|
-
}
|
|
654
|
-
function evaluateCodexPreflight(snapshot) {
|
|
655
|
-
if (snapshot.limitReached) return {
|
|
656
|
-
ok: false,
|
|
657
|
-
kind: "quota",
|
|
658
|
-
message: "codex quota limit reached",
|
|
659
|
-
resetAt: snapshot.windows.find((w) => w.resetAt)?.resetAt
|
|
660
|
-
};
|
|
661
|
-
const exhaustedWindow = snapshot.windows.find((w) => w.usedPercent >= 100);
|
|
662
|
-
if (exhaustedWindow) return {
|
|
663
|
-
ok: false,
|
|
664
|
-
kind: "quota",
|
|
665
|
-
message: "codex quota limit reached",
|
|
666
|
-
resetAt: exhaustedWindow.resetAt
|
|
667
|
-
};
|
|
668
|
-
return { ok: true };
|
|
669
|
-
}
|
|
670
|
-
function evaluateGrokPreflight(snapshot) {
|
|
671
|
-
if (snapshot.usedPercent !== void 0 && snapshot.usedPercent >= 100) return {
|
|
672
|
-
ok: false,
|
|
673
|
-
kind: "quota",
|
|
674
|
-
message: "grok credit quota exhausted",
|
|
675
|
-
resetAt: snapshot.periodEnd
|
|
676
|
-
};
|
|
677
|
-
return { ok: true };
|
|
678
|
-
}
|
|
679
|
-
async function preflightModel(resolved) {
|
|
680
|
-
if (resolved.spec.backend === "codex") return preflightCodex();
|
|
681
|
-
if (resolved.spec.backend === "grok") return preflightGrok();
|
|
682
|
-
if (resolved.spec.backend !== "agy") return { ok: true };
|
|
683
|
-
try {
|
|
684
|
-
return evaluateAgyPreflight(await fetchAgyQuota(), backendModelId(resolved));
|
|
685
|
-
} catch (err) {
|
|
686
|
-
if (isAuthExpired(err)) return {
|
|
687
|
-
ok: false,
|
|
688
|
-
kind: "auth",
|
|
689
|
-
message: err.message,
|
|
690
|
-
resetAt: void 0
|
|
691
|
-
};
|
|
692
|
-
return {
|
|
693
|
-
ok: true,
|
|
694
|
-
warning: `quota preflight failed (${err.message}); proceeding`
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
async function preflightCodex() {
|
|
699
|
-
try {
|
|
700
|
-
return evaluateCodexPreflight(await fetchCodexQuota());
|
|
701
|
-
} catch (err) {
|
|
702
|
-
if (isAuthExpired(err)) return {
|
|
703
|
-
ok: false,
|
|
704
|
-
kind: "auth",
|
|
705
|
-
message: err.message,
|
|
706
|
-
resetAt: void 0
|
|
707
|
-
};
|
|
708
|
-
return {
|
|
709
|
-
ok: true,
|
|
710
|
-
warning: `quota preflight failed (${err.message}); proceeding`
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
async function preflightGrok() {
|
|
715
|
-
try {
|
|
716
|
-
return evaluateGrokPreflight(await fetchGrokQuota());
|
|
717
|
-
} catch (err) {
|
|
718
|
-
if (isAuthExpired(err)) return {
|
|
719
|
-
ok: false,
|
|
720
|
-
kind: "auth",
|
|
721
|
-
message: err.message,
|
|
722
|
-
resetAt: void 0
|
|
723
|
-
};
|
|
724
|
-
return {
|
|
725
|
-
ok: true,
|
|
726
|
-
warning: `quota preflight failed (${err.message}); proceeding`
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
function formatReset$1(resetTime) {
|
|
731
|
-
if (!resetTime) return "-";
|
|
732
|
-
const ms = new Date(resetTime).getTime() - Date.now();
|
|
733
|
-
if (Number.isNaN(ms)) return resetTime;
|
|
734
|
-
if (ms <= 0) return "now";
|
|
735
|
-
const mins = Math.round(ms / 6e4);
|
|
736
|
-
const rel = mins < 60 ? `${mins}m` : `${Math.floor(mins / 60)}h${mins % 60}m`;
|
|
737
|
-
return `${new Date(resetTime).toLocaleTimeString()} (in ${rel})`;
|
|
738
|
-
}
|
|
739
|
-
function renderPreflightRefusal(cmd, verdict) {
|
|
740
|
-
if (verdict.kind === "auth") return `aibridge ${cmd}: refusing — ${verdict.message}. Running with --no-preflight would only send the delegate in unauthenticated. Or use a different --model.`;
|
|
741
|
-
const resetClause = verdict.resetAt ? ` Resets ${formatReset$1(verdict.resetAt)}.` : "";
|
|
742
|
-
return `aibridge ${cmd}: refusing — ${verdict.message}.${resetClause} Use --no-preflight to override, or a claude-backend fallback (subagent --model sonnet|opus — bills the Claude subscription).`;
|
|
743
|
-
}
|
|
744
|
-
//#endregion
|
|
745
760
|
//#region src/runlog.ts
|
|
746
761
|
function getTimestamp() {
|
|
747
762
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -1385,7 +1400,7 @@ async function review$1(flags) {
|
|
|
1385
1400
|
const run = startRun("review", `${model.spec.slug}: ${modeDetail}`);
|
|
1386
1401
|
const absOutPath = isAbsolute(flags.out) ? flags.out : resolve(cwd, flags.out);
|
|
1387
1402
|
let reviewPrompt;
|
|
1388
|
-
if (isDirty) reviewPrompt = `You are an expert code reviewer. Inspect the
|
|
1403
|
+
if (isDirty) reviewPrompt = `You are an expert code reviewer. Inspect the diff produced by \`git diff ${baseRef}\` (this covers committed and uncommitted changes) plus any 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"`;
|
|
1389
1404
|
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"`;
|
|
1390
1405
|
const outcome = await delegate({
|
|
1391
1406
|
model,
|
|
@@ -1447,7 +1462,7 @@ const review = buildCommand({
|
|
|
1447
1462
|
kind: "parsed",
|
|
1448
1463
|
parse: String,
|
|
1449
1464
|
optional: true,
|
|
1450
|
-
brief: "Base git ref to diff against (default: HEAD)"
|
|
1465
|
+
brief: "Base git ref or range to diff against, e.g. HEAD~3 or main (default: HEAD)"
|
|
1451
1466
|
},
|
|
1452
1467
|
out: {
|
|
1453
1468
|
kind: "parsed",
|
|
@@ -1467,7 +1482,7 @@ const review = buildCommand({
|
|
|
1467
1482
|
}
|
|
1468
1483
|
} },
|
|
1469
1484
|
docs: {
|
|
1470
|
-
brief: "Review working tree
|
|
1485
|
+
brief: "Review a diff (working tree, or any commit range via --base) or a plan contract",
|
|
1471
1486
|
fullDescription: fullDescription$2
|
|
1472
1487
|
}
|
|
1473
1488
|
});
|
|
@@ -1641,6 +1656,62 @@ const runs = buildCommand({
|
|
|
1641
1656
|
fullDescription: fullDescription$1
|
|
1642
1657
|
}
|
|
1643
1658
|
});
|
|
1659
|
+
const PACKAGE_VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
1660
|
+
//#endregion
|
|
1661
|
+
//#region src/commands/skill/impl.ts
|
|
1662
|
+
const TOPICS = {
|
|
1663
|
+
plan: "reference/plan.md",
|
|
1664
|
+
implement: "reference/implement.md",
|
|
1665
|
+
review: "reference/review.md",
|
|
1666
|
+
subagent: "reference/subagent.md",
|
|
1667
|
+
"image-gen": "reference/image-gen.md",
|
|
1668
|
+
why: "reference/why.md"
|
|
1669
|
+
};
|
|
1670
|
+
function instructionPath(relativePath) {
|
|
1671
|
+
const found = [new URL(`../instructions/${relativePath}`, import.meta.url), new URL(`../../../instructions/${relativePath}`, import.meta.url)].find((candidate) => existsSync(candidate));
|
|
1672
|
+
if (!found) throw new Error(`bundled instruction file is missing: ${relativePath}`);
|
|
1673
|
+
return found;
|
|
1674
|
+
}
|
|
1675
|
+
function readInstruction(relativePath) {
|
|
1676
|
+
return readFileSync(instructionPath(relativePath), "utf8").trimEnd();
|
|
1677
|
+
}
|
|
1678
|
+
function skillImpl(topic) {
|
|
1679
|
+
if (topic !== void 0 && !(topic in TOPICS)) {
|
|
1680
|
+
this.process.stderr.write(`aibridge skill: unknown topic ${JSON.stringify(topic)}; expected one of: ${Object.keys(TOPICS).join(", ")}\n`);
|
|
1681
|
+
this.process.exitCode = 2;
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
try {
|
|
1685
|
+
const sections = [`Command runner for these instructions: \`${`npx -y @aibridge/cli@${PACKAGE_VERSION}`}\`\nUse that exact prefix for every aibridge command below; do not substitute a global binary.`, readInstruction("SKILL.md")];
|
|
1686
|
+
if (topic !== void 0) sections.push(readInstruction(TOPICS[topic]));
|
|
1687
|
+
this.process.stdout.write(`${sections.join("\n\n---\n\n")}\n`);
|
|
1688
|
+
} catch (error) {
|
|
1689
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1690
|
+
this.process.stderr.write(`aibridge skill: ${message}\n`);
|
|
1691
|
+
this.process.exitCode = 1;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
//#endregion
|
|
1695
|
+
//#region src/commands/skill/command.ts
|
|
1696
|
+
function skillCommand(_flags, topic) {
|
|
1697
|
+
skillImpl.call(this, topic);
|
|
1698
|
+
}
|
|
1699
|
+
const skill = buildCommand({
|
|
1700
|
+
func: skillCommand,
|
|
1701
|
+
parameters: {
|
|
1702
|
+
flags: {},
|
|
1703
|
+
positional: {
|
|
1704
|
+
kind: "tuple",
|
|
1705
|
+
parameters: [{
|
|
1706
|
+
brief: "Command-specific instructions to append to the router",
|
|
1707
|
+
parse: String,
|
|
1708
|
+
placeholder: "topic",
|
|
1709
|
+
optional: true
|
|
1710
|
+
}]
|
|
1711
|
+
}
|
|
1712
|
+
},
|
|
1713
|
+
docs: { brief: "Print the canonical agent instructions bundled with this CLI" }
|
|
1714
|
+
});
|
|
1644
1715
|
//#endregion
|
|
1645
1716
|
//#region src/commands/subagent/impl.ts
|
|
1646
1717
|
async function subagent$1(flags, prompt) {
|
|
@@ -1762,9 +1833,6 @@ function normalizeExitCode(ctx) {
|
|
|
1762
1833
|
}
|
|
1763
1834
|
if (code !== 0 && code !== 1 && code !== 2 && code !== 3) ctx.process.exitCode = 1;
|
|
1764
1835
|
}
|
|
1765
|
-
//#endregion
|
|
1766
|
-
//#region src/app.ts
|
|
1767
|
-
const { version } = createRequire(import.meta.url)("../package.json");
|
|
1768
1836
|
const app = buildApplication(buildRouteMap({
|
|
1769
1837
|
routes: {
|
|
1770
1838
|
plan,
|
|
@@ -1774,12 +1842,13 @@ const app = buildApplication(buildRouteMap({
|
|
|
1774
1842
|
"image-gen": imageGen,
|
|
1775
1843
|
runs,
|
|
1776
1844
|
quota,
|
|
1777
|
-
models
|
|
1845
|
+
models,
|
|
1846
|
+
skill
|
|
1778
1847
|
},
|
|
1779
1848
|
docs: { brief: "Bridge tasks to the other AI CLIs on this machine — a plan → implement → review workflow, task delegation, and image generation (codex / agy / grok seats)." }
|
|
1780
1849
|
}), {
|
|
1781
1850
|
name: "aibridge",
|
|
1782
|
-
versionInfo: { currentVersion:
|
|
1851
|
+
versionInfo: { currentVersion: PACKAGE_VERSION },
|
|
1783
1852
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
1784
1853
|
});
|
|
1785
1854
|
/** Public entry used by cli.ts and index.ts — preserves runCli(ctx, argv) surface. */
|
|
@@ -1793,4 +1862,4 @@ function buildContext(process) {
|
|
|
1793
1862
|
return { process };
|
|
1794
1863
|
}
|
|
1795
1864
|
//#endregion
|
|
1796
|
-
export { supportsImageGen as S, backendModelId as _, readRunLogs as a, listModelHelpLines as b,
|
|
1865
|
+
export { supportsImageGen as S, backendModelId as _, readRunLogs as a, listModelHelpLines as b, evaluateAgyPreflight as c, preflightModel as d, renderPreflightRefusal as f, MODELS as g, positiveIntSeconds as h, listRuns as i, evaluateCodexPreflight as l, nonEmptyPrompt as m, app as n, startRun as o, getDriver as p, runCli as r, delegate as s, buildContext as t, preflightCodex as u, formatImageGenModelError as v, resolveModel as x, formatUnknownModelError as y };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { S as supportsImageGen, _ as backendModelId, a as readRunLogs, b as listModelHelpLines, c as
|
|
1
|
+
import { S as supportsImageGen, _ as backendModelId, a as readRunLogs, b as listModelHelpLines, c as evaluateAgyPreflight, d as preflightModel, f as renderPreflightRefusal, g as MODELS, h as positiveIntSeconds, i as listRuns, l as evaluateCodexPreflight, m as nonEmptyPrompt, n as app, o as startRun, p as getDriver, r as runCli, s as delegate, t as buildContext, u as preflightCodex, v as formatImageGenModelError, x as resolveModel, y as formatUnknownModelError } from "./context-DeJmEXLe.mjs";
|
|
2
2
|
export { MODELS, app, backendModelId, buildContext, delegate, evaluateAgyPreflight, evaluateCodexPreflight, formatImageGenModelError, formatUnknownModelError, getDriver, listModelHelpLines, listRuns, nonEmptyPrompt, positiveIntSeconds, preflightCodex, preflightModel, readRunLogs, renderPreflightRefusal, resolveModel, runCli, startRun, supportsImageGen };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# aibridge
|
|
2
|
+
|
|
3
|
+
Use other providers' authenticated AI CLIs as delegates. You choose the task,
|
|
4
|
+
model, and prompt; aibridge runs the backend and validates its output. Each run
|
|
5
|
+
spends the selected backend's quota.
|
|
6
|
+
|
|
7
|
+
Load the `why` topic only when a rule appears unsuitable.
|
|
8
|
+
|
|
9
|
+
## Running it
|
|
10
|
+
|
|
11
|
+
The output header defines the exact command runner for this instruction set.
|
|
12
|
+
Examples below abbreviate that runner as `aibridge`; always substitute the exact
|
|
13
|
+
runner. This keeps instructions and executable behavior on the same version.
|
|
14
|
+
|
|
15
|
+
Requires Node 24.11 or later. If Node is older, ask the user to upgrade with
|
|
16
|
+
`nvm install 24` or `mise use node@24`. Ask before any global install.
|
|
17
|
+
|
|
18
|
+
Backing CLIs must be on `PATH` and authed: `grok`, `agy` (Antigravity), `codex`,
|
|
19
|
+
`claude`. Any subset works; commands fail fast with install hints.
|
|
20
|
+
|
|
21
|
+
## Where `--out` goes
|
|
22
|
+
|
|
23
|
+
- Put permanent project assets in their final location, such as
|
|
24
|
+
`--out public/icons/settings.png`.
|
|
25
|
+
- Put plans, reviews, and drafts in `<repo root>/.aibridge/`. Outside a
|
|
26
|
+
repository, use `.aibridge/` under the current directory.
|
|
27
|
+
- Name files by topic. Promote a draft to its real path once it is the keeper.
|
|
28
|
+
- Once per session, before the first write: `git check-ignore -q .aibridge/`
|
|
29
|
+
Keep the trailing slash so a directory-only rule matches before the directory
|
|
30
|
+
exists. If the command fails, ask to add `.aibridge/` to `.gitignore`.
|
|
31
|
+
- An explicit path from the user wins over all of this.
|
|
32
|
+
|
|
33
|
+
## Subcommands
|
|
34
|
+
|
|
35
|
+
| Command | Description |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `plan` | Expand a task into a detailed implementation plan file |
|
|
38
|
+
| `implement` | Implement a plan file in place and run the real checks |
|
|
39
|
+
| `review` | Review a diff, commit range, or plan contract |
|
|
40
|
+
| `subagent` | Delegate a self-contained task to another model |
|
|
41
|
+
| `image-gen` | Generate a raster image with a Codex, Antigravity, or Grok seat |
|
|
42
|
+
| `runs` | Monitor and inspect execution runs |
|
|
43
|
+
| `quota` | Show backend quota and reset times |
|
|
44
|
+
| `models` | List registered model seats and capabilities |
|
|
45
|
+
|
|
46
|
+
`plan`, `review` and `image-gen` require `--out` and print only a verdict/path
|
|
47
|
+
line. `subagent` and `implement` have no `--out`; they print the delegate's
|
|
48
|
+
answer to stdout, and `subagent --out foo.md` exits 2 with
|
|
49
|
+
`No flag registered for --out`. Redirect if you want that answer in a file.
|
|
50
|
+
|
|
51
|
+
## Routing
|
|
52
|
+
|
|
53
|
+
1. **First word is a subcommand** → use the command-specific section appended
|
|
54
|
+
to this output. If it is missing, run `aibridge skill <subcommand>` before
|
|
55
|
+
taking action.
|
|
56
|
+
2. **No subcommand** → infer:
|
|
57
|
+
- an image, icon or graphic to make → `image-gen`;
|
|
58
|
+
- a self-contained task, cross-model second opinion, or red-team → `subagent`;
|
|
59
|
+
- sizeable or risky implementation work → `plan` → *you read, edit and
|
|
60
|
+
approve the plan file* → `implement` → `review --plan <file>`. For
|
|
61
|
+
high-risk designs only, add a gate before any code is written:
|
|
62
|
+
`review --plan` on a clean tree.
|
|
63
|
+
|
|
64
|
+
If genuinely ambiguous, show the table above and ask.
|
|
65
|
+
3. **Unsure of the current flags?** Run `aibridge <command> --help`.
|
|
66
|
+
|
|
67
|
+
## Model seats
|
|
68
|
+
|
|
69
|
+
`--model` is required on every command that spends a delegate (`plan`,
|
|
70
|
+
`implement`, `review`, `subagent`, `image-gen`); nothing is chosen for you.
|
|
71
|
+
`quota`, `models` and `runs` take no `--model`. Starting points, not
|
|
72
|
+
benchmarks:
|
|
73
|
+
|
|
74
|
+
| slug | plan | implement | review | image-gen |
|
|
75
|
+
|---|---|---|---|---|
|
|
76
|
+
| `xai-grok/grok-4.6` | ✅ small–mid, well-scoped | ○ | ✅ | ○ JPEG |
|
|
77
|
+
| `xai-grok/grok-4.5` | ○ | ✅ any fidelity | ○ | ○ JPEG |
|
|
78
|
+
| `openai-codex/gpt-5.6-sol` | ✅ mid–big, ambiguous | ○ | ✅ | ✅ PNG |
|
|
79
|
+
| `anthropic-claude/opus-5` | ✅ mid–big, ambiguous | ○ | ✅ | ✗ |
|
|
80
|
+
| `google-antigravity/gemini-3.7-flash` | ○ | ✅ needs high–xhigh detail | ○ | ○ JPEG |
|
|
81
|
+
| `anthropic-claude/sonnet-5` | ○ | ✅ needs high detail | ○ | ✗ |
|
|
82
|
+
|
|
83
|
+
✅ recommended · ○ supported · ✗ unsupported. For `plan`, the qualifier describes
|
|
84
|
+
how much ambiguity the model can resolve. For `implement`, it describes how
|
|
85
|
+
detailed the plan must be.
|
|
86
|
+
**If the task fits no row, or the user has said how they want work routed, ask
|
|
87
|
+
rather than guess.**
|
|
88
|
+
|
|
89
|
+
Also registered: `openai-codex/gpt-5.6-terra` / `-luna` (cheaper coding tiers),
|
|
90
|
+
`anthropic-claude/fable-5` (hardest, longest-running work),
|
|
91
|
+
`anthropic-claude/haiku-4-5` (quick answers),
|
|
92
|
+
`google-antigravity/gemini-3.6-flash`, `google-antigravity/gemini-3.1-pro`
|
|
93
|
+
(`-high`/`-low` only), and agy's `claude-sonnet-4-6` /
|
|
94
|
+
`claude-opus-4-6-thinking` / `gpt-oss-120b-medium`. Run `aibridge models
|
|
95
|
+
[--json]` for exact per-seat facts, or `aibridge <command> --help` for the list.
|
|
96
|
+
|
|
97
|
+
- **One grok stage at a time.** ~30 req/min, ~1k msgs/day, and both tiers share
|
|
98
|
+
that budget.
|
|
99
|
+
- **`grok-4.6` plans and reviews, `grok-4.5` implements.** They are different
|
|
100
|
+
seats, not old and new.
|
|
101
|
+
- **The reviewer must be a different model family from whoever implemented**,
|
|
102
|
+
including when that was you.
|
|
103
|
+
- **A backend that shares YOUR quota is a last resort**: `anthropic-claude/*`
|
|
104
|
+
for Claude-based agents, `google-antigravity/*` for Antigravity-based agents.
|
|
105
|
+
Say so when you reach for it.
|
|
106
|
+
- **Swap on quota**: `openai-codex/gpt-5.6-sol[-<effort>]` is the usual alternate.
|
|
107
|
+
- Preflight runs before every delegation. `aibridge quota` is the manual check
|
|
108
|
+
before you pipeline several stages.
|
|
109
|
+
|
|
110
|
+
## Trust
|
|
111
|
+
|
|
112
|
+
In tools mode, delegates can read files, write files, and run shell commands at
|
|
113
|
+
your trust level. The selected provider receives the prompt and any content the
|
|
114
|
+
delegate reads. Use `--no-tools` for untrusted input; it disables file and shell
|
|
115
|
+
access.
|