---
name: cli-auth-llm-fallback
description: Add a CLI-binary fallback (claude/opencode/codex) for InfiniBot LLM calls so users with only CLI/OAuth login (no provider API key) still work.
---
# CLI-Auth LLM Fallback

## When to use
- A goal/agent/orchestrator LLM call fails with "credentials unavailable" / "API key missing" for users who are logged into a CLI binary (claude, opencode, codex) but have no `ANTHROPIC_API_KEY`/`OPENAI_API_KEY` set.
- You're adding an LLM call that must work for CLI-OAuth users, not just direct-API-key users.
- You need to route an InfiniBot planner/synthesis call through a coder CLI instead of `complete()` from pi-ai.

## How to apply
1. **Detect the auth gap.** Where the code resolves a key (e.g. `getApiKeyForModel`), make the resolver return a discriminated union `{ kind: 'api', key }` vs `{ kind: 'cli', backend }`. When no API key is found, check `checkCliAvailable(backend)` and fall back to `kind:'cli'` for claude/opencode/codex; only error if neither exists.
2. **Probe availability** with `spawnSync(binary, ["--version"], { timeout: 5000, stdio: ["ignore","pipe","ignore"] })` and test `status === 0`. Binaries: claude-code→`claude`, opencode→`opencode`, codex→`codex`.
3. **Shell out non-interactively** with per-backend args:
   - claude: `-p --output-format stream-json --verbose --dangerously-skip-permissions <prompt>`
   - opencode: `run --format json --dangerously-skip-permissions <prompt>`
   - codex: `exec --json --dangerously-bypass-approvals-and-sandbox <prompt>`
   Combine system+user prompt as `${systemPrompt}\n\n${userMessage}`.
4. **Strip raw provider keys from the child env** (`delete env.ANTHROPIC_API_KEY/OPENAI_API_KEY/...`) so the CLI uses its own OAuth login. For opencode, inject system prompt via `OPENCODE_CONFIG_CONTENT` JSON (`claudeCoderConfig.systemPrompt`).
5. **Set stdin per backend:** `"ignore"` for opencode (it blocks on an open empty stdin pipe), `"pipe"` for claude/codex.
6. **Parse output per backend** — claude/opencode emit JSONL (find last `type:"result"`/`type:"text"`), codex emits a single JSON object (`result`/`text`); fall back to last non-empty line / raw trim.
7. **Wrap with reliability guards:** 60s timeout, `safeKill()` (`SIGKILL` if not already killed), `AbortSignal` support, single-settle flag, clear error on non-zero exit with empty stdout.
8. **Fix the pre-flight probe** so backend probing checks CLI availability even when the model-registry entry is missing (unregistered provider), or it preemptively fails goals the CLI could actually run.
9. Route every call site (planning, repair retry, synthesis) through the CLI path when `kind:'cli'`. Add unit tests for all three branches: api-key present, no-key+CLI-available, no-key+no-CLI.

## Rules
- Never require a direct provider API key when a logged-in CLI binary can serve the call — always add the CLI fallback path.
- Always strip `ANTHROPIC_API_KEY`/`OPENAI_API_KEY` from the child process env so the CLI uses its own OAuth, not a (possibly absent/stale) key.
- Always use `stdio[0]="ignore"` for opencode; a `"pipe"` empty stdin hangs it.
- Always guard the spawn with a timeout, `safeKill`, AbortSignal, and a single-settle flag — orchestrator turns have wallclock budgets.
- Never assume registry presence == runnability: probe CLI availability separately so unregistered providers aren't failed pre-flight.