@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,107 @@
1
+ # image-gen — generate an image via a model seat
2
+
3
+ Part A covers the command. Part B covers prompt design, which applies even when
4
+ you only need to return a prompt.
5
+
6
+ ## A. Calling it
7
+
8
+ ```bash
9
+ aibridge image-gen --model <slug> --out <file.png> "<full prompt — see part B>" \
10
+ [--aspect-ratio 16:9] [--image ref.png] [--transparent] \
11
+ [--timeout 600] [--no-preflight] [--json]
12
+ ```
13
+
14
+ | slug | renders via | format |
15
+ |---|---|---|
16
+ | `openai-codex/gpt-5.6-sol` (recommended) | Codex CLI | PNG |
17
+ | `google-antigravity/gemini-3.7-flash` | Antigravity CLI (`agy`) | JPEG |
18
+ | `xai-grok/grok-4.6` | `api.x.ai` directly, on `~/.grok/auth.json` | JPEG |
19
+
20
+ Other seats fail fast with a list of capable models. The grok seat renders
21
+ without `grok` on `PATH`; the CLI is spawned only to refresh the token, before a
22
+ request when `expires_at` is close and again as a backstop if one comes back
23
+ 401. `grok login` is still what sets the seat up.
24
+
25
+ - `--out` is required and its extension must match the seat's format above
26
+ (`.png` for any `--transparent` run). A mismatch is rejected before anything
27
+ runs. The file holds the model's own bytes verbatim, except on a
28
+ chroma-keyed `--transparent` run, which writes the locally keyed PNG.
29
+ - **Render into the asset's real home** (`public/icons/settings.png`) when the
30
+ project keeps it. Drafts go to `.aibridge/`; see [SKILL.md](../SKILL.md).
31
+ - `--aspect-ratio N:M` sets geometry. Exact pixels are whatever the model
32
+ renders; resize downstream.
33
+ - `--image a.png,b.png` attaches references and routes to the seat's edit path.
34
+ - `--json` prints `{ out, bytes, width, height, aspectRatio, model, backend, transparency, real }`.
35
+ - Preflight is on by default: an exhausted seat exits 3 and names the
36
+ alternatives instead of spending a paid render. Argument checks run first, so
37
+ bad flags still cost no network call.
38
+
39
+ ### Transparency
40
+
41
+ - `--transparent` works on every image seat and always writes PNG. Codex gives
42
+ native alpha; grok and gemini are chroma-keyed with binary edges. Fine for
43
+ flat icons, logos and stickers; not for hair, smoke or glass.
44
+ - **Say nothing about the background in the prompt when using it.** The CLI
45
+ writes the backdrop instruction per seat, and a colour of your own overrides
46
+ it: the key then finds nothing and the paid render comes back opaque. Writing
47
+ "transparent background" into the prompt without the flag is refused on
48
+ chroma seats.
49
+ - **A green subject is keyed away with the backdrop** on chroma seats. Anything
50
+ that must be green needs `openai-codex/*`.
51
+ - Both surfaces report the path taken: the result line says
52
+ `transparency: native` / `chroma-keyed`, `--json` carries
53
+ `"native" | "chroma" | null`. Quote it when the edges matter. Keying under 2%
54
+ of the image warns: the model likely ignored the backdrop instruction.
55
+
56
+ ### Reference images
57
+
58
+ ```bash
59
+ aibridge image-gen --model openai-codex/gpt-5.6-sol \
60
+ "the same woman, now in a denim shirt in a bright kitchen, waist-up" \
61
+ --out avatar2.png --image avatar.png --aspect-ratio 9:16
62
+ ```
63
+
64
+ With a reference, write the prompt as a **diff**: say only what changes. The
65
+ reference carries identity, framing and style.
66
+
67
+ ## B. Writing the prompt
68
+
69
+ 1. **Never state the image's purpose** (no "app icon", "hero banner"). Describe
70
+ how it looks, not its job.
71
+ 2. **Give the aspect ratio** via `--aspect-ratio` and/or the prompt text.
72
+ 3. **Always specify the background**: a hex colour, or `opaque`/`auto`. Except
73
+ on a `--transparent` run, where you say nothing about it.
74
+ 4. **Specify** subject, composition, palette, style/medium, mood, lighting.
75
+ 5. **Lock the critical, free the rest.** Lock verbatim text in straight quotes,
76
+ brand hex, required layout, the hero subject, and every "no X". Over-specified
77
+ incidental detail flattens the result.
78
+
79
+ ### Worked example
80
+
81
+ > `Square 1:1 aspect ratio. Solid warm cream #F7F1EA background. A single flat-design
82
+ > fanned stack of three rounded-corner cards in coral orange #FF5A1F, centered,
83
+ > ~55% of the frame, soft drop shadow. Calm, minimal, modern. No text, no border,
84
+ > no gradient.`
85
+
86
+ Locked: ratio, background hex, subject, brand orange, the no-text/border/gradient
87
+ constraints. Free: fan angle, shadow softness, spacing. On a `--transparent` run,
88
+ drop the background sentence and the drop shadow: a soft shadow has no flat
89
+ backdrop colour to key against and survives as a pale halo.
90
+
91
+ ### Edits and retries
92
+
93
+ 1. **Describe the desired result.** State what the finished image contains,
94
+ rather than only what to remove or correct.
95
+ 2. **Name the visual criterion first.** Phrases such as "the near plane" or
96
+ "everyone waiting" often work better than an object inventory. Enumerate
97
+ objects only if the criterion fails.
98
+ 3. **Rewrite failed prompts; do not append to them.** Simplify the request. For
99
+ a near-correct render, make the next instruction as short as possible.
100
+ 4. **Change strategy after the same failure occurs twice.** Try a different
101
+ reference, composition, or fresh generation instead of another rewording.
102
+ 5. **Retry from the original reference.** Chaining generated outputs causes
103
+ unrequested details to drift.
104
+ 6. **Check your terminology.** A near-miss word can produce a coherent image of
105
+ the wrong concept.
106
+ 7. **Verify unchanged details in context.** Inspect the asset in its final
107
+ placement and, when relevant, in motion.
@@ -0,0 +1,51 @@
1
+ # implement — execute a plan file
2
+
3
+ An implementer edits the working tree and runs the project's checks. This is the
4
+ third step in **`plan` → read and approve → `implement` → `review`**.
5
+
6
+ Run this only after reading and approving the plan. If the implementer would
7
+ need to make a product or architecture decision, fix the plan first.
8
+
9
+ ## Usage
10
+
11
+ ```bash
12
+ aibridge implement --model <slug> <plan-file>
13
+ --model <slug> implementer model (required, e.g. google-antigravity/gemini-3.7-flash)
14
+ --timeout <secs> max seconds (default: 1800)
15
+ --no-preflight skip the quota preflight
16
+ ```
17
+
18
+ ## Behaviour
19
+
20
+ The delegate runs with full tools at the repository root. It must follow the
21
+ plan, edit only named files, run the specified checks until they pass, and avoid
22
+ commits, pushes, and unrelated deletions.
23
+
24
+ ## Output
25
+
26
+ ```
27
+ <delegate's short summary>
28
+
29
+ <git diff --stat>
30
+ untracked files: <count>
31
+ run: <run id>
32
+ ```
33
+
34
+ Exit 0: completed with tree changes. Exit 1: delegate failed, timed out, gave no
35
+ usable answer, or changed nothing (a no-op implement is a failure). Exit 2: bad
36
+ args or missing plan file. Exit 3: quota preflight refusal.
37
+
38
+ ## After it returns
39
+
40
+ 1. **Re-run the checks yourself.** The summary reports the delegate's claim.
41
+ 2. Then `aibridge review --model xai-grok/grok-4.6 --plan <plan-file> --out .aibridge/review.md`.
42
+
43
+ ## Gotchas
44
+
45
+ - Keep the implementer a different model family from the reviewer. The
46
+ recommended seats (gemini implements, grok reviews) comply; if you override
47
+ one, check the other.
48
+ - agy quota is shared by model group. Two concurrent agy-heavy runs drain
49
+ the same window. Run `aibridge quota` before pipelining.
50
+ - The timeout covers the whole run including gate-fixing loops. Raise it for big
51
+ plans rather than letting a near-done run get killed.
@@ -0,0 +1,69 @@
1
+ # plan — write a detailed implementation plan file
2
+
3
+ A planner studies the codebase and writes a detailed plan to a file. This is the
4
+ first step in **`plan` → read and approve → `implement` → `review`**. Pass the
5
+ file path—not its contents—between stages.
6
+
7
+ Use it for sizeable or risky work. For small, fully-specified chunks use
8
+ `subagent`, or just do them.
9
+
10
+ ## Usage
11
+
12
+ ```bash
13
+ aibridge plan --model <slug> --out <file> "<task prompt>"
14
+ --model <slug> planner model (required, e.g. xai-grok/grok-4.6)
15
+ --out <file> where to write the plan (required)
16
+ --timeout <secs> max seconds (default: 1800)
17
+ --no-preflight skip the quota preflight
18
+ ```
19
+
20
+ The positional argument is the task prompt, not a file path.
21
+
22
+ ## Writing the task prompt
23
+
24
+ The planner can read the code. Do not paste file contents. Provide only what it
25
+ cannot infer:
26
+
27
+ - the goal and the user-visible behaviour change;
28
+ - hard constraints (APIs to keep stable, zero-dep rules, style conventions);
29
+ - scope boundaries and non-goals;
30
+ - architectural calls you have already made, since the planner details your
31
+ design rather than overruling it;
32
+ - starting files, if the repo is large.
33
+
34
+ ## Behaviour
35
+
36
+ - The planner writes exactly one file (`--out`). Any other change to the working
37
+ tree fails the run (exit 1, paths listed). An `--out` inside the repo is
38
+ exempt.
39
+ - The plan must end with `## Open questions` (`None.` when confident). The count
40
+ prints on stdout.
41
+
42
+ ## Output
43
+
44
+ ```
45
+ plan: /abs/path/to/plan.md
46
+ open questions: 2
47
+ run: <run id>
48
+ ```
49
+
50
+ Exit 0 even with open questions. Exit 1: plan missing, empty, no open-questions
51
+ section, tree dirtied, delegate failure or timeout. Exit 2: bad arguments.
52
+ Exit 3: quota preflight refusal.
53
+
54
+ ## After it returns
55
+
56
+ 1. **Read the plan file.** It is the implementation contract.
57
+ 2. Resolve every open question: edit the file directly, or re-run `plan` with a
58
+ sharpened prompt.
59
+ 3. High-risk design? Gate it first:
60
+ `aibridge review --model xai-grok/grok-4.6 --plan <file> --out .aibridge/review.md`
61
+ on a clean tree.
62
+ 4. Then `aibridge implement --model google-antigravity/gemini-3.7-flash <file>`.
63
+
64
+ ## Gotchas
65
+
66
+ - `--out` belongs in `.aibridge/` (see [SKILL.md](../SKILL.md)); check it is
67
+ gitignored once per session.
68
+ - grok is capped at ~30 req/min, ~1k msgs/day, one run at a time. Never run two
69
+ grok stages concurrently.
@@ -0,0 +1,75 @@
1
+ # review — cross-model review of a diff or a plan
2
+
3
+ A reviewer inspects a diff, optionally against a plan contract, and writes its
4
+ report to a file. Standard output contains only the verdict and paths.
5
+
6
+ ## Usage
7
+
8
+ ```bash
9
+ aibridge review --model <slug> --out <file> [options]
10
+ --model <slug> reviewer model (required, e.g. xai-grok/grok-4.6)
11
+ --plan <file> plan contract; over-reach against it is a finding
12
+ --base <ref> any git ref or range to diff against (default: HEAD)
13
+ --out <file> full report destination (required)
14
+ --timeout <secs> max seconds (default: 1200)
15
+ --no-preflight skip the quota preflight
16
+ ```
17
+
18
+ ## Picking the diff
19
+
20
+ `--base` is passed straight to `git diff`, so any ref or range git accepts
21
+ works: `HEAD~3`, a branch, a SHA, a tag, `v1.2.0..HEAD`.
22
+
23
+ ```bash
24
+ # uncommitted work only (the default base, HEAD)
25
+ aibridge review --model xai-grok/grok-4.6 --out .aibridge/review.md
26
+
27
+ # the 3 commits you just made
28
+ aibridge review --model xai-grok/grok-4.6 --base HEAD~3 --out .aibridge/review.md
29
+
30
+ # the whole branch, against the plan contract
31
+ aibridge review --model xai-grok/grok-4.6 --base main \
32
+ --plan .aibridge/plan.md --out .aibridge/review.md
33
+ ```
34
+
35
+ Use `--base` to review committed work. Do not copy a diff into a file for
36
+ `subagent`. Uncommitted changes are included unless the range fixes both ends.
37
+
38
+ ## Modes (detected before any model spend)
39
+
40
+ 1. **Diff review:** changes or untracked files exist relative to `--base`. With
41
+ `--plan`, unrequested changes count as over-reach.
42
+ 2. **Plan review:** nothing differs from `--base` and `--plan` is given.
43
+ Reviews the plan for soundness, edge cases, safety, feasibility. Use it as a
44
+ pre-implementation gate on high-risk designs.
45
+ 3. **No input:** nothing differs and no `--plan` is given. Exits 2.
46
+
47
+ ## Output & exit codes
48
+
49
+ ```
50
+ PASS | FINDINGS: 1 critical, 0 major, 3 minor
51
+ review: /abs/path/to/review.md
52
+ run: <run id>
53
+ ```
54
+
55
+ - `0`: PASS, or minor-only findings.
56
+ - `1`: critical or major findings; unparseable verdict (raw answer + paths still
57
+ print); missing or empty report file; delegate failure or timeout.
58
+ - `2`: bad flags, missing plan file, nothing to review, bad base ref.
59
+ - `3`: quota preflight refusal.
60
+
61
+ ## After it returns
62
+
63
+ - `PASS` → proceed (commit, or report done).
64
+ - Findings → read the report, then judge. Over-reach findings can be scope you
65
+ added deliberately: the reviewer flags, you decide. Fix what is real, re-run.
66
+ - Never let a model review its own diff. The recommended seats (grok reviews,
67
+ gemini implements) already comply; if you override one, check the other.
68
+
69
+ ## Gotchas
70
+
71
+ - The report file is required even for PASS. Missing or empty fails the run.
72
+ - Verdict parsing accepts the line first, last, or at the end of a narration
73
+ blob (grok concatenates progress prose). If it still fails, you get exit 1
74
+ with the raw answer plus the report path on stdout.
75
+ - A bad `--base` ref is a hard error (exit 2), not a silent dirty-tree fallback.
@@ -0,0 +1,48 @@
1
+ # subagent — delegate a task to another model
2
+
3
+ Delegate one self-contained task to another provider's model. Use this for a
4
+ second opinion, red-team review, long-context analysis, or a clearly specified
5
+ piece of work.
6
+
7
+ ## Usage
8
+
9
+ ```bash
10
+ aibridge subagent --model <slug> "<self-contained prompt>" [options]
11
+ --model <slug> required, canonical slug (no short aliases)
12
+ --timeout <secs> max seconds (default: 600)
13
+ --no-tools reasoning only: no file or shell access
14
+ --no-preflight skip the quota preflight
15
+ --json machine-readable, e.g. {"model": "grok-4.6", "slug": "xai-grok/grok-4.6", ...}
16
+ ```
17
+
18
+ The answer prints to stdout. There is no `--out`; redirect if you want a file.
19
+
20
+ Effort suffixes work on seats that support them
21
+ (`xai-grok/grok-4.6-medium`, `google-antigravity/gemini-3.7-flash-low`,
22
+ `anthropic-claude/sonnet-5-max`). The seat table is in [SKILL.md](../SKILL.md);
23
+ `aibridge subagent --help` prints the live list.
24
+
25
+ **Tools are ON by default** — the delegate reads/writes files and runs shell.
26
+
27
+ ## Writing the prompt
28
+
29
+ Write for a capable model with no conversation context:
30
+
31
+ 1. **Self-contained.** Paste the code or spec to act on. Never reference "what
32
+ we discussed".
33
+ 2. **Specify the approach.** Include design decisions, interfaces, files, and
34
+ constraints without prescribing every line of code.
35
+ 3. **State the constraints.** Run the real typecheck and tests until green;
36
+ write code only, no commit/push/deploy/delete; reply with a short summary.
37
+ 4. **Verify the result.** Re-run the real gates yourself.
38
+
39
+ ## When to stay native instead
40
+
41
+ - The task needs session-specific tools, skills, or MCP servers.
42
+ - It needs conversational context or live user guidance.
43
+ - It needs strict schema validation or guaranteed retry orchestration.
44
+
45
+ ## Scaling up
46
+
47
+ For large or multi-file work, use the three verbs instead of raw subagent calls:
48
+ [plan](plan.md) → you approve → [implement](implement.md) → [review](review.md).
@@ -0,0 +1,54 @@
1
+ # why — the reasoning behind the rules
2
+
3
+ This page explains rules that the command guides state without rationale. Read
4
+ it when a rule appears unsuitable for your case.
5
+
6
+ **Paths travel between stages, not contents.** A plan re-emitted into your
7
+ context costs you the tokens the split was meant to save. `plan` writes a file,
8
+ `implement` reads that path, `review` takes it as `--plan`. Nothing round-trips
9
+ through you except your judgment.
10
+
11
+ **You read the plan file.** That sign-off is the entire point of splitting plan
12
+ from implement. Skip it and you have an unreviewed contract that a second model
13
+ will now execute literally.
14
+
15
+ **The implementer is a pure do-er.** It executes a fully-designed plan and does
16
+ not hold the surrounding vision. When a plan turns out to need that vision, the
17
+ design work upstream was insufficient. Fix the plan rather than briefing the
18
+ implementer.
19
+
20
+ **Reviewers are cross-model on purpose.** A model reviewing its own diff shares
21
+ its own blind spots and will wave through the thing it just failed to see. This
22
+ applies to you too: prefer a reviewer outside your own model family for code you
23
+ wrote yourself.
24
+
25
+ **Over-reach is a finding.** A delegate that also refactors three neighbouring
26
+ files has left the contract, and the diff you now have to verify is bigger than
27
+ the one you asked for. The reviewer flags it; you decide whether it was scope
28
+ you wanted.
29
+
30
+ **Quota is relative to whoever runs the skill.** Every backend spends its own
31
+ CLI's login. A backend on the same provider as you (`anthropic-claude/*` for a
32
+ Claude-based agent) drains the pool you are already burning, so it buys no extra
33
+ capacity and no independent perspective. That is why it is a last resort rather
34
+ than merely a choice.
35
+
36
+ **Seats pin exact model versions.** A vendor alias like `opus` moves under you
37
+ when a release lands, silently changing what a documented pipeline does.
38
+
39
+ **Some commands write files, some print.** `plan`, `review` and `image-gen`
40
+ produce artifacts worth keeping and re-reading, so they take `--out` and keep
41
+ stdout to a verdict line. `subagent` returns an answer you consume immediately,
42
+ and `implement`'s output IS the working tree.
43
+
44
+ **The CLI catches fake successes, not bad work.** Backends fail in ways that
45
+ look like success: an agy model with no quota returns an empty answer and exit 0;
46
+ codex sometimes draws a tiny image in code instead of rendering one. So a
47
+ too-small render is rejected as fake, an empty answer is an error, and an
48
+ `implement` that changed nothing exits 1. A review with no report file fails as
49
+ well, because a verdict without evidence is not a review. None of that judges
50
+ the work itself, which is why you still re-run the real gates on a diff.
51
+
52
+ **Chroma keying is a fallback, not a feature.** Only codex renders true alpha.
53
+ On the JPEG seats aibridge asks for a flat backdrop and removes it locally,
54
+ which gives binary edges and eats any subject the same colour as the backdrop.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aibridge/cli",
3
- "version": "0.8.0",
4
- "description": "CLI that bridges tasks to AI CLIs on your machine (plan / implement / review / subagent / image-gen)",
3
+ "version": "0.10.0",
4
+ "description": "Agent instructions and CLI for delegating work to authenticated AI CLIs",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "engines": {
@@ -31,16 +31,17 @@
31
31
  },
