Skip to content

Auto-Import Credentials: detect existing API keys and configure providers automatically #449

Description

@jeonghun-jj-lee

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.ts module: scans sources in priority order, returns DetectedCredential[]
  • 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.json and account.json) is the primary source and uses plaintext keys
  • Shell RC files use export VAR=value patterns parseable by regex without eval
  • Claude Code's .credentials.json fallback may contain type: "api" entries (importable) or type: "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 model via radio selection
  • Default model per provider is auto-selected (first entry in PROVIDER_MODELS)
  • "Confirm & Save" writes all detected providers to opencode.json with correct schema (provider.<id>.options.apiKey, env as string[])
  • 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 postMessage payload from host→webview contains a string field matching /key|secret|token|credential/i with 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), env as string[], valid model IDs from PROVIDER_MODELS

Prior Art

  • RebuildStatusIndicator in developer-tools.tsx — the orange/green/red dot + text pattern this feature replicates
  • devtools-status-dot CSS classes and devtools-pulse keyframe in amicode.css (lines 1487-1525)
  • hasProviderEnvVar() in onboarding_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() in onboarding_panel.ts (existing config writer, correct schema)
  • testConnection() in onboarding_panel.ts (existing per-provider HTTP test)

Source

Sub-issue of #359 (Enhanced Onboarding: Orient > Demo > Collect)

Sub-issues

Metadata

Metadata

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions