@aibridge/cli 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -28
- package/dist/cli.mjs +1 -1
- package/dist/{context-DJtEcg6f.mjs → context-DeJmEXLe.mjs} +194 -125
- package/dist/index.mjs +1 -1
- package/instructions/SKILL.md +115 -0
- package/instructions/reference/image-gen.md +107 -0
- package/instructions/reference/implement.md +51 -0
- package/instructions/reference/plan.md +69 -0
- package/instructions/reference/review.md +75 -0
- package/instructions/reference/subagent.md +48 -0
- package/instructions/reference/why.md +54 -0
- package/package.json +8 -7
- package/src/app.exit-code.test.ts +10 -1
- package/src/app.ts +4 -5
- package/src/commands/image-gen/command.ts +5 -0
- package/src/commands/image-gen/impl.test.ts +1 -0
- package/src/commands/image-gen/impl.ts +14 -0
- package/src/commands/review/command.ts +2 -2
- package/src/commands/review/impl.ts +1 -1
- package/src/commands/skill/command.ts +28 -0
- package/src/commands/skill/impl.test.ts +59 -0
- package/src/commands/skill/impl.ts +60 -0
- package/src/package.ts +5 -0
- package/src/quotaPreflight.test.ts +13 -1
- package/src/quotaPreflight.ts +10 -2
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import type { LocalContext } from '../../context.ts';
|
|
3
|
+
import { PACKAGE_VERSION } from '../../package.ts';
|
|
4
|
+
|
|
5
|
+
const TOPICS = {
|
|
6
|
+
plan: 'reference/plan.md',
|
|
7
|
+
implement: 'reference/implement.md',
|
|
8
|
+
review: 'reference/review.md',
|
|
9
|
+
subagent: 'reference/subagent.md',
|
|
10
|
+
'image-gen': 'reference/image-gen.md',
|
|
11
|
+
why: 'reference/why.md',
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export type SkillTopic = keyof typeof TOPICS;
|
|
15
|
+
|
|
16
|
+
function instructionPath(relativePath: string): URL {
|
|
17
|
+
const candidates = [
|
|
18
|
+
// Built package: dist/cli.mjs -> instructions/
|
|
19
|
+
new URL(`../instructions/${relativePath}`, import.meta.url),
|
|
20
|
+
// Source tree: src/commands/skill/impl.ts -> instructions/
|
|
21
|
+
new URL(`../../../instructions/${relativePath}`, import.meta.url),
|
|
22
|
+
];
|
|
23
|
+
const found = candidates.find(candidate => existsSync(candidate));
|
|
24
|
+
if (!found) {
|
|
25
|
+
throw new Error(`bundled instruction file is missing: ${relativePath}`);
|
|
26
|
+
}
|
|
27
|
+
return found;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readInstruction(relativePath: string): string {
|
|
31
|
+
return readFileSync(instructionPath(relativePath), 'utf8').trimEnd();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function skillImpl(this: LocalContext, topic?: string): void {
|
|
35
|
+
if (topic !== undefined && !(topic in TOPICS)) {
|
|
36
|
+
this.process.stderr.write(
|
|
37
|
+
`aibridge skill: unknown topic ${JSON.stringify(topic)}; expected one of: ${Object.keys(TOPICS).join(', ')}\n`,
|
|
38
|
+
);
|
|
39
|
+
this.process.exitCode = 2;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const runner = `npx -y @aibridge/cli@${PACKAGE_VERSION}`;
|
|
45
|
+
const sections = [
|
|
46
|
+
`Command runner for these instructions: \`${runner}\`\nUse that exact prefix for every aibridge command below; do not substitute a global binary.`,
|
|
47
|
+
readInstruction('SKILL.md'),
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
if (topic !== undefined) {
|
|
51
|
+
sections.push(readInstruction(TOPICS[topic as SkillTopic]));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.process.stdout.write(`${sections.join('\n\n---\n\n')}\n`);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
57
|
+
this.process.stderr.write(`aibridge skill: ${message}\n`);
|
|
58
|
+
this.process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/package.ts
ADDED
|
@@ -234,10 +234,22 @@ test('renderPreflightRefusal: auth kind uses unauthenticated wording', () => {
|
|
|
234
234
|
});
|
|
235
235
|
assert.strictEqual(
|
|
236
236
|
msg,
|
|
237
|
-
'aibridge plan: refusing — grok session expired (401) — run `grok login`, then retry. Running with --no-preflight would only
|
|
237
|
+
'aibridge plan: refusing — grok session expired (401) — run `grok login`, then retry. Running with --no-preflight would only fail unauthenticated later. Or use a different --model.',
|
|
238
238
|
);
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
+
test('renderPreflightRefusal: image-gen quota refusal points at other image seats', () => {
|
|
242
|
+
const msg = renderPreflightRefusal('image-gen', {
|
|
243
|
+
kind: 'quota',
|
|
244
|
+
message: 'grok credit quota exhausted',
|
|
245
|
+
resetAt: undefined,
|
|
246
|
+
});
|
|
247
|
+
// No claude seat renders images, so the delegation fallback would be dead advice.
|
|
248
|
+
assert.ok(!msg.includes('claude-backend fallback'));
|
|
249
|
+
assert.ok(msg.includes('another image seat'));
|
|
250
|
+
assert.ok(msg.includes('openai-codex/gpt-5.6-sol'));
|
|
251
|
+
});
|
|
252
|
+
|
|
241
253
|
test('renderPreflightRefusal: quota kind keeps override wording', () => {
|
|
242
254
|
const msg = renderPreflightRefusal('subagent', {
|
|
243
255
|
kind: 'quota',
|
package/src/quotaPreflight.ts
CHANGED
|
@@ -141,8 +141,16 @@ export function renderPreflightRefusal(
|
|
|
141
141
|
verdict: { kind: 'auth' | 'quota'; message: string; resetAt: string | undefined },
|
|
142
142
|
): string {
|
|
143
143
|
if (verdict.kind === 'auth') {
|
|
144
|
-
|
|
144
|
+
// "the delegate" was wrong for image-gen, which has no delegate — the grok
|
|
145
|
+
// seat is a direct API call. Verified: --no-preflight there just fails at
|
|
146
|
+
// the render with exit 1.
|
|
147
|
+
return `aibridge ${cmd}: refusing — ${verdict.message}. Running with --no-preflight would only fail unauthenticated later. Or use a different --model.`;
|
|
145
148
|
}
|
|
146
149
|
const resetClause = verdict.resetAt ? ` Resets ${formatReset(verdict.resetAt)}.` : '';
|
|
147
|
-
|
|
150
|
+
// The claude fallback is delegation-only advice: no claude seat renders images.
|
|
151
|
+
const fallback =
|
|
152
|
+
cmd === 'image-gen'
|
|
153
|
+
? 'Use --no-preflight to override, or another image seat (--model openai-codex/gpt-5.6-sol | google-antigravity/gemini-3.7-flash | xai-grok/grok-4.6).'
|
|
154
|
+
: 'Use --no-preflight to override, or a claude-backend fallback (subagent --model sonnet|opus — bills the Claude subscription).';
|
|
155
|
+
return `aibridge ${cmd}: refusing — ${verdict.message}.${resetClause} ${fallback}`;
|
|
148
156
|
}
|