Pular para o conteúdo principal

garra ask — Non-interactive AI query

GAR-579 — single message in, single answer out. LLM-only (no tools). Designed for Claude Code, CI, hooks, scripts, and future MCP wrappers.

garra ask is the non-interactive counterpart to garra chat. It takes one message, returns one answer, and exits with a sysexits-style code. No banner, no ANSI escapes, no voce > / garra > prompts — stdout is the answer (or a garra.ask.v1 JSON envelope when --json).

Surface​

ComandostdoutExit
garra ask "ping"resposta como texto puro0
garra ask --json "ping"linha JSON garra.ask.v10
garra ask --provider openrouter "ping"usa modelo do config.yml, ou o padrão z-ai/glm-5.3-flash (precedência GAR-576)0
garra ask --provider openrouter --model openrouter/auto "ping"usa openrouter/auto explícito0
echo "ping" | garra asklê stdin (cap 64 KiB) quando arg posicional ausente0
garra ask "" (arg vazio)erro claro em stderr (ou JSON em stdout se --json)2
garra ask --timeout-secs 1 "msg lenta"erro timeout124
provider auth/network falhaerro provider_error com mensagem sanitizada69

Flags​

FlagDefaultDescrição
--provider, -p <nome>autodetectollama | llamacpp | anthropic | openai | openrouter, ou o nome de uma entrada llm: (ex.: lmstudio)
--model, -m <name>resolve via config (GAR-576)sobrescreve modelo (precedência absoluta)
--url, -u <url>—endpoint custom (LM Studio, vLLM, etc.); chave só de LLM_API_KEY ou da entrada llm: com a mesma base_url
--jsonfalseemit JSON envelope em stdout
--timeout-secs <N>60range [1, 600]; excede → exit 124
--system-prompt <STR>minimum defaultsobrescreve system prompt inline

JSON envelope (schema: garra.ask.v1)​

Sucesso:

{
"schema": "garra.ask.v1",
"ok": true,
"answer": "GAR-ASK-OK",
"provider": "openrouter",
"model": "z-ai/glm-5.3-flash",
"latency_ms": 1234
}

Erro:

{
"schema": "garra.ask.v1",
"ok": false,
"error": {
"kind": "provider_error",
"message": "401 Unauthorized — key sk-or-v1-[REDACTED] invalid"
}
}

error.kind é estável: usage, no_provider, provider_error, timeout, io. Strings podem ser parseadas por scripts.

Exit codes​

CodeSignificado
0sucesso
2erro de uso (mensagem vazia, stdin acima do cap, etc.)
65arquivo de config inválido (EX_DATAERR) — reservado, propagado pela inicialização
69provider indisponível, auth, network (EX_UNAVAILABLE)
74falha de I/O ao ler stdin (EX_IOERR)
124LLM excedeu --timeout-secs (convenção Unix timeout(1))

Resolução de provider e model​

Idêntica ao garra chat — ver docs/configuration.md §"Provider / model resolution precedence" (GAR-576):

  1. --model <X> (precedência absoluta).
  2. config.llm[<key>].model (key match).
  3. Primeiro config.llm[*] cujo provider: casa.
  4. Fallback hardcoded por kind.

Para provider:

  1. --provider <X> (precedência absoluta).
  2. config.agent.default_provider.
  3. Cadeia autodetect (Anthropic → OpenAI → OpenRouter com credencial, depois Ollama).

Endpoint e credencial saem sempre da mesma entrada: -p openai usa llm.openai.base_url com llm.openai.api_key — nunca a chave de uma entrada com o endereço de outra, nem a chave de uma entrada apontando para outro lugar enviada ao host padrão do provider. A variável de ambiente do tipo (OPENAI_API_KEY etc., inclusive a de um .env no diretório corrente) só vai para o host padrão: uma entrada com base_url própria e sem api_key não a recebe. Detalhes em docs/configuration.md §"Endpoint and credential".

Exemplos​

Smoke sem custo (escolha explícita para CI / testes)​

garra ask --provider openrouter --model openrouter/free \
--json --timeout-secs 30 "Responda apenas: GAR-ASK-OK"

Saída (uma linha):

{"schema":"garra.ask.v1","ok":true,"answer":"GAR-ASK-OK","provider":"openrouter","model":"openrouter/free","latency_ms":1432}

Tarefa real (uso humano)​

garra ask --provider openrouter --model openrouter/auto \
"Explique em uma frase o que é o GarraIA."

Saída (texto puro, sem banner):

O GarraIA é um gateway de IA multi-canal escrito em Rust que orquestra LLMs
via Telegram, Discord, Slack, WhatsApp e API REST.

Stdin pipeline​

cat README.md | garra ask --json --provider openrouter \
--system-prompt "Resuma em 3 bullets."

Em scripts / Claude Code / MCP​

RESULT=$(garra ask --json --provider openrouter \
--timeout-secs 30 "$prompt")
ANSWER=$(echo "$RESULT" | jq -r '.answer // .error.message')

Tools e segurança​

garra ask é LLM-only. Tool registration (bash, file_read, file_write, git_diff) está ausente do runtime por design. Auditável via teste ask_module_never_registers_a_tool que escaneia o código de produção em build time.

Suporte a tools com allowlist + auditoria entra em PR separado (eg. GAR-579+); requer security-auditor.

Segurança operacional​

  • Response body de erro do provider passa por sanitize_provider_error antes de virar JSON ou stderr — fingerprints sk-… / sk-or-v1-… são redactionados a [REDACTED], mensagens longas truncadas em 512 caracteres.
  • Stdin é lido até 64 KiB; input maior retorna usage error (exit 2), não trunca silenciosamente.
  • Sem panic! em paths de I/O / provider / network — todos os erros voltam pelo enum AskError e viram exit code previsível.
  • Defesa em profundidade: RedactingWriter provider-aware fica em PR dedicado de garraia-security.

Não está aqui​

PRs separados manterão escopo limpo:

  • --stream para emit incremental.
  • --enable-tools + tool allowlist + auditoria.
  • --system-prompt-file <PATH>.
  • MCP server wrapper expondo ask como tool MCP — entregue por GAR-583, veja docs/cli-mcp-server.md.
  • --session <id> para continuação de conversa.
  • Upgrade automático do modelo padrão para um mais caro.

Ver também​

  • docs/cli-mcp-server.md — garra mcp-server (GAR-583), MCP stdio wrapper que reutiliza ask::ask_oneshot in-process.
  • docs/configuration.md — provider/model resolution (GAR-576).
  • docs/src/providers.md — OpenRouter cost policy.
  • plans/0098-gar-576-cli-openrouter-model-respect.md — base já estável.
  • plans/0100-gar-579-cli-non-interactive-ask.md — este PR.
  • plans/0102-gar-583-mcp-server-stdio.md — MCP wrapper sobre este comando.