Skip to content

Commit caf47ed

Browse files
authored
enable agent-browse without API key using Claude Code subscription (#21)
1 parent 9696b2a commit caf47ed

File tree

2 files changed

+88
-9
lines changed

2 files changed

+88
-9
lines changed

src/browser-utils.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,77 @@
11
import { Page } from '@browserbasehq/stagehand';
2-
import { existsSync, cpSync, mkdirSync } from 'fs';
2+
import { existsSync, cpSync, mkdirSync, readFileSync } from 'fs';
33
import { platform } from 'os';
44
import { join } from 'path';
5+
import { execSync } from 'child_process';
6+
7+
// Retrieve Claude Code API key from system keychain
8+
export function getClaudeCodeApiKey(): string | null {
9+
try {
10+
if (platform() === 'darwin') {
11+
const result = execSync(
12+
'security find-generic-password -s "Claude Code" -w 2>/dev/null',
13+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
14+
).trim();
15+
if (result && result.startsWith('sk-ant-')) {
16+
return result;
17+
}
18+
} else if (platform() === 'win32') {
19+
try {
20+
const psCommand = `$cred = Get-StoredCredential -Target "Claude Code" -ErrorAction SilentlyContinue; if ($cred) { $cred.GetNetworkCredential().Password }`;
21+
const result = execSync(`powershell -Command "${psCommand}"`, {
22+
encoding: 'utf-8',
23+
stdio: ['pipe', 'pipe', 'pipe']
24+
}).trim();
25+
if (result && result.startsWith('sk-ant-')) {
26+
return result;
27+
}
28+
} catch {}
29+
} else {
30+
// Linux
31+
const configPaths = [
32+
join(process.env.HOME || '', '.claude', 'credentials'),
33+
join(process.env.HOME || '', '.config', 'claude-code', 'credentials'),
34+
join(process.env.XDG_CONFIG_HOME || join(process.env.HOME || '', '.config'), 'claude-code', 'credentials'),
35+
];
36+
for (const configPath of configPaths) {
37+
if (existsSync(configPath)) {
38+
try {
39+
const content = readFileSync(configPath, 'utf-8').trim();
40+
if (content.startsWith('sk-ant-')) {
41+
return content;
42+
}
43+
const parsed = JSON.parse(content);
44+
if (parsed.apiKey && parsed.apiKey.startsWith('sk-ant-')) {
45+
return parsed.apiKey;
46+
}
47+
} catch {}
48+
}
49+
}
50+
try {
51+
const result = execSync(
52+
'secret-tool lookup service "Claude Code" 2>/dev/null',
53+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
54+
).trim();
55+
if (result && result.startsWith('sk-ant-')) {
56+
return result;
57+
}
58+
} catch {}
59+
}
60+
} catch {}
61+
return null;
62+
}
63+
64+
// Get API key from env or Claude Code keychain
65+
export function getAnthropicApiKey(): { apiKey: string; source: 'env' | 'claude-code' } | null {
66+
if (process.env.ANTHROPIC_API_KEY) {
67+
return { apiKey: process.env.ANTHROPIC_API_KEY, source: 'env' };
68+
}
69+
const claudeCodeKey = getClaudeCodeApiKey();
70+
if (claudeCodeKey) {
71+
return { apiKey: claudeCodeKey, source: 'claude-code' };
72+
}
73+
return null;
74+
}
575

676
/**
777
* Finds the local Chrome installation path based on the operating system

src/cli.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } from '
44
import { spawn, ChildProcess } from 'child_process';
55
import { join, resolve, dirname } from 'path';
66
import { fileURLToPath } from 'url';
7-
import { findLocalChrome, prepareChromeProfile, takeScreenshot } from './browser-utils.js';
7+
import { findLocalChrome, prepareChromeProfile, takeScreenshot, getAnthropicApiKey } from './browser-utils.js';
88
import { z } from 'zod';
99
import dotenv from 'dotenv';
1010

@@ -24,15 +24,24 @@ const PLUGIN_ROOT = resolve(__dirname, '..', '..');
2424
// Load .env from plugin root directory
2525
dotenv.config({ path: join(PLUGIN_ROOT, '.env'), quiet: true });
2626

27-
// Check for API key
28-
if (!process.env.ANTHROPIC_API_KEY) {
29-
console.error('Error: ANTHROPIC_API_KEY not found.');
30-
console.error('\nTo set up your API key, choose one option:');
31-
console.error(' 1. (RECOMMENDED) Export in terminal: export ANTHROPIC_API_KEY="your-api-key"');
32-
console.error(' 2. Create a .env file: cp .env.example .env');
33-
console.error(' Then edit .env and add your API key');
27+
const apiKeyResult = getAnthropicApiKey();
28+
if (!apiKeyResult) {
29+
console.error('Error: No Anthropic API key found.');
30+
console.error('\n📋 Option 1: Use your Claude subscription (RECOMMENDED)');
31+
console.error(' If you have Claude Pro/Max, run: claude setup-token');
32+
console.error(' This will store your subscription token in the system keychain.');
33+
console.error('\n🔑 Option 2: Use an API key');
34+
console.error(' Export in terminal: export ANTHROPIC_API_KEY="your-api-key"');
35+
console.error(' Or create a .env file with: ANTHROPIC_API_KEY="your-api-key"');
3436
process.exit(1);
3537
}
38+
process.env.ANTHROPIC_API_KEY = apiKeyResult.apiKey;
39+
40+
if (process.env.DEBUG) {
41+
console.error(apiKeyResult.source === 'claude-code'
42+
? '🔐 Using Claude Code subscription token from keychain'
43+
: '🔑 Using ANTHROPIC_API_KEY from environment');
44+
}
3645

3746
// Persistent browser state
3847
let stagehandInstance: Stagehand | null = null;

0 commit comments

Comments
 (0)