#!/usr/bin/env bash # ask-grok.sh - ask Grok Build a question and print its answer. # # ./ask-grok.sh "why is this print warping?" # ./ask-grok.sh -C ~/projects/myrepo "review the lid geometry" # echo "long prompt" | ./ask-grok.sh - # ./ask-grok.sh --resume "and what about the fillet?" # # READ-ONLY BY DEFAULT. Grok gets read_file, grep and list_dir - it can read the # repo you point it at and nothing else. --write hands it a shell and an editor. # # !! HEADLESS CANNOT PROMPT FOR PERMISSION. That is why the safety here is an # ALLOWLIST of tools rather than a permission mode - there is no human in the # loop to answer a prompt, so a mode that asks would simply hang. # # !! EVERY EXCHANGE IS LOGGED to exchanges/ with its cost. Two agents talking is # a thing you want a transcript of - both for auditing what the other one was # told, and because it is billed. # # !! DEFAULT TIMEOUT IS 110s, deliberately just UNDER Claude Code's 120s default # Bash timeout. The usual caller here is an agent, not a person, and an agent # that gets killed by its own harness never learns why. Failing at 110 means # the error is OURS to report. --timeout N for a heavy consult (raise the # caller's Bash timeout to match, or background it and read exchanges/). set -euo pipefail GROK="$HOME/.grok/bin/grok" HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CWD="$PWD"; WRITE=0; WEB=0; RAW=0; MODEL=""; EFFORT="high"; RESUME=""; MAXTURNS=30 # effort: 2nd of low|medium|high|xhigh, set 2026-09-13 TIMEOUT=110 PROMPT="" while [[ $# -gt 0 ]]; do case "$1" in -C|--cwd) CWD="$2"; shift 2 ;; --write) WRITE=1; shift ;; --web) WEB=1; shift ;; --raw) RAW=1; shift ;; -m|--model) MODEL="$2"; shift 2 ;; --effort) EFFORT="$2"; shift 2 ;; -r|--resume) RESUME="$2"; shift 2 ;; --max-turns) MAXTURNS="$2"; shift 2 ;; --timeout) TIMEOUT="$2"; shift 2 ;; -) PROMPT="$(cat)"; shift ;; -h|--help) sed -n '2,24p' "$0"; exit 0 ;; *) PROMPT="${PROMPT:+$PROMPT }$1"; shift ;; esac done [[ -x "$GROK" ]] || { echo "ask-grok: no grok at $GROK" >&2; exit 1; } [[ -n "$PROMPT" ]] || { echo "ask-grok: no prompt given (see --help)" >&2; exit 2; } [[ -d "$CWD" ]] || { echo "ask-grok: no such directory: $CWD" >&2; exit 2; } TOOLS="read_file,grep,list_dir" (( WRITE )) && TOOLS="$TOOLS,search_replace,run_terminal_cmd" (( WEB )) && TOOLS="$TOOLS,web_search,web_fetch" args=(-p "$PROMPT" --cwd "$CWD" --output-format json --tools "$TOOLS" --max-turns "$MAXTURNS") (( WRITE )) && args+=(--permission-mode bypassPermissions) [[ -n "$MODEL" ]] && args+=(-m "$MODEL") [[ -n "$EFFORT" ]] && args+=(--effort "$EFFORT") [[ -n "$RESUME" ]] && args+=(--resume "$RESUME") ERRLOG="$(mktemp -t ask-grok-err.XXXXXX)" trap 'rm -f "$ERRLOG"' EXIT set +e OUT="$(timeout "$TIMEOUT" "$GROK" "${args[@]}" 2>"$ERRLOG")" RC=$? set -e if (( RC == 124 )); then echo "ask-grok: TIMED OUT after ${TIMEOUT}s. Nothing was logged." >&2 echo " Retry with --timeout $(( TIMEOUT * 2 )), and raise your own Bash timeout to match." >&2 exit 124 fi if (( RC != 0 )); then echo "ask-grok: grok exited $RC. Its stderr:" >&2 head -c 1200 "$ERRLOG" >&2; echo >&2 echo " Reproduce with: $GROK ${args[*]}" >&2 exit 3 fi (( RAW )) && { printf '%s\n' "$OUT"; exit 0; } STAMP="$(date +%Y%m%d-%H%M%S)" printf '%s' "$OUT" | HERE="$HERE" STAMP="$STAMP" CWD="$CWD" PROMPT="$PROMPT" \ ERRLOG="$ERRLOG" WHO=grok python3 "$HERE/_render.py"