@aibridge/cli 0.3.0 → 0.5.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
@@ -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 --model xai-grok/grok-4.5 "summarize the architecture of this repo"
31
+ npx -y @aibridge/cli subagent --model xai-grok/grok-4.6 "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,22 @@ npx -y @aibridge/cli subagent --model xai-grok/grok-4.5 "summarize the architect
37
37
 
38
38
  | Command | Use when |
39
39
  |---|---|
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 |
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
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) |
45
46
  | `aibridge quota` | Two-second check of every backend's remaining quota before you pipeline work |
46
47
  | `aibridge runs` | Inspect or watch past delegation runs (`~/.aibridge/runs`) |
47
48
 
48
49
  The three verbs compose into an orchestrator-driven loop your agent stays in charge of:
49
50
 
50
51
  ```
51
- aibridge plan --model xai-grok/grok-4.5 --out plan.md "add rate limiting to the API" # delegate writes plan.md
52
+ aibridge plan --model xai-grok/grok-4.6 --out plan.md "add rate limiting to the API" # delegate writes plan.md
52
53
  # → your agent reads, edits, approves the plan
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
54
+ aibridge implement --model google-antigravity/gemini-3.7-flash plan.md # another model executes it, runs your gates
55
+ aibridge review --model xai-grok/grok-4.6 --out review.md --plan plan.md # a third seat cross-checks the diff
55
56
  ```
56
57
 
57
58
  Plan files — not their contents — travel between stages, so the loop is nearly free on your agent's context.
@@ -61,7 +62,37 @@ Plan files — not their contents — travel between stages, so the loop is near
61
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.
62
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.
63
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.)
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
+ - **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.
66
+
67
+ ## Tell your agent when to reach for it
68
+
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:
73
+
74
+ ```markdown
75
+ ## Delegation gate — decide before you implement
76
+
77
+ The moment a task becomes implementation you could fully specify, say the call out
78
+ loud — **solo** or **aibridge** — plus one line of why. Delegating is the default.
79
+ Stay solo only when the edit is smaller than the spec would be, or the work needs
80
+ live judgment, your own session's tools, or tight back-and-forth. Never default to
81
+ solo silently.
82
+
83
+ Route by size and risk:
84
+
85
+ - tiny → solo
86
+ - clearly specified and self-contained → `aibridge subagent`
87
+ - large or risky → `aibridge plan` → read and approve the plan file →
88
+ `aibridge implement` → `aibridge review`
89
+
90
+ Delegated work is yours to verify: re-run the real gates before trusting a diff.
91
+ Prefer a reviewer from a different model family than whoever implemented.
92
+ ```
93
+
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.
65
96
 
66
97
  ## Requirements
67
98
 
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-B6I4QI9z.mjs";
2
+ import { r as runCli, t as buildContext } from "./context-F8WLXzPv.mjs";
3
3
  //#region src/cli.ts
4
4
  await runCli(buildContext(process), process.argv.slice(2));
5
5
  //#endregion
@@ -10,10 +10,22 @@ import { fetchClaudeQuota } from "@aibridge/driver-claude";
10
10
  import * as codex from "@aibridge/driver-codex";
11
11
  import { fetchCodexQuota } from "@aibridge/driver-codex";
12
12
  import * as grok from "@aibridge/driver-grok";
13
- import { runCaptured } from "@aibridge/proc";
13
+ import { fetchGrokQuota } from "@aibridge/driver-grok";
14
+ import { isAuthExpired, runCaptured } from "@aibridge/proc";
14
15
  import { randomBytes } from "node:crypto";
15
16
  //#region src/models.ts
