@sabaiway/agent-workflow-kit 14.21.0 → 14.23.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/README.md +1 -1
  3. package/SKILL.md +1 -1
  4. package/bridges/antigravity-cli-bridge/SKILL.md +16 -10
  5. package/bridges/antigravity-cli-bridge/bin/agy-jev.test.mjs +105 -0
  6. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +20 -6
  7. package/bridges/antigravity-cli-bridge/bin/agy.sh +26 -11
  8. package/bridges/antigravity-cli-bridge/capability.json +3 -3
  9. package/bridges/antigravity-cli-bridge/references/models-and-flags.md +4 -3
  10. package/bridges/antigravity-cli-bridge/setup/README.md +2 -1
  11. package/bridges/codex-cli-bridge/SKILL.md +44 -24
  12. package/bridges/codex-cli-bridge/bin/codex-exec-jev.test.mjs +385 -0
  13. package/bridges/codex-cli-bridge/bin/codex-exec.sh +262 -42
  14. package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +16 -18
  15. package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +16 -7
  16. package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -0
  17. package/bridges/codex-cli-bridge/bin/codex-review-jev.test.mjs +301 -0
  18. package/bridges/codex-cli-bridge/bin/codex-review.sh +240 -26
  19. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +9 -9
  20. package/bridges/codex-cli-bridge/capability.json +10 -10
  21. package/bridges/codex-cli-bridge/references/driving-codex.md +18 -14
  22. package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +61 -22
  23. package/bridges/codex-cli-bridge/setup/README.md +8 -5
  24. package/capability.json +1 -1
  25. package/package.json +1 -1
  26. package/references/authoring/TASK_TEMPLATE.md +11 -16
  27. package/references/modes/agents.md +2 -2
  28. package/references/modes/procedures.md +7 -7
  29. package/references/modes/recipes.md +1 -1
  30. package/references/modes/recommendations.md +1 -1
  31. package/references/modes/set-recipe.md +1 -1
  32. package/references/modes/tier.md +1 -1
  33. package/references/modes/velocity.md +2 -2
  34. package/references/reference-profile.json +1 -1
  35. package/references/templates/SPEC_TEMPLATE.md +2 -1
  36. package/references/templates/agent_rules.md +7 -7
  37. package/tools/carriers.mjs +6 -4
  38. package/tools/cheap-agents.mjs +1 -1
  39. package/tools/detect-backends.mjs +3 -3
  40. package/tools/held-session.mjs +1 -33
  41. package/tools/manifest/schema.md +2 -2
  42. package/tools/procedures.mjs +83 -113
  43. package/tools/rules-regions.mjs +2 -1
  44. package/tools/tier-guide-facts.mjs +145 -159
  45. package/tools/tier-guide.mjs +8 -14
