Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion src/browser-utils.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
25 changes: 17 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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;
Expand Down