@aibridge/cli 0.2.0 → 0.4.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-DQxvMUd2.mjs → context-v_ns4n_W.mjs} +186 -48
- package/dist/index.d.mts +4 -6
- package/dist/index.mjs +2 -2
- package/package.json +6 -6
- package/src/app.ts +3 -1
- package/src/commands/image-gen/command.ts +3 -4
- package/src/commands/image-gen/impl.ts +3 -4
- package/src/commands/implement/command.ts +2 -3
- package/src/commands/implement/impl.ts +3 -3
- 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/plan/command.ts +3 -5
- package/src/commands/plan/impl.ts +5 -9
- package/src/commands/quota/command.ts +3 -1
- package/src/commands/quota/impl.ts +24 -2
- package/src/commands/review/command.ts +3 -5
- package/src/commands/review/impl.ts +5 -9
- package/src/commands/subagent/command.ts +4 -5
- package/src/commands/subagent/impl.ts +3 -8
- package/src/driver.ts +6 -1
- package/src/drivers.ts +1 -0
- package/src/flagMapping.test.ts +27 -4
- package/src/index.ts +0 -3
- package/src/models.ts +48 -19
- package/src/quotaPreflight.test.ts +46 -1
- package/src/quotaPreflight.ts +29 -0
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 "summarize the architecture of this repo"
|
|
31
|
+
npx -y @aibridge/cli subagent --model xai-grok/grok-4.5 "summarize the architecture of this repo"
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
<sup>Want the `aibridge` command on your PATH for manual use? `npm i -g @aibridge/cli` (optional).</sup>
|
|
@@ -37,21 +37,22 @@ npx -y @aibridge/cli subagent "summarize the architecture of this repo"
|
|
|
37
37
|
|
|
38
38
|
| Command | Use when |
|
|
39
39
|
|---|---|
|
|
40
|
-
| `aibridge plan "<task>"` | You want a delegate model to study the repo and expand a task into a detailed, reviewable **plan file** before any code is written |
|
|
41
|
-
| `aibridge implement <plan.md>` | You have an approved plan file and want it executed in place — with your project's **real typecheck and tests** run until green |
|
|
42
|
-
| `aibridge review [--plan <plan.md>]` | You want a **different model** to pressure-test the working-tree diff against the plan contract (over-reach is a finding) — or to review the plan itself before implementing |
|
|
43
|
-
| `aibridge subagent "<task>"` | A self-contained task deserves a concurrent delegate, a cross-model second opinion, or a red-team pass |
|
|
44
|
-
| `aibridge image-gen
|
|
40
|
+
| `aibridge plan --model xai-grok/grok-4.5 --out plan.md "<task>"` | You want a delegate model to study the repo and expand a task into a detailed, reviewable **plan file** before any code is written |
|
|
41
|
+
| `aibridge implement --model google-antigravity/gemini-3.6-flash <plan.md>` | You have an approved plan file and want it executed in place — with your project's **real typecheck and tests** run until green |
|
|
42
|
+
| `aibridge review --model xai-grok/grok-4.5 --out review.md [--plan <plan.md>]` | You want a **different model** to pressure-test the working-tree diff against the plan contract (over-reach is a finding) — or to review the plan itself before implementing |
|
|
43
|
+
| `aibridge subagent --model xai-grok/grok-4.5 "<task>"` | A self-contained task deserves a concurrent delegate, a cross-model second opinion, or a red-team pass |
|
|
44
|
+
| `aibridge image-gen --model openai-codex/gpt-5.6-sol --out out.png "<prompt>"` | You need a real raster image — on a Codex, Antigravity, or Grok seat, with render verification |
|
|
45
|
+
| `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 "add rate limiting to the API" # delegate writes plan.md
|
|
52
|
+
aibridge plan --model xai-grok/grok-4.5 --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 plan.md
|
|
54
|
-
aibridge review --plan plan.md
|
|
54
|
+
aibridge implement --model google-antigravity/gemini-3.6-flash plan.md # another model executes it, runs your gates
|
|
55
|
+
aibridge review --model xai-grok/grok-4.5 --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.
|
|
@@ -63,6 +64,36 @@ Plan files — not their contents — travel between stages, so the loop is near
|
|
|
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
65
|
- **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
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.
|
|
96
|
+
|
|
66
97
|
## Requirements
|
|
67
98
|
|
|
68
99
|
- **Node ≥ 24.11**
|
package/dist/cli.mjs
CHANGED
|
@@ -10,6 +10,7 @@ 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 { fetchGrokQuota } from "@aibridge/driver-grok";
|
|
13
14
|
import { runCaptured } from "@aibridge/proc";
|
|
14
15
|
import { randomBytes } from "node:crypto";
|
|
15
16
|
//#region src/models.ts
|
|
@@ -23,7 +24,7 @@ const MODELS = {
|
|
|
23
24
|
"medium",
|
|
24
25
|
"high"
|
|
25
26
|
],
|
|
26
|
-
brief: "xAI Grok 4.5 via grok CLI —
|
|
27
|
+
brief: "xAI Grok 4.5 via grok CLI — own xAI login; ~30 req/min, ~1k msgs/day, single-flight"
|
|
27
28
|
},
|
|
28
29
|
"google-antigravity/gemini-3.6-flash": {
|
|
29
30
|
slug: "google-antigravity/gemini-3.6-flash",
|
|
@@ -35,7 +36,15 @@ const MODELS = {
|
|
|
35
36
|
"high"
|
|
36
37
|
],
|
|
37
38
|
defaultEffort: "high",
|
|
38
|
-
brief: "Google Gemini 3.6 Flash via agy —
|
|
39
|
+
brief: "Google Gemini 3.6 Flash via agy — own Antigravity login; quota shared across all Gemini tiers"
|
|
40
|
+
},
|
|
41
|
+
"google-antigravity/gemini-3.1-pro": {
|
|
42
|
+
slug: "google-antigravity/gemini-3.1-pro",
|
|
43
|
+
backend: "agy",
|
|
44
|
+
efforts: ["low", "high"],
|
|
45
|
+
backendModel: "gemini-3.1-pro",
|
|
46
|
+
defaultEffort: "high",
|
|
47
|
+
brief: "Google Gemini 3.1 Pro via agy — own Antigravity login; quota shared across all Gemini tiers"
|
|
39
48
|
},
|
|
40
49
|
"google-antigravity/claude-sonnet-4-6": {
|
|
41
50
|
slug: "google-antigravity/claude-sonnet-4-6",
|
|
@@ -68,12 +77,36 @@ const MODELS = {
|
|
|
68
77
|
"high",
|
|
69
78
|
"xhigh"
|
|
70
79
|
],
|
|
71
|
-
brief: "OpenAI
|
|
80
|
+
brief: "OpenAI gpt-5.6-sol via codex CLI — frontier agentic coding; own ChatGPT login"
|
|
72
81
|
},
|
|
73
|
-
"
|
|
74
|
-
slug: "
|
|
82
|
+
"openai-codex/gpt-5.6-terra": {
|
|
83
|
+
slug: "openai-codex/gpt-5.6-terra",
|
|
84
|
+
backend: "codex",
|
|
85
|
+
backendModel: "gpt-5.6-terra",
|
|
86
|
+
efforts: [
|
|
87
|
+
"low",
|
|
88
|
+
"medium",
|
|
89
|
+
"high",
|
|
90
|
+
"xhigh"
|
|
91
|
+
],
|
|
92
|
+
brief: "OpenAI gpt-5.6-terra via codex CLI — balanced, everyday coding; own ChatGPT login"
|
|
93
|
+
},
|
|
94
|
+
"openai-codex/gpt-5.6-luna": {
|
|
95
|
+
slug: "openai-codex/gpt-5.6-luna",
|
|
96
|
+
backend: "codex",
|
|
97
|
+
backendModel: "gpt-5.6-luna",
|
|
98
|
+
efforts: [
|
|
99
|
+
"low",
|
|
100
|
+
"medium",
|
|
101
|
+
"high",
|
|
102
|
+
"xhigh"
|
|
103
|
+
],
|
|
104
|
+
brief: "OpenAI gpt-5.6-luna via codex CLI — fast and affordable coding; own ChatGPT login"
|
|
105
|
+
},
|
|
106
|
+
"anthropic-claude/fable-5": {
|
|
107
|
+
slug: "anthropic-claude/fable-5",
|
|
75
108
|
backend: "claude",
|
|
76
|
-
backendModel: "claude-
|
|
109
|
+
backendModel: "claude-fable-5",
|
|
77
110
|
efforts: [
|
|
78
111
|
"low",
|
|
79
112
|
"medium",
|
|
@@ -81,12 +114,13 @@ const MODELS = {
|
|
|
81
114
|
"xhigh",
|
|
82
115
|
"max"
|
|
83
116
|
],
|
|
84
|
-
|
|
117
|
+
defaultEffort: "high",
|
|
118
|
+
brief: "Claude Fable 5 via claude CLI — hardest, longest-running work; bills the claude CLI subscription"
|
|
85
119
|
},
|
|
86
120
|
"anthropic-claude/opus-5": {
|
|
87
121
|
slug: "anthropic-claude/opus-5",
|
|
88
122
|
backend: "claude",
|
|
89
|
-
backendModel: "claude-opus-5",
|
|
123
|
+
backendModel: "claude-opus-5[1m]",
|
|
90
124
|
efforts: [
|
|
91
125
|
"low",
|
|
92
126
|
"medium",
|
|
@@ -95,12 +129,12 @@ const MODELS = {
|
|
|
95
129
|
"max"
|
|
96
130
|
],
|
|
97
131
|
defaultEffort: "high",
|
|
98
|
-
brief: "Claude Opus 5 via claude CLI
|
|
132
|
+
brief: "Claude Opus 5, 1M context via claude CLI — everyday complex work; bills the claude CLI subscription"
|
|
99
133
|
},
|
|
100
|
-
"anthropic-claude/
|
|
101
|
-
slug: "anthropic-claude/
|
|
134
|
+
"anthropic-claude/sonnet-5": {
|
|
135
|
+
slug: "anthropic-claude/sonnet-5",
|
|
102
136
|
backend: "claude",
|
|
103
|
-
backendModel: "claude-
|
|
137
|
+
backendModel: "claude-sonnet-5",
|
|
104
138
|
efforts: [
|
|
105
139
|
"low",
|
|
106
140
|
"medium",
|
|
@@ -108,13 +142,22 @@ const MODELS = {
|
|
|
108
142
|
"xhigh",
|
|
109
143
|
"max"
|
|
110
144
|
],
|
|
111
|
-
|
|
112
|
-
|
|
145
|
+
brief: "Claude Sonnet 5 via claude CLI — routine work; bills the claude CLI subscription"
|
|
146
|
+
},
|
|
147
|
+
"anthropic-claude/haiku-4-5": {
|
|
148
|
+
slug: "anthropic-claude/haiku-4-5",
|
|
149
|
+
backend: "claude",
|
|
150
|
+
backendModel: "claude-haiku-4-5-20251001",
|
|
151
|
+
efforts: [
|
|
152
|
+
"low",
|
|
153
|
+
"medium",
|
|
154
|
+
"high",
|
|
155
|
+
"xhigh",
|
|
156
|
+
"max"
|
|
157
|
+
],
|
|
158
|
+
brief: "Claude Haiku 4.5 via claude CLI — quick answers; bills the claude CLI subscription"
|
|
113
159
|
}
|
|
114
160
|
};
|
|
115
|
-
const DEFAULT_MODEL = "xai-grok/grok-4.5";
|
|
116
|
-
const DEFAULT_IMPLEMENTER = "google-antigravity/gemini-3.6-flash";
|
|
117
|
-
const DEFAULT_IMAGE_GEN = "openai-codex/gpt-5.6-sol";
|
|
118
161
|
const IMAGE_GEN_FORMATS = /* @__PURE__ */ new Map([
|
|
119
162
|
["agy", "jpg"],
|
|
120
163
|
["codex", "png"],
|
|
@@ -212,6 +255,7 @@ const DRIVERS = {
|
|
|
212
255
|
grok: {
|
|
213
256
|
probe: () => grok.probe(),
|
|
214
257
|
run: (task) => grok.run(task),
|
|
258
|
+
quota: () => grok.fetchGrokQuota(),
|
|
215
259
|
generateImage: (req) => grok.generateImage(req)
|
|
216
260
|
},
|
|
217
261
|
codex: {
|
|
@@ -240,11 +284,11 @@ async function imageGen$1(flags, prompt) {
|
|
|
240
284
|
this.process.stderr.write(`aibridge image-gen: ${msg}\n`);
|
|
241
285
|
this.process.exitCode = 1;
|
|
242
286
|
};
|
|
243
|
-
const inputSlug = flags.model
|
|
287
|
+
const inputSlug = flags.model;
|
|
244
288
|
const model = resolveModel(inputSlug);
|
|
245
289
|
if (!model) return fail(formatUnknownModelError(inputSlug));
|
|
246
290
|
if (!supportsImageGen(model)) return fail(formatImageGenModelError(inputSlug, model));
|
|
247
|
-
if (model.spec.backend === "codex" && model.effort) return fail(`effort "-${model.effort}" has no effect on image-gen (the image tool renders, not the seat model);
|
|
291
|
+
if (model.spec.backend === "codex" && model.effort) return fail(`effort "-${model.effort}" has no effect on image-gen (the image tool renders, not the seat model); pass the un-suffixed slug "${model.spec.slug}" instead.`);
|
|
248
292
|
const expected = imageFormatFor(model);
|
|
249
293
|
if (expected === void 0) return fail(formatImageGenModelError(inputSlug, model));
|
|
250
294
|
const label = expected === "png" ? "PNG" : "JPEG";
|
|
@@ -378,7 +422,7 @@ const fullDescription$6 = [
|
|
|
378
422
|
"",
|
|
379
423
|
"Image-gen seats (canonical slug):",
|
|
380
424
|
...listModelHelpLines({ imageOnly: true }),
|
|
381
|
-
|
|
425
|
+
"Recommended seat: openai-codex/gpt-5.6-sol."
|
|
382
426
|
].join("\n");
|
|
383
427
|
const imageGen = buildCommand({
|
|
384
428
|
func: imageGen$1,
|
|
@@ -387,8 +431,7 @@ const imageGen = buildCommand({
|
|
|
387
431
|
model: {
|
|
388
432
|
kind: "parsed",
|
|
389
433
|
parse: String,
|
|
390
|
-
|
|
391
|
-
brief: `Model slug (default: ${DEFAULT_IMAGE_GEN})`
|
|
434
|
+
brief: "Model slug (required) — see the seat list above"
|
|
392
435
|
},
|
|
393
436
|
out: {
|
|
394
437
|
kind: "parsed",
|
|
@@ -492,8 +535,17 @@ function evaluateCodexPreflight(snapshot) {
|
|
|
492
535
|
};
|
|
493
536
|
return { ok: true };
|
|
494
537
|
}
|
|
538
|
+
function evaluateGrokPreflight(snapshot) {
|
|
539
|
+
if (snapshot.usedPercent !== void 0 && snapshot.usedPercent >= 100) return {
|
|
540
|
+
ok: false,
|
|
541
|
+
message: "grok credit quota exhausted",
|
|
542
|
+
resetAt: snapshot.periodEnd
|
|
543
|
+
};
|
|
544
|
+
return { ok: true };
|
|
545
|
+
}
|
|
495
546
|
async function preflightModel(resolved) {
|
|
496
547
|
if (resolved.spec.backend === "codex") return preflightCodex();
|
|
548
|
+
if (resolved.spec.backend === "grok") return preflightGrok();
|
|
497
549
|
if (resolved.spec.backend !== "agy") return { ok: true };
|
|
498
550
|
try {
|
|
499
551
|
return evaluateAgyPreflight(await fetchAgyQuota(), backendModelId(resolved));
|
|
@@ -514,6 +566,16 @@ async function preflightCodex() {
|
|
|
514
566
|
};
|
|
515
567
|
}
|
|
516
568
|
}
|
|
569
|
+
async function preflightGrok() {
|
|
570
|
+
try {
|
|
571
|
+
return evaluateGrokPreflight(await fetchGrokQuota());
|
|
572
|
+
} catch (err) {
|
|
573
|
+
return {
|
|
574
|
+
ok: true,
|
|
575
|
+
warning: `quota preflight failed (${err.message}); proceeding`
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
}
|
|
517
579
|
function formatReset$1(resetTime) {
|
|
518
580
|
if (!resetTime) return "-";
|
|
519
581
|
const ms = new Date(resetTime).getTime() - Date.now();
|
|
@@ -649,7 +711,7 @@ function readRunLogs(id) {
|
|
|
649
711
|
//#endregion
|
|
650
712
|
//#region src/commands/implement/impl.ts
|
|
651
713
|
async function implement$1(flags, planFile) {
|
|
652
|
-
const inputSlug = flags.model
|
|
714
|
+
const inputSlug = flags.model;
|
|
653
715
|
const model = resolveModel(inputSlug);
|
|
654
716
|
if (!model) {
|
|
655
717
|
this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
|
|
@@ -719,8 +781,7 @@ const implement = buildCommand({
|
|
|
719
781
|
model: {
|
|
720
782
|
kind: "parsed",
|
|
721
783
|
parse: String,
|
|
722
|
-
|
|
723
|
-
brief: `Model slug (default: ${DEFAULT_IMPLEMENTER})`
|
|
784
|
+
brief: "Model slug (required) — see the seat list above"
|
|
724
785
|
},
|
|
725
786
|
timeout: {
|
|
726
787
|
kind: "parsed",
|
|
@@ -749,6 +810,70 @@ const implement = buildCommand({
|
|
|
749
810
|
}
|
|
750
811
|
});
|
|
751
812
|
//#endregion
|
|
813
|
+
//#region src/commands/models/impl.ts
|
|
814
|
+
const BACKEND_DISPLAY_NAMES = {
|
|
815
|
+
grok: "grok (Grok CLI)",
|
|
816
|
+
agy: "agy (Antigravity)",
|
|
817
|
+
codex: "codex (Codex CLI)",
|
|
818
|
+
claude: "claude (Claude Code CLI)"
|
|
819
|
+
};
|
|
820
|
+
function modelsImpl(flags) {
|
|
821
|
+
const specs = Object.values(MODELS);
|
|
822
|
+
if (flags.json) {
|
|
823
|
+
const jsonOutput = specs.map((spec) => ({
|
|
824
|
+
slug: spec.slug,
|
|
825
|
+
backend: spec.backend,
|
|
826
|
+
backendModel: spec.backendModel,
|
|
827
|
+
efforts: spec.efforts ? [...spec.efforts] : [],
|
|
828
|
+
defaultEffort: spec.defaultEffort ?? null,
|
|
829
|
+
image: imageFormatFor({
|
|
830
|
+
spec,
|
|
831
|
+
effort: void 0
|
|
832
|
+
}) ?? null,
|
|
833
|
+
brief: spec.brief
|
|
834
|
+
}));
|
|
835
|
+
this.process.stdout.write(`${JSON.stringify(jsonOutput)}\n`);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const backends = [];
|
|
839
|
+
for (const spec of specs) if (!backends.includes(spec.backend)) backends.push(spec.backend);
|
|
840
|
+
let firstBackend = true;
|
|
841
|
+
for (const backend of backends) {
|
|
842
|
+
if (!firstBackend) this.process.stdout.write("\n");
|
|
843
|
+
firstBackend = false;
|
|
844
|
+
this.process.stdout.write(`=== ${BACKEND_DISPLAY_NAMES[backend]} ===\n`);
|
|
845
|
+
const backendSpecs = specs.filter((spec) => spec.backend === backend);
|
|
846
|
+
for (const spec of backendSpecs) {
|
|
847
|
+
this.process.stdout.write(` ${spec.slug}\n`);
|
|
848
|
+
const segments = [];
|
|
849
|
+
if (spec.efforts) {
|
|
850
|
+
const formattedEfforts = spec.efforts.map((e) => e === spec.defaultEffort ? `${e}*` : e).join(" | ");
|
|
851
|
+
segments.push(`efforts: ${formattedEfforts}`);
|
|
852
|
+
}
|
|
853
|
+
const img = imageFormatFor({
|
|
854
|
+
spec,
|
|
855
|
+
effort: void 0
|
|
856
|
+
});
|
|
857
|
+
segments.push(`image: ${img ?? "—"}`);
|
|
858
|
+
segments.push(`id: ${spec.backendModel}`);
|
|
859
|
+
this.process.stdout.write(` ${segments.join(" · ")}\n`);
|
|
860
|
+
this.process.stdout.write(` ${spec.brief}\n`);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (specs.some((spec) => spec.defaultEffort !== void 0)) this.process.stdout.write("\n* = effort used when the slug has no -<effort> suffix\n");
|
|
864
|
+
}
|
|
865
|
+
//#endregion
|
|
866
|
+
//#region src/commands/models/command.ts
|
|
867
|
+
const models = buildCommand({
|
|
868
|
+
func: modelsImpl,
|
|
869
|
+
parameters: { flags: { json: {
|
|
870
|
+
kind: "boolean",
|
|
871
|
+
withNegated: false,
|
|
872
|
+
brief: "Emit the registry as JSON"
|
|
873
|
+
} } },
|
|
874
|
+
docs: { brief: "List every model seat in the registry (slug, efforts, image format)" }
|
|
875
|
+
});
|
|
876
|
+
//#endregion
|
|
752
877
|
//#region src/commands/plan/impl.ts
|
|
753
878
|
function countOpenQuestions(markdown) {
|
|
754
879
|
const headingIdx = markdown.search(/^## Open questions[ \t]*$/m);
|
|
@@ -781,7 +906,7 @@ function extractPathFromPorcelainLine(line) {
|
|
|
781
906
|
return content;
|
|
782
907
|
}
|
|
783
908
|
async function plan$1(flags, taskPrompt) {
|
|
784
|
-
const inputSlug = flags.model
|
|
909
|
+
const inputSlug = flags.model;
|
|
785
910
|
const model = resolveModel(inputSlug);
|
|
786
911
|
if (!model) {
|
|
787
912
|
this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
|
|
@@ -801,7 +926,7 @@ async function plan$1(flags, taskPrompt) {
|
|
|
801
926
|
const cwd = this.process.cwd();
|
|
802
927
|
const promptSnippet = taskPrompt.replace(/\r?\n/g, " ").slice(0, 80);
|
|
803
928
|
const run = startRun("plan", `${model.spec.slug}: ${promptSnippet}`);
|
|
804
|
-
const absOutPath =
|
|
929
|
+
const absOutPath = isAbsolute(flags.out) ? flags.out : resolve(cwd, flags.out);
|
|
805
930
|
const beforePorcelain = await getPorcelainStatus(cwd);
|
|
806
931
|
const outcome = await delegate({
|
|
807
932
|
model,
|
|
@@ -866,14 +991,12 @@ const plan = buildCommand({
|
|
|
866
991
|
model: {
|
|
867
992
|
kind: "parsed",
|
|
868
993
|
parse: String,
|
|
869
|
-
|
|
870
|
-
brief: `Model slug (default: ${DEFAULT_MODEL})`
|
|
994
|
+
brief: "Model slug (required) — see the seat list above"
|
|
871
995
|
},
|
|
872
996
|
out: {
|
|
873
997
|
kind: "parsed",
|
|
874
998
|
parse: String,
|
|
875
|
-
|
|
876
|
-
brief: "Where to write the plan (default: <run.dir>/plan.md)"
|
|
999
|
+
brief: "Where to write the plan file (required)"
|
|
877
1000
|
},
|
|
878
1001
|
timeout: {
|
|
879
1002
|
kind: "parsed",
|
|
@@ -912,6 +1035,17 @@ function formatReset(resetTime) {
|
|
|
912
1035
|
const rel = mins < 60 ? `${mins}m` : `${Math.floor(mins / 60)}h${mins % 60}m`;
|
|
913
1036
|
return `${new Date(resetTime).toLocaleTimeString()} (in ${rel})`;
|
|
914
1037
|
}
|
|
1038
|
+
function renderGrok(ctx, snapshot) {
|
|
1039
|
+
ctx.process.stdout.write("=== grok (xAI) — used this period ===\n");
|
|
1040
|
+
ctx.process.stdout.write(`${"PERIOD".padEnd(10)} ${"USED".padEnd(10)} RESET\n`);
|
|
1041
|
+
const usedPctStr = snapshot.usedPercent !== void 0 ? `${snapshot.usedPercent}%` : "?";
|
|
1042
|
+
const periodStr = snapshot.periodType ?? "-";
|
|
1043
|
+
ctx.process.stdout.write(`${periodStr.padEnd(10)} ${usedPctStr.padEnd(10)} ${formatReset(snapshot.periodEnd)}\n`);
|
|
1044
|
+
if (snapshot.products.length > 0) {
|
|
1045
|
+
const prods = snapshot.products.map((p) => `${p.product} ${p.usedPercent}%`).join(" · ");
|
|
1046
|
+
ctx.process.stdout.write(` ${prods}\n`);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
915
1049
|
function renderAgy(ctx, snapshot) {
|
|
916
1050
|
ctx.process.stdout.write("=== agy (Antigravity) — remaining per model group ===\n");
|
|
917
1051
|
for (const group of snapshot.groups) {
|
|
@@ -941,14 +1075,16 @@ function renderSection(ctx, result, title, render) {
|
|
|
941
1075
|
else ctx.process.stdout.write(`=== ${title} ===\nunavailable: ${result.reason.message}\n`);
|
|
942
1076
|
}
|
|
943
1077
|
async function quotaImpl(flags) {
|
|
944
|
-
const [agy, codex, claude] = await Promise.allSettled([
|
|
1078
|
+
const [grok, agy, codex, claude] = await Promise.allSettled([
|
|
1079
|
+
fetchGrokQuota(),
|
|
945
1080
|
fetchAgyQuota(),
|
|
946
1081
|
fetchCodexQuota(),
|
|
947
1082
|
fetchClaudeQuota()
|
|
948
1083
|
]);
|
|
949
|
-
const allFailed = agy.status === "rejected" && codex.status === "rejected" && claude.status === "rejected";
|
|
1084
|
+
const allFailed = grok.status === "rejected" && agy.status === "rejected" && codex.status === "rejected" && claude.status === "rejected";
|
|
950
1085
|
if (flags.json) {
|
|
951
1086
|
this.process.stdout.write(`${JSON.stringify({
|
|
1087
|
+
grok: grok.status === "fulfilled" ? grok.value : { error: String(grok.reason) },
|
|
952
1088
|
agy: agy.status === "fulfilled" ? agy.value : { error: String(agy.reason) },
|
|
953
1089
|
codex: codex.status === "fulfilled" ? codex.value : { error: String(codex.reason) },
|
|
954
1090
|
claude: claude.status === "fulfilled" ? claude.value : { error: String(claude.reason) }
|
|
@@ -956,6 +1092,8 @@ async function quotaImpl(flags) {
|
|
|
956
1092
|
if (allFailed) this.process.exitCode = 1;
|
|
957
1093
|
return;
|
|
958
1094
|
}
|
|
1095
|
+
renderSection(this, grok, "grok (xAI)", renderGrok);
|
|
1096
|
+
this.process.stdout.write("\n");
|
|
959
1097
|
renderSection(this, agy, "agy (Antigravity)", renderAgy);
|
|
960
1098
|
this.process.stdout.write("\n");
|
|
961
1099
|
renderSection(this, codex, "codex (ChatGPT)", renderCodex);
|
|
@@ -971,8 +1109,10 @@ const quota = buildCommand({
|
|
|
971
1109
|
brief: "Emit the raw snapshot as JSON"
|
|
972
1110
|
} } },
|
|
973
1111
|
docs: {
|
|
974
|
-
brief: "Show agy / codex / claude quota with reset times",
|
|
1112
|
+
brief: "Show grok / agy / codex / claude quota with reset times",
|
|
975
1113
|
fullDescription: [
|
|
1114
|
+
"grok: reads ~/.grok/auth.json and asks the xAI billing endpoint for the",
|
|
1115
|
+
"weekly credit usage percentage and per-product split.",
|
|
976
1116
|
"agy: reads its cached OAuth token (~/.gemini/antigravity-cli/) and asks the",
|
|
977
1117
|
"Cloud Code API for per-model remaining quota. EXHAUSTED means agy turns on",
|
|
978
1118
|
"that model fail with an empty answer until the reset time.",
|
|
@@ -1032,7 +1172,7 @@ function parseReviewVerdict(response) {
|
|
|
1032
1172
|
};
|
|
1033
1173
|
}
|
|
1034
1174
|
async function review$1(flags) {
|
|
1035
|
-
const inputSlug = flags.model
|
|
1175
|
+
const inputSlug = flags.model;
|
|
1036
1176
|
const model = resolveModel(inputSlug);
|
|
1037
1177
|
if (!model) {
|
|
1038
1178
|
this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
|
|
@@ -1082,7 +1222,7 @@ async function review$1(flags) {
|
|
|
1082
1222
|
const timeoutSec = flags.timeout ?? 1200;
|
|
1083
1223
|
const modeDetail = isDirty ? absPlanPath ? `diff + plan (${absPlanPath})` : `diff (${baseRef})` : `plan-only (${absPlanPath})`;
|
|
1084
1224
|
const run = startRun("review", `${model.spec.slug}: ${modeDetail}`);
|
|
1085
|
-
const absOutPath =
|
|
1225
|
+
const absOutPath = isAbsolute(flags.out) ? flags.out : resolve(cwd, flags.out);
|
|
1086
1226
|
let reviewPrompt;
|
|
1087
1227
|
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"`;
|
|
1088
1228
|
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"`;
|
|
@@ -1134,8 +1274,7 @@ const review = buildCommand({
|
|
|
1134
1274
|
model: {
|
|
1135
1275
|
kind: "parsed",
|
|
1136
1276
|
parse: String,
|
|
1137
|
-
|
|
1138
|
-
brief: `Model slug (default: ${DEFAULT_MODEL})`
|
|
1277
|
+
brief: "Model slug (required) — see the seat list above"
|
|
1139
1278
|
},
|
|
1140
1279
|
plan: {
|
|
1141
1280
|
kind: "parsed",
|
|
@@ -1152,8 +1291,7 @@ const review = buildCommand({
|
|
|
1152
1291
|
out: {
|
|
1153
1292
|
kind: "parsed",
|
|
1154
1293
|
parse: String,
|
|
1155
|
-
|
|
1156
|
-
brief: "Where to write the review report (default: <run.dir>/review.md)"
|
|
1294
|
+
brief: "Where to write the review report (required)"
|
|
1157
1295
|
},
|
|
1158
1296
|
timeout: {
|
|
1159
1297
|
kind: "parsed",
|
|
@@ -1345,7 +1483,7 @@ const runs = buildCommand({
|
|
|
1345
1483
|
//#endregion
|
|
1346
1484
|
//#region src/commands/subagent/impl.ts
|
|
1347
1485
|
async function subagent$1(flags, prompt) {
|
|
1348
|
-
const inputSlug = flags.model
|
|
1486
|
+
const inputSlug = flags.model;
|
|
1349
1487
|
const model = resolveModel(inputSlug);
|
|
1350
1488
|
if (!model) {
|
|
1351
1489
|
this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
|
|
@@ -1399,8 +1537,8 @@ const fullDescription = [
|
|
|
1399
1537
|
"",
|
|
1400
1538
|
"Available models (canonical slug):",
|
|
1401
1539
|
...listModelHelpLines(),
|
|
1402
|
-
|
|
1403
|
-
"
|
|
1540
|
+
"Recommended first choice: xai-grok/grok-4.5. Whichever seat runs on the same provider as the",
|
|
1541
|
+
"agent you orchestrate from is your last resort — it spends the pool you are already burning."
|
|
1404
1542
|
].join("\n");
|
|
1405
1543
|
const subagent = buildCommand({
|
|
1406
1544
|
func: subagent$1,
|
|
@@ -1409,8 +1547,7 @@ const subagent = buildCommand({
|
|
|
1409
1547
|
model: {
|
|
1410
1548
|
kind: "parsed",
|
|
1411
1549
|
parse: String,
|
|
1412
|
-
|
|
1413
|
-
brief: `Model slug to delegate to (default: ${DEFAULT_MODEL})`
|
|
1550
|
+
brief: "Model slug (required) — see the seat list above"
|
|
1414
1551
|
},
|
|
1415
1552
|
timeout: {
|
|
1416
1553
|
kind: "parsed",
|
|
@@ -1475,9 +1612,10 @@ const app = buildApplication(buildRouteMap({
|
|
|
1475
1612
|
subagent,
|
|
1476
1613
|
"image-gen": imageGen,
|
|
1477
1614
|
runs,
|
|
1478
|
-
quota
|
|
1615
|
+
quota,
|
|
1616
|
+
models
|
|
1479
1617
|
},
|
|
1480
|
-
docs: { brief: "Bridge tasks to
|
|
1618
|
+
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)." }
|
|
1481
1619
|
}), {
|
|
1482
1620
|
name: "aibridge",
|
|
1483
1621
|
versionInfo: { currentVersion: version },
|
|
@@ -1494,4 +1632,4 @@ function buildContext(process) {
|
|
|
1494
1632
|
return { process };
|
|
1495
1633
|
}
|
|
1496
1634
|
//#endregion
|
|
1497
|
-
export {
|
|
1635
|
+
export { supportsImageGen as S, backendModelId as _, readRunLogs as a, listModelHelpLines as b, evaluateCodexPreflight as c, renderPreflightRefusal as d, delegate as f, MODELS as g, positiveIntSeconds as h, listRuns as i, preflightCodex as l, nonEmptyPrompt as m, app as n, startRun as o, getDriver as p, runCli as r, evaluateAgyPreflight as s, buildContext as t, preflightModel as u, formatImageGenModelError as v, resolveModel as x, formatUnknownModelError as y };
|
package/dist/index.d.mts
CHANGED
|
@@ -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';
|
|
@@ -39,9 +40,6 @@ interface ResolvedModel {
|
|
|
39
40
|
readonly effort: Effort | undefined;
|
|
40
41
|
}
|
|
41
42
|
declare const MODELS: Record<string, ModelSpec>;
|
|
42
|
-
declare const DEFAULT_MODEL = "xai-grok/grok-4.5";
|
|
43
|
-
declare const DEFAULT_IMPLEMENTER = "google-antigravity/gemini-3.6-flash";
|
|
44
|
-
declare const DEFAULT_IMAGE_GEN = "openai-codex/gpt-5.6-sol";
|
|
45
43
|
declare function supportsImageGen(resolved: ResolvedModel): boolean;
|
|
46
44
|
declare function resolveModel(input: string): ResolvedModel | undefined;
|
|
47
45
|
declare function backendModelId(resolved: ResolvedModel): string;
|
|
@@ -80,7 +78,7 @@ type DelegationResult = {
|
|
|
80
78
|
readonly message: string;
|
|
81
79
|
readonly exitCode: number | null;
|
|
82
80
|
};
|
|
83
|
-
type QuotaSnapshot = AgyQuotaSnapshot | CodexQuotaSnapshot | ClaudeQuotaSnapshot;
|
|
81
|
+
type QuotaSnapshot = AgyQuotaSnapshot | CodexQuotaSnapshot | ClaudeQuotaSnapshot | GrokQuotaSnapshot;
|
|
84
82
|
interface ImageGenRequest {
|
|
85
83
|
readonly prompt: string;
|
|
86
84
|
readonly workDir: string;
|
|
@@ -179,4 +177,4 @@ declare function renderPreflightRefusal(cmd: string, verdict: {
|
|
|
179
177
|
resetAt: string | undefined;
|
|
180
178
|
}): string;
|
|
181
179
|
//#endregion
|
|
182
|
-
export { type AgentCliDriver, type Backend,
|
|
180
|
+
export { type AgentCliDriver, type Backend, type DelegateOptions, type DelegateOutcome, type DelegationResult, type DelegationTask, type Effort, type LocalContext, MODELS, type ModelSpec, type PreflightVerdict, type ResolvedModel, type RunLog, type RunMeta, app, backendModelId, buildContext, delegate, evaluateAgyPreflight, evaluateCodexPreflight, formatImageGenModelError, formatUnknownModelError, getDriver, listModelHelpLines, listRuns, nonEmptyPrompt, positiveIntSeconds, preflightCodex, preflightModel, readRunLogs, renderPreflightRefusal, resolveModel, runCli, startRun, supportsImageGen };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
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-v_ns4n_W.mjs";
|
|
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.4.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.4.0",
|
|
39
|
+
"@aibridge/driver-grok": "0.4.0",
|
|
40
|
+
"@aibridge/driver-agy": "0.4.0",
|
|
41
|
+
"@aibridge/driver-codex": "0.4.0",
|
|
42
|
+
"@aibridge/driver-claude": "0.4.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,
|