package/CHANGELOG.md CHANGED
@@ -4,6 +4,101 @@ Semantically versioned ([semver](https://semver.org)), newest first. The `versio
4
4
  is the current release. `upgrade` mode reads a project's `docs/ai/.workflow-version` and applies
5
5
  every `migrations/<version>-<slug>.md` newer than it, in semver order.
6
6
 
7
+ ## 14.23.0 — a delegated codex or agy run uses Jev as your own run does; ships codex-cli-bridge 5.0.0 and antigravity-cli-bridge 7.0.0 (AD-171)
8
+
9
+ **Every bridge wrapper now passes `TYPESAFE_API_KEY` to the run, and on Linux with codex-cli 0.160.0 or newer a
10
+ `codex-exec` or `codex-review` run reaches `api.typesafe.ai` and `docs.typesafe.ai` and no other host.** The other
11
+ `*_API_KEY` values and `OPENAI_BASE_URL` stay unset. Where the codex permissions profile is not proven, the run keeps
12
+ the earlier flags with network off and its banner says why. An agy run gets the key and nothing more: no flag, no
13
+ setting.
14
+
15
+ - **One key rule in every wrapper.** The shared block of `codex-exec.sh`, `codex-review.sh`, `agy.sh` and
16
+ `agy-review.sh` gains `aw_scrub_billing_keys`, called before the first CLI spawn (login status, version, catalog,
17
+ run). `test/settings-reader-parity.test.mjs` finds the wrappers from the bridge manifests and
18
+ `test/jev-every-run.test.mjs` proves that discovery on fixture trees, so a wrapper added later is held to the same
19
+ block without a test edit.
20
+ - **codex: the permissions profile `jev`.** On Linux (WSL2 included) with codex-cli 0.160.0 or newer, fresh runs,
21
+ resumes and reviews carry the profile: network to the two Jev hosts only; `codex-exec` writes the repository, `/tmp`
22
+ and `$TMPDIR`; `codex-review` writes nothing. Not Linux, an older or unreadable codex version, an exec repository
23
+ or its git common dir under `/tmp` or `$TMPDIR`, or a version and profile recorded as refused: the run takes the
24
+ earlier flags with network off.
25
+ - **The banner says what the run reaches.** Both codex banners gain `jev=api.typesafe.ai,docs.typesafe.ai` or
26
+ `jev=unreachable (<reason>)` before `source=`; the exec banner's sandbox field reads `sandbox=jev-profile` or
27
+ `sandbox=workspace-write`. Receipt schemas are unchanged.
28
+ - **A refused profile stops loudly.** When codex rejects the profile before its first turn, or bwrap fails inside a
29
+ failed command, the run exits 72 with no success receipt. A rejected profile key records the codex version and
30
+ profile digest in `${XDG_STATE_HOME:-~/.local/state}/agent-workflow/codex-jev-profile-refused` when that file can
31
+ be written (otherwise one warning names it), and that wrapper's next run on that pair takes the earlier flags
32
+ before any spend; a `bwrap: execvp` failure records nothing.
33
+ - **`codex-review` sees the skill.** Its fence home links `.agents/skills` to your real Codex skill root when it
34
+ exists; the review directives allow reading it and name the two hosts on a profile run only.
35
+ - **Probe passthrough under the profile.** With `CODEX_PROBE=1`, `--add-dir`, `-C`, `--cd`, `--enable` and `--disable`
36
+ are refused before any spend (exit 2) while the profile is on; under the fallback they stay admitted.
37
+ - **agy.** With the key set, `agy-run` prints one line before dispatch: `jev: TYPESAFE_API_KEY passed; a headless agy
38
+ runs the Jev request only where your agy permissions allow commands — the first denied command ends the run`.
39
+ `agy-review` makes no Jev request while its model obeys its prompts, which forbid every tool (an advisory
40
+ prohibition, not a sandbox).
41
+ - **Manifests.** Both bridges' `networkHosts` gain `api.typesafe.ai` and `docs.typesafe.ai`, so the sandbox
42
+ Recommendations item shows once more on a project that acknowledged it. Guardrails and banner notes are rewritten,
43
+ and `tools/detect-backends.mjs`'s role-contract copy follows them.
44
+ - **The sandbox notes no longer promise the tier routes wrappers outside.** On 2026-10-06 Claude Code 2.1.291 ran
45
+ an agent's plain `codex-review` inside its sandbox although the project excluded it; a sandboxed run's Jev request
46
+ gets `Tunnel connection failed: 403` from the harness proxy (observed 2026-10-05). `references/modes/recommendations.md`
47
+ now says the tier routes the wrappers outside only where the harness honours that entry, names the refusal, and
48
+ points to the hand-apply `allowedDomains` shape with the recipe's hosts, which the kit never writes for you;
49
+ `references/modes/velocity.md` and the manifest schema's `networkHosts` note say the same.
50
+ - **Stated residuals.** Both bridge `SKILL.md` paragraphs say the key sits in every run's environment; the codex one
51
+ names codex's server-side `web_search`, which reaches pages outside the profile, the agy one a `web_search` or
52
+ fetch tool agy is permitted to run. A codex release that stops recognizing a profile key
53
+ costs one run per version and profile digest before the recorded fallback takes over.
54
+ - The tarball pin rises 355 → 358 (the mirrored bridge suites `codex-exec-jev`, `codex-review-jev`, `agy-jev`).
55
+
56
+ ## 14.22.0 — a story runs in four sessions, each step with one checked result; the advisor renders the new review loop (AD-170)
57
+
58
+ **Your project's rules now state the story flow the engine's canon (5.12.0) defines: four sessions — spec and plan,
59
+ tests, code, then diff review, release and record — with review on the spec with the plan's ledger and on the staged
60
+ diff.** The canon adds the epic's review by the review lens alone and the one commit after the diff review;
61
+ `/agent-workflow-kit procedures` renders both. Both rules templates' §2.6 (the lens) and §2.7
62
+ (Story sessions) carry the new bodies; the outgoing ones are priors, so an upgrade refreshes an unedited region and
63
+ keeps a custom edit, flagged.
64
+
65
+ - **`/agent-workflow-kit procedures` renders the loop the canon states.** One fold ask per member per round, carrying
66
+ all of its findings; round two with the folded artifact, the decided register and the fold diffs; a fold of the
67
+ last round re-read by its raiser; for `plan-execution`, the re-check of a staged tree that moves after a member's
68
+ last ship-class verdict. The round render, the cap triage and the red-proof instrument are gone from the
69
+ plan-authoring and plan-execution renders; `feedback-triage` keeps its round table. plan-authoring's agy review
70
+ reads the spec review file, and the unarmed flow line names no arming command. On a project that armed the flow
71
+ chain itself, `plan-execution` still renders the armed pre-fold line, a stated residual.
72
+ - **The epic by the lens.** `procedures epic` renders the lens review and, whatever `epic.review` resolves to, no
73
+ bridge wrapper, driving contract or grounding pre-step; the slot keys and the Slots line are unchanged.
74
+ `references/modes/agents.md` and the README say the lens is the whole review of an epic and never replaces the
75
+ review recipe of a plan or a diff; the `agents` report and `dispatch advise` say it never replaces a plan or diff
76
+ review recipe.
77
+ - **Cleanup after the commit.** The rules template's §1.3 Task Completion asks before committing first and runs the
78
+ plan's Phase: Cleanup after the commit.
79
+ - **One fold line.** Under a delegated execute slot the fold lane is one line: resume the run's session with
80
+ `codex-exec --resume <session id> --nonce <n> <prompt file>`, the id read from the run's exec receipt. The advisor
81
+ no longer reads the delegation ledger; `tools/held-session.mjs` drops `foldLaneLines`, which nothing else read.
82
+ - **`task.author` dispatches no slice.** `tools/carriers.mjs`: the orchestrator writes each row's prompt, so under
83
+ `subagent` `dispatchForm` prints the slot's sentence alone, without the executor dispatch lines; under `delegated`
84
+ the slot still renders `codex-exec` and its driving contract, which no step reads (display only, recorded debt).
85
+ `task.execute`'s slice is one prompt the orchestrator wrote from one ledger row; `references/modes/{agents,recipes,
86
+ set-recipe,procedures}.md` say the same.
87
+ - `tools/tier-guide.mjs`, `references/modes/tier.md` and the reference profile say four story sessions;
88
+ `references/templates/SPEC_TEMPLATE.md` says a draft exists at the spec review.
89
+ - Recorded debt (`docs/debt.md`): `recipes --active-line` (`→ codex-review`), the orchestration README default
90
+ sentence, the tier offer and `status` still render `epic.review` as a bridge review (`reviewed`), and `recipes
91
+ --active-line`, `procedures task` and the tier offer still name `codex-exec` for a delegated `task.author`; no
92
+ step reads either slot; display only.
93
+ - **The tier guide walks a story on this flow.** `tools/tier-guide.mjs` says a task is one plan row handed out in
94
+ one prompt from the task template, and the story commits once, after its diff review.
95
+ `references/authoring/TASK_TEMPLATE.md` is that prompt's form: Row, Files, Reads, Run and Do not run, seven keys;
96
+ a bridge prompt adds the contract block with a fresh nonce that the engine's *The task* names. The
97
+ guide's story stages are S1-S4: it reads each row's prompt at its canonical name, and a landed story's leftover
98
+ (read the staged diff, check the review state, the one commit, remove the plan) holds every other story and
99
+ every other epic's close until it is done, so the story commits before its epic closes. The guide no longer judges the plan, mints a checkpoint, stamps
100
+ or checks a brief, or commits per task; it prints `plan-shape-cli --check` as a step.
101
+
7
102
  ## 14.21.0 — the bridge model is one host setting: your choice, else the default, checked against the installed CLI before any run (AD-169)
8
103
 
9
104
  **Each bridge now runs the model you set, and `/agent-workflow-kit bridge-settings` shows and sets it.** The read-out
package/README.md CHANGED
@@ -252,7 +252,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
252
252
  | `/agent-workflow-kit worktrees` | any time | **parallel feature worktrees** — run several features in DIFFERENT agent sessions on one repo, zero interference on working-tree files (the ONE exception is the dependency cache, below): `provision <slug> --plan <file>` creates a sibling git worktree on branch `aw/<slug>` and populates it (registry-derived footprint copy-if-missing — a tracked file is never overwritten; EXACTLY ONE seeded feature plan; the `handoff-<slug>.md` record from minute zero; `node_modules` symlinked where the link stays ignored — a shared MUTABLE dependency cache: writes through it hit MAIN's node_modules; for isolation run the printed isolated-install command (`--install` only PRINTS it; on `--resume` run the printed unlink-first recovery first — printed only for a link whose RAW TARGET is MAIN's `node_modules`, since a foreign or unreadable link is reported as such with no removal advised); absolute root-pinned gate commands rebased on untracked copies only, and only while their bytes equal the MAIN source or its rebased form — user-modified copies stay untouched); `list` is read-only (slug, branch, base, dirty, handoff); `prompt <slug>` is read-only too and re-prints the satellite's cold-start prompt — the same text `provision` ends its report with: where the worktree is, its ONE seeded plan, the handoff as the one channel back, MAIN's orientation (shared series index, landing) and, under its own heading because it is probed on the satellite, that checkout's install posture — every value derived LIVE rather than replayed from the frozen provision record, a recorded value that no longer matches NAMED as a divergence (with the cause its source makes likely) instead of printed as the runnable one, every offered command marked with WHO runs it (`MAIN $ …` for the landing, `HERE $ …` for this checkout's own install, a posture with nothing to run staying prose), and a control character in any rendered value a typed STOP rather than a forged prompt line; `land <slug> --prepare` locks the common git dir, fail-closes on divergence or incomplete satellite state, transfers the complete accepted satellite diff onto a CLEAN main, runs sync plus the declared gates, and reports HEAD/TRANSFER/PREPARED OIDs — the commit ALWAYS stays a dialogue ask; `cleanup <slug>` takes the same lock and removes a LANDED worktree only after live landed-verification against main HEAD, while `--abandon` is the ONE destructive arm (destroys unlanded work; **no preview step** on any writer). The parent dir is the `docs/ai/worktrees.json` `parentDir` setting (default: the repo's sibling parent); an unwritable parent degrades to printed maintainer-pasted commands, and the one-time host consent that makes it promptless surfaces via `recommendations`. Never commits, never pushes, never runs a subscription CLI. |
253
253
  | `/agent-workflow-kit uninstall` | opt-in, any time | **guarded teardown** — the inverse of `init` / `setup`. Removes only what's **provably ours** (managed skill dirs + bridge wrappers; in a project, the hidden-mode git-ignore block it added + the pre-commit hook it installed); **never deletes** your `docs/ai` / `AGENTS.md` (prints the exact `rm` to run by hand) or your `.claude/settings.json` (prints an **edit** — remove the attribution key, review any velocity `permissions.*` — never an `rm`). Always `--dry-run` first; preflight-then-mutate; never commits. |
254
254
  | `/agent-workflow-kit velocity` | Claude Code · opt-in | **onboarding velocity profile** — seeds a fixed, audited **read-only** allowlist into `.claude/settings.json` so routine read-only commands stop idling on approval prompts while you're away; opt-in `acceptEdits`; plus a **read-only advisory** of likely project gate commands to add by hand. Writes **only** `.claude/settings.json` — **never** allowlists commit/push/publish, never writes `settings.local.json`, never commits. A seeded entry is a **trust posture, not a sandbox** (a runtime residual remains at the settings level — its guard ships as the opt-in `hook` command); a direct commit/push/publish still asks. `--dry-run` first. |
255
- | `/agent-workflow-kit agents` | Claude Code · opt-in | **subagent vehicles** — places the five bundled subagent definitions into `.claude/agents/`: **four read-only vehicles + one full-tool `executor`**. **No read-only vehicle gets `Bash`**, and that is the load-bearing property: a read-only fan-out on a full-tool subagent shells out for facts it could have read, and every shelled command is an approval prompt you never needed to see. Three of the four ride a **cheap model** (haiku, low effort) for mechanical work — extraction sweeps, changelog fact-skeletons, gate-failure triage; the fourth, **`review-lens`**, is an ADDITIONAL independent read-only review opinion on code your configured backends have already seen (never a replacement for your review recipe, advisory like every review). The fifth, **`executor`** (all tools; model and effort from `docs/ai/vehicles.json`), is the ONE vehicle with a shell and the instrument behind the **Subagent** recipe: dispatched only for a bounded, file-disjoint execution / authoring / routine slice you verify afterwards — never for read-only work, never as a review backend, and it never commits. Its state (`placed` / `customized` / `unusable` / `missing`) is what a `subagent` slot's readiness resolves from. Writing code and running gates stay off the four read-only vehicles; the executor carries a bounded slice you re-verify on your main lane, and every vehicle's output is verified. Preview by default (`--apply` writes); an existing customized read-only vehicle or lens is **preserved, never overwritten**; the executor's body is re-derived from `docs/ai/vehicles.json` (a hand edit is replaced); never touches `settings*.json`, never commits. |
255
+ | `/agent-workflow-kit agents` | Claude Code · opt-in | **subagent vehicles** — places the five bundled subagent definitions into `.claude/agents/`: **four read-only vehicles + one full-tool `executor`**. **No read-only vehicle gets `Bash`**, and that is the load-bearing property: a read-only fan-out on a full-tool subagent shells out for facts it could have read, and every shelled command is an approval prompt you never needed to see. Three of the four ride a **cheap model** (haiku, low effort) for mechanical work — extraction sweeps, changelog fact-skeletons, gate-failure triage; the fourth, **`review-lens`**, is an ADDITIONAL independent read-only review opinion on code your configured backends have already seen (never a replacement for your plan or diff review recipe, advisory like every review; an epic is reviewed by the lens alone, whatever `epic.review` resolves to). The fifth, **`executor`** (all tools; model and effort from `docs/ai/vehicles.json`), is the ONE vehicle with a shell and the instrument behind the **Subagent** recipe: dispatched only for a bounded, file-disjoint execution / authoring / routine slice you verify afterwards — never for read-only work, never as a review backend, and it never commits. Its state (`placed` / `customized` / `unusable` / `missing`) is what a `subagent` slot's readiness resolves from. Writing code and running gates stay off the four read-only vehicles; the executor carries a bounded slice you re-verify on your main lane, and every vehicle's output is verified. Preview by default (`--apply` writes); an existing customized read-only vehicle or lens is **preserved, never overwritten**; the executor's body is re-derived from `docs/ai/vehicles.json` (a hand edit is replaced); never touches `settings*.json`, never commits. |
256
256
  | `/agent-workflow-kit hook` | Claude Code · opt-in | **gate-approval hook** — places a self-contained PreToolUse hook (`.claude/hooks/`) and wires it into `.claude/settings.json`: a Bash command **byte-identical** to a gate you declared in `docs/ai/gates.json` (run from the project root) is auto-approved — no prompt, no idle; a seeded read-only command carrying a runtime residual (output redirection, command substitution, `--output` writes) now **asks** even where an allow rule would have silently passed it (proven live — hook `ask` overrides an allow rule). Detection is a conservative string scan, so it over-asks on a byte that is ordinary text — searching for `=>`, or a read wearing `2>/dev/null`, prompts. That is a documented open limit: 4.1.0 built three mechanisms to narrow it and removed all three in review, each counterexample kept as a test (see Mode: hook). Exact matches only, never patterns; never `deny`; a broken `gates.json` only disables auto-approval, never the guard. An opt-in **read-lane** (`--read-lane` → `docs/ai/lanes.json`, a **separate** file from `gates.json`) additionally auto-approves *compounds* of seeded read-only commands carrying no shell metaprogramming; `--apply --read-lane` verifies the placed hook is current first (delete-to-reseed otherwise). Preview by default; never writes `settings.local.json`; never commits. |
257
257
  | `/agent-workflow-kit state-block-guard` | Claude Code · opt-in | **closing-block detector** — a `Stop` hook that reads the turn's final assistant message and warns when the closing state block is defective: the «what I need from you» slot answering *nothing* (a turn that ENDS always needs a resume, so the answer is false, not merely unhelpful), or a first-person promise of imminent work in a turn that is over. Judged on the slot's first clause, so a real ask followed by a clause break and "nothing else" passes, and a promise gated on something named passes; matching is word-bounded and Unicode-aware, quoted and fenced examples are stripped, and the last STARTED block decides — an incomplete tail counts as no block rather than falling back to an earlier one. A message carrying no block at all is silent unless you pass `--require-block` — this kit does not mandate the block, and a hook that runs every turn must not warn every turn. The judgement is lexical and the mode doc names every residual it leaves. **Detection, never prevention** — a `Stop` hook cannot un-send the message it judges; what it buys is that a silent recurrence becomes a visible one. Warns via `systemMessage` and **exits 0 on every path**: never `deny`, never blocks the stop, never re-enters the model. Reads nothing but the message, makes no network call, approves nothing. This mode is **read-only and has no writer** — it ships the self-contained hook and a paste-ready wiring block you merge into `.claude/settings.json` yourself. |
258
258
  | `/agent-workflow-kit bridge-settings` | opt-in, any time | **host-level bridge settings** — read or change the bridges' `KEY=VALUE` config file (`${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf`), the **one place a knob survives kit upgrades** (it lives outside every kit tree; a refresh never touches it, and now states loudly if it ever overwrote a local edit). The **model each bridge runs**: `CODEX_MODEL` + `CODEX_EFFORT` (default `gpt-6.1-sol` at `high`) and `AGY_MODEL` (default `Gemini 3.8 Flash (High)`) — the read-out shows the models each installed CLI offers, and a set one is checked against that list before anything is written. Other knobs: the **codex Fast tier** (`CODEX_SERVICE_TIER=priority` — "2x speed, **increased usage**" in the catalog's own words, quality-neutral, default off — a consented spend), the codex/agy hard timeouts, the codex review byte cap, and `AGY_REVIEW_MAX_TOTAL_BYTES` (the ceiling on what an oversized agy code review may feed before it refuses pre-spend). `AGY_REVIEW_ALLOW_ADDDIR` is a **retired** compatibility key: still recognized so an existing line never warns as unknown, but it arms nothing — the writer refuses to set it and `--unset` clears it. Allowed keys + value rules come from the bundled bridge manifests. **Previews by default**; refuses an unknown key, an out-of-range value, a model or effort the catalog does not offer, or a duplicate-carrying file; writes atomically (symlink/TOCTOU-safe). Never commits, never runs a model (only the read-only catalog commands). |
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: agent-workflow-kit
3
3
  description: Deploy or upgrade a portable AI-agent memory-and-workflow system in any project. Use when the user wants to bootstrap `docs/ai/` + an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) + cap/archive/index enforcement in a new or existing repo, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-kit` / `/agent-workflow-kit upgrade`. Triggers on phrases like "set up the memory system", "deploy the AI workflow here", "bootstrap docs/ai", "upgrade the workflow". New to the epic, story and task tier? Run `/agent-workflow-kit tier`. A typed decision with a confidence from Jev? Run `/agent-workflow-kit jev`.
4
4
  disable-model-invocation: true
5
5
  metadata:
6
- version: '14.21.0'
6
+ version: '14.23.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-kit
@@ -2,7 +2,7 @@
2
2
  name: antigravity-cli-bridge
3
3
  description: Delegate work to Google's Antigravity CLI (`agy`) — the successor to Gemini CLI — to reach Gemini, Claude, and GPT-OSS models under a Google AI Pro/Ultra subscription from the terminal. Use when the user wants to run a headless `agy` prompt, hand a focused task or second-opinion review to `agy`, install or authenticate Antigravity CLI, check or economise its quota/models, bridge project context into `agy`, set up a second delegated-execution backend beside Codex, or troubleshoot `agy` flags, models, auth, conversations, or its headless behaviour.
4
4
  metadata:
5
- version: '6.0.0'
5
+ version: '7.0.0'
6
6
  ---
7
7
 
8
8
  # antigravity-cli-bridge
@@ -43,9 +43,10 @@ wrapper [`bin/agy.sh`](bin/agy.sh) on `PATH` as `agy-run`.
43
43
  ```
44
44
 
45
45
  Never read, print, copy, commit, or package that token — it is personal and is **never bundled** with
46
- this skill. The wrapper [`bin/agy.sh`](bin/agy.sh) **unsets every `*_API_KEY`** (`ANTIGRAVITY_API_KEY`,
47
- `GEMINI_API_KEY`, `GOOGLE_API_KEY`, `GOOGLE_GENAI_API_KEY`) before invoking `agy`, so a stray key can
48
- never silently switch you to pay-as-you-go billing.
46
+ this skill. The wrapper [`bin/agy.sh`](bin/agy.sh) **unsets `OPENAI_BASE_URL` and every `*_API_KEY` except
47
+ `TYPESAFE_API_KEY`** (`ANTIGRAVITY_API_KEY`, `GEMINI_API_KEY`, `GOOGLE_API_KEY`, `GOOGLE_GENAI_API_KEY`
48
+ included; `TYPESAFE_API_KEY` passes as given) before invoking `agy`, so a stray key can never silently
49
+ switch you to pay-as-you-go billing.
49
50
 
50
51
  **Caveat:** the subscription has a finite quota. Keep probes short (see *How the main agent drives
51
52
  agy*); the model every review and kept run uses is the host posture below, never a per-call pick.
@@ -223,11 +224,15 @@ AGY_MODEL="<an offered display string>" agy-run \
223
224
  ```
224
225
 
225
226
  **The Jev skill in a delegated run.** A headless `agy-run` loads the vendor's Jev skill from the user root the kit's
226
- `jev-skill` command fills, `~/.gemini/config/skills` — measured 2026-10-02 on agy 1.2.14: a probe skill under that
227
- root was listed, and none under `~/.gemini/antigravity-cli/skills`. The run never inherits `TYPESAFE_API_KEY`: the
228
- wrapper unsets every `*_API_KEY` before agy starts. Jev requests are the orchestrator's own, made in the user's
229
- environment, and their answers are passed into a brief as text; whether a headless agy command reaches a network
230
- host is governed by the user's agy permissions and sandbox settings, not by the bridge.
227
+ `jev-skill` command fills, `~/.gemini/config/skills` (measured 2026-10-02 on agy 1.2.14: a probe skill under that
228
+ root was listed, none under `~/.gemini/antigravity-cli/skills`), and `TYPESAFE_API_KEY` passes the wrappers as given
229
+ while every other `*_API_KEY` is unset; nothing in agy's permissions is widened, no flag is added and no setting is
230
+ written. With the key set, `agy-run` prints one line before dispatch: a headless agy runs the Jev request only where
231
+ the user's agy permissions allow commands, and the first denied command ends the run. `agy-review` makes no Jev
232
+ request while its model obeys its prompts, which forbid every tool; that prohibition is advisory, not a sandbox. The documented opt-in `-- --dangerously-skip-permissions`
233
+ stays admitted, and such a run carries the key into an agy whose network and file tool are unconfined. Two residuals
234
+ stand: the key now sits in every run's environment, and a `web_search` or fetch tool agy is permitted to run may reach
235
+ hosts the bridge never names.
231
236
 
232
237
  ## How the main agent drives `agy` efficiently
233
238
 
@@ -250,7 +255,8 @@ checklist, prompt templates, output handling). Essentials:
250
255
  writes (branch/add/commit/stash/reset/rewrite) with the orchestrator, and tell `agy` in the prompt
251
256
  to return findings only. Add `-- --sandbox` when delegating anything that could trigger
252
257
  terminal/tool work; opt into `-- --add-dir . --dangerously-skip-permissions` only for a flow that
253
- genuinely needs writes, then review the diff afterwards.
258
+ genuinely needs writes, then review the diff afterwards (such a run also carries `TYPESAFE_API_KEY`,
259
+ when set, into an agy whose network and file tool are unconfined).
254
260
 
255
261
  ## Complementary skills (optional, standalone-first)
256
262
 
@@ -0,0 +1,105 @@
1
+ // agy-jev.test.mjs — spec jev-every-run, the agy group (D5(a)): agy-run and agy-review pass TYPESAFE_API_KEY by the
2
+ // shared key rule and widen nothing; agy-run prints its one limit line, never as agy-review's child; the documented
3
+ // opt-in stays admitted. A recording stub stands in front of the harness fake agy.
4
+ import { describe, it, after } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
7
+ import { join, dirname } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { spawnSync } from 'node:child_process';
10
+ import { makeSandbox, farmFor, FAKE_AGY } from './agy-review-harness.test.mjs';
11
+
12
+ const HERE = dirname(fileURLToPath(import.meta.url));
13
+ const REVIEW = join(HERE, 'agy-review.sh');
14
+ const AGY_RUN = join(HERE, 'agy.sh');
15
+ const LIMIT = `jev: TYPESAFE_API_KEY passed; a headless agy runs the Jev request only where your agy permissions allow commands ${String.fromCharCode(0x2014)} the first denied command ends the run`;
16
+ const KEYS = { TYPESAFE_API_KEY: 'jev-key', ANTIGRAVITY_API_KEY: 'a', GEMINI_API_KEY: 'g', GOOGLE_API_KEY: 'o', GOOGLE_GENAI_API_KEY: 'n', FOO_API_KEY: 'foo' };
17
+ const PASSED = 'key=[jev-key] google=unset,unset,unset,unset foo=unset';
18
+
19
+ // Every spawn of agy appends its kind and the key states; a run also says so on stderr before the fake answers.
20
+ const RECORDER = [
21
+ '#!/usr/bin/env bash',
22
+ 'st() { if [[ -n "${!1+x}" ]]; then printf "[%s]" "${!1}"; else printf unset; fi; }',
23
+ 'case "${1:-}" in --help|-h) kind=help ;; --version) kind=version ;; models) kind=models ;; *) kind=run; echo STUB-DISPATCH >&2 ;; esac',
24
+ 'echo "$kind key=$(st TYPESAFE_API_KEY) google=$(st ANTIGRAVITY_API_KEY),$(st GEMINI_API_KEY),$(st GOOGLE_API_KEY),$(st GOOGLE_GENAI_API_KEY) foo=$(st FOO_API_KEY) child=$(st AW_AGY_REVIEW_CHILD)" >>"$AGY_JEV_LOG"',
25
+ 'exec "$AGY_JEV_FAKE" "$@"',
26
+ '',
27
+ ].join('\n');
28
+
29
+ const made = [];
30
+ after(() => {
31
+ for (const home of made) rmSync(home, { recursive: true, force: true });
32
+ });
33
+ const sandbox = () => {
34
+ const sb = makeSandbox();
35
+ made.push(sb.home);
36
+ writeFileSync(join(sb.home, 'agy-fake'), FAKE_AGY, { mode: 0o755 });
37
+ writeFileSync(join(sb.bin, 'agy'), RECORDER, { mode: 0o755 });
38
+ writeFileSync(join(sb.repo, 'plan.md'), '# a plan under review\n');
39
+ return sb;
40
+ };
41
+ const drive = (sb, wrapper, args, env = {}) => {
42
+ const cap = (name) => join(sb.home, name);
43
+ for (const name of ['jev-log', 'jev-argv']) rmSync(cap(name), { force: true });
44
+ const r = spawnSync('bash', [wrapper, ...args], {
45
+ cwd: sb.repo, input: '', encoding: 'utf8', timeout: 60000,
46
+ env: { HOME: sb.home, PATH: `${sb.bin}:${farmFor(['agy', 'agy-run'])}`, TMPDIR: process.env.TMPDIR ?? '/tmp',
47
+ AGY_JEV_LOG: cap('jev-log'), AGY_JEV_FAKE: cap('agy-fake'), AGY_FAKE_ARGV: cap('jev-argv'), ...env },
48
+ });
49
+ const read = (name) => (existsSync(cap(name)) ? readFileSync(cap(name), 'utf8') : '');
50
+ const spawns = read('jev-log').split('\n').filter(Boolean).map((line) => [line.slice(0, line.indexOf(' ')), line.slice(line.indexOf(' ') + 1)]);
51
+ return { status: r.status, stdout: r.stdout, stderr: r.stderr, spawns, argv: read('jev-argv').split('\n').slice(0, -1) };
52
+ };
53
+ const runDirect = (sb, env, extra = []) => drive(sb, AGY_RUN, ['a probe prompt', ...extra], env);
54
+ const keysOf = (env) => env.split(' child=')[0];
55
+ const runSpawn = (r) => (r.spawns.find(([kind]) => kind === 'run') ?? [])[1] ?? '';
56
+ const limitLines = (r) => r.stderr.split('\n').filter((line) => line.startsWith('jev:'));
57
+
58
+ describe('agy-run and agy-review pass the key and unset the rest — spec:jev-every-run/S4', () => {
59
+ for (const [label, wrapper, args] of [['agy-run', AGY_RUN, ['a probe prompt']], ['agy-review plan', REVIEW, ['plan', 'plan.md']]]) {
60
+ it(`${label}: every spawn of agy sees TYPESAFE_API_KEY with the four Google keys and FOO_API_KEY unset`, () => {
61
+ const r = drive(sandbox(), wrapper, args, KEYS);
62
+ assert.equal(r.status, 0, r.stderr);
63
+ assert.ok(r.spawns.some(([kind]) => kind === 'run'), 'agy ran');
64
+ for (const [kind, env] of r.spawns) assert.equal(keysOf(env), PASSED, `${kind} spawn`);
65
+ });
66
+ }
67
+ });
68
+
69
+ describe('the agy half of S5: an empty key and an unset key on agy-run', () => {
70
+ it('an empty TYPESAFE_API_KEY reaches agy empty, an unset one stays unset, and the argv is the same with the key set', () => {
71
+ const runs = ['jev-key', '', undefined].map((value) => runDirect(sandbox(), { TYPESAFE_API_KEY: value }));
72
+ assert.deepEqual(runs.map((r) => keysOf(runSpawn(r)).split(' ')[0]), ['key=[jev-key]', 'key=[]', 'key=unset']);
73
+ assert.deepEqual([runs[1].argv, runs[2].argv], [runs[0].argv, runs[0].argv]);
74
+ });
75
+ });
76
+
77
+ describe('agy-run prints its one limit line — spec:jev-every-run/S20', () => {
78
+ it('a non-empty key: exactly the one limit line, before dispatch; an empty or an unset key: none', () => {
79
+ const r = runDirect(sandbox(), { TYPESAFE_API_KEY: 'jev-key' });
80
+ assert.equal(r.status, 0, r.stderr);
81
+ assert.deepEqual(limitLines(r), [LIMIT]);
82
+ const lines = r.stderr.split('\n');
83
+ assert.ok(lines.indexOf(LIMIT) < lines.indexOf('STUB-DISPATCH'), r.stderr);
84
+ for (const value of ['', undefined]) {
85
+ const quiet = runDirect(sandbox(), { TYPESAFE_API_KEY: value });
86
+ assert.equal(quiet.status, 0, quiet.stderr);
87
+ assert.deepEqual(limitLines(quiet), [], JSON.stringify(value));
88
+ }
89
+ });
90
+ it('as agy-review\'s child: agy-run runs marked AW_AGY_REVIEW_CHILD=1 with the key, and no limit line is printed', () => {
91
+ const r = drive(sandbox(), REVIEW, ['plan', 'plan.md'], { TYPESAFE_API_KEY: 'jev-key' });
92
+ assert.equal(r.status, 0, r.stderr);
93
+ assert.equal(runSpawn(r), `${PASSED} child=[1]`);
94
+ assert.deepEqual(limitLines(r), []);
95
+ });
96
+ });
97
+
98
+ describe('the documented opt-in stays admitted — spec:jev-every-run/S21', () => {
99
+ it('agy-run -- --dangerously-skip-permissions reaches agy\'s argv, the key beside it', () => {
100
+ const r = runDirect(sandbox(), { TYPESAFE_API_KEY: 'jev-key' }, ['--', '--dangerously-skip-permissions']);
101
+ assert.equal(r.status, 0, r.stderr);
102
+ assert.ok(r.argv.includes('--dangerously-skip-permissions'), r.argv.join(' '));
103
+ assert.equal(keysOf(runSpawn(r)), PASSED);
104
+ });
105
+ });
@@ -372,11 +372,24 @@ aw_resolve_timeout_bin() {
372
372
  [[ -f "$bin" && -x "$bin" ]] || { printf ''; return 0; }
373
373
  printf '%s' "$bin"
374
374
  }
375
+ aw_scrub_billing_keys() {
376
+ # The one key rule (spec jev-every-run): OPENAI_BASE_URL and every *_API_KEY are unset, so a stray key can never
377
+ # switch a run to paid billing; TYPESAFE_API_KEY is neither read nor set, so it reaches the CLI exactly as given.
378
+ local name
379
+ unset OPENAI_BASE_URL 2>/dev/null || true
380
+ for name in $(compgen -v); do
381
+ case "$name" in
382
+ TYPESAFE_API_KEY) ;;
383
+ *_API_KEY) unset "$name" 2>/dev/null || true ;;
384
+ esac
385
+ done
386
+ return 0
387
+ }
375
388
 
