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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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