@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.
@@ -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
@@ -0,0 +1,5 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ const require = createRequire(import.meta.url);
4
+
5
+ export const PACKAGE_VERSION = (require('../package.json') as { version: string }).version;
@@ -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 send the delegate in unauthenticated. Or use a different --model.',
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',
@@ -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
- return `aibridge ${cmd}: refusing ${verdict.message}. Running with --no-preflight would only send the delegate in unauthenticated. Or use a different --model.`;
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
- 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).`;
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
  }