376
389
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
377
390
  # version (drift-guarded by agy-review.test.mjs against capability.json).
378
391
  AW_RECEIPT_BACKEND="agy"
379
- AW_BRIDGE_VERSION="6.0.0" # aw-version-anchor
392
+ AW_BRIDGE_VERSION="7.0.0" # aw-version-anchor
380
393
  # D5 control-byte screen — IMMEDIATELY after resolution, BEFORE any interpolation can echo raw
381
394
  # newline/ESC bytes into stderr/the terminal (round-2 fold).
382
395
  if [[ "$AGY_MODEL" == *[$'\x01'-$'\x1f'$'\x7f']* ]]; then
@@ -563,12 +576,9 @@ read_agy_envelope() { # $1 = captured payload, $2 = response destination, $3 =
563
576
  node "$AGY_ENVELOPE_READER" "${args[@]}"
564
577
  }
565
578
 
566
- # --- Subscription invariant (reuse agy.sh's security pattern verbatim) --------
579
+ # --- Subscription invariant: the shared key rule, before the first spawn of agy --------
567
580
  export PATH="$HOME/.local/bin:$PATH"
568
- unset ANTIGRAVITY_API_KEY GEMINI_API_KEY GOOGLE_API_KEY GOOGLE_GENAI_API_KEY 2>/dev/null || true
569
- while IFS= read -r _api_key_var; do
570
- unset "$_api_key_var" 2>/dev/null || true
571
- done < <(compgen -v 2>/dev/null | grep '_API_KEY$' || true)
581
+ aw_scrub_billing_keys
572
582
 