16
17
  const MODELS = {
18
+ "xai-grok/grok-4.6": {
19
+ slug: "xai-grok/grok-4.6",
20
+ backend: "grok",
21
+ backendModel: "grok-4.6",
22
+ efforts: [
23
+ "low",
24
+ "medium",
25
+ "high"
26
+ ],
27
+ brief: "xAI Grok 4.6 via grok CLI — own xAI login; ~30 req/min, ~1k msgs/day, single-flight"
28
+ },
17
29
  "xai-grok/grok-4.5": {
18
30
  slug: "xai-grok/grok-4.5",
19
31
  backend: "grok",
@@ -23,7 +35,19 @@ const MODELS = {
23
35
  "medium",
24
36
  "high"
25
37
  ],
26
- brief: "xAI Grok 4.5 via grok CLI — default for plan & review; own xAI login"
38
+ brief: "xAI Grok 4.5 via grok CLI — own xAI login; ~30 req/min, ~1k msgs/day, single-flight"
39
+ },
40
+ "google-antigravity/gemini-3.7-flash": {
41
+ slug: "google-antigravity/gemini-3.7-flash",
42
+ backend: "agy",
43
+ backendModel: "gemini-3.7-flash",
44
+ efforts: [
45
+ "low",
46
+ "medium",
47
+ "high"
48
+ ],
49
+ defaultEffort: "high",
50
+ brief: "Google Gemini 3.7 Flash via agy — own Antigravity login; quota shared across all Gemini tiers (not yet itemised per-model, so no exhaustion preflight)"
27
51
  },
28
52
  "google-antigravity/gemini-3.6-flash": {
29
53
  slug: "google-antigravity/gemini-3.6-flash",
@@ -35,7 +59,15 @@ const MODELS = {
35
59
  "high"
36
60
  ],
37
61
  defaultEffort: "high",
38
- brief: "Google Gemini 3.6 Flash via agy — default for implement, also image-gen; own Antigravity login"
62
+ brief: "Google Gemini 3.6 Flash via agy — own Antigravity login; quota shared across all Gemini tiers"
63
+ },
64
+ "google-antigravity/gemini-3.1-pro": {
65
+ slug: "google-antigravity/gemini-3.1-pro",
66
+ backend: "agy",
67
+ efforts: ["low", "high"],
68
+ backendModel: "gemini-3.1-pro",
69
+ defaultEffort: "high",
70
+ brief: "Google Gemini 3.1 Pro via agy — own Antigravity login; quota shared across all Gemini tiers"
39
71
  },
40
72
  "google-antigravity/claude-sonnet-4-6": {
41
73
  slug: "google-antigravity/claude-sonnet-4-6",
@@ -68,12 +100,36 @@ const MODELS = {
68
100
  "high",
69
101
  "xhigh"
70
102
  ],
71
- brief: "OpenAI Codex gpt-5.6-sol via codex CLI"
103
+ brief: "OpenAI gpt-5.6-sol via codex CLI — frontier agentic coding; own ChatGPT login"
72
104
  },
73
- "anthropic-claude/sonnet-5": {
74
- slug: "anthropic-claude/sonnet-5",
105
+ "openai-codex/gpt-5.6-terra": {
106
+ slug: "openai-codex/gpt-5.6-terra",
107
+ backend: "codex",
108
+ backendModel: "gpt-5.6-terra",
109
+ efforts: [
110
+ "low",
111
+ "medium",
112
+ "high",
113
+ "xhigh"
114
+ ],
115
+ brief: "OpenAI gpt-5.6-terra via codex CLI — balanced, everyday coding; own ChatGPT login"
116
+ },
117
+ "openai-codex/gpt-5.6-luna": {
118
+ slug: "openai-codex/gpt-5.6-luna",
119
+ backend: "codex",
120
+ backendModel: "gpt-5.6-luna",
121
+ efforts: [
122
+ "low",
123
+ "medium",
124
+ "high",
125
+ "xhigh"
126
+ ],
127
+ brief: "OpenAI gpt-5.6-luna via codex CLI — fast and affordable coding; own ChatGPT login"
128
+ },
129
+ "anthropic-claude/fable-5": {
130
+ slug: "anthropic-claude/fable-5",
75
131
  backend: "claude",
76
- backendModel: "claude-sonnet-5",
132
+ backendModel: "claude-fable-5",
77
133
  efforts: [
78
134
  "low",
79
135
  "medium",
@@ -81,12 +137,13 @@ const MODELS = {
81
137
  "xhigh",
82
138
  "max"
83
139
  ],
84
- brief: "Claude Sonnet 5 via claude CLI — bills your Claude subscription"
140
+ defaultEffort: "high",
141
+ brief: "Claude Fable 5 via claude CLI — hardest, longest-running work; bills the claude CLI subscription"
85
142
  },
86
143
  "anthropic-claude/opus-5": {
87
144
  slug: "anthropic-claude/opus-5",
88
145
  backend: "claude",
89
- backendModel: "claude-opus-5",
146
+ backendModel: "claude-opus-5[1m]",
90
147
  efforts: [
91
148
  "low",
92
149
  "medium",
@@ -95,12 +152,12 @@ const MODELS = {
95
152
  "max"
96
153
  ],
97
154
  defaultEffort: "high",
98
- brief: "Claude Opus 5 via claude CLI (default effort: high) bills subscription"
155
+ brief: "Claude Opus 5, 1M context via claude CLI everyday complex work; bills the claude CLI subscription"
99
156
  },
100
- "anthropic-claude/opus-5-1m": {
101
- slug: "anthropic-claude/opus-5-1m",
157
+ "anthropic-claude/sonnet-5": {
158
+ slug: "anthropic-claude/sonnet-5",
102
159
  backend: "claude",
103
- backendModel: "claude-opus-5[1m]",
160
+ backendModel: "claude-sonnet-5",
104
161
  efforts: [
105
162
  "low",
106
163
  "medium",
@@ -108,8 +165,20 @@ const MODELS = {
108
165
  "xhigh",
109
166
  "max"
110
167
  ],
111
- defaultEffort: "high",
112
- brief: "Claude Opus 5, 1M context via claude CLI — long-context work; bills subscription"
168
+ brief: "Claude Sonnet 5 via claude CLI — routine work; bills the claude CLI subscription"
169
+ },
170
+ "anthropic-claude/haiku-4-5": {
171
+ slug: "anthropic-claude/haiku-4-5",
172
+ backend: "claude",
173
+ backendModel: "claude-haiku-4-5-20251001",
174
+ efforts: [
175
+ "low",
176
+ "medium",
177
+ "high",
178
+ "xhigh",
179
+ "max"
180
+ ],
181
+ brief: "Claude Haiku 4.5 via claude CLI — quick answers; bills the claude CLI subscription"
113
182
  }
114
183
  };
115
184
  const IMAGE_GEN_FORMATS = /* @__PURE__ */ new Map([
@@ -209,6 +278,7 @@ const DRIVERS = {
209
278
  grok: {
210
279
  probe: () => grok.probe(),
211
280
  run: (task) => grok.run(task),
281
+ quota: () => grok.fetchGrokQuota(),
212
282
  generateImage: (req) => grok.generateImage(req)
213
283
  },
214
284
  codex: {
@@ -468,6 +538,7 @@ function evaluateAgyPreflight(snapshot, backendModel) {
468
538
  }
469
539
  return {
470
540
  ok: false,
541
+ kind: "quota",
471
542
  message: `agy model "${backendModel}" is quota-exhausted`,
472
543
  resetAt
473
544
  };
@@ -477,23 +548,41 @@ function evaluateAgyPreflight(snapshot, backendModel) {
477
548
  function evaluateCodexPreflight(snapshot) {
478
549
  if (snapshot.limitReached) return {
479
550
  ok: false,
551
+ kind: "quota",
480
552
  message: "codex quota limit reached",
481
553
  resetAt: snapshot.windows.find((w) => w.resetAt)?.resetAt
482
554
  };
483
555
  const exhaustedWindow = snapshot.windows.find((w) => w.usedPercent >= 100);
484
556
  if (exhaustedWindow) return {
485
557
  ok: false,
558
+ kind: "quota",
486
559
  message: "codex quota limit reached",
487
560
  resetAt: exhaustedWindow.resetAt
488
561
  };
489
562
  return { ok: true };
490
563
  }
564
+ function evaluateGrokPreflight(snapshot) {
565
+ if (snapshot.usedPercent !== void 0 && snapshot.usedPercent >= 100) return {
566
+ ok: false,
567
+ kind: "quota",
568
+ message: "grok credit quota exhausted",
569
+ resetAt: snapshot.periodEnd
570
+ };
571
+ return { ok: true };
572
+ }
491
573
  async function preflightModel(resolved) {
492
574
  if (resolved.spec.backend === "codex") return preflightCodex();
575
+ if (resolved.spec.backend === "grok") return preflightGrok();
493
576
  if (resolved.spec.backend !== "agy") return { ok: true };
494
577
  try {
495
578
  return evaluateAgyPreflight(await fetchAgyQuota(), backendModelId(resolved));
496
579
  } catch (err) {
580
+ if (isAuthExpired(err)) return {
581
+ ok: false,
582
+ kind: "auth",
583
+ message: err.message,
584
+ resetAt: void 0
585
+ };
497
586
  return {
498
587
  ok: true,
499
588
  warning: `quota preflight failed (${err.message}); proceeding`
@@ -504,6 +593,28 @@ async function preflightCodex() {
504
593
  try {
505
594
  return evaluateCodexPreflight(await fetchCodexQuota());
506
595
  } catch (err) {
596
+ if (isAuthExpired(err)) return {
597
+ ok: false,
598
+ kind: "auth",
599
+ message: err.message,
600
+ resetAt: void 0
601
+ };
602
+ return {
603
+ ok: true,
604
+ warning: `quota preflight failed (${err.message}); proceeding`
605
+ };
606
+ }
607
+ }
608
+ async function preflightGrok() {
609
+ try {
610
+ return evaluateGrokPreflight(await fetchGrokQuota());
611
+ } catch (err) {
612
+ if (isAuthExpired(err)) return {
613
+ ok: false,
614
+ kind: "auth",
615
+ message: err.message,
616
+ resetAt: void 0
617
+ };
507
618
  return {
508
619
  ok: true,
509
620
  warning: `quota preflight failed (${err.message}); proceeding`
@@ -520,6 +631,7 @@ function formatReset$1(resetTime) {
520
631
  return `${new Date(resetTime).toLocaleTimeString()} (in ${rel})`;
521
632
  }
522
633
  function renderPreflightRefusal(cmd, verdict) {
634
+ 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.`;
523
635
  const resetClause = verdict.resetAt ? ` Resets ${formatReset$1(verdict.resetAt)}.` : "";
524
636
  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).`;
525
637
  }
@@ -744,6 +856,70 @@ const implement = buildCommand({
744
856
  }
745
857
  });
746
858
  //#endregion
859
+ //#region src/commands/models/impl.ts
860
+ const BACKEND_DISPLAY_NAMES = {
861
+ grok: "grok (Grok CLI)",
862
+ agy: "agy (Antigravity)",
863
+ codex: "codex (Codex CLI)",
864
+ claude: "claude (Claude Code CLI)"
865
+ };
866
+ function modelsImpl(flags) {
867
+ const specs = Object.values(MODELS);
868
+ if (flags.json) {
869
+ const jsonOutput = specs.map((spec) => ({
870
+ slug: spec.slug,
871
+ backend: spec.backend,
872
+ backendModel: spec.backendModel,
873
+ efforts: spec.efforts ? [...spec.efforts] : [],
874
+ defaultEffort: spec.defaultEffort ?? null,
875
+ image: imageFormatFor({
876
+ spec,
877
+ effort: void 0
878
+ }) ?? null,
879
+ brief: spec.brief
880
+ }));
881
+ this.process.stdout.write(`${JSON.stringify(jsonOutput)}\n`);
882
+ return;
883
+ }
884
+ const backends = [];
885
+ for (const spec of specs) if (!backends.includes(spec.backend)) backends.push(spec.backend);
886
+ let firstBackend = true;
887
+ for (const backend of backends) {
888
+ if (!firstBackend) this.process.stdout.write("\n");
889
+ firstBackend = false;
890
+ this.process.stdout.write(`=== ${BACKEND_DISPLAY_NAMES[backend]} ===\n`);
891
+ const backendSpecs = specs.filter((spec) => spec.backend === backend);
892
+ for (const spec of backendSpecs) {
893
+ this.process.stdout.write(` ${spec.slug}\n`);
894
+ const segments = [];
895
+ if (spec.efforts) {
896
+ const formattedEfforts = spec.efforts.map((e) => e === spec.defaultEffort ? `${e}*` : e).join(" | ");
897
+ segments.push(`efforts: ${formattedEfforts}`);
898
+ }
899
+ const img = imageFormatFor({
900
+ spec,
901
+ effort: void 0
902
+ });
903
+ segments.push(`image: ${img ?? "—"}`);
904
+ segments.push(`id: ${spec.backendModel}`);
905
+ this.process.stdout.write(` ${segments.join(" · ")}\n`);
906
+ this.process.stdout.write(` ${spec.brief}\n`);
907
+ }
908
+ }
909
+ if (specs.some((spec) => spec.defaultEffort !== void 0)) this.process.stdout.write("\n* = effort used when the slug has no -<effort> suffix\n");
910
+ }
911
+ //#endregion
912
+ //#region src/commands/models/command.ts
913
+ const models = buildCommand({
914
+ func: modelsImpl,
915
+ parameters: { flags: { json: {
916
+ kind: "boolean",
917
+ withNegated: false,
918
+ brief: "Emit the registry as JSON"
919
+ } } },
920
+ docs: { brief: "List every model seat in the registry (slug, efforts, image format)" }
921
+ });
922
+ //#endregion
747
923
  //#region src/commands/plan/impl.ts
748
924
  function countOpenQuestions(markdown) {
749
925
  const headingIdx = markdown.search(/^## Open questions[ \t]*$/m);
@@ -905,6 +1081,17 @@ function formatReset(resetTime) {
905
1081
  const rel = mins < 60 ? `${mins}m` : `${Math.floor(mins / 60)}h${mins % 60}m`;
906
1082
  return `${new Date(resetTime).toLocaleTimeString()} (in ${rel})`;
907
1083
  }
1084
+ function renderGrok(ctx, snapshot) {
1085
+ ctx.process.stdout.write("=== grok (xAI) — used this period ===\n");
1086
+ ctx.process.stdout.write(`${"PERIOD".padEnd(10)} ${"USED".padEnd(10)} RESET\n`);
1087
+ const usedPctStr = snapshot.usedPercent !== void 0 ? `${snapshot.usedPercent}%` : "?";
1088
+ const periodStr = snapshot.periodType ?? "-";
1089
+ ctx.process.stdout.write(`${periodStr.padEnd(10)} ${usedPctStr.padEnd(10)} ${formatReset(snapshot.periodEnd)}\n`);
1090
+ if (snapshot.products.length > 0) {
1091
+ const prods = snapshot.products.map((p) => `${p.product} ${p.usedPercent}%`).join(" · ");
1092
+ ctx.process.stdout.write(` ${prods}\n`);
1093
+ }
1094
+ }
908
1095
  function renderAgy(ctx, snapshot) {
909
1096
  ctx.process.stdout.write("=== agy (Antigravity) — remaining per model group ===\n");
910
1097
  for (const group of snapshot.groups) {
@@ -934,14 +1121,16 @@ function renderSection(ctx, result, title, render) {
934
1121
  else ctx.process.stdout.write(`=== ${title} ===\nunavailable: ${result.reason.message}\n`);
935
1122
  }
936
1123
  async function quotaImpl(flags) {
937
- const [agy, codex, claude] = await Promise.allSettled([
1124
+ const [grok, agy, codex, claude] = await Promise.allSettled([
1125
+ fetchGrokQuota(),
938
1126
  fetchAgyQuota(),
939
1127
  fetchCodexQuota(),
940
1128
  fetchClaudeQuota()
941
1129
  ]);
942
- const allFailed = agy.status === "rejected" && codex.status === "rejected" && claude.status === "rejected";
1130
+ const allFailed = grok.status === "rejected" && agy.status === "rejected" && codex.status === "rejected" && claude.status === "rejected";
943
1131
  if (flags.json) {
944
1132
  this.process.stdout.write(`${JSON.stringify({
1133
+ grok: grok.status === "fulfilled" ? grok.value : { error: String(grok.reason) },
945
1134
  agy: agy.status === "fulfilled" ? agy.value : { error: String(agy.reason) },
946
1135
  codex: codex.status === "fulfilled" ? codex.value : { error: String(codex.reason) },
947
1136
  claude: claude.status === "fulfilled" ? claude.value : { error: String(claude.reason) }
@@ -949,6 +1138,8 @@ async function quotaImpl(flags) {
949
1138
  if (allFailed) this.process.exitCode = 1;
950
1139
  return;
951
1140
  }
1141
+ renderSection(this, grok, "grok (xAI)", renderGrok);
1142
+ this.process.stdout.write("\n");
952
1143
  renderSection(this, agy, "agy (Antigravity)", renderAgy);
953
1144
  this.process.stdout.write("\n");
954
1145
  renderSection(this, codex, "codex (ChatGPT)", renderCodex);
@@ -964,8 +1155,10 @@ const quota = buildCommand({
964
1155
  brief: "Emit the raw snapshot as JSON"
965
1156
  } } },
966
1157
  docs: {
967
- brief: "Show agy / codex / claude quota with reset times",
1158
+ brief: "Show grok / agy / codex / claude quota with reset times",
968
1159
  fullDescription: [
1160
+ "grok: reads ~/.grok/auth.json and asks the xAI billing endpoint for the",
1161
+ "weekly credit usage percentage and per-product split.",
969
1162
  "agy: reads its cached OAuth token (~/.gemini/antigravity-cli/) and asks the",
970
1163
  "Cloud Code API for per-model remaining quota. EXHAUSTED means agy turns on",
971
1164
  "that model fail with an empty answer until the reset time.",
@@ -1390,8 +1583,8 @@ const fullDescription = [
1390
1583
  "",
1391
1584
  "Available models (canonical slug):",
1392
1585
  ...listModelHelpLines(),
1393
- "Recommended first choice: xai-grok/grok-4.5 (off-budget). The claude-backend slugs are FALLBACKS for",
1394
- "when the off-budget CLIs are quota-exhaustedthey bill your Claude subscription."
1586
+ "Recommended first choice: xai-grok/grok-4.6. Whichever seat runs on the same provider as the",
1587
+ "agent you orchestrate from is your last resort it spends the pool you are already burning."
1395
1588
  ].join("\n");
1396
1589
  const subagent = buildCommand({
1397
1590
  func: subagent$1,
@@ -1465,9 +1658,10 @@ const app = buildApplication(buildRouteMap({
1465
1658
  subagent,
1466
1659
  "image-gen": imageGen,
1467
1660
  runs,
1468
- quota
1661
+ quota,
1662
+ models
1469
1663
  },
1470
- docs: { brief: "Bridge tasks to non-Claude AI CLIs — a plan → implement → review workflow, task delegation, and image generation (codex / agy / grok seats)." }
1664
+ 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)." }
1471
1665
  }), {
1472
1666
  name: "aibridge",
1473
1667
  versionInfo: { currentVersion: version },
package/dist/index.d.mts CHANGED
@@ -2,6 +2,7 @@ import { CommandContext } from "@stricli/core";
2
2
  import { AgyQuotaSnapshot } from "@aibridge/driver-agy";
3
3
  import { ClaudeQuotaSnapshot } from "@aibridge/driver-claude";
4
4
  import { CodexQuotaSnapshot } from "@aibridge/driver-codex";
5
+ import { GrokQuotaSnapshot } from "@aibridge/driver-grok";
5
6
  //#region src/context.d.ts
6
7
  interface LocalContext extends CommandContext {
7
8
  /** Full Node process — satisfies stricli WritableStreams + exitCode/env/cwd used by impls. */
@@ -21,7 +22,7 @@ declare function runCli(ctx: LocalContext, argv: readonly string[]): Promise<voi
21
22
  * Models are registered by canonical, provider-qualified slug —
22
23
  * `<vendor>-<cli>/<model>[-<effort>]`, e.g. `openai-codex/gpt-5.6-sol-high`.
23
24
  * 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
+ * `backendModel` is a pinned model id (`claude-sonnet-5`), never a moving vendor
25
26
  * alias (`opus`), so a seat never silently changes model under you.
26
27
  */
27
28
  type Backend = 'agy' | 'claude' | 'codex' | 'grok';
@@ -77,7 +78,7 @@ type DelegationResult = {
77
78
  readonly message: string;
78
79
  readonly exitCode: number | null;
79
80
  };
80
- type QuotaSnapshot = AgyQuotaSnapshot | CodexQuotaSnapshot | ClaudeQuotaSnapshot;
81
+ type QuotaSnapshot = AgyQuotaSnapshot | CodexQuotaSnapshot | ClaudeQuotaSnapshot | GrokQuotaSnapshot;
81
82
  interface ImageGenRequest {
82
83
  readonly prompt: string;
83
84
  readonly workDir: string;
@@ -164,6 +165,7 @@ type PreflightVerdict = {
164
165
  readonly warning?: string;
165
166
  } | {
166
167
  readonly ok: false;
168
+ readonly kind: 'auth' | 'quota';
167
169
  readonly message: string;
168
170
  readonly resetAt: string | undefined;
169
171
  };
@@ -172,6 +174,7 @@ declare function evaluateCodexPreflight(snapshot: CodexQuotaSnapshot): Preflight
172
174
  declare function preflightModel(resolved: ResolvedModel): Promise<PreflightVerdict>;
173
175
  declare function preflightCodex(): Promise<PreflightVerdict>;
174
176
  declare function renderPreflightRefusal(cmd: string, verdict: {
177
+ kind: 'auth' | 'quota';
175
178
  message: string;
176
179
  resetAt: string | undefined;
177
180
  }): string;
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-B6I4QI9z.mjs";
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-F8WLXzPv.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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aibridge/cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI that bridges tasks to AI CLIs on your machine (plan / implement / review / subagent / image-gen)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,11 +35,11 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@stricli/core": "1.3.0",
38
- "@aibridge/proc": "0.3.0",
39
- "@aibridge/driver-agy": "0.3.0",
40
- "@aibridge/driver-grok": "0.3.0",
41
- "@aibridge/driver-codex": "0.3.0",
42
- "@aibridge/driver-claude": "0.3.0"
38
+ "@aibridge/proc": "0.5.0",
39
+ "@aibridge/driver-codex": "0.5.0",
40
+ "@aibridge/driver-claude": "0.5.0",
41
+ "@aibridge/driver-agy": "0.5.0",
42
+ "@aibridge/driver-grok": "0.5.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "tsdown": "0.22.14"
package/src/app.ts CHANGED
@@ -2,6 +2,7 @@ import { createRequire } from 'node:module';
2
2
  import { buildApplication, buildRouteMap, run } from '@stricli/core';
3
3
  import { imageGen } from './commands/image-gen/command.ts';
4
4
  import { implement } from './commands/implement/command.ts';
5
+ import { models } from './commands/models/command.ts';
5
6
  import { plan } from './commands/plan/command.ts';
6
7
  import { quota } from './commands/quota/command.ts';
7
8
  import { review } from './commands/review/command.ts';
@@ -14,7 +15,7 @@ const require = createRequire(import.meta.url);
14
15
  const { version } = require('../package.json') as { version: string };
15
16
 
16
17
  const BRIEF =
17
- 'Bridge tasks to non-Claude AI CLIs — a plan → implement → review workflow, task delegation, and image generation (codex / agy / grok seats).';
18
+ 'Bridge tasks to the other AI CLIs on this machine — a plan → implement → review workflow, task delegation, and image generation (codex / agy / grok seats).';
18
19
 
19
20
  const routes = buildRouteMap({
20
21
  routes: {
@@ -25,6 +26,7 @@ const routes = buildRouteMap({
25
26
  'image-gen': imageGen,
26
27
  runs,
27
28
  quota,
29
+ models,
28
30
  },
29
31
  docs: {
30
32
  brief: BRIEF,
@@ -0,0 +1,18 @@
1
+ import { buildCommand } from '@stricli/core';
2
+ import modelsImpl from './impl.ts';
3
+
4
+ export const models = buildCommand({
5
+ func: modelsImpl,
6
+ parameters: {
7
+ flags: {
8
+ json: {
9
+ kind: 'boolean',
10
+ withNegated: false,
11
+ brief: 'Emit the registry as JSON',
12
+ },
13
+ },
14
+ },
15
+ docs: {
16
+ brief: 'List every model seat in the registry (slug, efforts, image format)',
17
+ },
18
+ });