import { existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { homedir, platform } from 'node:os'; import { execa } from 'execa'; export interface ExtractedAccount { email: string; accountUuid: string; configJson: object; credentials: string; } /** * Resolve the Claude Code config path with fallback logic. * Primary: ~/.claude/.claude.json (if it exists and has oauthAccount) * Fallback: ~/.claude.json */ function getClaudeConfigPath(): string { const home = homedir(); const primary = join(home, '.claude', '.claude.json'); const fallback = join(home, '.claude.json'); if (existsSync(primary)) { try { const json = JSON.parse(readFileSync(primary, 'utf-8')); if (json.oauthAccount) return primary; } catch { // invalid JSON, fall through } } return fallback; } export async function extractCurrentClaudeAccount(): Promise { const home = homedir(); // 1. Read Claude config (with fallback logic matching ccswitch) const configPath = getClaudeConfigPath(); const configRaw = readFileSync(configPath, 'utf-8'); const configJson = JSON.parse(configRaw); const email = configJson.oauthAccount?.emailAddress; const accountUuid = configJson.oauthAccount?.accountUuid; if (!email || !accountUuid) { throw new Error('No Claude account found. Please log in with `claude` first.'); } // 2. Read credentials (platform-specific) let credentials: string; if (platform() === 'darwin') { // macOS: read from Keychain const { stdout } = await execa('security', [ 'find-generic-password', '-s', 'Claude Code-credentials', '-w', ]); credentials = stdout; } else { // Linux: read from file const credPath = join(home, '.claude', '.credentials.json'); credentials = readFileSync(credPath, 'utf-8'); } return { email, accountUuid, configJson, credentials }; }