feat: wire cookie-import-browser into browse server

Add cookie-picker route dispatch (no auth, localhost-only), add
cookie-import-browser to WRITE_COMMANDS and CHAIN_WRITE, add serverPort
property to BrowserManager, add write command with two modes (picker UI
vs --domain direct import), update CLI help text.
This commit is contained in:
Garry Tan
2026-03-12 18:27:33 -07:00
parent c38be94eb9
commit bdba864617
5 changed files with 53 additions and 1 deletions
+3
View File
@@ -27,6 +27,9 @@ export class BrowserManager {
private extraHeaders: Record<string, string> = {};
private customUserAgent: string | null = null;
/** Server port — set after server starts, used by cookie-import-browser command */
public serverPort: number = 0;
// ─── Ref Map (snapshot → @e1, @e2, @c1, @c2, ...) ────────
private refMap: Map<string, Locator> = new Map();
+1
View File
@@ -188,6 +188,7 @@ Interaction: click <sel> | fill <sel> <val> | select <sel> <val>
scroll [sel] | wait <sel|--networkidle|--load> | viewport <WxH>
upload <sel> <file1> [file2...]
cookie-import <json-file>
cookie-import-browser [browser] [--domain <d>]
Inspection: js <expr> | eval <file> | css <sel> <prop> | attrs <sel>
console [--clear|--errors] | network [--clear] | dialog [--clear]
cookies | storage [set <k> <v>] | perf
+1
View File
@@ -20,6 +20,7 @@ const CHAIN_WRITE = new Set([
'click', 'fill', 'select', 'hover', 'type', 'press', 'scroll', 'wait',
'viewport', 'cookie', 'header', 'useragent',
'upload', 'dialog-accept', 'dialog-dismiss',
'cookie-import-browser',
]);
const CHAIN_META = new Set([
'tabs', 'tab', 'newtab', 'closetab',
+8 -1
View File
@@ -12,6 +12,7 @@ import { BrowserManager } from './browser-manager';
import { handleReadCommand } from './read-commands';
import { handleWriteCommand } from './write-commands';
import { handleMetaCommand } from './meta-commands';
import { handleCookiePickerRoute } from './cookie-picker-routes';
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
@@ -115,7 +116,7 @@ export const READ_COMMANDS = new Set([
export const WRITE_COMMANDS = new Set([
'goto', 'back', 'forward', 'reload',
'click', 'fill', 'select', 'hover', 'type', 'press', 'scroll', 'wait',
'viewport', 'cookie', 'cookie-import', 'header', 'useragent',
'viewport', 'cookie', 'cookie-import', 'cookie-import-browser', 'header', 'useragent',
'upload', 'dialog-accept', 'dialog-dismiss',
]);
@@ -264,6 +265,11 @@ async function start() {
const url = new URL(req.url);
// Cookie picker routes — no auth required (localhost-only)
if (url.pathname.startsWith('/cookie-picker')) {
return handleCookiePickerRoute(url, req, browserManager);
}
// Health check — no auth required (now async)
if (url.pathname === '/health') {
const healthy = await browserManager.isHealthy();
@@ -305,6 +311,7 @@ async function start() {
};
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), { mode: 0o600 });
browserManager.serverPort = port;
console.log(`[browse] Server running on http://127.0.0.1:${port} (PID: ${process.pid})`);
console.log(`[browse] State file: ${STATE_FILE}`);
console.log(`[browse] Idle timeout: ${IDLE_TIMEOUT_MS / 1000}s`);
+40
View File
@@ -6,6 +6,7 @@
*/
import type { BrowserManager } from './browser-manager';
import { findInstalledBrowsers, importCookies } from './cookie-import-browser';
import * as fs from 'fs';
import * as path from 'path';
@@ -253,6 +254,45 @@ export async function handleWriteCommand(
return `Loaded ${cookies.length} cookies from ${filePath}`;
}
case 'cookie-import-browser': {
// Two modes:
// 1. Direct CLI import: cookie-import-browser <browser> --domain <domain>
// 2. Open picker UI: cookie-import-browser [browser]
const browserArg = args[0];
const domainIdx = args.indexOf('--domain');
if (domainIdx !== -1 && domainIdx + 1 < args.length) {
// Direct import mode — no UI
const domain = args[domainIdx + 1];
const browser = browserArg || 'comet';
const result = await importCookies(browser, [domain]);
if (result.cookies.length > 0) {
await page.context().addCookies(result.cookies);
}
const msg = [`Imported ${result.count} cookies for ${domain} from ${browser}`];
if (result.failed > 0) msg.push(`(${result.failed} failed to decrypt)`);
return msg.join(' ');
}
// Picker UI mode — open in user's browser
const port = bm.serverPort;
if (!port) throw new Error('Server port not available');
const browsers = findInstalledBrowsers();
if (browsers.length === 0) {
throw new Error('No Chromium browsers found. Supported: Comet, Chrome, Arc, Brave, Edge');
}
const pickerUrl = `http://127.0.0.1:${port}/cookie-picker`;
try {
Bun.spawn(['open', pickerUrl], { stdout: 'ignore', stderr: 'ignore' });
} catch {
// open may fail silently — URL is in the message below
}
return `Cookie picker opened at ${pickerUrl}\nDetected browsers: ${browsers.map(b => b.name).join(', ')}\nSelect domains to import, then close the picker when done.`;
}
default:
throw new Error(`Unknown write command: ${command}`);
}