573
583
  if ! command -v agy >/dev/null 2>&1; then
574
584
  echo "error: 'agy' (Antigravity CLI) not found on PATH. See this skill's setup/README.md." >&2
@@ -763,6 +773,10 @@ aw_agy_catalog_check() {
763
773
  exit 2
764
774
  }
765
775
  export AW_AGY_CATALOG_READ=1
776
+ # Every child agy-run is this review's (spec jev-every-run): its prompts forbid every tool, so a review whose model
777
+ # obeys them sends no request to api.typesafe.ai (an advisory prohibition, not a sandbox), and the child prints no
778
+ # TYPESAFE_API_KEY limit line.
779
+ export AW_AGY_REVIEW_CHILD=1
766
780
  if [[ -n "$AGY_MODEL" ]]; then aw_agy_catalog_check "$aw_review_timeout_bin" "$DEFAULT_AGY_REVIEW_MODEL"; fi
767
781
  if [[ "$AGY_MODEL" != "$HOST_AGY_MODEL" && "$AGY_PROBE" != "1" ]]; then
768
782
  echo "error: $(aw_agy_shown_model) is a one-off — the host posture is '$HOST_AGY_MODEL', and only a probe (AGY_PROBE=1) runs a one-off; refusing before any run is spent." >&2
@@ -8,9 +8,9 @@
8
8
  #
