@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 CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # aibridge
5
5
 
6
- **Let your coding agent drive the other AIs on your machine.** Cross-provider agent-to-agent delegation — plan, implement, review, red-team, and generate images across Grok, Gemini, Codex & Claude through the CLIs you already have. No API keys.
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
  [![skills.sh](https://skills.sh/b/ycmjason/aibridge)](https://skills.sh/ycmjason/aibridge)
9
9
  [![npm](https://img.shields.io/npm/v/%40aibridge%2Fcli)](https://www.npmjs.com/package/@aibridge/cli)
@@ -15,38 +15,42 @@
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 (Codex, Antigravity, or Grok seats).
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
- One step — install the skill into your agent(s):
25
+ Install the skill into your agent:
23
26
 
24
27
  ```bash
25
28
  npx skills add ycmjason/aibridge
26
29
  ```
27
30
 
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:
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>Want the `aibridge` command on your PATH for manual use? `npm i -g @aibridge/cli` (optional).</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>"` | 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.7-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.6 --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.6 "<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
- | `aibridge models [--json]` | You need the exact facts for every registered model seat (accepted efforts, image format, pinned model ID) |
46
- | `aibridge quota` | Two-second check of every backend's remaining quota before you pipeline work |
47
- | `aibridge runs` | Inspect or watch past delegation runs (`~/.aibridge/runs`) |
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
- The three verbs compose into an orchestrator-driven loop your agent stays in charge of:
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
- Plan files not their contents — travel between stages, so the loop is nearly free on your agent's context.
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 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.
63
- - **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.
64
- - **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.)
65
- - **Models are canonical slugs**: `<vendor>-<cli>/<model>[-<effort>]` e.g. `xai-grok/grok-4.6`, `google-antigravity/gemini-3.7-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.
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 doesn't decide when to delegate your agent does, and left alone it will
70
- mostly keep the work for itself. Put the routing rule in whatever instructions file
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
- Tune the seats and thresholds to your own quotas. The value is that the decision is
95
- explicit and made *before* the work starts, rather than rationalised afterwards.
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
- - The backing CLIs you want to use, on `PATH` and authed: [`grok`](https://github.com/superagent-ai/grok-cli), `agy` (Antigravity), [`codex`](https://github.com/openai/codex), [`claude`](https://claude.com/claude-code) — any subset works; commands fail fast with install hints for missing ones.
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
- 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.
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
- 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.
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as runCli, t as buildContext } from "./context-DJtEcg6f.mjs";
2
+ import { r as runCli, t as buildContext } from "./context-DeJmEXLe.mjs";
3
3
  //#region src/cli.ts
4
4
  await runCli(buildContext(process), process.argv.slice(2));
5
5
  //#endregion
@@ -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 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"`;
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 diff or plan contract",
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: version },
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, 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 };
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 evaluateCodexPreflight, d as renderPreflightRefusal, f as delegate, g as MODELS, h as positiveIntSeconds, i as listRuns, l as preflightCodex, m as nonEmptyPrompt, n as app, o as startRun, p as getDriver, r as runCli, s as evaluateAgyPreflight, t as buildContext, u as preflightModel, v as formatImageGenModelError, x as resolveModel, y as formatUnknownModelError } from "./context-DJtEcg6f.mjs";
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.