diff --git a/src/browser-utils.ts b/src/browser-utils.ts index 8b2e7d5..6ad4b6d 100644 --- a/src/browser-utils.ts +++ b/src/browser-utils.ts @@ -1,7 +1,77 @@ import { Page } from '@browserbasehq/stagehand'; -import { existsSync, cpSync, mkdirSync } from 'fs'; +import { existsSync, cpSync, mkdirSync, readFileSync } from 'fs'; import { platform } from 'os'; import { join } from 'path'; +import { execSync } from 'child_process'; + +// Retrieve Claude Code API key from system keychain +export function getClaudeCodeApiKey(): string | null { + try { + if (platform() === 'darwin') { + const result = execSync( + 'security find-generic-password -s "Claude Code" -w 2>/dev/null', + { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] } + ).trim(); + if (result && result.startsWith('sk-ant-')) { + return result; + } + } else if (platform() === 'win32') { + try { + const psCommand = `$cred = Get-StoredCredential -Target "Claude Code" -ErrorAction SilentlyContinue; if ($cred) { $cred.GetNetworkCredential().Password }`; + const result = execSync(`powershell -Command "${psCommand}"`, { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'] + }).trim(); + if (result && result.startsWith('sk-ant-')) { + return result; + } + } catch {} + } else { + // Linux + const configPaths = [ + join(process.env.HOME || '', '.claude', 'credentials'), + join(process.env.HOME || '', '.config', 'claude-code', 'credentials'), + join(process.env.XDG_CONFIG_HOME || join(process.env.HOME || '', '.config'), 'claude-code', 'credentials'), + ]; + for (const configPath of configPaths) { + if (existsSync(configPath)) { + try { + const content = readFileSync(configPath, 'utf-8').trim(); + if (content.startsWith('sk-ant-')) { + return content; + } + const parsed = JSON.parse(content); + if (parsed.apiKey && parsed.apiKey.startsWith('sk-ant-')) { + return parsed.apiKey; + } + } catch {} + } + } + try { + const result = execSync( + 'secret-tool lookup service "Claude Code" 2>/dev/null', + { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] } + ).trim(); + if (result && result.startsWith('sk-ant-')) { + return result; + } + } catch {} + } + } catch {} + return null; +} + +// Get API key from env or Claude Code keychain +export function getAnthropicApiKey(): { apiKey: string; source: 'env' | 'claude-code' } | null { + if (process.env.ANTHROPIC_API_KEY) { + return { apiKey: process.env.ANTHROPIC_API_KEY, source: 'env' }; + } + const claudeCodeKey = getClaudeCodeApiKey(); + if (claudeCodeKey) { + return { apiKey: claudeCodeKey, source: 'claude-code' }; + } + return null; +} /** * Finds the local Chrome installation path based on the operating system diff --git a/src/cli.ts b/src/cli.ts index 72503bd..961e449 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,7 +4,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } from ' import { spawn, ChildProcess } from 'child_process'; import { join, resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; -import { findLocalChrome, prepareChromeProfile, takeScreenshot } from './browser-utils.js'; +import { findLocalChrome, prepareChromeProfile, takeScreenshot, getAnthropicApiKey } from './browser-utils.js'; import { z } from 'zod'; import dotenv from 'dotenv'; @@ -24,15 +24,24 @@ const PLUGIN_ROOT = resolve(__dirname, '..', '..'); // Load .env from plugin root directory dotenv.config({ path: join(PLUGIN_ROOT, '.env'), quiet: true }); -// Check for API key -if (!process.env.ANTHROPIC_API_KEY) { - console.error('Error: ANTHROPIC_API_KEY not found.'); - console.error('\nTo set up your API key, choose one option:'); - console.error(' 1. (RECOMMENDED) Export in terminal: export ANTHROPIC_API_KEY="your-api-key"'); - console.error(' 2. Create a .env file: cp .env.example .env'); - console.error(' Then edit .env and add your API key'); +const apiKeyResult = getAnthropicApiKey(); +if (!apiKeyResult) { + console.error('Error: No Anthropic API key found.'); + console.error('\nšŸ“‹ Option 1: Use your Claude subscription (RECOMMENDED)'); + console.error(' If you have Claude Pro/Max, run: claude setup-token'); + console.error(' This will store your subscription token in the system keychain.'); + console.error('\nšŸ”‘ Option 2: Use an API key'); + console.error(' Export in terminal: export ANTHROPIC_API_KEY="your-api-key"'); + console.error(' Or create a .env file with: ANTHROPIC_API_KEY="your-api-key"'); process.exit(1); } +process.env.ANTHROPIC_API_KEY = apiKeyResult.apiKey; + +if (process.env.DEBUG) { + console.error(apiKeyResult.source === 'claude-code' + ? 'šŸ” Using Claude Code subscription token from keychain' + : 'šŸ”‘ Using ANTHROPIC_API_KEY from environment'); +} // Persistent browser state let stagehandInstance: Stagehand | null = null;