9
9
  # Auth: SUBSCRIPTION ONLY. We use the cached OAuth token at
10
10
  # ~/.gemini/antigravity-cli/antigravity-oauth-token (Google AI Pro) and the
11
- # quota that comes with it. To make that guarantee hard, the wrapper unsets any
12
- # API-key env var so a stray key can never silently switch us to paid
13
- # pay-as-you-go billing.
11
+ # quota that comes with it. To make that guarantee hard, the wrapper unsets
12
+ # every *_API_KEY except TYPESAFE_API_KEY (which passes as given), so a stray
13
+ # key can never silently switch us to paid pay-as-you-go billing.
14
14
  #
15
15
  # This is a THIN, FLOW-AGNOSTIC wrapper on purpose: it just runs one headless
16
16
  # prompt and prints the text response. It does NOT encode any orchestration
@@ -65,14 +65,6 @@ esac
65
65
  # 1. Make `agy` findable even when ~/.bashrc was not sourced.
66
66
  export PATH="$HOME/.local/bin:$PATH"
67
67
 
68
- # 2. Force the subscription path: never let an API key hijack billing. Unset EVERY *_API_KEY for the
69
- # agy subprocess — the explicit Google/Antigravity ones first, then any other *_API_KEY that may
70
- # have been added later (`compgen` is a bash builtin; the shebang guarantees bash).
71
- unset ANTIGRAVITY_API_KEY GEMINI_API_KEY GOOGLE_API_KEY GOOGLE_GENAI_API_KEY 2>/dev/null || true
72
- while IFS= read -r _api_key_var; do
73
- unset "$_api_key_var" 2>/dev/null || true
74
- done < <(compgen -v 2>/dev/null | grep '_API_KEY$' || true)
75
-
76
68
  # This wrapper's applied settings-file subset (see the shared reader block below).
