Files
SuperCharged-Claude-Code-Up…/skills/plugins/agent-browse/src/browser-utils.ts
admin b723e2bd7d Reorganize: Move all skills to skills/ folder
- Created skills/ directory
- Moved 272 skills to skills/ subfolder
- Kept agents/ at root level
- Kept installation scripts and docs at root level

Repository structure:
- skills/           - All 272 skills from skills.sh
- agents/           - Agent definitions
- *.sh, *.ps1       - Installation scripts
- README.md, etc.   - Documentation

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-23 18:05:17 +00:00

218 lines
7.4 KiB
TypeScript

import { Stagehand } from '@browserbasehq/stagehand';
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
* @returns The path to the Chrome executable, or undefined if not found
*/
export function findLocalChrome(): string | undefined {
const systemPlatform = platform();
const chromePaths: string[] = [];
if (systemPlatform === 'darwin') {
// macOS paths
chromePaths.push(
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium',
`${process.env.HOME}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,
`${process.env.HOME}/Applications/Chromium.app/Contents/MacOS/Chromium`
);
} else if (systemPlatform === 'win32') {
// Windows paths
chromePaths.push(
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
`${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`,
`${process.env.PROGRAMFILES}\\Google\\Chrome\\Application\\chrome.exe`,
`${process.env['PROGRAMFILES(X86)']}\\Google\\Chrome\\Application\\chrome.exe`,
'C:\\Program Files\\Chromium\\Application\\chrome.exe',
'C:\\Program Files (x86)\\Chromium\\Application\\chrome.exe'
);
} else {
// Linux paths
chromePaths.push(
'/usr/bin/google-chrome',
'/usr/bin/google-chrome-stable',
'/usr/bin/chromium',
'/usr/bin/chromium-browser',
'/snap/bin/chromium',
'/usr/local/bin/google-chrome',
'/usr/local/bin/chromium',
'/opt/google/chrome/chrome',
'/opt/google/chrome/google-chrome'
);
}
// Find the first existing Chrome installation
for (const path of chromePaths) {
if (path && existsSync(path)) {
return path;
}
}
return undefined;
}
/**
* Gets the Chrome user data directory path based on the operating system
* @returns The path to Chrome's user data directory, or undefined if not found
*/
export function getChromeUserDataDir(): string | undefined {
const systemPlatform = platform();
if (systemPlatform === 'darwin') {
return `${process.env.HOME}/Library/Application Support/Google/Chrome`;
} else if (systemPlatform === 'win32') {
return `${process.env.LOCALAPPDATA}\\Google\\Chrome\\User Data`;
} else {
// Linux
return `${process.env.HOME}/.config/google-chrome`;
}
}
/**
* Prepares the Chrome profile by copying it to .chrome-profile directory (first run only)
* This should be called before initializing Stagehand to avoid timeouts
* @param pluginRoot The root directory of the plugin
*/
export function prepareChromeProfile(pluginRoot: string) {
const sourceUserDataDir = getChromeUserDataDir();
const tempUserDataDir = join(pluginRoot, '.chrome-profile');
// Only copy if the temp directory doesn't exist yet
if (!existsSync(tempUserDataDir)) {
const dim = '\x1b[2m';
const reset = '\x1b[0m';
// Show copying message
console.log(`${dim}Copying Chrome profile to .chrome-profile/ (this may take a minute)...${reset}`);
mkdirSync(tempUserDataDir, { recursive: true });
// Copy the Default profile directory (contains cookies, local storage, etc.)
const sourceDefaultProfile = join(sourceUserDataDir!, 'Default');
const destDefaultProfile = join(tempUserDataDir, 'Default');
if (existsSync(sourceDefaultProfile)) {
cpSync(sourceDefaultProfile, destDefaultProfile, { recursive: true });
console.log(`${dim}✓ Profile copied successfully${reset}\n`);
} else {
console.log(`${dim}No existing profile found, using fresh profile${reset}\n`);
}
}
}
// Use CDP to take screenshot directly
export async function takeScreenshot(stagehand: Stagehand, pluginRoot: string) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const screenshotDir = join(pluginRoot, 'agent/browser_screenshots');
const screenshotPath = join(screenshotDir, `screenshot-${timestamp}.png`);
// Create directory if it doesn't exist
if (!existsSync(screenshotDir)) {
mkdirSync(screenshotDir, { recursive: true });
}
const page = stagehand.context.pages()[0];
const screenshotResult = await page.screenshot({
type: 'png',
});
// Save the base64 screenshot data to file with resizing if needed
const fs = await import('fs');
const sharp = (await import('sharp')).default;
// Check image dimensions
const image = sharp(screenshotResult);
const metadata = await image.metadata();
const { width, height } = metadata;
let finalBuffer: Buffer = screenshotResult;
// Only resize if image exceeds 2000x2000
if (width && height && (width > 2000 || height > 2000)) {
finalBuffer = await sharp(screenshotResult)
.resize(2000, 2000, {
fit: 'inside',
withoutEnlargement: true
})
.png()
.toBuffer();
}
fs.writeFileSync(screenshotPath, finalBuffer);
return screenshotPath;
}