@aibridge/cli 0.1.1 → 0.3.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 +15 -11
- package/dist/cli.mjs +1 -1
- package/dist/{context-8JjDzZBG.mjs → context-B6I4QI9z.mjs} +92 -134
- package/dist/index.d.mts +9 -14
- package/dist/index.mjs +2 -2
- package/package.json +6 -6
- package/src/app.ts +1 -1
- package/src/commands/image-gen/command.ts +9 -17
- package/src/commands/image-gen/impl.ts +46 -87
- package/src/commands/implement/command.ts +2 -3
- package/src/commands/implement/impl.ts +3 -3
- package/src/commands/plan/command.ts +3 -5
- package/src/commands/plan/impl.ts +5 -9
- package/src/commands/review/command.ts +3 -5
- package/src/commands/review/impl.ts +5 -9
- package/src/commands/subagent/command.ts +3 -4
- package/src/commands/subagent/impl.ts +4 -10
- package/src/delegate.ts +1 -1
- package/src/driver.ts +3 -4
- package/src/drivers.ts +1 -0
- package/src/flagMapping.test.ts +27 -4
- package/src/index.ts +0 -3
- package/src/models.test.ts +28 -14
- package/src/models.ts +35 -21
- package/src/quotaPreflight.ts +1 -2
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-B6I4QI9z.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.3.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-agy": "0.
|
|
40
|
-
"@aibridge/driver-grok": "0.
|
|
41
|
-
"@aibridge/driver-codex": "0.
|
|
42
|
-
"@aibridge/driver-claude": "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"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"tsdown": "0.22.14"
|
package/src/app.ts
CHANGED
|
@@ -14,7 +14,7 @@ const require = createRequire(import.meta.url);
|
|
|
14
14
|
const { version } = require('../package.json') as { version: string };
|
|
15
15
|
|
|
16
16
|
const BRIEF =
|
|
17
|
-
'Bridge tasks to non-Claude AI CLIs — a plan → implement → review workflow, task delegation, and image generation (codex
|
|
17
|
+
'Bridge tasks to non-Claude AI CLIs — a plan → implement → review workflow, task delegation, and image generation (codex / agy / grok seats).';
|
|
18
18
|
|
|
19
19
|
const routes = buildRouteMap({
|
|
20
20
|
routes: {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { buildCommand } from '@stricli/core';
|
|
2
|
-
import {
|
|
2
|
+
import { listModelHelpLines } from '../../models.ts';
|
|
3
3
|
import { nonEmptyPrompt, positiveIntSeconds } from '../../parsers.ts';
|
|
4
4
|
import imageGenImpl from './impl.ts';
|
|
5
5
|
|
|
6
6
|
const fullDescription = [
|
|
7
|
-
"Renders an image by driving the seat's CLI
|
|
8
|
-
'
|
|
7
|
+
"Renders an image by driving the seat's own CLI, then verifies the result is",
|
|
8
|
+
'a real render before returning it.',
|
|
9
9
|
'',
|
|
10
10
|
'Image-gen seats (canonical slug):',
|
|
11
11
|
...listModelHelpLines({ imageOnly: true }),
|
|
12
|
-
|
|
12
|
+
'Recommended seat: openai-codex/gpt-5.6-sol.',
|
|
13
13
|
].join('\n');
|
|
14
14
|
|
|
15
15
|
export const imageGen = buildCommand({
|
|
@@ -19,21 +19,19 @@ export const imageGen = buildCommand({
|
|
|
19
19
|
model: {
|
|
20
20
|
kind: 'parsed',
|
|
21
21
|
parse: String,
|
|
22
|
-
|
|
23
|
-
brief: `Model slug (default: ${DEFAULT_IMAGE_GEN})`,
|
|
22
|
+
brief: 'Model slug (required) — see the seat list above',
|
|
24
23
|
},
|
|
25
24
|
out: {
|
|
26
25
|
kind: 'parsed',
|
|
27
26
|
parse: String,
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
brief:
|
|
28
|
+
'Path to write the image — extension must match the seat format (.png for codex, .jpg for agy/grok)',
|
|
30
29
|
},
|
|
31
|
-
|
|
30
|
+
aspectRatio: {
|
|
32
31
|
kind: 'parsed',
|
|
33
32
|
parse: String,
|
|
34
33
|
optional: true,
|
|
35
|
-
brief:
|
|
36
|
-
'WIDTHxHEIGHT (codex: each edge ÷16; grok: mapped to aspect_ratio, then optionally resized)',
|
|
34
|
+
brief: 'Aspect ratio N:M, e.g. 16:9 (agy/grok: real tool param; codex: prompt hint)',
|
|
37
35
|
},
|
|
38
36
|
image: {
|
|
39
37
|
kind: 'parsed',
|
|
@@ -41,12 +39,6 @@ export const imageGen = buildCommand({
|
|
|
41
39
|
optional: true,
|
|
42
40
|
brief: 'Reference image path(s), comma-separated — visual reference',
|
|
43
41
|
},
|
|
44
|
-
quality: {
|
|
45
|
-
kind: 'parsed',
|
|
46
|
-
parse: String,
|
|
47
|
-
optional: true,
|
|
48
|
-
brief: 'low | medium | high (codex/gpt-image-2; default high)',
|
|
49
|
-
},
|
|
50
42
|
timeout: {
|
|
51
43
|
kind: 'parsed',
|
|
52
44
|
parse: positiveIntSeconds,
|
|
@@ -10,30 +10,29 @@ import {
|
|
|
10
10
|
} from 'node:fs';
|
|
11
11
|
import { tmpdir } from 'node:os';
|
|
12
12
|
import { join, resolve } from 'node:path';
|
|
13
|
-
import { isNotFound, runCaptured } from '@aibridge/proc';
|
|
14
13
|
import type { LocalContext } from '../../context.ts';
|
|
15
14
|
import type { ImageResult } from '../../driver.ts';
|
|
16
15
|
import { getDriver } from '../../drivers.ts';
|
|
17
16
|
import {
|
|
18
|
-
|
|
17
|
+
backendModelId,
|
|
19
18
|
formatImageGenModelError,
|
|
20
19
|
formatUnknownModelError,
|
|
20
|
+
imageFormatFor,
|
|
21
21
|
resolveModel,
|
|
22
22
|
supportsImageGen,
|
|
23
23
|
} from '../../models.ts';
|
|
24
24
|
|
|
25
25
|
export interface ImageGenFlags {
|
|
26
|
-
readonly model
|
|
27
|
-
readonly out
|
|
28
|
-
readonly
|
|
26
|
+
readonly model: string;
|
|
27
|
+
readonly out: string;
|
|
28
|
+
readonly aspectRatio?: string;
|
|
29
29
|
readonly image?: string;
|
|
30
|
-
readonly quality?: string;
|
|
31
30
|
readonly timeout?: number;
|
|
32
31
|
readonly json: boolean;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
const MIN_REAL_BYTES_CODEX = 100_000;
|
|
36
|
-
const
|
|
35
|
+
const MIN_REAL_BYTES_TOOL = 10_000;
|
|
37
36
|
|
|
38
37
|
export default async function imageGen(
|
|
39
38
|
this: LocalContext,
|
|
@@ -45,37 +44,39 @@ export default async function imageGen(
|
|
|
45
44
|
this.process.exitCode = 1;
|
|
46
45
|
};
|
|
47
46
|
|
|
48
|
-
const inputSlug = flags.model
|
|
47
|
+
const inputSlug = flags.model;
|
|
49
48
|
const model = resolveModel(inputSlug);
|
|
50
49
|
if (!model) return fail(formatUnknownModelError(inputSlug));
|
|
51
50
|
if (!supportsImageGen(model)) return fail(formatImageGenModelError(inputSlug, model));
|
|
52
51
|
|
|
53
52
|
if (model.spec.backend === 'codex' && model.effort) {
|
|
54
53
|
return fail(
|
|
55
|
-
`effort "-${model.effort}" has no effect on image-gen (
|
|
54
|
+
`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.`,
|
|
56
55
|
);
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
58
|
+
const expected = imageFormatFor(model);
|
|
59
|
+
if (expected === undefined) return fail(formatImageGenModelError(inputSlug, model));
|
|
60
|
+
|
|
61
|
+
const label = expected === 'png' ? 'PNG' : 'JPEG';
|
|
62
|
+
const extValid = expected === 'png' ? /\.png$/i.test(flags.out) : /\.jpe?g$/i.test(flags.out);
|
|
63
|
+
if (!extValid) {
|
|
64
|
+
return fail(
|
|
65
|
+
`--out "${flags.out}" must end in ${expected === 'png' ? '.png' : '.jpg or .jpeg'} — the ${model.spec.slug} seat renders ${label} and aibridge does not convert.`,
|
|
66
|
+
);
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
let
|
|
65
|
-
if (flags.
|
|
66
|
-
const m = flags.
|
|
67
|
-
if (!m
|
|
68
|
-
|
|
69
|
-
if (model.spec.backend === 'codex') {
|
|
70
|
-
const constraint = sizeConstraintError(size.w, size.h);
|
|
71
|
-
if (constraint) return fail(`invalid --size ${size.w}x${size.h}: ${constraint}`);
|
|
72
|
-
} else if (size.w < 1 || size.h < 1) {
|
|
73
|
-
return fail(`invalid --size ${size.w}x${size.h}: dimensions must be positive`);
|
|
69
|
+
let aspectRatio: string | undefined;
|
|
70
|
+
if (flags.aspectRatio !== undefined) {
|
|
71
|
+
const m = flags.aspectRatio.match(/^(\d+)\s*:\s*(\d+)$/);
|
|
72
|
+
if (!m || Number(m[1]) < 1 || Number(m[2]) < 1) {
|
|
73
|
+
return fail(`invalid --aspect-ratio "${flags.aspectRatio}" (expected e.g. 16:9)`);
|
|
74
74
|
}
|
|
75
|
+
aspectRatio = `${Number(m[1])}:${Number(m[2])}`;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
const timeoutSec = flags.timeout ?? 600;
|
|
78
|
-
const outPath = resolve(this.process.cwd(), flags.out
|
|
79
|
+
const outPath = resolve(this.process.cwd(), flags.out);
|
|
79
80
|
|
|
80
81
|
const imagePaths: string[] = [];
|
|
81
82
|
if (flags.image !== undefined) {
|
|
@@ -94,17 +95,16 @@ export default async function imageGen(
|
|
|
94
95
|
return fail(formatImageGenModelError(inputSlug, model));
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
const minBytes = model.spec.backend === 'codex' ? MIN_REAL_BYTES_CODEX :
|
|
98
|
+
const minBytes = model.spec.backend === 'codex' ? MIN_REAL_BYTES_CODEX : MIN_REAL_BYTES_TOOL;
|
|
98
99
|
const work = mkdtempSync(join(tmpdir(), 'aibridge-imagegen-'));
|
|
99
100
|
|
|
100
101
|
try {
|
|
101
102
|
let outcome: ImageResult = await driver.generateImage({
|
|
102
103
|
prompt,
|
|
103
104
|
workDir: work,
|
|
104
|
-
backendModel: model
|
|
105
|
+
backendModel: backendModelId(model),
|
|
105
106
|
effort: model.effort,
|
|
106
|
-
|
|
107
|
-
size,
|
|
107
|
+
aspectRatio,
|
|
108
108
|
imagePaths,
|
|
109
109
|
timeoutSec,
|
|
110
110
|
forceful: false,
|
|
@@ -115,10 +115,9 @@ export default async function imageGen(
|
|
|
115
115
|
outcome = await driver.generateImage({
|
|
116
116
|
prompt,
|
|
117
117
|
workDir: work,
|
|
118
|
-
backendModel: model
|
|
118
|
+
backendModel: backendModelId(model),
|
|
119
119
|
effort: model.effort,
|
|
120
|
-
|
|
121
|
-
size,
|
|
120
|
+
aspectRatio,
|
|
122
121
|
imagePaths,
|
|
123
122
|
timeoutSec,
|
|
124
123
|
forceful: true,
|
|
@@ -133,42 +132,29 @@ export default async function imageGen(
|
|
|
133
132
|
if (outcome.kind === 'error') return fail(outcome.reason);
|
|
134
133
|
if (outcome.kind === 'suspect') {
|
|
135
134
|
return fail(
|
|
136
|
-
model.spec.backend === '
|
|
137
|
-
? '
|
|
138
|
-
:
|
|
139
|
-
|
|
135
|
+
model.spec.backend === 'agy'
|
|
136
|
+
? 'agy produced no usable image. Re-run with a simpler prompt, or check Antigravity image access.'
|
|
137
|
+
: model.spec.backend === 'grok'
|
|
138
|
+
? 'grok produced no usable image. Check SuperGrok image access and re-run with a simpler prompt.'
|
|
139
|
+
: 'codex produced only a tiny/code-drawn image, not a real render. ' +
|
|
140
|
+
'Try a clearer, simpler prompt.',
|
|
140
141
|
);
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
const local = join(work, 'result.bin');
|
|
144
145
|
copyFileSync(outcome.path, local);
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const resized = await magick([local, '-resize', `${size.w}x${size.h}!`, local]);
|
|
149
|
-
if (resized) {
|
|
150
|
-
dims = imageSize(local) ?? dims;
|
|
151
|
-
} else {
|
|
152
|
-
this.process.stderr.write(
|
|
153
|
-
`aibridge image-gen: rendered ${dims.width}x${dims.height}, wanted ${size.w}x${size.h}, ` +
|
|
154
|
-
'and ImageMagick (magick/convert) is unavailable to resize.\n',
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
147
|
+
const dims = imageSize(local);
|
|
148
|
+
const actual = pngSize(local) ? 'png' : jpegSize(local) ? 'jpg' : null;
|
|
158
149
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
this.process.stderr.write(
|
|
165
|
-
`aibridge image-gen: render is ${actualFmt.toUpperCase()} but out path wants ` +
|
|
166
|
-
`${outExt.toUpperCase()}, and ImageMagick (magick/convert) is unavailable to convert; ` +
|
|
167
|
-
'writing the raw bytes as-is.\n',
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
copyFileSync(local, outPath);
|
|
150
|
+
// ponytail: guard for a backend changing formats in the future without throwing away a paid render
|
|
151
|
+
if (actual !== null && actual !== expected) {
|
|
152
|
+
this.process.stderr.write(
|
|
153
|
+
`aibridge image-gen: expected a ${label} render from this seat but got ${actual === 'png' ? 'PNG' : 'JPEG'}; wrote the raw bytes to ${outPath} anyway — the extension does not match the contents.\n`,
|
|
154
|
+
);
|
|
171
155
|
}
|
|
156
|
+
|
|
157
|
+
copyFileSync(local, outPath);
|
|
172
158
|
const bytes = statSync(outPath).size;
|
|
173
159
|
|
|
174
160
|
if (flags.json) {
|
|
@@ -178,8 +164,7 @@ export default async function imageGen(
|
|
|
178
164
|
bytes,
|
|
179
165
|
width: dims?.width ?? null,
|
|
180
166
|
height: dims?.height ?? null,
|
|
181
|
-
|
|
182
|
-
quality: model.spec.backend === 'codex' ? quality : null,
|
|
167
|
+
aspectRatio: flags.aspectRatio ?? null,
|
|
183
168
|
model: model.spec.slug,
|
|
184
169
|
backend: model.spec.backend,
|
|
185
170
|
real: true,
|
|
@@ -188,27 +173,13 @@ export default async function imageGen(
|
|
|
188
173
|
} else {
|
|
189
174
|
const kb = Math.round(bytes / 1024);
|
|
190
175
|
const dimStr = dims ? `${dims.width}x${dims.height}, ` : '';
|
|
191
|
-
|
|
192
|
-
this.process.stdout.write(
|
|
193
|
-
`✓ Wrote ${outPath} (${dimStr}${kb} KB${qualityStr}, ${model.spec.slug})\n`,
|
|
194
|
-
);
|
|
176
|
+
this.process.stdout.write(`✓ Wrote ${outPath} (${dimStr}${kb} KB, ${model.spec.slug})\n`);
|
|
195
177
|
}
|
|
196
178
|
} finally {
|
|
197
179
|
rmSync(work, { recursive: true, force: true });
|
|
198
180
|
}
|
|
199
181
|
}
|
|
200
182
|
|
|
201
|
-
function sizeConstraintError(w: number, h: number): string | null {
|
|
202
|
-
if (w % 16 !== 0 || h % 16 !== 0) return 'each edge must be divisible by 16';
|
|
203
|
-
const long = Math.max(w, h);
|
|
204
|
-
const short = Math.min(w, h);
|
|
205
|
-
if (long / short > 3) return 'aspect ratio must be within 1:3–3:1';
|
|
206
|
-
if (long > 3840) return 'longest edge must be <= 3840px';
|
|
207
|
-
const px = w * h;
|
|
208
|
-
if (px < 655_360 || px > 8_294_400) return 'total pixels must be 655,360–8,294,400';
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
183
|
function imageSize(path: string): { width: number; height: number } | null {
|
|
213
184
|
return pngSize(path) ?? jpegSize(path);
|
|
214
185
|
}
|
|
@@ -254,15 +225,3 @@ function jpegSize(path: string): { width: number; height: number } | null {
|
|
|
254
225
|
return null;
|
|
255
226
|
}
|
|
256
227
|
}
|
|
257
|
-
|
|
258
|
-
async function magick(args: readonly string[]): Promise<boolean> {
|
|
259
|
-
for (const tool of ['magick', 'convert']) {
|
|
260
|
-
try {
|
|
261
|
-
const r = await runCaptured(tool, [...args], { timeoutMs: 60_000 });
|
|
262
|
-
if (!r.timedOut && r.code === 0) return true;
|
|
263
|
-
} catch (err) {
|
|
264
|
-
if (!isNotFound(err)) throw err;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildCommand } from '@stricli/core';
|
|
2
|
-
import {
|
|
2
|
+
import { listModelHelpLines } from '../../models.ts';
|
|
3
3
|
import { positiveIntSeconds } from '../../parsers.ts';
|
|
4
4
|
import implementImpl from './impl.ts';
|
|
5
5
|
|
|
@@ -17,8 +17,7 @@ export const implement = buildCommand({
|
|
|
17
17
|
model: {
|
|
18
18
|
kind: 'parsed',
|
|
19
19
|
parse: String,
|
|
20
|
-
|
|
21
|
-
brief: `Model slug (default: ${DEFAULT_IMPLEMENTER})`,
|
|
20
|
+
brief: 'Model slug (required) — see the seat list above',
|
|
22
21
|
},
|
|
23
22
|
timeout: {
|
|
24
23
|
kind: 'parsed',
|
|
@@ -3,12 +3,12 @@ import { isAbsolute, resolve } from 'node:path';
|
|
|
3
3
|
import { runCaptured } from '@aibridge/proc';
|
|
4
4
|
import type { LocalContext } from '../../context.ts';
|
|
5
5
|
import { delegate } from '../../delegate.ts';
|
|
6
|
-
import {
|
|
6
|
+
import { formatUnknownModelError, resolveModel } from '../../models.ts';
|
|
7
7
|
import { preflightModel, renderPreflightRefusal } from '../../quotaPreflight.ts';
|
|
8
8
|
import { startRun } from '../../runlog.ts';
|
|
9
9
|
|
|
10
10
|
export interface ImplementFlags {
|
|
11
|
-
readonly model
|
|
11
|
+
readonly model: string;
|
|
12
12
|
readonly timeout?: number;
|
|
13
13
|
readonly preflight: boolean;
|
|
14
14
|
}
|
|
@@ -18,7 +18,7 @@ export default async function implement(
|
|
|
18
18
|
flags: ImplementFlags,
|
|
19
19
|
planFile: string,
|
|
20
20
|
): Promise<void> {
|
|
21
|
-
const inputSlug = flags.model
|
|
21
|
+
const inputSlug = flags.model;
|
|
22
22
|
const model = resolveModel(inputSlug);
|
|
23
23
|
if (!model) {
|
|
24
24
|
this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildCommand } from '@stricli/core';
|
|
2
|
-
import {
|
|
2
|
+
import { listModelHelpLines } from '../../models.ts';
|
|
3
3
|
import { nonEmptyPrompt, positiveIntSeconds } from '../../parsers.ts';
|
|
4
4
|
import planImpl from './impl.ts';
|
|
5
5
|
|
|
@@ -17,14 +17,12 @@ export const plan = buildCommand({
|
|
|
17
17
|
model: {
|
|
18
18
|
kind: 'parsed',
|
|
19
19
|
parse: String,
|
|
20
|
-
|
|
21
|
-
brief: `Model slug (default: ${DEFAULT_MODEL})`,
|
|
20
|
+
brief: 'Model slug (required) — see the seat list above',
|
|
22
21
|
},
|
|
23
22
|
out: {
|
|
24
23
|
kind: 'parsed',
|
|
25
24
|
parse: String,
|
|
26
|
-
|
|
27
|
-
brief: 'Where to write the plan (default: <run.dir>/plan.md)',
|
|
25
|
+
brief: 'Where to write the plan file (required)',
|
|
28
26
|
},
|
|
29
27
|
timeout: {
|
|
30
28
|
kind: 'parsed',
|
|
@@ -3,13 +3,13 @@ import { isAbsolute, resolve } from 'node:path';
|
|
|
3
3
|
import { runCaptured } from '@aibridge/proc';
|
|
4
4
|
import type { LocalContext } from '../../context.ts';
|
|
5
5
|
import { delegate } from '../../delegate.ts';
|
|
6
|
-
import {
|
|
6
|
+
import { formatUnknownModelError, resolveModel } from '../../models.ts';
|
|
7
7
|
import { preflightModel, renderPreflightRefusal } from '../../quotaPreflight.ts';
|
|
8
8
|
import { startRun } from '../../runlog.ts';
|
|
9
9
|
|
|
10
10
|
export interface PlanFlags {
|
|
11
|
-
readonly model
|
|
12
|
-
readonly out
|
|
11
|
+
readonly model: string;
|
|
12
|
+
readonly out: string;
|
|
13
13
|
readonly timeout?: number;
|
|
14
14
|
readonly preflight: boolean;
|
|
15
15
|
}
|
|
@@ -63,7 +63,7 @@ export default async function plan(
|
|
|
63
63
|
flags: PlanFlags,
|
|
64
64
|
taskPrompt: string,
|
|
65
65
|
): Promise<void> {
|
|
66
|
-
const inputSlug = flags.model
|
|
66
|
+
const inputSlug = flags.model;
|
|
67
67
|
const model = resolveModel(inputSlug);
|
|
68
68
|
if (!model) {
|
|
69
69
|
this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
|
|
@@ -86,11 +86,7 @@ export default async function plan(
|
|
|
86
86
|
const promptSnippet = taskPrompt.replace(/\r?\n/g, ' ').slice(0, 80);
|
|
87
87
|
const run = startRun('plan', `${model.spec.slug}: ${promptSnippet}`);
|
|
88
88
|
|
|
89
|
-
const absOutPath = flags.out
|
|
90
|
-
? isAbsolute(flags.out)
|
|
91
|
-
? flags.out
|
|
92
|
-
: resolve(cwd, flags.out)
|
|
93
|
-
: resolve(run.dir, 'plan.md');
|
|
89
|
+
const absOutPath = isAbsolute(flags.out) ? flags.out : resolve(cwd, flags.out);
|
|
94
90
|
|
|
95
91
|
const beforePorcelain = await getPorcelainStatus(cwd);
|
|
96
92
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildCommand } from '@stricli/core';
|
|
2
|
-
import {
|
|
2
|
+
import { listModelHelpLines } from '../../models.ts';
|
|
3
3
|
import { positiveIntSeconds } from '../../parsers.ts';
|
|
4
4
|
import reviewImpl from './impl.ts';
|
|
5
5
|
|
|
@@ -17,8 +17,7 @@ export const review = buildCommand({
|
|
|
17
17
|
model: {
|
|
18
18
|
kind: 'parsed',
|
|
19
19
|
parse: String,
|
|
20
|
-
|
|
21
|
-
brief: `Model slug (default: ${DEFAULT_MODEL})`,
|
|
20
|
+
brief: 'Model slug (required) — see the seat list above',
|
|
22
21
|
},
|
|
23
22
|
plan: {
|
|
24
23
|
kind: 'parsed',
|
|
@@ -35,8 +34,7 @@ export const review = buildCommand({
|
|
|
35
34
|
out: {
|
|
36
35
|
kind: 'parsed',
|
|
37
36
|
parse: String,
|
|
38
|
-
|
|
39
|
-
brief: 'Where to write the review report (default: <run.dir>/review.md)',
|
|
37
|
+
brief: 'Where to write the review report (required)',
|
|
40
38
|
},
|
|
41
39
|
timeout: {
|
|
42
40
|
kind: 'parsed',
|
|
@@ -3,15 +3,15 @@ import { isAbsolute, resolve } from 'node:path';
|
|
|
3
3
|
import { runCaptured } from '@aibridge/proc';
|
|
4
4
|
import type { LocalContext } from '../../context.ts';
|
|
5
5
|
import { delegate } from '../../delegate.ts';
|
|
6
|
-
import {
|
|
6
|
+
import { formatUnknownModelError, resolveModel } from '../../models.ts';
|
|
7
7
|
import { preflightModel, renderPreflightRefusal } from '../../quotaPreflight.ts';
|
|
8
8
|
import { startRun } from '../../runlog.ts';
|
|
9
9
|
|
|
10
10
|
export interface ReviewFlags {
|
|
11
|
-
readonly model
|
|
11
|
+
readonly model: string;
|
|
12
12
|
readonly plan?: string;
|
|
13
13
|
readonly base?: string;
|
|
14
|
-
readonly out
|
|
14
|
+
readonly out: string;
|
|
15
15
|
readonly timeout?: number;
|
|
16
16
|
readonly preflight: boolean;
|
|
17
17
|
}
|
|
@@ -78,7 +78,7 @@ export function parseReviewVerdict(response: string): ReviewVerdictResult {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export default async function review(this: LocalContext, flags: ReviewFlags): Promise<void> {
|
|
81
|
-
const inputSlug = flags.model
|
|
81
|
+
const inputSlug = flags.model;
|
|
82
82
|
const model = resolveModel(inputSlug);
|
|
83
83
|
if (!model) {
|
|
84
84
|
this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
|
|
@@ -140,11 +140,7 @@ export default async function review(this: LocalContext, flags: ReviewFlags): Pr
|
|
|
140
140
|
|
|
141
141
|
const run = startRun('review', `${model.spec.slug}: ${modeDetail}`);
|
|
142
142
|
|
|
143
|
-
const absOutPath = flags.out
|
|
144
|
-
? isAbsolute(flags.out)
|
|
145
|
-
? flags.out
|
|
146
|
-
: resolve(cwd, flags.out)
|
|
147
|
-
: resolve(run.dir, 'review.md');
|
|
143
|
+
const absOutPath = isAbsolute(flags.out) ? flags.out : resolve(cwd, flags.out);
|
|
148
144
|
|
|
149
145
|
let reviewPrompt: string;
|
|
150
146
|
if (isDirty) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildCommand } from '@stricli/core';
|
|
2
|
-
import {
|
|
2
|
+
import { listModelHelpLines } from '../../models.ts';
|
|
3
3
|
import { nonEmptyPrompt, positiveIntSeconds } from '../../parsers.ts';
|
|
4
4
|
import subagentImpl from './impl.ts';
|
|
5
5
|
|
|
@@ -8,7 +8,7 @@ const fullDescription = [
|
|
|
8
8
|
'',
|
|
9
9
|
'Available models (canonical slug):',
|
|
10
10
|
...listModelHelpLines(),
|
|
11
|
-
|
|
11
|
+
'Recommended first choice: xai-grok/grok-4.5 (off-budget). The claude-backend slugs are FALLBACKS for',
|
|
12
12
|
'when the off-budget CLIs are quota-exhausted — they bill your Claude subscription.',
|
|
13
13
|
].join('\n');
|
|
14
14
|
|
|
@@ -19,8 +19,7 @@ export const subagent = buildCommand({
|
|
|
19
19
|
model: {
|
|
20
20
|
kind: 'parsed',
|
|
21
21
|
parse: String,
|
|
22
|
-
|
|
23
|
-
brief: `Model slug to delegate to (default: ${DEFAULT_MODEL})`,
|
|
22
|
+
brief: 'Model slug (required) — see the seat list above',
|
|
24
23
|
},
|
|
25
24
|
timeout: {
|
|
26
25
|
kind: 'parsed',
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import type { LocalContext } from '../../context.ts';
|
|
2
2
|
import { delegate } from '../../delegate.ts';
|
|
3
|
-
import {
|
|
4
|
-
backendModelId,
|
|
5
|
-
DEFAULT_MODEL,
|
|
6
|
-
formatUnknownModelError,
|
|
7
|
-
resolveModel,
|
|
8
|
-
} from '../../models.ts';
|
|
3
|
+
import { backendModelId, formatUnknownModelError, resolveModel } from '../../models.ts';
|
|
9
4
|
import { preflightModel, renderPreflightRefusal } from '../../quotaPreflight.ts';
|
|
10
5
|
import { startRun } from '../../runlog.ts';
|
|
11
6
|
|
|
12
7
|
export interface SubagentFlags {
|
|
13
|
-
readonly model
|
|
8
|
+
readonly model: string;
|
|
14
9
|
readonly timeout?: number;
|
|
15
10
|
readonly tools: boolean;
|
|
16
11
|
readonly preflight: boolean;
|
|
@@ -22,7 +17,7 @@ export default async function subagent(
|
|
|
22
17
|
flags: SubagentFlags,
|
|
23
18
|
prompt: string,
|
|
24
19
|
): Promise<void> {
|
|
25
|
-
const inputSlug = flags.model
|
|
20
|
+
const inputSlug = flags.model;
|
|
26
21
|
const model = resolveModel(inputSlug);
|
|
27
22
|
if (!model) {
|
|
28
23
|
this.process.stderr.write(`${formatUnknownModelError(inputSlug)}\n`);
|
|
@@ -72,10 +67,9 @@ export default async function subagent(
|
|
|
72
67
|
}
|
|
73
68
|
|
|
74
69
|
if (flags.json) {
|
|
75
|
-
const modelId = backendModelId(model) ?? null;
|
|
76
70
|
this.process.stdout.write(
|
|
77
71
|
`${JSON.stringify({
|
|
78
|
-
model:
|
|
72
|
+
model: backendModelId(model),
|
|
79
73
|
slug: model.spec.slug,
|
|
80
74
|
response: outcome.response,
|
|
81
75
|
exitCode: outcome.exitCode,
|
package/src/delegate.ts
CHANGED
|
@@ -29,7 +29,7 @@ export async function delegate(
|
|
|
29
29
|
tools: opts.tools,
|
|
30
30
|
timeoutSec: opts.timeoutSec,
|
|
31
31
|
cwd: opts.cwd,
|
|
32
|
-
backendModel: backendModelId(opts.model)
|
|
32
|
+
backendModel: backendModelId(opts.model),
|
|
33
33
|
effort: opts.model.effort,
|
|
34
34
|
onStdout: c => opts.run.stdout(c),
|
|
35
35
|
onStderr: c => opts.run.stderr(c),
|
package/src/driver.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface DelegationTask {
|
|
|
12
12
|
readonly tools: boolean;
|
|
13
13
|
readonly timeoutSec: number;
|
|
14
14
|
readonly cwd: string;
|
|
15
|
-
readonly backendModel: string
|
|
15
|
+
readonly backendModel: string;
|
|
16
16
|
readonly effort: Effort | undefined;
|
|
17
17
|
readonly onStdout?: (chunk: string) => void;
|
|
18
18
|
readonly onStderr?: (chunk: string) => void;
|
|
@@ -33,10 +33,9 @@ export type QuotaSnapshot = AgyQuotaSnapshot | CodexQuotaSnapshot | ClaudeQuotaS
|
|
|
33
33
|
export interface ImageGenRequest {
|
|
34
34
|
readonly prompt: string;
|
|
35
35
|
readonly workDir: string;
|
|
36
|
-
readonly backendModel: string
|
|
36
|
+
readonly backendModel: string;
|
|
37
37
|
readonly effort: Effort | undefined;
|
|
38
|
-
readonly
|
|
39
|
-
readonly size: { readonly w: number; readonly h: number } | undefined;
|
|
38
|
+
readonly aspectRatio: string | undefined;
|
|
40
39
|
readonly imagePaths: readonly string[];
|
|
41
40
|
readonly timeoutSec: number;
|
|
42
41
|
readonly forceful: boolean;
|