77
69
  AW_SETTINGS_APPLIED="AGY_HARD_TIMEOUT"
78
70
  DEFAULT_AGY_MODEL="Gemini 3.8 Flash (High)"
@@ -262,6 +254,23 @@ aw_resolve_timeout_bin() {
262
254
  [[ -f "$bin" && -x "$bin" ]] || { printf ''; return 0; }
263
255
  printf '%s' "$bin"
264
256
  }
257
+ aw_scrub_billing_keys() {
258
+ # The one key rule (spec jev-every-run): OPENAI_BASE_URL and every *_API_KEY are unset, so a stray key can never
259
+ # switch a run to paid billing; TYPESAFE_API_KEY is neither read nor set, so it reaches the CLI exactly as given.
260
+ local name
261
+ unset OPENAI_BASE_URL 2>/dev/null || true
262
+ for name in $(compgen -v); do
263
+ case "$name" in
264
+ TYPESAFE_API_KEY) ;;
265
+ *_API_KEY) unset "$name" 2>/dev/null || true ;;
266
+ esac
267
+ done
268
+ return 0
269
+ }
270
+
271
+ # 2. Force the subscription path before the first spawn of agy: the shared key rule (the Google and
272
+ # Antigravity keys included) — no API key can hijack billing.
273
+ aw_scrub_billing_keys
265
274
 
