Ctrl K
ComunidadGuías…

scripts/grunt

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
# grunt — dispatch a one-shot prompt to opencode.ai Zen (Go subscription).
#
# Usage:
#   scripts/grunt [-m MODEL] [-s SYSTEM] [-f FILE]... [-t MAX_TOKENS] [-j] [PROMPT]
#
# Flags:
#   -m MODEL       Model id (default: deepseek-v4-flash). See `scripts/grunt --models`.
#   -s SYSTEM      System prompt. May be passed multiple times (concatenated with blank lines).
#   -f FILE        Attach a file. Repeatable. Each file is appended to the user message
#                  as a fenced block headed by its path.
#   -t MAX_TOKENS  Max output tokens (default: 4096).
#   -j             Print the raw JSON response instead of just the assistant text.
#   --models       List available models for this account and exit.
#   -h, --help     Show this help.
#
# Input: PROMPT may be passed as a positional arg, or piped on stdin (or both — stdin
# is appended after the positional arg, separated by a blank line). At least one
# source of prompt text is required.
#
# Output: assistant message text on stdout. Reasoning-only finishes (where the model
# spent all its tokens thinking and produced no content) emit a single line to stderr
# and exit 2 — bump -t and retry.
#
# Auth: reads OPENCODE_API_KEY from the environment and trims surrounding whitespace.
# Exits 1 if unset.
#
# Designed for parallel fan-out:
#   ( scripts/grunt -f a.ts "summarize" > a.out ) &
#   ( scripts/grunt -f b.ts "summarize" > b.out ) &
#   wait

set -euo pipefail

ENDPOINT="https://opencode.ai/zen/go/v1"
DEFAULT_MODEL="deepseek-v4-flash"

usage() { sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'; }

model="$DEFAULT_MODEL"
system_parts=()
files=()
max_tokens=4096
raw_json=0
list_models=0

while [[ $# -gt 0 ]]; do
  case "$1" in
    -m) model="$2"; shift 2 ;;
    -s) system_parts+=("$2"); shift 2 ;;
    -f) files+=("$2"); shift 2 ;;
    -t) max_tokens="$2"; shift 2 ;;
    -j) raw_json=1; shift ;;
    --models) list_models=1; shift ;;
    -h|--help) usage; exit 0 ;;
    --) shift; break ;;
    -*) echo "grunt: unknown flag: $1" >&2; exit 64 ;;
    *) break ;;
  esac
done

key="${OPENCODE_API_KEY:-}"
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
if [[ -z "$key" ]]; then
  echo "grunt: OPENCODE_API_KEY is not set" >&2
  exit 1
fi

if (( list_models )); then
  curl -fsS "$ENDPOINT/models" -H "Authorization: Bearer $key" \
    | python3 -c 'import json,sys;[print(m["id"]) for m in json.load(sys.stdin)["data"]]'
  exit 0
fi

prompt="${1:-}"
if [[ ! -t 0 ]]; then
  stdin_text=$(cat)
  if [[ -n "$stdin_text" ]]; then
    [[ -n "$prompt" ]] && prompt="$prompt"$'\n\n'"$stdin_text" || prompt="$stdin_text"
  fi
fi
if [[ -z "$prompt" && ${#files[@]} -eq 0 ]]; then
  echo "grunt: no prompt (pass as arg, on stdin, or via -f)" >&2
  exit 64
fi

for f in "${files[@]}"; do
  [[ -r "$f" ]] || { echo "grunt: cannot read file: $f" >&2; exit 66; }
  prompt+=$'\n\n'"### $f"$'\n```\n'"$(cat "$f")"$'\n```'
done

system=""
if (( ${#system_parts[@]} > 0 )); then
  system=$(printf '%s\n\n' "${system_parts[@]}")
fi

body=$(
  jq -n \
    --arg model "$model" \
    --arg system "$system" \
    --arg prompt "$prompt" \
    --argjson max_tokens "$max_tokens" \
    '{model:$model, max_tokens:$max_tokens,
      messages: ((if $system == "" then [] else [{role:"system",content:$system}] end)
                + [{role:"user",content:$prompt}])}'
)

response=$(curl -fsS "$ENDPOINT/chat/completions" \
  -H "Authorization: Bearer $key" \
  -H "Content-Type: application/json" \
  --data-binary "$body")

if (( raw_json )); then
  printf '%s\n' "$response"
  exit 0
fi

python3 - "$response" <<'PY'
import json, sys
d = json.loads(sys.argv[1])
if "error" in d:
    sys.stderr.write(f"grunt: api error: {d['error'].get('message','?')}\n")
    sys.exit(1)
msg = d["choices"][0]["message"]
text = msg.get("content") or ""
if not text.strip():
    reasoning = msg.get("reasoning") or msg.get("reasoning_content") or ""
    sys.stderr.write(
        f"grunt: model produced reasoning only (finish={d['choices'][0].get('finish_reason')}); "
        f"raise -t. reasoning preview: {reasoning[:200]!r}\n"
    )
    sys.exit(2)
sys.stdout.write(text)
if not text.endswith("\n"):
    sys.stdout.write("\n")
PY