32
32
  "files": [
33
33
  "dist",
34
+ "instructions",
34
35
  "src"
35
36
  ],
36
37
  "dependencies": {
37
38
  "@stricli/core": "1.3.0",
38
39
  "sharp": "^0.35.3",
39
- "@aibridge/proc": "0.8.0",
40
- "@aibridge/driver-agy": "0.8.0",
41
- "@aibridge/driver-codex": "0.8.0",
42
- "@aibridge/driver-claude": "0.8.0",
43
- "@aibridge/driver-grok": "0.8.0"
40
+ "@aibridge/driver-agy": "0.9.0",
41
+ "@aibridge/proc": "0.9.0",
42
+ "@aibridge/driver-codex": "0.9.0",
43
+ "@aibridge/driver-claude": "0.9.0",
44
+ "@aibridge/driver-grok": "0.9.0"
44
45
  },
45
46
  "devDependencies": {
46
47
  "tsdown": "0.22.14"
@@ -84,7 +84,16 @@ describe('stricli exit-code lock & routing', () => {
84
84
  const ctx = fakeCtx();
85
85
  await runCli(ctx, ['--help']);
86
86
  const output = ctx._stdout.join('');
87
- for (const cmd of ['plan', 'implement', 'review', 'subagent', 'image-gen', 'runs', 'quota']) {
87
+ for (const cmd of [
88
+ 'plan',
89
+ 'implement',
90
+ 'review',
91
+ 'subagent',
92
+ 'image-gen',
93
+ 'runs',
94
+ 'quota',
95
+ 'skill',
96
+ ]) {
88
97
  expect(output).toContain(cmd);
89
98
  }
90
99
  });
package/src/app.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { createRequire } from 'node:module';
2
1
  import { buildApplication, buildRouteMap, run } from '@stricli/core';
3
2
  import { imageGen } from './commands/image-gen/command.ts';
4
3
  import { implement } from './commands/implement/command.ts';
@@ -7,12 +6,11 @@ import { plan } from './commands/plan/command.ts';
7
6
  import { quota } from './commands/quota/command.ts';
8
7
  import { review } from './commands/review/command.ts';
9
8
  import { runs } from './commands/runs/command.ts';
9
+ import { skill } from './commands/skill/command.ts';
10
10
  import { subagent } from './commands/subagent/command.ts';
11
11
  import type { LocalContext } from './context.ts';
12
12
  import { normalizeExitCode } from './exitCode.ts';
13
-
14
- const require = createRequire(import.meta.url);
15
- const { version } = require('../package.json') as { version: string };
13
+ import { PACKAGE_VERSION } from './package.ts';
16
14
 
17
15
  const BRIEF =
18
16
  'Bridge tasks to the other AI CLIs on this machine — a plan → implement → review workflow, task delegation, and image generation (codex / agy / grok seats).';
@@ -27,6 +25,7 @@ const routes = buildRouteMap({
27
25
  runs,
28
26
  quota,
29
27
  models,
28
+ skill,
30
29
  },
31
30
  docs: {
32
31
  brief: BRIEF,
@@ -36,7 +35,7 @@ const routes = buildRouteMap({
36
35
  export const app = buildApplication(routes, {
37
36
  name: 'aibridge',
38
37
  versionInfo: {
39
- currentVersion: version,
38
+ currentVersion: PACKAGE_VERSION,
40
39
  },
41
40
  scanner: {
42
41
  // Accept --no-preflight / --no-tools while flag keys stay camelCase in TS
@@ -51,6 +51,11 @@ export const imageGen = buildCommand({
51
51
  optional: true,
52
52
  brief: 'Max seconds to wait for the render (default: 600)',
53
53
  },
54
+ preflight: {
55
+ kind: 'boolean',
56
+ default: true,
57
+ brief: 'Check model quota before rendering (use --no-preflight to skip)',
58
+ },
54
59
  json: {
55
60
  kind: 'boolean',
56
61
  withNegated: false,
@@ -30,6 +30,7 @@ const flags = (over: Partial<ImageGenFlags>): ImageGenFlags => ({
30
30
  out: '/tmp/out.jpg',
31
31
  json: false,
32
32
  transparent: false,
33
+ preflight: false,
33
34
  ...over,
34
35
  });
35
36
 
@@ -25,6 +25,7 @@ import {
25
25
  resolveModel,
26
26
  supportsImageGen,
27
27
  } from '../../models.ts';
28
+ import { preflightModel, renderPreflightRefusal } from '../../quotaPreflight.ts';
28
29
  import {
29
30
  CHROMA_CLAUSE,
30
31
  chromaKeyToPng,
@@ -38,6 +39,7 @@ export interface ImageGenFlags {
38
39
  readonly aspectRatio?: string;
39
40
  readonly image?: string;
40
41
  readonly timeout?: number;
42
+ readonly preflight: boolean;
41
43
  readonly json: boolean;
42
44
  readonly transparent: boolean;
43
45
  }
@@ -122,6 +124,18 @@ export default async function imageGen(
122
124
  return fail(formatImageGenModelError(inputSlug, model));
123
125
  }
124
126
 
127
+ // Last gate before a paid render. Every check above is local and must stay
128
+ // above it, so a bad --out or aspect ratio still fails without a network call.
129
+ if (flags.preflight) {
130
+ const verdict = await preflightModel(model);
131
+ if (!verdict.ok) {
132
+ this.process.stderr.write(`${renderPreflightRefusal('image-gen', verdict)}\n`);
133
+ this.process.exitCode = 3;
134
+ return;
135
+ }
136
+ if (verdict.warning) this.process.stderr.write(`aibridge image-gen: ${verdict.warning}\n`);
137
+ }
138
+
125
139
  const minBytes = model.spec.backend === 'codex' ? MIN_REAL_BYTES_CODEX : MIN_REAL_BYTES_TOOL;
126
140
  const work = mkdtempSync(join(tmpdir(), 'aibridge-imagegen-'));
127
141
 
@@ -29,7 +29,7 @@ export const review = buildCommand({
29
29
  kind: 'parsed',
30
30
  parse: String,
31
31
  optional: true,
32
- brief: 'Base git ref to diff against (default: HEAD)',
32
+ brief: 'Base git ref or range to diff against, e.g. HEAD~3 or main (default: HEAD)',
33
33
  },
34
34
  out: {
35
35
  kind: 'parsed',
@@ -50,7 +50,7 @@ export const review = buildCommand({
50
50
  },
51
51
  },
52
52
  docs: {
53
- brief: 'Review working tree diff or plan contract',
53
+ brief: 'Review a diff (working tree, or any commit range via --base) or a plan contract',
54
54
  fullDescription,
55
55
  },
56
56
  });
@@ -145,7 +145,7 @@ export default async function review(this: LocalContext, flags: ReviewFlags): Pr
145
145
  let reviewPrompt: string;
146
146
  if (isDirty) {
147
147
  reviewPrompt =
148
- `You are an expert code reviewer. Inspect the working tree diff against base '${baseRef}' and untracked files at ${cwd}.\n` +
148
+ `You are an expert code reviewer. Inspect the diff produced by \`git diff ${baseRef}\` (this covers committed and uncommitted changes) plus any untracked files at ${cwd}.\n` +
149
149
  (absPlanPath
150
150
  ? `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`
151
151
  : '') +
@@ -0,0 +1,28 @@
1
+ import { buildCommand } from '@stricli/core';
2
+ import type { LocalContext } from '../../context.ts';
3
+ import skillImpl from './impl.ts';
4
+
5
+ function skillCommand(this: LocalContext, _flags: Record<never, never>, topic?: string): void {
6
+ skillImpl.call(this, topic);
7
+ }
8
+
9
+ export const skill = buildCommand({
10
+ func: skillCommand,
11
+ parameters: {
12
+ flags: {},
13
+ positional: {
14
+ kind: 'tuple',
15
+ parameters: [
16
+ {
17
+ brief: 'Command-specific instructions to append to the router',
18
+ parse: String,
19
+ placeholder: 'topic',
20
+ optional: true,
21
+ },
22
+ ],
23
+ },
24
+ },
25
+ docs: {
26
+ brief: 'Print the canonical agent instructions bundled with this CLI',
27
+ },
28
+ });
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { LocalContext } from '../../context.ts';
3
+ import { PACKAGE_VERSION } from '../../package.ts';
4
+ import skillImpl from './impl.ts';
5
+
6
+ function fakeCtx(): LocalContext & { stdout: string[]; stderr: string[] } {
7
+ const stdout: string[] = [];
8
+ const stderr: string[] = [];
9
+ return {
10
+ process: {
11
+ stdout: {
12
+ write: (value: string) => {
13
+ stdout.push(value);
14
+ return true;
15
+ },
16
+ },
17
+ stderr: {
18
+ write: (value: string) => {
19
+ stderr.push(value);
20
+ return true;
21
+ },
22
+ },
23
+ exitCode: undefined,
24
+ } as unknown as NodeJS.Process,
25
+ stdout,
26
+ stderr,
27
+ };
28
+ }
29
+
30
+ describe('skill command', () => {
31
+ it('prints the router with an exact-version runner', () => {
32
+ const ctx = fakeCtx();
33
+ skillImpl.call(ctx);
34
+ const output = ctx.stdout.join('');
35
+
36
+ expect(output).toContain(`npx -y @aibridge/cli@${PACKAGE_VERSION}`);
37
+ expect(output).toContain('# aibridge');
38
+ expect(output).not.toContain('# plan —');
39
+ expect(ctx.process.exitCode).toBeUndefined();
40
+ });
41
+
42
+ it('appends command-specific instructions', () => {
43
+ const ctx = fakeCtx();
44
+ skillImpl.call(ctx, 'plan');
45
+ const output = ctx.stdout.join('');
46
+
47
+ expect(output).toContain('# aibridge');
48
+ expect(output).toContain('# plan — write a detailed implementation plan file');
49
+ });
50
+
51
+ it('rejects an unknown topic', () => {
52
+ const ctx = fakeCtx();
53
+ skillImpl.call(ctx, 'nope');
54
+
55
+ expect(ctx.process.exitCode).toBe(2);
56
+ expect(ctx.stderr.join('')).toContain('unknown topic "nope"');
57
+ expect(ctx.stdout).toEqual([]);
58
+ });
59
+ });