From 2cd0c3ddd0ae6277d5d54da98e6dfabd846aef65 Mon Sep 17 00:00:00 2001 From: Mike Swanson Date: Fri, 5 Jun 2026 06:44:10 -0700 Subject: [PATCH] =?UTF-8?q?feat(skills):=20add=20AGY=20=E2=80=94=20Google?= =?UTF-8?q?=20Gemini=20CLI=20second-opinion=20router?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sibling of the grok skill: routes text/verify/review (+ review-files, review-diff, raw) to the official Google Gemini CLI (gemini, npm global, v0.45.1) for an independent second model. ask-gemini.sh mirrors ask-grok.sh (identity-aware gating, binary auto-locate, cygpath hardening, prompt-file inputs, clean stdout/stderr separation, JSON .response extraction). review modes copy targets into a temp dir + --include-directories to bypass Gemini's gitignore/workspace sandbox. verify/review pinned to gemini-3.1-pro-preview (GEMINI_MODEL overridable). migrate-identity.sh auto-detects gemini and writes a per-machine identity.json gemini block. Auth: Google OAuth (no key). Fleet Gemini host: GURU-5070. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/scripts/migrate-identity.sh | 34 +++ .claude/skills/agy/SKILL.md | 128 +++++++++++ .claude/skills/agy/scripts/ask-gemini.sh | 272 +++++++++++++++++++++++ 3 files changed, 434 insertions(+) create mode 100644 .claude/skills/agy/SKILL.md create mode 100644 .claude/skills/agy/scripts/ask-gemini.sh diff --git a/.claude/scripts/migrate-identity.sh b/.claude/scripts/migrate-identity.sh index 51b49e2..64396fd 100755 --- a/.claude/scripts/migrate-identity.sh +++ b/.claude/scripts/migrate-identity.sh @@ -96,6 +96,28 @@ else echo " Grok: not installed" fi +# Detect Google Gemini CLI — optional capability extension (independent second +# model: verify / review / text). Sibling of Grok. Per-machine; sets identity +# gemini.installed so the /agy skill knows whether it can run locally. Does NOT +# set is_fleet_host (manual fleet-coordination choice, preserved if present). +GEMINI_BIN="" +if command -v gemini >/dev/null 2>&1; then + GEMINI_BIN="$(command -v gemini)" +else + for c in "${APPDATA:-}/npm/gemini" "$HOME/AppData/Roaming/npm/gemini" \ + "/usr/local/bin/gemini" "$HOME/.npm-global/bin/gemini"; do + if [ -n "$c" ] && [ -x "$c" ]; then GEMINI_BIN="$c"; break; fi + done +fi +if [ -n "$GEMINI_BIN" ]; then + GEMINI_BIN="$(cygpath -m "$GEMINI_BIN" 2>/dev/null || echo "$GEMINI_BIN")" + GEMINI_INSTALLED="true" + echo " Gemini: installed ($GEMINI_BIN)" +else + GEMINI_INSTALLED="false" + echo " Gemini: not installed" +fi + # Build updated identity.json echo "" echo "[INFO] Updating identity.json..." @@ -136,6 +158,17 @@ else: g['installed'] = False data['grok'] = g +# Gemini capability flag (per-machine, sibling of grok). Preserve manual is_fleet_host. +gm = data.get('gemini') or {} +if '$GEMINI_INSTALLED' == 'true': + gm['installed'] = True + gm['binary'] = r'$GEMINI_BIN' + gm.setdefault('auth', 'oauth') + gm.setdefault('capabilities', ['text', 'verify', 'review']) +else: + gm['installed'] = False +data['gemini'] = gm + # Coord API endpoint — populate only if absent so existing machines keep their override. if 'coord_api' not in data: data['coord_api'] = '$COORD_API_DEFAULT' @@ -158,6 +191,7 @@ echo " ollama.prose_model: $PROSE_MODEL" echo " platform: $PLATFORM" echo " architecture: $ARCH" echo " grok.installed: $GROK_INSTALLED" +echo " gemini.installed: $GEMINI_INSTALLED" echo " coord_api: (default $COORD_API_DEFAULT if not already set)" echo "" echo "Review: cat $IDENTITY_PATH" diff --git a/.claude/skills/agy/SKILL.md b/.claude/skills/agy/SKILL.md new file mode 100644 index 0000000..46ae34b --- /dev/null +++ b/.claude/skills/agy/SKILL.md @@ -0,0 +1,128 @@ +--- +name: agy +description: > + Route a task to the official Google Gemini CLI for an independent second + model — a sibling of the `grok` second-opinion router. Use for: an + independent, different-vendor SECOND OPINION or adversarial VERIFICATION of a + Claude finding/design before acting on it, a Gemini code REVIEW of a file / + set of files / git diff, and one-shot Gemini TEXT answers. Invoke on: + "ask gemini", "gemini verify", "second opinion from gemini", "gemini review", + "agy ...". Gemini is an independent second model (and Google-ecosystem reach), + NOT a replacement for Claude's own codebase work. +--- + +# AGY — Gemini capability router + +Claude shells out to the locally-installed **Google Gemini CLI** (`gemini`, npm +global, v0.45.1) for a genuinely independent, different-vendor second model. +AGY is the sibling of [`grok`](../grok/SKILL.md): both are second-opinion / +review routers. Use whichever you want a second model from (or both, to triangulate). +Verified working on this machine (2026-06-05): text, verify, review (single +file / file set / git diff). + +**Auth:** Gemini uses **Google login (OAuth)** — **no API key**. Creds live at +`~/.gemini/oauth_creds.json`. If calls fail with an auth error, run `gemini` +interactively once and choose **"Login with Google"**, then retry. + +## The wrapper + +``` +bash "$CLAUDETOOLS_ROOT/.claude/skills/agy/scripts/ask-gemini.sh" ... +``` + +| Mode | Usage | What it does | +|------|-------|--------------| +| `text` | `ask-gemini.sh text ""` or `text --prompt-file ` | One-shot text answer from an independent model. `--prompt-file` for long content (review/summarize a doc). Default model routing. | +| `verify` | `ask-gemini.sh verify ""` or `verify --prompt-file ` | Adversarial second opinion — Gemini tries to REFUTE / find gaps, returns a verdict + reasons. Pinned to the strong model. | +| `review` | `ask-gemini.sh review [""]` | Gemini reads the file itself (its `read_file` tool, read-only `plan` mode) and reviews it. Accepts absolute or repo-relative paths, and paths with spaces. Works even on gitignored files. | +| `review-files` | `ask-gemini.sh review-files [-i ""] [f2 …]` | Review a **set** of files together (cross-file consistency, multi-file change). Paths absolute or repo-relative; spaces OK. No code passed as a shell arg. | +| `review-diff` | `ask-gemini.sh review-diff [-C ] [-i ""] [-- ]` | Review a **git diff** (`git diff ` from ``; default repo root, use `-C` for a submodule e.g. `-C projects/msp-tools/guru-rmm`). Diff goes via the prompt file; Gemini can `read_file` changed files for full context. | +| `raw` | `ask-gemini.sh raw ` | Escape hatch — passes args straight to `gemini`. | + +The script runs Gemini headless with `-o json`, extracts the answer from +`.response` (parsing from the first `{` so the CLI's cosmetic warning lines are +ignored), and keeps stderr separate from the JSON so 429-backoff / warning noise +never corrupts the parse. + +### Model + +- `text` uses Gemini's **default routing** (currently a flash-tier model) — fast, cheap. +- `verify` / `review*` pin a **strong** model — `gemini-3.1-pro-preview` (verified + available on this account 2026-06-05; the CLI's own pro tier). +- Override either with `GEMINI_MODEL=` (e.g. `GEMINI_MODEL=gemini-2.5-pro`). + +## Machine availability (fleet) + +AGY is **per-machine** — the skill syncs fleet-wide but the `gemini` binary does +not. Availability is gated by `identity.json` (per-machine, gitignored): + +```json +"gemini": { "installed": true, + "binary": "C:/Users/guru/AppData/Roaming/npm/gemini", + "auth": "oauth", "is_fleet_host": true, + "capabilities": ["text","verify","review"] } +``` + +- If `gemini.installed` is `false` (or the block is absent), `ask-gemini.sh` exits + **3** with routing guidance instead of failing obscurely. Claude on such a + machine should NOT attempt local Gemini. +- **Current fleet Gemini host: `GURU-5070`** — the only machine with the Gemini + CLI installed and Google-OAuth'd right now. When others get it, install + `@google/gemini-cli`, run `gemini` once to log in with Google, then set their + `identity.json` `gemini` block (and update this line). + +**Remote routing (NOT yet wired):** a non-host machine cannot run Gemini locally. +To fulfill an AGY request from elsewhere, route it to the host (`GURU-5070`) — +same pending channels as Grok (GuruRMM agent exec, a relay, or a coord-API job +queue). Until that's built, AGY requests originate on the host machine. + +## When to route to Gemini (AGY) + +- **Independent verification** — a genuinely different vendor/model to red-team a + Claude finding or design before acting on it. (`verify`) +- **Second-model code review** — have Gemini read and critique a file, a set of + files, or a diff independently of Claude. (`review`, `review-files`, `review-diff`) +- **Diverse drafts / second opinion** — alternative phrasing or approach to + compare. (`text`) +- **Google-ecosystem reach** — when a Google-side model/behavior is specifically + wanted as the comparison point. + +AGY and [GROK](../grok/SKILL.md) are sibling second-opinion routers. Pick one, or +run both and compare — disagreement between them is a strong signal to slow down. + +## When NOT to + +- Pure classify / extract / summarize → cheaper via Tier-0 Ollama (`.claude/OLLAMA.md`). +- Editing this repo's code → Claude's own agents own the codebase work. Gemini's + `review*` modes are read-only (`--approval-mode plan`) by design; do not give + Gemini write access to this repo. +- Image / video generation → that's GROK's lane (`grok image` / `grok video`), + not Gemini here. +- **Never** delegate unsupervised destructive / production actions to Gemini. + Always review Gemini output before acting on it — like Grok, it can over-claim. + +## Safety / operational notes + +- `--skip-trust` is REQUIRED for headless runs (the CWD isn't a Gemini "trusted + folder"). Equivalent env: `GEMINI_CLI_TRUST_WORKSPACE=true`. The wrapper passes it. +- `review*` runs under `--approval-mode plan` (read-only): Gemini can read files + but cannot modify anything. Do not change this to `auto_edit`/`yolo`. +- Gemini's `read_file` honors `.gitignore` **and** a workspace sandbox (only files + inside the workspace are readable). The wrapper sidesteps both by copying each + review target into a temp dir added via `--include-directories` — so review + works for tracked, gitignored, and spaced-path files alike. +- Prompts are passed via `-p "$(cat )"` built from a temp file, not + inline shell args (avoids quote hell with long/structured content). +- stdin is always closed (`" -o json --skip-trust Google Gemini CLI router (independent second model). +# +# Sibling of ask-grok.sh. Routes a task to the official Google Gemini CLI +# (`gemini`, npm global) for an independent, different-vendor second opinion, +# verification, or a Gemini code review. Headless, safe-by-default, JSON-parsed. +# +# Auth is Google login (OAuth) — NO API key. Creds: ~/.gemini/oauth_creds.json. +# If a call fails with an auth error, run `gemini` interactively once and pick +# "Login with Google". +# +# Output contract (VERIFIED on GURU-5070, gemini 0.45.1): +# - Prefer JSON: `gemini -p ... -o json` -> {session_id, response, stats}. +# The answer text is `.response`. stdout may carry two cosmetic warning lines +# ("True color..." / "Ripgrep is not available...") before the JSON; we extract +# the object starting at the FIRST '{' to ignore them. stderr (429 backoff, +# warnings) is captured SEPARATELY and never fed to the JSON parser. +# - `--skip-trust` is REQUIRED headless (the CWD isn't a trusted folder). +# - stdin is always closed (" # one-shot answer +# ask-gemini.sh text --prompt-file # long content +# ask-gemini.sh verify "" # adversarial check +# ask-gemini.sh verify --prompt-file +# ask-gemini.sh review [instructions] # gemini reads + reviews one file +# ask-gemini.sh review-files [-i "instr"] [f2 ...] # review a SET of files together +# ask-gemini.sh review-diff [-C ] [-i "instr"] [-- ] +# ask-gemini.sh raw # escape hatch +# +# Exit: 0 ok, 1 no result, 2 usage, 3 not installed here, 127 gemini/python not found. +set -uo pipefail +SELF="ask-gemini" + +PY="$(command -v py 2>/dev/null || command -v python 2>/dev/null || command -v python3 2>/dev/null || true)" +[ -z "$PY" ] && { echo "[$SELF] python (py/python/python3) required for JSON parsing" >&2; exit 127; } + +# --- path conversion: native-Windows path for the gemini args (no-op off Windows) --- +# gemini is a native Windows binary (npm shim -> node.exe); Git Bash hands it POSIX +# paths (/tmp, /c/.., /d/..) it cannot resolve. cygpath -w converts to C:\... on +# MSYS/Cygwin; on Linux/macOS it passes through unchanged. Explicit conversion +# removes reliance on MSYS auto-conversion (which breaks on spaces/edge cases). +if command -v cygpath >/dev/null 2>&1; then + winpath() { cygpath -w -- "$1" 2>/dev/null || printf '%s' "$1"; } +else + winpath() { printf '%s' "$1"; } +fi + +# --- identity.json (per-machine, gitignored) declares whether gemini is installed here --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)" +IDFILE="" +[ -n "${CLAUDETOOLS_ROOT:-}" ] && [ -f "$CLAUDETOOLS_ROOT/.claude/identity.json" ] && IDFILE="$CLAUDETOOLS_ROOT/.claude/identity.json" +[ -z "$IDFILE" ] && IDFILE="$(cd "$SCRIPT_DIR/../../.." 2>/dev/null && pwd)/identity.json" +idgem() { # read field $1 from identity.json .gemini (empty if absent) + [ -f "$IDFILE" ] || { echo ""; return; } + "$PY" -c "import json,sys +try: + g=(json.load(sys.stdin).get('gemini') or {}); v=g.get('$1','') + print('' if v is None else (str(v).lower() if isinstance(v,bool) else v)) +except Exception: print('')" < "$IDFILE" +} + +# If identity explicitly says gemini is NOT installed here, fail fast with guidance. +if [ "$(idgem installed)" = "false" ]; then + echo "[$SELF] gemini is not installed on this machine (identity.json gemini.installed=false)." >&2 + echo "[$SELF] Gemini runs only on the fleet host. Route this request there, or install the gemini CLI (npm i -g @google/gemini-cli) + set identity.json gemini.installed=true." >&2 + exit 3 +fi + +# --- locate the gemini binary: GEMINI env > identity.json gemini.binary > auto-locate --- +# An explicit GEMINI= override that isn't runnable is a user error -> fail clearly up front +# (covers absolute paths AND a bare name resolvable on PATH, e.g. GEMINI=gemini). +GEMINI="${GEMINI:-}" +if [ -n "$GEMINI" ] && [ ! -x "$GEMINI" ] && ! command -v "$GEMINI" >/dev/null 2>&1; then + echo "[$SELF] GEMINI='$GEMINI' is not an executable gemini binary." >&2; exit 127 +fi +cand="$(idgem binary)" +[ -z "$GEMINI" ] && [ -n "$cand" ] && [ -x "$cand" ] && GEMINI="$cand" +if [ -z "$GEMINI" ]; then + if command -v gemini >/dev/null 2>&1; then GEMINI="$(command -v gemini)"; else + for c in "${APPDATA:-}/npm/gemini" "/c/Users/${USERNAME:-${USER:-x}}/AppData/Roaming/npm/gemini" \ + "$HOME/AppData/Roaming/npm/gemini" "/usr/local/bin/gemini" "$HOME/.npm-global/bin/gemini"; do + [ -n "$c" ] && [ -x "$c" ] && { GEMINI="$c"; break; } + done + fi +fi +[ -z "$GEMINI" ] && { echo "[$SELF] gemini CLI not found (set identity.json gemini.binary, GEMINI=, or install: npm i -g @google/gemini-cli)" >&2; exit 127; } + +# Model: default routing for text; a strong pinned model for verify/review. +# gemini-3.1-pro-preview verified available on this account (2026-06-05); overridable. +STRONG_MODEL="${GEMINI_MODEL:-gemini-3.1-pro-preview}" + +MODE="${1:-}"; shift 2>/dev/null || true +[ -z "$MODE" ] && { echo "usage: $SELF {text|verify|review|review-files|review-diff|raw} ..." >&2; exit 2; } + +TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT +PF="$TMP/prompt.txt"; OUT="$TMP/out.txt"; ERR="$TMP/err.txt" +REPO_ROOT="${CLAUDETOOLS_ROOT:-$(cd "$SCRIPT_DIR/../../../.." 2>/dev/null && pwd)}" + +# gtimeout on macOS (brew coreutils), timeout elsewhere. +TIMEOUT_CMD="timeout" +if [[ "${OSTYPE:-}" == "darwin"* ]]; then + TIMEOUT_CMD="$(command -v gtimeout 2>/dev/null || echo timeout)" +fi + +# run gemini headless reading the prompt file. $1=timeout secs; rest=extra flags. +# stdout -> $OUT, stderr -> $ERR (kept separate so warning/429 noise never reaches +# the JSON parser). Never fail the script on gemini's exit code; we judge by output. +# Records the invocation so emit_or_fail can replay it once on a transient empty turn. +LAST_RUN=() +run_gemini() { + local to="$1"; shift + LAST_RUN=("$to" "$@") + "$TIMEOUT_CMD" "$to" "$GEMINI" -p "$(cat "$PF")" -o json --skip-trust "$@" \ + >"$OUT" 2>"$ERR" /dev/null; } + +emit_or_fail() { # print .response, or retry once on a transient empty turn, else fail + local txt; txt="$(gresponse)" + if [ -n "$txt" ]; then printf '%s\n' "$txt"; return 0; fi + # Auth failures won't be fixed by a retry — report immediately. + if auth_failed; then + echo "[$SELF] Gemini auth error — run 'gemini' interactively and choose 'Login with Google', then retry." >&2 + exit 1 + fi + # Gemini occasionally returns an empty turn (or absorbs a 429 backoff into the + # timeout). Replay the identical call once before giving up. + if [ ${#LAST_RUN[@]} -gt 0 ]; then + echo "[$SELF] empty response — retrying once..." >&2 + run_gemini "${LAST_RUN[@]}" + txt="$(gresponse)" + if [ -n "$txt" ]; then printf '%s\n' "$txt"; return 0; fi + if auth_failed; then + echo "[$SELF] Gemini auth error — run 'gemini' interactively and choose 'Login with Google', then retry." >&2 + exit 1 + fi + fi + echo "[$SELF] no response from gemini. stderr tail:" >&2 + tail -3 "$ERR" >&2 2>/dev/null || true + exit 1 +} + +# Copy target files into an included temp workspace dir so gemini's read_file can +# reach them regardless of .gitignore / workspace sandbox. Echoes the included dir. +INCLUDE_DIR="$TMP/inbox" +prep_includes() { mkdir -p "$INCLUDE_DIR"; } + +case "$MODE" in + text|verify) + SRC="" + if [ "${1:-}" = "--prompt-file" ]; then + [ -f "${2:-}" ] || { echo "[$SELF] prompt file not found: ${2:-}" >&2; exit 2; } + SRC="$(cat "$2")" + else + SRC="${1:-}" + fi + [ -z "$SRC" ] && { echo "usage: $SELF $MODE \"\" | $SELF $MODE --prompt-file " >&2; exit 2; } + if [ "$MODE" = "verify" ]; then + printf 'You are an adversarial reviewer giving an independent second opinion. Evaluate the following claim/finding/document: try hard to find any way it is WRONG, incomplete, unsupported, or overstated. Then give a clear VERDICT (e.g. correct / partly correct / incorrect) plus specific justification. Answer in text only; do not use any tools.\n\nContent:\n%s' "$SRC" > "$PF" + run_gemini 180 -m "$STRONG_MODEL" + else + printf 'Answer the following directly in text. Do not use any tools.\n\n%s' "$SRC" > "$PF" + run_gemini 180 + fi + emit_or_fail + ;; + + review|file) + [ -z "${1:-}" ] && { echo "usage: $SELF review [instructions]" >&2; exit 2; } + target="$1" + instr="${2:-Give an independent, critical review of this file: accuracy, gaps/omissions, bugs, and concrete improvements. Be specific.}" + if [ -f "$target" ]; then resolved="$target" + elif [ -f "$REPO_ROOT/$target" ]; then resolved="$REPO_ROOT/$target" + else echo "[$SELF] file not found: $target" >&2; exit 2; fi + prep_includes + base="$(basename "$resolved")" + cp -f "$resolved" "$INCLUDE_DIR/$base" + tgt_win="$(winpath "$INCLUDE_DIR/$base")" + inc_win="$(winpath "$INCLUDE_DIR")" + printf 'Use your read_file tool to read the file at this absolute path, then perform the task and stop. Do not modify anything.\nPath: %s\n\nTask: %s' "$tgt_win" "$instr" > "$PF" + run_gemini 240 -m "$STRONG_MODEL" --approval-mode plan --include-directories "$inc_win" + emit_or_fail + ;; + + review-files) + instr='Independently review these files together as a unit: correctness/bugs, gaps, cross-file consistency, and concrete improvements. Be specific and cite file:line.' + files=() + while [ $# -gt 0 ]; do + case "$1" in + -i|--instr) instr="${2:-}"; shift 2 2>/dev/null || shift ;; + *) files+=("$1"); shift ;; + esac + done + [ ${#files[@]} -eq 0 ] && { echo "usage: $SELF review-files [-i \"instructions\"] [file ...]" >&2; exit 2; } + prep_includes + list="" + declare -A seen=() + for f in "${files[@]}"; do + if [ -f "$f" ]; then r="$f" + elif [ -f "$REPO_ROOT/$f" ]; then r="$REPO_ROOT/$f" + else echo "[$SELF] file not found: $f" >&2; exit 2; fi + base="$(basename "$r")" + # de-collide identical basenames from different dirs + if [ -n "${seen[$base]:-}" ]; then + n=1; while [ -e "$INCLUDE_DIR/${n}_${base}" ]; do n=$((n+1)); done; base="${n}_${base}" + fi + seen[$base]=1 + cp -f "$r" "$INCLUDE_DIR/$base" + list+="- $(winpath "$INCLUDE_DIR/$base") +" + done + inc_win="$(winpath "$INCLUDE_DIR")" + printf 'Use your read_file tool to read EACH of these files (absolute paths), then perform the task across ALL of them and stop. Do not modify anything.\n\nFiles:\n%s\nTask: %s' "$list" "$instr" > "$PF" + run_gemini 300 -m "$STRONG_MODEL" --approval-mode plan --include-directories "$inc_win" + emit_or_fail + ;; + + review-diff) + gdir="$REPO_ROOT" + instr='Review this git diff: correctness/bugs introduced, regressions, missing edge cases, and concrete fixes. Focus on the CHANGES. Be specific and cite file:line.' + ref=""; pathspec=() + while [ $# -gt 0 ]; do + case "$1" in + -C|--dir) gdir="${2:-}"; shift 2 2>/dev/null || shift ;; + -i|--instr) instr="${2:-}"; shift 2 2>/dev/null || shift ;; + --) shift; while [ $# -gt 0 ]; do pathspec+=("$1"); shift; done ;; + *) if [ -z "$ref" ]; then ref="$1"; else pathspec+=("$1"); fi; shift ;; + esac + done + [ -z "$ref" ] && { echo "usage: $SELF review-diff [-C ] [-i \"instr\"] [-- ]" >&2; exit 2; } + [ -d "$gdir" ] || { [ -d "$REPO_ROOT/$gdir" ] && gdir="$REPO_ROOT/$gdir"; } + git -C "$gdir" rev-parse --git-dir >/dev/null 2>&1 || { echo "[$SELF] not a git repo: $gdir" >&2; exit 2; } + if [ ${#pathspec[@]} -gt 0 ]; then + git -C "$gdir" diff "$ref" -- "${pathspec[@]}" > "$TMP/diff.txt" 2>"$TMP/differr.txt" + else + git -C "$gdir" diff "$ref" > "$TMP/diff.txt" 2>"$TMP/differr.txt" + fi + [ -s "$TMP/diff.txt" ] || { echo "[$SELF] empty/failed diff for '$ref' in $gdir: $(head -1 "$TMP/differr.txt" 2>/dev/null)" >&2; exit 1; } + gdir_win="$(winpath "$gdir")" + { printf 'Review the following unified git diff. %s\nYou may use your read_file tool on any changed file for full context (paths in the diff are relative to %s; strip the a/ b/ prefixes). Do not modify anything.\n\n=== BEGIN DIFF ===\n' "$instr" "$gdir_win"; cat "$TMP/diff.txt"; printf '\n=== END DIFF ===\n'; } > "$PF" + run_gemini 300 -m "$STRONG_MODEL" --approval-mode plan --include-directories "$gdir_win" + emit_or_fail + ;; + + raw) + "$GEMINI" "$@" + ;; + + *) + echo "[$SELF] unknown mode '$MODE' (use text|verify|review|review-files|review-diff|raw)" >&2; exit 2 ;; +esac