agy: rewire skill from dead gemini npm CLI to Antigravity 'agy' binary
The old Google gemini CLI stopped working on this account (throwIneligibleOrProjectIdError - needs a GOOGLE_CLOUD_PROJECT the personal account can't supply). Replace it with the Antigravity CLI (agy, native Go binary, own auth, no project ID). - New scripts/ask-agy.sh: plain-text output (no JSON), -p prompt-last, --model friendly names (Gemini 3.1 Pro (High) default for verify/review*/vision/search), --add-dir for file/vision reads, --dangerously-skip-permissions to avoid print-mode approval hangs. All modes smoke-tested (text/verify/review/search). - scripts/ask-gemini.sh: deprecated shim -> exec ask-agy.sh (back-compat). - SKILL.md: rewired header/flags/models/auth/availability/reference to agy. - ask-grok.sh: xsearch fallback now execs ask-agy.sh (was the dead gemini). - identity.json (local, gitignored): agy block added, gemini marked retired. Authoritative flags sourced from 'agy --help' (web docs are JS SPAs, unreadable by fetch tools; grok fetch/search timed out). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
280
.claude/skills/agy/scripts/ask-agy.sh
Normal file
280
.claude/skills/agy/scripts/ask-agy.sh
Normal file
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env bash
|
||||
# ask-agy.sh — Claude -> Google Antigravity CLI (`agy`) router (independent second model).
|
||||
#
|
||||
# Sibling of ask-grok.sh. Routes a task to the Antigravity CLI (`agy`, a native Go
|
||||
# binary at ~/AppData/Local/agy/bin/agy) for an independent, different-vendor second
|
||||
# opinion, verification, code review, vision, or live web search. Multi-model backend
|
||||
# (Gemini 3.5 Flash / Gemini 3.1 Pro / Claude / GPT-OSS) selectable via --model.
|
||||
#
|
||||
# REPLACES the old `gemini` npm CLI (ask-gemini.sh), which broke on this account with
|
||||
# a Cloud-project eligibility error (throwIneligibleOrProjectIdError). `agy` uses
|
||||
# Antigravity's own auth (~/.gemini/antigravity-cli/), no GOOGLE_CLOUD_PROJECT needed.
|
||||
#
|
||||
# Authoritative flag reference (from `agy --help`, Go flag package — 2026-07-02, v1.0.16):
|
||||
# -p | --print | --prompt Run ONE prompt non-interactively and print the response.
|
||||
# STRING flag: the prompt is its VALUE. MUST be LAST on the
|
||||
# line (a bare `-p --model` makes "--model" the prompt).
|
||||
# --model "<name>" Model for the session. Friendly names from `agy models`,
|
||||
# e.g. "Gemini 3.1 Pro (High)", "Gemini 3.5 Flash (Medium)",
|
||||
# "Claude Opus 4.6 (Thinking)". Omit -> CLI default (Flash).
|
||||
# --add-dir <dir> Add a directory to the workspace (repeatable). Needed so
|
||||
# the agent's file tools can read a review/vision target.
|
||||
# --dangerously-skip-permissions Auto-approve tool calls (else print mode can HANG
|
||||
# on an approval prompt until --print-timeout). Headless-safe.
|
||||
# --print-timeout <dur> Print-mode wait timeout (default 5m0s). We also wrap in the
|
||||
# shell `timeout` as a hard belt-and-suspenders bound.
|
||||
# (There is NO JSON/structured-output flag — stdout is plain text/markdown.)
|
||||
#
|
||||
# Output contract: plain text on stdout. Tool-using turns (review/search/vision) may
|
||||
# emit a line or two of tool narration ("I will view the content of ...") before the
|
||||
# answer; we ask the model to suppress it but tolerate residual. No JSON parsing.
|
||||
#
|
||||
# Usage (unchanged from the old skill so callers/SKILL.md stay valid):
|
||||
# ask-agy.sh text "<prompt>" # one-shot answer
|
||||
# ask-agy.sh text --prompt-file <path> # long content
|
||||
# ask-agy.sh verify "<claim or finding to refute>" # adversarial check
|
||||
# ask-agy.sh verify --prompt-file <path>
|
||||
# ask-agy.sh review <file> [instructions] # agy reads + reviews one file
|
||||
# ask-agy.sh review-files [-i "instr"] <f1> [f2 ...] # review a SET together
|
||||
# ask-agy.sh review-diff [-C <repo-dir>] [-i "instr"] <gitref> [-- <pathspec>]
|
||||
# ask-agy.sh image-analyze <image-path> ["question"] # vision: read image + describe
|
||||
# ask-agy.sh search "<query>" # live web search + sources
|
||||
# ask-agy.sh raw <agy args...> # escape hatch
|
||||
#
|
||||
# Exit: 0 ok, 1 no result, 2 usage, 3 not installed here, 127 agy not found.
|
||||
set -uo pipefail
|
||||
SELF="ask-agy"
|
||||
|
||||
# --- path conversion: native-Windows path for agy args (no-op off Windows) ---
|
||||
# agy is a native Windows binary; Git Bash hands it POSIX paths it cannot resolve.
|
||||
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): is agy installed here? ---
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)"
|
||||
PYBIN="$(command -v py 2>/dev/null || command -v python 2>/dev/null || command -v python3 2>/dev/null || true)"
|
||||
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"
|
||||
idagy() { # read field $1 from identity.json .agy (fallback .gemini), empty if absent
|
||||
[ -f "$IDFILE" ] || { echo ""; return; }
|
||||
[ -n "$PYBIN" ] || { echo ""; return; }
|
||||
"$PYBIN" -c "import json,sys
|
||||
try:
|
||||
d=json.load(sys.stdin); g=(d.get('agy') or d.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 [ "$(idagy installed)" = "false" ]; then
|
||||
echo "[$SELF] agy is not installed on this machine (identity.json agy.installed=false)." >&2
|
||||
echo "[$SELF] Antigravity CLI runs only on the fleet host. Route this request there, or install agy + set identity.json agy.installed=true." >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# --- locate the agy binary: AGY env > identity.json agy.binary > PATH > known paths ---
|
||||
AGY="${AGY:-}"
|
||||
if [ -n "$AGY" ] && [ ! -x "$AGY" ] && ! command -v "$AGY" >/dev/null 2>&1; then
|
||||
echo "[$SELF] AGY='$AGY' is not an executable agy binary." >&2; exit 127
|
||||
fi
|
||||
cand="$(idagy binary)"
|
||||
[ -z "$AGY" ] && [ -n "$cand" ] && [ -x "$cand" ] && AGY="$cand"
|
||||
if [ -z "$AGY" ]; then
|
||||
if command -v agy >/dev/null 2>&1; then AGY="$(command -v agy)"; else
|
||||
for c in "/c/Users/${USERNAME:-${USER:-x}}/AppData/Local/agy/bin/agy" \
|
||||
"$HOME/AppData/Local/agy/bin/agy" "$LOCALAPPDATA/agy/bin/agy" \
|
||||
"/usr/local/bin/agy" "$HOME/.local/bin/agy"; do
|
||||
[ -n "$c" ] && [ -x "$c" ] && { AGY="$c"; break; }
|
||||
done
|
||||
fi
|
||||
fi
|
||||
[ -z "$AGY" ] && { echo "[$SELF] agy CLI not found (set identity.json agy.binary, AGY=, or install the Antigravity CLI)" >&2; exit 127; }
|
||||
|
||||
# Strong model for verify/review*/vision/search; text uses the CLI default (Flash).
|
||||
STRONG_MODEL="${AGY_MODEL:-Gemini 3.1 Pro (High)}"
|
||||
|
||||
MODE="${1:-}"; shift 2>/dev/null || true
|
||||
[ -z "$MODE" ] && { echo "usage: $SELF {text|verify|review|review-files|review-diff|image-analyze|search|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)}"
|
||||
_logerr() { bash "$REPO_ROOT/.claude/scripts/log-skill-error.sh" "agy" "$@" >/dev/null 2>&1 || true; }
|
||||
|
||||
TIMEOUT_CMD="timeout"
|
||||
if [[ "${OSTYPE:-}" == "darwin"* ]]; then
|
||||
TIMEOUT_CMD="$(command -v gtimeout 2>/dev/null || echo timeout)"
|
||||
fi
|
||||
|
||||
# run agy headless. $1 = hard timeout secs; remaining args = flags placed BEFORE -p.
|
||||
# The prompt (from $PF) is always the LAST token, as the value of -p. agy has no JSON
|
||||
# mode, so stdout is the answer (plain text); stderr kept separate for diagnostics.
|
||||
LAST_FLAGS=()
|
||||
run_agy() {
|
||||
local to="$1"; shift
|
||||
LAST_FLAGS=("$@")
|
||||
"$TIMEOUT_CMD" "$to" "$AGY" "$@" --print-timeout "${to}s" -p "$(cat "$PF")" \
|
||||
>"$OUT" 2>"$ERR" </dev/null || true
|
||||
}
|
||||
|
||||
# agy occasionally returns an empty turn; retry a couple times with backoff, then fail.
|
||||
emit_or_fail() {
|
||||
local txt tries=0 max="${AGY_MAX_TRIES:-3}"
|
||||
txt="$(sed -e 's/[[:space:]]*$//' "$OUT" | sed -e :a -e '/^\s*$/{$d;N;ba}')"
|
||||
while [ -z "${txt//[[:space:]]/}" ] && [ "$tries" -lt $((max-1)) ] && [ ${#LAST_FLAGS[@]} -ge 0 ]; do
|
||||
tries=$((tries+1))
|
||||
echo "[$SELF] empty response - retry $tries/$((max-1)) (backoff ${tries}x3s)..." >&2
|
||||
sleep $((tries*3))
|
||||
run_agy 240 "${LAST_FLAGS[@]}"
|
||||
txt="$(cat "$OUT")"
|
||||
done
|
||||
if [ -n "${txt//[[:space:]]/}" ]; then cat "$OUT"; return 0; fi
|
||||
if grep -qiE 'not authenticated|please (log|sign).?in|auth(entication)? (failed|error|required)|token (has )?expired|unauthorized' "$ERR" 2>/dev/null; then
|
||||
echo "[$SELF] agy auth error - run 'agy' interactively once to sign in, then retry." >&2
|
||||
_logerr "agy auth/login failure" --context "mode=$MODE"
|
||||
else
|
||||
echo "[$SELF] no response from agy after $max attempts. stderr tail:" >&2
|
||||
tail -3 "$ERR" >&2 2>/dev/null || true
|
||||
_logerr "agy returned no response (empty after $max attempts)" --context "mode=$MODE err=$(tail -1 "$ERR" 2>/dev/null | tr -d '\n' | cut -c1-80)"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Copy review/vision targets into an included workspace dir so agy's file tools can
|
||||
# reach them regardless of location/spaces; --add-dir this dir.
|
||||
INCLUDE_DIR="$TMP/inbox"
|
||||
prep_includes() { mkdir -p "$INCLUDE_DIR"; }
|
||||
|
||||
NO_TOOLS='Do not use any tools; answer directly in text.'
|
||||
NO_NARRATE='Do not narrate your tool use or steps; output only the final answer.'
|
||||
|
||||
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 \"<prompt>\" | $SELF $MODE --prompt-file <path>" >&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 (correct / partly correct / incorrect) plus specific justification. %s\n\nContent:\n%s' "$NO_TOOLS" "$SRC" > "$PF"
|
||||
run_agy 240 --dangerously-skip-permissions --model "$STRONG_MODEL"
|
||||
else
|
||||
printf '%s\n\n%s' "$NO_TOOLS" "$SRC" > "$PF"
|
||||
run_agy 240 --dangerously-skip-permissions
|
||||
fi
|
||||
emit_or_fail
|
||||
;;
|
||||
|
||||
review|file)
|
||||
[ -z "${1:-}" ] && { echo "usage: $SELF review <file-path> [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 'Read the file at this absolute path, then perform the task and stop. Do not modify anything. %s\nPath: %s\n\nTask: %s' "$NO_NARRATE" "$tgt_win" "$instr" > "$PF"
|
||||
run_agy 300 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$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> [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")"
|
||||
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 'Read EACH of these files (absolute paths), then perform the task across ALL of them and stop. Do not modify anything. %s\n\nFiles:\n%s\nTask: %s' "$NO_NARRATE" "$list" "$instr" > "$PF"
|
||||
run_agy 360 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$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 <repo-dir>] [-i \"instr\"] <gitref> [-- <pathspec>]" >&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\n%s\nYou may read any changed file for full context (paths are relative to %s; strip a/ b/ prefixes). Do not modify anything.\n\n=== BEGIN DIFF ===\n' "$instr" "$NO_NARRATE" "$gdir_win"; cat "$TMP/diff.txt"; printf '\n=== END DIFF ===\n'; } > "$PF"
|
||||
run_agy 360 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$gdir_win"
|
||||
emit_or_fail
|
||||
;;
|
||||
|
||||
image-analyze|image|vision)
|
||||
[ -z "${1:-}" ] && { echo "usage: $SELF image-analyze <image-path> [\"question\"]" >&2; exit 2; }
|
||||
target="$1"; question="${2:-Describe exactly what is in this image.}"
|
||||
if [ -f "$target" ]; then resolved="$target"
|
||||
elif [ -f "$REPO_ROOT/$target" ]; then resolved="$REPO_ROOT/$target"
|
||||
else echo "[$SELF] image not found: $target" >&2; exit 2; fi
|
||||
prep_includes
|
||||
base="$(basename "$resolved")"; cp -f "$resolved" "$INCLUDE_DIR/$base"
|
||||
img_win="$(winpath "$INCLUDE_DIR/$base")"; inc_win="$(winpath "$INCLUDE_DIR")"
|
||||
printf 'Read the image at this absolute path and describe exactly what you see. Report only what is actually present; do not guess or invent content. Then stop. %s\nImage path: %s\n\nQuestion: %s' "$NO_NARRATE" "$img_win" "$question" > "$PF"
|
||||
run_agy 300 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$inc_win"
|
||||
emit_or_fail
|
||||
;;
|
||||
|
||||
search|websearch)
|
||||
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 search \"<query>\" | $SELF search --prompt-file <path>" >&2; exit 2; }
|
||||
printf 'Search the web for current, live information answering the following, then stop. Answer concisely and ALWAYS include the source URLs you used (a Sources list of full URLs). Do not fabricate URLs. %s\n\nQuery: %s' "$NO_NARRATE" "$SRC" > "$PF"
|
||||
run_agy 240 --dangerously-skip-permissions --model "$STRONG_MODEL"
|
||||
emit_or_fail
|
||||
;;
|
||||
|
||||
raw)
|
||||
"$AGY" "$@"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "[$SELF] unknown mode '$MODE' (use text|verify|review|review-files|review-diff|image-analyze|search|raw)" >&2; exit 2 ;;
|
||||
esac
|
||||
@@ -1,388 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# ask-gemini.sh — Claude -> 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.2):
|
||||
# - 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 (</dev/null) so `-p` never hangs waiting on stdin.
|
||||
#
|
||||
# File reads (review*): Gemini's read_file honors .gitignore AND a workspace
|
||||
# sandbox (only files under the workspace/included dirs are readable). To make
|
||||
# review robust for ANY file (tracked, gitignored, with spaces), we copy each
|
||||
# target into a temp dir and add it to the workspace via --include-directories.
|
||||
# review-diff runs with the repo dir included so changed files read in place.
|
||||
#
|
||||
# Usage:
|
||||
# ask-gemini.sh text "<prompt>" # one-shot answer
|
||||
# ask-gemini.sh text --prompt-file <path> # long content
|
||||
# ask-gemini.sh verify "<claim or finding to refute>" # adversarial check
|
||||
# ask-gemini.sh verify --prompt-file <path>
|
||||
# ask-gemini.sh review <file> [instructions] # gemini reads + reviews one file
|
||||
# ask-gemini.sh review-files [-i "instr"] <f1> [f2 ...] # review a SET of files together
|
||||
# ask-gemini.sh review-diff [-C <repo-dir>] [-i "instr"] <gitref> [-- <pathspec>]
|
||||
# ask-gemini.sh image-analyze <image-path> ["question"] # vision: read_file image + describe (PRO model)
|
||||
# ask-gemini.sh search "<query>" # Google-grounded live web search + sources
|
||||
# ask-gemini.sh raw <gemini args...> # 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|image-analyze|search|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)}"
|
||||
# Functional-error logger (skill name "agy"); soft-fails, never breaks the caller.
|
||||
_logerr() { bash "$REPO_ROOT/.claude/scripts/log-skill-error.sh" "agy" "$@" >/dev/null 2>&1 || true; }
|
||||
|
||||
# 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 || true
|
||||
}
|
||||
|
||||
# extract .response from the JSON object starting at the first '{' in $OUT.
|
||||
# Parsed via stdin so Windows python never resolves a git-bash (/c/...) path.
|
||||
#
|
||||
# Some pinned-pro tool-using turns (notably image-analyze) leak the model's
|
||||
# internal reasoning stream into .response: a stray token + a 'thought' marker
|
||||
# followed by 'CRITICAL INSTRUCTION N:' lines, then the real answer. We strip
|
||||
# that preamble ONLY when the signature is clearly present, so clean responses
|
||||
# (text/verify/review/search) pass through byte-for-byte unchanged.
|
||||
gresponse() { "$PY" -c "import json,sys,re,os
|
||||
raw=sys.stdin.read()
|
||||
i=raw.find('{')
|
||||
if i < 0:
|
||||
print(''); sys.exit(0)
|
||||
try:
|
||||
r=json.loads(raw[i:]).get('response','') or ''
|
||||
except Exception:
|
||||
print(''); sys.exit(0)
|
||||
head=r[:40].lower()
|
||||
leak=('thought' in head) or ('critical instruction' in r.lower()[:600])
|
||||
if leak:
|
||||
lines=r.split('\n')
|
||||
keep=[]; dropping=True
|
||||
for ln in lines:
|
||||
s=ln.strip()
|
||||
low=s.lower()
|
||||
if dropping and (
|
||||
low.endswith('thought') or low.startswith('critical instruction')
|
||||
or low.startswith('thought:') or low=='' ):
|
||||
continue
|
||||
dropping=False
|
||||
keep.append(ln)
|
||||
cleaned='\n'.join(keep).strip()
|
||||
r=cleaned if cleaned else r.strip()
|
||||
# AGY_CLEAN: aggressive prefix scrub for tool-using turns (image-analyze), which
|
||||
# can fuse a stray stream/tool token onto the front of the answer (e.g. '.',
|
||||
# '.94>', 'uem_image_0_0_png}'). Off by default so text/verify/review/search are
|
||||
# byte-exact. We only remove a junk run that ends in a stream delimiter (} > :)
|
||||
# or a lone leading punctuation char, immediately before the first real sentence.
|
||||
if os.environ.get('AGY_CLEAN') == '1' and r:
|
||||
# The pro-preview tool loop sometimes prepends a numbered/markdown reasoning
|
||||
# block before the actual answer. If a clear answer pivot follows such a
|
||||
# preamble, keep from the pivot onward (the user-facing answer).
|
||||
if re.search(r'(?im)^\s*\d+[.)]\s', r) or 'thought' in r[:60].lower():
|
||||
pivs=list(re.finditer(r'(?i)(Based on the image\b|\*\*Answer:?\*\*|The image (?:contains|shows|displays)\b)', r))
|
||||
if pivs:
|
||||
r=r[pivs[-1].start():]
|
||||
m=re.match(r'^[^\n]{0,40}?(?:\.png\)|\.jpe?g\)|[}>:)])\s*([\"A-Z].*)$', r, re.S)
|
||||
if m and m.group(1):
|
||||
r=m.group(1)
|
||||
else:
|
||||
# a short leading junk run (ASCII punctuation/digits or non-Latin stream
|
||||
# tokens) before a capitalized/quoted sentence start. Bounded length so we
|
||||
# never eat a real lowercase sentence or real prose.
|
||||
m=re.match(r'^(?:[^A-Za-z\"]|[^\x00-\x7f]){1,8}([A-Z\"].*)$', r, re.S)
|
||||
if m and m.group(1):
|
||||
r=m.group(1)
|
||||
r=r.strip()
|
||||
print(r)" < "$OUT"; }
|
||||
|
||||
# detect a GENUINE auth failure in stderr (precise remediation hint). Tightened 2026-06-17 -
|
||||
# the old broad regex (bare login|credential|authenticat|oauth|401) matched benign mid-run
|
||||
# token-refresh lines and false-flagged working sessions as auth failures.
|
||||
auth_failed() { grep -qiE 'invalid_grant|unauthorized|not authenticated|authentication failed|re-?authenticat|please (log|sign).?in|login with google|token (has )?expired|no (valid )?credentials' "$ERR" 2>/dev/null; }
|
||||
# detect a quota / rate-capacity exhaustion (the pinned strong model can be capped mid-session)
|
||||
quota_exhausted() { grep -qiE 'exhausted your capacity|quota|resource[_ ]?exhausted|rate limit|too many requests|429' "$ERR" 2>/dev/null; }
|
||||
|
||||
emit_or_fail() { # print .response; gemini intermittently returns an empty turn, so retry a few
|
||||
# times with backoff before giving up (single retry was insufficient - 2 empties
|
||||
# in a row caused spurious failures during live research, 2026-06-17).
|
||||
# Do ALL retries first; only classify the failure (auth vs generic) AFTER exhausting them.
|
||||
# (Checking auth_failed INSIDE the loop caused false aborts: a benign mid-run credential-refresh
|
||||
# line in stderr matched the auth regex and killed the retries even though auth was fine. 2026-06-17.)
|
||||
local txt tries=0 max="${AGY_MAX_TRIES:-3}"
|
||||
txt="$(gresponse)"
|
||||
while [ -z "$txt" ] && [ "$tries" -lt $((max-1)) ] && [ ${#LAST_RUN[@]} -gt 0 ]; do
|
||||
tries=$((tries+1))
|
||||
echo "[$SELF] empty response - retry $tries/$((max-1)) (backoff ${tries}x3s)..." >&2
|
||||
sleep $((tries*3)) # 3s, 6s backoff (covers transient empties / 429s / token refresh)
|
||||
run_gemini "${LAST_RUN[@]}"
|
||||
txt="$(gresponse)"
|
||||
done
|
||||
if [ -n "$txt" ]; then printf '%s\n' "$txt"; return 0; fi
|
||||
# Quota fallback: if the pinned strong model is capacity/quota-capped, retry ONCE on the default
|
||||
# (lighter) model by stripping -m from the last invocation - the default model has a separate quota.
|
||||
if quota_exhausted && [ ${#LAST_RUN[@]} -gt 0 ]; then
|
||||
echo "[$SELF] '$STRONG_MODEL' quota exhausted - retrying once on the default (lighter) model..." >&2
|
||||
local nr=() a skip=0
|
||||
for a in "${LAST_RUN[@]}"; do
|
||||
if [ "$skip" = 1 ]; then skip=0; continue; fi
|
||||
if [ "$a" = "-m" ]; then skip=1; continue; fi
|
||||
nr+=("$a")
|
||||
done
|
||||
run_gemini "${nr[@]}"
|
||||
txt="$(gresponse)"
|
||||
if [ -n "$txt" ]; then printf '%s\n' "$txt"; return 0; fi
|
||||
fi
|
||||
if auth_failed; then
|
||||
echo "[$SELF] Gemini auth error - run 'gemini' interactively and choose 'Login with Google', then retry." >&2
|
||||
_logerr "gemini auth/login failure" --context "mode=$MODE"
|
||||
else
|
||||
echo "[$SELF] no response from gemini after $max attempts. stderr tail:" >&2
|
||||
tail -3 "$ERR" >&2 2>/dev/null || true
|
||||
_logerr "gemini returned no response (empty after $max attempts)" --context "mode=$MODE err=$(tail -1 "$ERR" 2>/dev/null | tr -d '\n' | cut -c1-80)"
|
||||
fi
|
||||
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 \"<prompt>\" | $SELF $MODE --prompt-file <path>" >&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 <file-path> [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.}"
|
||||
# GOTCHA: a relative path resolves against ONLY CWD or $REPO_ROOT ($CLAUDETOOLS_ROOT) --
|
||||
# NOT a submodule/subdir. "server/src/x.rs" relative to a submodule fails ("file not found")
|
||||
# unless CWD is that submodule. Pass ABSOLUTE paths for submodule/subtree files.
|
||||
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> [file ...]" >&2; exit 2; }
|
||||
prep_includes
|
||||
list=""
|
||||
declare -A seen=()
|
||||
# GOTCHA: each relative path resolves against ONLY CWD or $REPO_ROOT ($CLAUDETOOLS_ROOT) --
|
||||
# NOT a submodule/subdir. Paths relative to a submodule fail unless CWD is that submodule.
|
||||
# Pass ABSOLUTE paths for submodule/subtree files (e.g. build the list with `find "$(pwd)/..."`).
|
||||
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 <repo-dir>] [-i \"instr\"] <gitref> [-- <pathspec>]" >&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
|
||||
;;
|
||||
|
||||
image-analyze|image|vision)
|
||||
# Independent second-model VISION. The default flash-lite router hallucinates
|
||||
# image content, so we PIN the pro vision model (STRONG_MODEL) and run with
|
||||
# yolo approval so read_file can execute. The image is copied into an included
|
||||
# temp dir (like the review modes) and handed to Gemini by absolute winpath.
|
||||
[ -z "${1:-}" ] && { echo "usage: $SELF image-analyze <image-path> [\"question\"]" >&2; exit 2; }
|
||||
target="$1"
|
||||
question="${2:-Describe exactly what is in this image.}"
|
||||
if [ -f "$target" ]; then resolved="$target"
|
||||
elif [ -f "$REPO_ROOT/$target" ]; then resolved="$REPO_ROOT/$target"
|
||||
else echo "[$SELF] image not found: $target" >&2; exit 2; fi
|
||||
prep_includes
|
||||
base="$(basename "$resolved")"
|
||||
cp -f "$resolved" "$INCLUDE_DIR/$base"
|
||||
img_win="$(winpath "$INCLUDE_DIR/$base")"
|
||||
inc_win="$(winpath "$INCLUDE_DIR")"
|
||||
# Image path goes in via %s (never as a printf format string).
|
||||
printf 'Use your read_file tool to read the image at this absolute path, then describe exactly what you see. Report only what is actually present in the image; do not guess or invent content. Then stop. Do not modify anything.\nImage path: %s\n\nQuestion: %s' "$img_win" "$question" > "$PF"
|
||||
run_gemini 240 -m "$STRONG_MODEL" --approval-mode yolo --include-directories "$inc_win"
|
||||
AGY_CLEAN=1 emit_or_fail
|
||||
;;
|
||||
|
||||
search|websearch)
|
||||
# Google-grounded LIVE web search (mirrors grok xsearch). Gemini's
|
||||
# google_web_search tool works on OAuth; run with yolo so the tool can fire.
|
||||
# Query goes via the prompt file so long queries don't hit shell-quote limits.
|
||||
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 search \"<query>\" | $SELF search --prompt-file <path>" >&2; exit 2; }
|
||||
printf 'Use your google_web_search tool to find current, live information answering the following, then stop. Answer concisely and ALWAYS include the source URLs you used (a Sources list of full URLs). Do not fabricate URLs.\n\nQuery: %s' "$SRC" > "$PF"
|
||||
run_gemini 180 -m "$STRONG_MODEL" --approval-mode yolo
|
||||
emit_or_fail
|
||||
;;
|
||||
|
||||
raw)
|
||||
"$GEMINI" "$@"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "[$SELF] unknown mode '$MODE' (use text|verify|review|review-files|review-diff|image-analyze|search|raw)" >&2; exit 2 ;;
|
||||
esac
|
||||
# ask-gemini.sh — DEPRECATED shim. The AGY skill now routes to the Antigravity CLI
|
||||
# (`agy`), not the old Google `gemini` npm CLI (which broke on this account with a
|
||||
# Cloud-project eligibility error). This shim forwards to ask-agy.sh so any existing
|
||||
# references keep working. New callers should use ask-agy.sh directly.
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)"
|
||||
exec bash "$DIR/ask-agy.sh" "$@"
|
||||
|
||||
Reference in New Issue
Block a user