From bdba8646174d8666de3cf35e48e11c4a97298eff Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 12 Mar 2026 18:27:33 -0700 Subject: [PATCH] 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. --- browse/src/browser-manager.ts | 3 +++ browse/src/cli.ts | 1 + browse/src/meta-commands.ts | 1 + browse/src/server.ts | 9 +++++++- browse/src/write-commands.ts | 40 +++++++++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/browse/src/browser-manager.ts b/browse/src/browser-manager.ts index ab1b8d73..f6726e1e 100644 --- a/browse/src/browser-manager.ts +++ b/browse/src/browser-manager.ts @@ -27,6 +27,9 @@ export class BrowserManager { private extraHeaders: Record = {}; 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 = new Map(); diff --git a/browse/src/cli.ts b/browse/src/cli.ts index 43cf0839..eb7a54bd 100644 --- a/browse/src/cli.ts +++ b/browse/src/cli.ts @@ -188,6 +188,7 @@ Interaction: click | fill | select scroll [sel] | wait | viewport upload [file2...] cookie-import + cookie-import-browser [browser] [--domain ] Inspection: js | eval | css | attrs console [--clear|--errors] | network [--clear] | dialog [--clear] cookies | storage [set ] | perf diff --git a/browse/src/meta-commands.ts b/browse/src/meta-commands.ts index 595f30c0..1078e2b7 100644 --- a/browse/src/meta-commands.ts +++ b/browse/src/meta-commands.ts @@ -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', diff --git a/browse/src/server.ts b/browse/src/server.ts index c4073abc..0825b176 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -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`); diff --git a/browse/src/write-commands.ts b/browse/src/write-commands.ts index 9892acf6..1d6e8eee 100644 --- a/browse/src/write-commands.ts +++ b/browse/src/write-commands.ts @@ -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 --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}`); }