266
275
  if ! command -v agy >/dev/null 2>&1; then
267
276
  echo "error: 'agy' (Antigravity CLI) not found on PATH. Install it and run 'agy' once to sign in." >&2
@@ -422,6 +431,12 @@ fi
422
431
 
423
432
  agy_cmd=(agy "${model_flag[@]}" --print-timeout "$AGY_TIMEOUT" "${passthrough[@]}" -p "$prompt")
424
433
 
434
+ # A non-empty TYPESAFE_API_KEY rides into agy and nothing is widened (spec jev-every-run, D5(a)): the limit, once,
435
+ # before dispatch — never in agy-review's child, whose prompts forbid every tool.
436
+ if [[ -n "${TYPESAFE_API_KEY:-}" && "${AW_AGY_REVIEW_CHILD:-}" != "1" ]]; then
437
+ echo "jev: TYPESAFE_API_KEY passed; a headless agy runs the Jev request only where your agy permissions allow commands — the first denied command ends the run" >&2
438
+ fi
439
+
425
440
  # Hard wall-clock cap via timeout(1) (GNU `timeout` on Linux, `gtimeout` from coreutils on macOS).
426
441
  # This is the real guard — a backgrounded, hung agy survives its own --print-timeout otherwise.
427
442
  # aw_resolve_timeout_bin: builtin type -P (an exported function can shadow neither `timeout` nor
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "antigravity-cli-bridge",
5
5
  "kind": "execution-backend",
