v0.4.1: Chrome cookie import, config.json, session persistence
CI / test (push) Has been cancelled
style / style (push) Has been cancelled

- Add Chrome cookie importer (macOS Keychain AES-128-CBC decryption)
- Add ~/.config/browse/config.json (headless, fullscreen, stealth, preview defaults)
- Add ~/.config/browse/session.json (auto-save/restore cookies + localStorage)
- Auto-import from all browsers (Safari + Firefox + Chrome) on first launch
- Deduplicate cookies across browsers (domain+name+path key)
- Defaults: headed, fullscreen, stealth, preview all enabled
- Fix BigInt overflow for Chrome timestamps (CAST to TEXT in SQL)
This commit is contained in:
marauder-actual
2026-06-06 16:07:12 +02:00
parent 16bc55bcb9
commit 5e1375f1a1
26 changed files with 1343 additions and 31 deletions
Vendored
+97 -10
View File
@@ -6,19 +6,21 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { ClaudeBrowser } from './browser.js';
import { importAllToSession, loadConfig, loadSession, saveSession, } from './config.js';
import * as image from './image.js';
import { stderrLogger as log } from './logger.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'));
// Browser options configurable via launch tool
// Load user config from ~/.config/browse/config.json
const userConfig = loadConfig();
let browserOptions = {
headless: true,
width: 1280,
height: 800,
fullscreen: false,
preview: false,
previewDelay: 2000,
stealth: false,
headless: userConfig.headless,
width: userConfig.width,
height: userConfig.height,
fullscreen: userConfig.fullscreen,
preview: userConfig.preview,
previewDelay: userConfig.previewDelay,
stealth: userConfig.stealth,
};
let browser = new ClaudeBrowser(browserOptions);
let launched = false;
@@ -27,6 +29,56 @@ async function ensureLaunched() {
if (!launched) {
await browser.launch();
launched = true;
// Auto-restore session if enabled
// If no session.json exists, import all browser cookies first
if (userConfig.autoRestore) {
let session = loadSession();
if (!session) {
try {
const result = await importAllToSession();
log.command({
cmd: 'auto_import',
url: `${result.total} cookies from ${Object.entries(result.sources)
.filter(([, v]) => v > 0)
.map(([k, v]) => `${k}:${v}`)
.join(', ')}`,
});
session = loadSession();
}
catch (err) {
log.result({ cmd: 'auto_import' }, { ok: false, error: err.message });
}
}
if (session) {
try {
const context = browser.getContext();
if (context && session.cookies?.length) {
await context.addCookies(session.cookies);
}
const page = browser.getPage();
if (page && session.url && session.url !== 'about:blank') {
await page.goto(session.url, { waitUntil: 'domcontentloaded' });
await Promise.race([
page.waitForLoadState('networkidle'),
page.waitForTimeout(5000),
]).catch(() => { });
// Restore localStorage/sessionStorage after navigation
if (session.localStorage || session.sessionStorage) {
const local = session.localStorage || {};
const sessionStorage = session.sessionStorage || {};
await page.evaluate(`((data) => {
for (const [k, v] of Object.entries(data.local)) localStorage.setItem(k, v);
for (const [k, v] of Object.entries(data.session)) sessionStorage.setItem(k, v);
})({ local: ${JSON.stringify(local)}, session: ${JSON.stringify(sessionStorage)} })`);
}
}
log.command({ cmd: 'auto_restore', url: session.url });
}
catch (err) {
log.result({ cmd: 'auto_restore' }, { ok: false, error: err.message });
}
}
}
}
}
function textResult(text) {
@@ -399,6 +451,41 @@ server.tool('wait', 'Wait for a specified time in milliseconds', { ms: z.number(
// Session management
server.tool('close', 'Close the browser and end the current session', {}, withLogging('close', async () => {
if (launched) {
// Auto-save session before closing
if (userConfig.autoSave) {
try {
const page = browser.getPage();
const context = browser.getContext();
if (page && context) {
const url = page.url();
const title = await page.title();
const cookies = await context.cookies();
const storage = (await page.evaluate(`({
localStorage: Object.fromEntries(
Array.from({ length: localStorage.length }, (_, i) => localStorage.key(i))
.filter(k => k !== null)
.map(k => [k, localStorage.getItem(k) || ''])
),
sessionStorage: Object.fromEntries(
Array.from({ length: sessionStorage.length }, (_, i) => sessionStorage.key(i))
.filter(k => k !== null)
.map(k => [k, sessionStorage.getItem(k) || ''])
),
})`));
saveSession({
url,
title,
cookies,
localStorage: storage.localStorage,
sessionStorage: storage.sessionStorage,
});
log.command({ cmd: 'auto_save', url });
}
}
catch (err) {
log.result({ cmd: 'auto_save' }, { ok: false, error: err.message });
}
}
await browser.close();
launched = false;
}
@@ -481,8 +568,8 @@ server.tool('session_restore', 'Restore a previously saved session state from a
}));
}));
// Browser import
server.tool('import', 'Import cookies from Safari or Firefox browser. Safari requires Full Disk Access permission (macOS only). Firefox works on macOS, Linux, and Windows.', {
source: z.enum(['safari', 'firefox']).describe('Browser to import from'),
server.tool('import', 'Import cookies from Safari, Firefox, or Chrome browser. Safari requires Full Disk Access (macOS). Chrome requires Keychain access (macOS). Firefox works on all platforms.', {
source: z.enum(['safari', 'firefox', 'chrome']).describe('Browser to import from'),
domain: z
.string()
.optional()