Repository navigation
Auto-Import Credentials: detect existing API keys and configure providers automatically #449
Description
Important
Problem: When a user re-runs Amicode onboarding (or installs fresh on a machine with existing LLM tool configs), they must manually re-enter API keys they've already configured elsewhere — in opencode's own auth store, shell environment variables, or other AI coding tools. This is friction that shouldn't exist.
Approach: Add an "Import existing credentials" alternative path to the Stage 0 onboarding webview. The extension host scans flat-file credential sources in priority order, deduplicates by provider, tests each key in the background, and presents a preview card (provider + source + live test status). The user picks which provider becomes their default model, confirms with one click, and all detected providers are written to opencode.json. Keys never leave the host process — the webview sees only provider names and test status.
Approaches Considered:
| Approach | Verdict |
|---|---|
| Webview-only scanner (chosen) | Scans flat-file sources from the host, reuses existing testConnection and writeOnboardingConfig, keys stay in Node, cross-platform |
Keychain integration (macOS security CLI) |
Finds more credentials (Claude Code OAuth, Copilot tokens) but platform-specific, OAuth tokens aren't portable as static API keys, Keychain prompts confuse users |
| Passive notification on activation | Zero-click discovery but notifications are dismissible/missable, harder to present model selection, feels presumptuous |
Scope:
- New
credential_scanner.tsmodule: scans sources in priority order, returnsDetectedCredential[] - New message types:
scan-credentials,scan-results,test-status-update,confirm-import - Webview UI: "Import existing credentials" link below manual form, preview card with provider rows and radio for default selection
- Batch-write variant of
writeOnboardingConfig(writes multiple providers in one pass) - Does NOT include: Keychain access, OAuth token import, passive auto-detection notification
Assumptions:
- The opencode auth store (
~/.local/share/opencode/auth.jsonandaccount.json) is the primary source and uses plaintext keys - Shell RC files use
export VAR=valuepatterns parseable by regex without eval - Claude Code's
.credentials.jsonfallback may containtype: "api"entries (importable) ortype: "oauth"entries (skipped) - At least one detected provider must pass the connection test for "Confirm & Save" to enable
Acceptance Criteria
- "Import existing credentials" link appears below the manual provider/key form in the onboarding webview
- Clicking it triggers a scan and shows a status indicator: pulsing orange dot + "Searching..." (matches the devtools rebuild indicator pattern — same CSS classes, same animation)
- On scan completion, the status transitions to static green dot + "Found N providers!" (or inline "No credentials found" message if empty)
- On scan failure, the status transitions to static red dot + error message
- Preview card shows each detected provider with: name, source label, and live connection test status (spinning/checkmark/X)
- User can select which provider becomes the active
modelvia radio selection - Default model per provider is auto-selected (first entry in
PROVIDER_MODELS) - "Confirm & Save" writes all detected providers to
opencode.jsonwith correct schema (provider.<id>.options.apiKey,envasstring[]) - No key material is ever sent to the webview — only provider names, source labels, and test results
- If no credentials are found, an inline message appears and the manual form remains available
- If a source file is unreadable or malformed, it is skipped silently (logged to output channel)
- Duplicate providers across sources are deduplicated (first source wins per priority order)
- Connection tests run in parallel in the background and update the preview asynchronously
- "Back" link returns to the manual form and drops held credentials from memory
- Panel close mid-scan aborts without writing config
Key Decisions
Credential Source Priority
1. ~/.local/share/opencode/account.json (v2, active account per serviceID)
2. ~/.local/share/opencode/auth.json (v1, provider.<id>.key)
3. process.env (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.)
4. Shell RC files (~/.zshrc, ~/.bashrc, ~/.zprofile, ~/.bash_profile)
5. ~/.claude/.credentials.json (type: "api" only, skip OAuth)
First hit per provider wins. No Keychain access.
Provider ID Normalization
| Source ID | Normalized to |
|---|---|
amazon-bedrock (account.json) |
amazon-bedrock |
opencode-go / opencode (account.json) |
opencode |
ANTHROPIC_API_KEY (env) |
anthropic |
OPENAI_API_KEY (env) |
openai |
GOOGLE_API_KEY (env) |
google |
OPENROUTER_API_KEY (env) |
openrouter |
OPENCODE_API_KEY (env) |
opencode |
Scan Status Indicator (reuses devtools rebuild pattern)
Three states, matching the RebuildStatusIndicator in developer-tools.tsx:
| State | Dot | Text | Trigger |
|---|---|---|---|
searching |
8px orange, pulsing (devtools-pulse keyframe) |
"Searching..." | User clicks "Import existing credentials" |
found |
8px green, static | "Found N providers!" | Scan completes with results |
failed / empty |
8px red, static (failed) or no dot (empty) | Error message / "No credentials found" | Scan errors or finds nothing |
Reuse the existing CSS classes: devtools-status-dot--orange, devtools-status-dot--green, devtools-status-dot--red, and the devtools-pulse keyframe animation. The onboarding webview is a separate document context, so the relevant CSS must be inlined or duplicated (not imported from the app bundle).
Security Model
- Keys exist only in the extension host's memory (the
DetectedCredential[]array) - Webview messages contain
{ provider, source, modelDefault }— never key material - Shell RC parsing uses strict regex:
/^\s*export\s+([\w]+)=["']?([^"'\s]+)/— no eval, no subshell - On "Back" or panel close, the held credential array is dropped immediately
- A security test asserts no
postMessagepayload from host→webview contains a string field matching/key|secret|token|credential/iwith value length > 8
Data Contract (host → webview messages)
// scan-status (sent immediately on scan start, then on completion)
{ type: "scan-status", payload: { state: "searching" | "found" | "empty" | "failed", count?: number, error?: string } }
// scan-results (sent once after scan completes successfully)
{ type: "scan-results", payload: { providers: Array<{ provider: string, source: string, model: string }> } }
// test-status-update (sent per-provider as connection tests complete)
{ type: "test-status-update", payload: { provider: string, ok: boolean, error?: string } }Data Contract (webview → host messages)
// scan-credentials (triggers the scan)
{ type: "scan-credentials" }
// confirm-import (user confirms selection)
{ type: "confirm-import", payload: { activeProvider: string } }Constraints & Invariants
- Keys are NEVER serialized to the webview process
- Shell RC parsing NEVER uses
eval,child_process, or subshell execution - OAuth tokens (from Claude Code or any source) are NEVER imported — only
type: "api"entries - The scan NEVER blocks the manual form — it is an alternative path, not a prerequisite
- Written config MUST match the existing schema:
provider.<id>.options.apiKey(nested),envasstring[], valid model IDs fromPROVIDER_MODELS
Prior Art
RebuildStatusIndicatorindeveloper-tools.tsx— the orange/green/red dot + text pattern this feature replicatesdevtools-status-dotCSS classes anddevtools-pulsekeyframe inamicode.css(lines 1487-1525)hasProviderEnvVar()inonboarding_routing.ts(exported but unused — checks env vars exist)- Stage 3 context-seed in the overture score (opt-in scan → grouped preview → confirm pattern)
writeOnboardingConfig()inonboarding_panel.ts(existing config writer, correct schema)testConnection()inonboarding_panel.ts(existing per-provider HTTP test)
Source
Sub-issue of #359 (Enhanced Onboarding: Orient > Demo > Collect)
Sub-issues
- BUG: Onboarding writes phantom provider entries (OpenCode Zen, Anthropic) without user selection #455 — Only write user-selected providers to config during onboarding
Activity
feat(onboarding): integrate credential scanner into panel + webview UI (
feat(onboarding): auto-send greeting to trigger overture after config (…
feat(onboarding): add credential_scanner module with TDD tests (#449)
feat(onboarding): integrate credential scanner into panel + webview UI (
feat(onboarding): auto-send greeting to trigger overture after config (…
Merge pull request #450 from harmoniqs/449-auto-import-credentials
feat(onboarding): auto-send greeting to trigger overture after config (…
feat(onboarding): auto-send greeting to trigger overture after config (…
feat(onboarding): splash transition, overture rewrite, de-quantum copy (