6
- "version": "6.0.0",
6
+ "version": "7.0.0",
7
7
  "provides": ["review", "probe"],
8
8
  "posture": { "model": "Gemini 3.8 Flash (High)" },
9
9
  "roles": {
@@ -180,7 +180,7 @@
180
180
  { "slot": "<extra agy flags...>", "required": false, "description": "raw agy flags after -- (a --model* or --effort* flag refuses pre-spend: the model is the host posture or the AGY_MODEL environment value)" }
181
181
  ],
182
182
  "guardrails": [
183
- { "value": "subscription-only — every *_API_KEY env var is unset before the run", "enforcement": "enforced", "source": "bin/agy.sh" },
183
+ { "value": "subscription-only — every *_API_KEY env var except TYPESAFE_API_KEY is unset before the run; TYPESAFE_API_KEY passes as given and nothing in agy's permissions is widened", "enforcement": "enforced", "source": "bin/agy.sh" },
184
184
  { "value": "the model is the host posture (AGY_MODEL in the bridge settings file, else Gemini 3.8 Flash (High)); AGY_MODEL in the environment sets it for one run", "enforcement": "enforced", "source": "bin/agy.sh" },
185
185
  { "value": "hard wall-clock cap AGY_HARD_TIMEOUT (built-in default 5m)", "enforcement": "enforced", "condition": "only while timeout(1)/gtimeout is on PATH — otherwise the wrapper warns and runs uncapped", "source": "capability.json settings.AGY_HARD_TIMEOUT" },
186
186
  { "value": "the prompt rides a single argv — over AGY_MAX_PROMPT_BYTES (120000) the run refuses", "enforcement": "enforced", "source": "bin/agy.sh" },
@@ -240,7 +240,7 @@
240
240
  "effect": "agy-review code: the ceiling on the SUM of all outgoing prompt bytes the chunked feed may send (every feed-turn framing block, every body, the final turn). Checked BEFORE the first turn is spent; past it the fed review refuses. An economy guard, not the correctness guard — correctness is the per-part delivery echo."
241
241
  }
242
242
  ],
243
- "networkHosts": ["*.googleapis.com", "accounts.google.com", "antigravity-unleash.goog", "lh3.googleusercontent.com"],
243
+ "networkHosts": ["*.googleapis.com", "accounts.google.com", "antigravity-unleash.goog", "lh3.googleusercontent.com", "api.typesafe.ai", "docs.typesafe.ai"],
244
244
  "writableDirs": [{ "env": null, "default": "~/.gemini/antigravity-cli" }],
245
245
  "detect": {
246
246
  "installed": {
@@ -51,9 +51,10 @@ Environment:
51
51
  | `AGY_HARD_TIMEOUT` | `= AGY_TIMEOUT` | hard `timeout(1)` wall-clock cap (a duration string) |
52
52
  | `AGY_MAX_PROMPT_BYTES` | `120000` | single-argv byte ceiling. `agy` takes the prompt as ONE `-p` argv; past `MAX_ARG_STRLEN` (~131072) `execve` fails with a cryptic `Argument list too long`. The wrapper measures the resolved `-`/`@file` prompt and fails loud over the ceiling. A huge **literal** `agy-run "<huge>"` fails at the wrapper's own `exec`, so route large prompts via `-`/`@file`. |
53
53
 
54
- Subscription invariant: the wrapper prepends `$HOME/.local/bin` to `PATH` and clears
55
- `ANTIGRAVITY_API_KEY` / `GEMINI_API_KEY` / `GOOGLE_API_KEY` / `GOOGLE_GENAI_API_KEY` before execution.
56
- Auth comes from the user's cached OAuth token, never from bundled credentials.
54
+ Subscription invariant: the wrapper prepends `$HOME/.local/bin` to `PATH` and clears `OPENAI_BASE_URL`
55
+ and every `*_API_KEY` except `TYPESAFE_API_KEY` (`ANTIGRAVITY_API_KEY` / `GEMINI_API_KEY` /
56
+ `GOOGLE_API_KEY` / `GOOGLE_GENAI_API_KEY` included) before execution; `TYPESAFE_API_KEY` passes as
57
+ given and widens nothing. Auth comes from the user's cached OAuth token, never from bundled credentials.
57
58
 
58
59
  ## `agy-review` contract (review role)
59
60
 
@@ -34,7 +34,8 @@ agy
34
34
  This caches an OAuth token under `~/.gemini/antigravity-cli/` (`antigravity-oauth-token`). That token
35
35
  is **personal** — never copy, commit, package, print, or share that directory or token. This skill
36
36
  needs no API keys and must not be configured with API-key billing; the wrapper unsets every
37
- `*_API_KEY` so billing can never silently fall back to pay-as-you-go.
37
+ `*_API_KEY` except `TYPESAFE_API_KEY` (the one key a run passes, as given) so billing can never
38
+ silently fall back to pay-as-you-go.
38
39
 
39
40
  ## 3. Put the wrappers on `PATH` (`agy-run` + `agy-review`)
40
41