@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 +40 -9
- package/dist/cli.mjs +1 -1
- package/dist/{context-B6I4QI9z.mjs → context-F8WLXzPv.mjs} +216 -22
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +1 -1
- package/package.json +6 -6
- package/src/app.ts +3 -1
- package/src/commands/models/command.ts +18 -0
- package/src/commands/models/impl.test.ts +89 -0
- package/src/commands/models/impl.ts +71 -0
- package/src/commands/quota/command.ts +3 -1
- package/src/commands/quota/impl.ts +24 -2
- package/src/commands/subagent/command.ts +2 -2
- package/src/delegate.test.ts +1 -1
- package/src/driver.ts +6 -1
- package/src/drivers.ts +1 -0
- package/src/flagMapping.test.ts +8 -8
- package/src/models.test.ts +20 -20
- package/src/models.ts +68 -15
- package/src/quotaPreflight.test.ts +74 -1
- package/src/quotaPreflight.ts +59 -13
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.
|
|
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.
|
|
41
|
-
| `aibridge implement --model google-antigravity/gemini-3.
|
|
42
|
-
| `aibridge review --model xai-grok/grok-4.
|
|
43
|
-
| `aibridge subagent --model xai-grok/grok-4.
|
|
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.
|
|
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.
|
|
54
|
-
aibridge review --model xai-grok/grok-4.
|
|
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.
|
|
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
|
@@ -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 {
|
|
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 —
|
|
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 —
|
|
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
|
|
103
|
+
brief: "OpenAI gpt-5.6-sol via codex CLI — frontier agentic coding; own ChatGPT login"
|
|
72
104
|
},
|
|
73
|
-
"
|
|
74
|
-
slug: "
|
|
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-
|
|
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
|
-
|
|
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
|
|
155
|
+
brief: "Claude Opus 5, 1M context via claude CLI — everyday complex work; bills the claude CLI subscription"
|
|
99
156
|
},
|
|
100
|
-
"anthropic-claude/
|
|
101
|
-
slug: "anthropic-claude/
|
|
157
|
+
"anthropic-claude/sonnet-5": {
|
|
158
|
+
slug: "anthropic-claude/sonnet-5",
|
|
102
159
|
backend: "claude",
|
|
103
|
-
backendModel: "claude-
|
|
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
|
-
|
|
112
|
-
|
|
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.
|
|
1394
|
-
"
|
|
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
|
|
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-
|
|
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-
|
|
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
|
+
"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.
|
|
39
|
-
"@aibridge/driver-
|
|
40
|
-
"@aibridge/driver-
|
|
41
|
-
"@aibridge/driver-
|
|
42
|
-
"@aibridge/driver-
|
|
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
|
|
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
|
+
});
|