lobster-cli 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -268
  3. package/dist/agent/core.js +63 -0
  4. package/dist/agent/core.js.map +1 -1
  5. package/dist/agent/index.js +63 -0
  6. package/dist/agent/index.js.map +1 -1
  7. package/dist/browser/chrome-attach.js +102 -0
  8. package/dist/browser/chrome-attach.js.map +1 -0
  9. package/dist/browser/dom/compact-snapshot.js +162 -0
  10. package/dist/browser/dom/compact-snapshot.js.map +1 -0
  11. package/dist/browser/dom/index.js +160 -0
  12. package/dist/browser/dom/index.js.map +1 -1
  13. package/dist/browser/index.js +1201 -70
  14. package/dist/browser/index.js.map +1 -1
  15. package/dist/browser/manager.js +443 -11
  16. package/dist/browser/manager.js.map +1 -1
  17. package/dist/browser/page-adapter.js +370 -1
  18. package/dist/browser/page-adapter.js.map +1 -1
  19. package/dist/browser/profiles.js +238 -0
  20. package/dist/browser/profiles.js.map +1 -0
  21. package/dist/browser/semantic-find.js +152 -0
  22. package/dist/browser/semantic-find.js.map +1 -0
  23. package/dist/browser/stealth.js +187 -0
  24. package/dist/browser/stealth.js.map +1 -0
  25. package/dist/config/index.js +8 -1
  26. package/dist/config/index.js.map +1 -1
  27. package/dist/config/schema.js +8 -1
  28. package/dist/config/schema.js.map +1 -1
  29. package/dist/doc/index.js +31715 -0
  30. package/dist/doc/index.js.map +1 -0
  31. package/dist/domain-guard.js +103 -0
  32. package/dist/domain-guard.js.map +1 -0
  33. package/dist/index.js +32914 -262
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib.js +1488 -241
  36. package/dist/lib.js.map +1 -1
  37. package/dist/llm/client.js +63 -0
  38. package/dist/llm/client.js.map +1 -1
  39. package/dist/llm/index.js +63 -0
  40. package/dist/llm/index.js.map +1 -1
  41. package/dist/llm/openai-client.js +63 -0
  42. package/dist/llm/openai-client.js.map +1 -1
  43. package/dist/router/index.js +925 -61
  44. package/dist/router/index.js.map +1 -1
  45. package/package.json +16 -2
@@ -1,6 +1,6 @@
1
1
  // src/browser/manager.ts
2
2
  import puppeteer from "puppeteer-core";
3
- import { existsSync } from "fs";
3
+ import { existsSync as existsSync3 } from "fs";
4
4
 
5
5
  // src/utils/logger.ts
6
6
  import chalk from "chalk";
@@ -16,20 +16,431 @@ var log = {
16
16
  dim: (msg) => console.log(chalk.dim(msg))
17
17
  };
18
18
 
19
+ // src/browser/profiles.ts
20
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync, rmSync, statSync } from "fs";
21
+ import { join as join2 } from "path";
22
+
23
+ // src/config/index.ts
24
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
25
+ import { join } from "path";
26
+ import { homedir } from "os";
27
+ import yaml from "js-yaml";
28
+
29
+ // src/config/schema.ts
30
+ import { z } from "zod";
31
+ var configSchema = z.object({
32
+ llm: z.object({
33
+ provider: z.enum(["openai", "anthropic", "gemini", "ollama"]).default("openai"),
34
+ baseURL: z.string().default("https://api.openai.com/v1"),
35
+ model: z.string().default("gpt-4o"),
36
+ apiKey: z.string().default(""),
37
+ temperature: z.number().min(0).max(2).default(0.1),
38
+ maxRetries: z.number().int().min(0).default(3)
39
+ }).default({}),
40
+ browser: z.object({
41
+ executablePath: z.string().default(""),
42
+ headless: z.boolean().default(true),
43
+ connectTimeout: z.number().default(30),
44
+ commandTimeout: z.number().default(60),
45
+ cdpEndpoint: z.string().default(""),
46
+ profile: z.string().default(""),
47
+ stealth: z.boolean().default(false)
48
+ }).default({}),
49
+ agent: z.object({
50
+ maxSteps: z.number().int().default(40),
51
+ stepDelay: z.number().default(0.4)
52
+ }).default({}),
53
+ domains: z.object({
54
+ allow: z.array(z.string()).default([]),
55
+ block: z.array(z.string()).default([]),
56
+ blockMessage: z.string().default("")
57
+ }).default({}),
58
+ output: z.object({
59
+ defaultFormat: z.enum(["table", "json", "yaml", "markdown", "csv"]).default("table"),
60
+ color: z.boolean().default(true)
61
+ }).default({})
62
+ });
63
+
64
+ // src/config/index.ts
65
+ var CONFIG_DIR = join(homedir(), ".lobster");
66
+ var CONFIG_FILE = join(CONFIG_DIR, "config.yaml");
67
+ function getConfigDir() {
68
+ return CONFIG_DIR;
69
+ }
70
+
71
+ // src/browser/profiles.ts
72
+ var PROFILES_DIR = () => join2(getConfigDir(), "profiles");
73
+ var META_FILE = ".lobster-meta.json";
74
+ var VALID_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
75
+ var RESERVED_NAMES = /* @__PURE__ */ new Set([
76
+ "default",
77
+ "system",
78
+ "con",
79
+ "prn",
80
+ "aux",
81
+ "nul",
82
+ "com1",
83
+ "com2",
84
+ "com3",
85
+ "com4",
86
+ "com5",
87
+ "com6",
88
+ "com7",
89
+ "com8",
90
+ "com9",
91
+ "lpt1",
92
+ "lpt2",
93
+ "lpt3",
94
+ "lpt4",
95
+ "lpt5",
96
+ "lpt6",
97
+ "lpt7",
98
+ "lpt8",
99
+ "lpt9"
100
+ ]);
101
+ function ensureProfilesDir() {
102
+ const dir = PROFILES_DIR();
103
+ if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
104
+ }
105
+ function validateName(name) {
106
+ if (!VALID_NAME.test(name)) {
107
+ throw new Error(`Invalid profile name "${name}". Use only letters, numbers, hyphens, underscores (max 64 chars).`);
108
+ }
109
+ if (RESERVED_NAMES.has(name.toLowerCase())) {
110
+ throw new Error(`"${name}" is a reserved name. Choose a different profile name.`);
111
+ }
112
+ }
113
+ function getProfileDir(name) {
114
+ return join2(PROFILES_DIR(), name);
115
+ }
116
+ function readMeta(profileDir) {
117
+ const metaPath = join2(profileDir, META_FILE);
118
+ if (!existsSync2(metaPath)) return null;
119
+ try {
120
+ return JSON.parse(readFileSync2(metaPath, "utf-8"));
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+ function writeMeta(profileDir, meta) {
126
+ writeFileSync2(join2(profileDir, META_FILE), JSON.stringify(meta, null, 2));
127
+ }
128
+ function createProfile(name) {
129
+ validateName(name);
130
+ ensureProfilesDir();
131
+ const dir = getProfileDir(name);
132
+ if (existsSync2(dir)) {
133
+ throw new Error(`Profile "${name}" already exists.`);
134
+ }
135
+ mkdirSync2(dir, { recursive: true });
136
+ const meta = {
137
+ name,
138
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
139
+ lastUsed: (/* @__PURE__ */ new Date()).toISOString()
140
+ };
141
+ writeMeta(dir, meta);
142
+ log.success(`Profile "${name}" created at ${dir}`);
143
+ return meta;
144
+ }
145
+ function getProfileDataDir(name) {
146
+ validateName(name);
147
+ const dir = getProfileDir(name);
148
+ if (!existsSync2(dir)) {
149
+ createProfile(name);
150
+ } else {
151
+ const meta = readMeta(dir) || { name, createdAt: "unknown", lastUsed: "" };
152
+ meta.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
153
+ writeMeta(dir, meta);
154
+ }
155
+ return dir;
156
+ }
157
+
158
+ // src/browser/chrome-attach.ts
159
+ import http from "http";
160
+ var DEFAULT_PORTS = [9222, 9229, 9333, 9515];
161
+ var PROBE_TIMEOUT = 1500;
162
+ function probePort(port) {
163
+ return new Promise((resolve) => {
164
+ const req = http.get(`http://127.0.0.1:${port}/json/version`, {
165
+ timeout: PROBE_TIMEOUT
166
+ }, (res) => {
167
+ let data = "";
168
+ res.on("data", (chunk) => {
169
+ data += chunk;
170
+ });
171
+ res.on("end", () => {
172
+ try {
173
+ const info = JSON.parse(data);
174
+ if (info.webSocketDebuggerUrl) {
175
+ resolve({
176
+ wsEndpoint: info.webSocketDebuggerUrl,
177
+ port,
178
+ version: info["Protocol-Version"] || "",
179
+ browser: info.Browser || ""
180
+ });
181
+ } else {
182
+ resolve(null);
183
+ }
184
+ } catch {
185
+ resolve(null);
186
+ }
187
+ });
188
+ });
189
+ req.on("error", () => resolve(null));
190
+ req.on("timeout", () => {
191
+ req.destroy();
192
+ resolve(null);
193
+ });
194
+ });
195
+ }
196
+ async function discoverChrome(ports) {
197
+ const portsToCheck = ports || DEFAULT_PORTS;
198
+ log.debug(`Scanning ports for Chrome: ${portsToCheck.join(", ")}`);
199
+ const results = await Promise.all(portsToCheck.map(probePort));
200
+ const found = results.find(Boolean) || null;
201
+ if (found) {
202
+ log.info(`Found Chrome on port ${found.port}: ${found.browser}`);
203
+ } else {
204
+ log.debug("No running Chrome instance found on debug ports.");
205
+ }
206
+ return found;
207
+ }
208
+ async function getWebSocketDebuggerUrl(port) {
209
+ const result = await probePort(port);
210
+ return result?.wsEndpoint || null;
211
+ }
212
+ async function resolveAttachTarget(target) {
213
+ if (target === true || target === "true") {
214
+ const result = await discoverChrome();
215
+ if (!result) {
216
+ throw new Error(
217
+ "No running Chrome found. Start Chrome with:\n google-chrome --remote-debugging-port=9222\n # or on Mac:\n /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222"
218
+ );
219
+ }
220
+ return result.wsEndpoint;
221
+ }
222
+ if (typeof target === "string") {
223
+ if (target.startsWith("ws://") || target.startsWith("wss://")) {
224
+ return target;
225
+ }
226
+ const port = parseInt(target, 10);
227
+ if (!isNaN(port) && port > 0 && port < 65536) {
228
+ const url = await getWebSocketDebuggerUrl(port);
229
+ if (!url) {
230
+ throw new Error(`No Chrome found on port ${port}. Make sure Chrome is running with --remote-debugging-port=${port}`);
231
+ }
232
+ return url;
233
+ }
234
+ throw new Error(`Invalid attach target: "${target}". Use "true" for auto-discover, a port number, or a ws:// URL.`);
235
+ }
236
+ throw new Error("Invalid attach target.");
237
+ }
238
+
239
+ // src/browser/stealth.ts
240
+ var STEALTH_SCRIPT = `
241
+ (() => {
242
+ // \u2500\u2500 1. navigator.webdriver removal \u2500\u2500
243
+ // Most important: this is the #1 detection vector
244
+ Object.defineProperty(navigator, 'webdriver', {
245
+ get: () => undefined,
246
+ configurable: true,
247
+ });
248
+
249
+ // Also delete from prototype
250
+ delete Object.getPrototypeOf(navigator).webdriver;
251
+
252
+ // \u2500\u2500 2. CDP marker removal \u2500\u2500
253
+ // Chrome DevTools Protocol injects cdc_* properties on window
254
+ for (const key of Object.keys(window)) {
255
+ if (/^cdc_|^__webdriver|^__selenium|^__driver/.test(key)) {
256
+ try { delete window[key]; } catch {}
257
+ }
258
+ }
259
+
260
+ // \u2500\u2500 3. Chrome runtime spoofing \u2500\u2500
261
+ // Real Chrome has window.chrome with runtime, loadTimes, csi
262
+ if (!window.chrome) {
263
+ window.chrome = {};
264
+ }
265
+ if (!window.chrome.runtime) {
266
+ window.chrome.runtime = {
267
+ connect: function() {},
268
+ sendMessage: function() {},
269
+ onMessage: { addListener: function() {} },
270
+ id: undefined,
271
+ };
272
+ }
273
+ if (!window.chrome.loadTimes) {
274
+ window.chrome.loadTimes = function() {
275
+ return {
276
+ commitLoadTime: Date.now() / 1000 - 0.5,
277
+ connectionInfo: 'h2',
278
+ finishDocumentLoadTime: Date.now() / 1000 - 0.1,
279
+ finishLoadTime: Date.now() / 1000 - 0.05,
280
+ firstPaintAfterLoadTime: 0,
281
+ firstPaintTime: Date.now() / 1000 - 0.3,
282
+ navigationType: 'Other',
283
+ npnNegotiatedProtocol: 'h2',
284
+ requestTime: Date.now() / 1000 - 1,
285
+ startLoadTime: Date.now() / 1000 - 0.8,
286
+ wasAlternateProtocolAvailable: false,
287
+ wasFetchedViaSpdy: true,
288
+ wasNpnNegotiated: true,
289
+ };
290
+ };
291
+ }
292
+ if (!window.chrome.csi) {
293
+ window.chrome.csi = function() {
294
+ return {
295
+ onloadT: Date.now(),
296
+ startE: Date.now() - 500,
297
+ pageT: 500,
298
+ tran: 15,
299
+ };
300
+ };
301
+ }
302
+
303
+ // \u2500\u2500 4. Plugin array spoofing \u2500\u2500
304
+ // Headless Chrome reports empty plugins; real Chrome has at least 2
305
+ const fakePlugins = [
306
+ { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 1 },
307
+ { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '', length: 1 },
308
+ { name: 'Native Client', filename: 'internal-nacl-plugin', description: '', length: 2 },
309
+ ];
310
+
311
+ Object.defineProperty(navigator, 'plugins', {
312
+ get: () => {
313
+ const arr = fakePlugins.map(p => {
314
+ const plugin = { ...p, item: (i) => plugin, namedItem: (n) => plugin };
315
+ return plugin;
316
+ });
317
+ arr.item = (i) => arr[i];
318
+ arr.namedItem = (n) => arr.find(p => p.name === n);
319
+ arr.refresh = () => {};
320
+ return arr;
321
+ },
322
+ });
323
+
324
+ // \u2500\u2500 5. Languages \u2500\u2500
325
+ Object.defineProperty(navigator, 'languages', {
326
+ get: () => ['en-US', 'en'],
327
+ });
328
+ Object.defineProperty(navigator, 'language', {
329
+ get: () => 'en-US',
330
+ });
331
+
332
+ // \u2500\u2500 6. Platform consistency \u2500\u2500
333
+ // Ensure platform matches user agent
334
+ const platform = navigator.userAgent.includes('Mac') ? 'MacIntel' :
335
+ navigator.userAgent.includes('Win') ? 'Win32' :
336
+ navigator.userAgent.includes('Linux') ? 'Linux x86_64' : navigator.platform;
337
+ Object.defineProperty(navigator, 'platform', { get: () => platform });
338
+
339
+ // \u2500\u2500 7. Hardware concurrency & device memory \u2500\u2500
340
+ // Headless often reports unusual values
341
+ if (navigator.hardwareConcurrency < 2) {
342
+ Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
343
+ }
344
+ if (!navigator.deviceMemory || navigator.deviceMemory < 2) {
345
+ Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
346
+ }
347
+
348
+ // \u2500\u2500 8. WebGL vendor/renderer spoofing \u2500\u2500
349
+ // Headless reports "Google SwiftShader" which is a dead giveaway
350
+ const origGetParameter = WebGLRenderingContext.prototype.getParameter;
351
+ WebGLRenderingContext.prototype.getParameter = function(param) {
352
+ // UNMASKED_VENDOR_WEBGL
353
+ if (param === 0x9245) return 'Intel Inc.';
354
+ // UNMASKED_RENDERER_WEBGL
355
+ if (param === 0x9246) return 'Intel Iris OpenGL Engine';
356
+ return origGetParameter.call(this, param);
357
+ };
358
+
359
+ // Also for WebGL2
360
+ if (typeof WebGL2RenderingContext !== 'undefined') {
361
+ const origGetParameter2 = WebGL2RenderingContext.prototype.getParameter;
362
+ WebGL2RenderingContext.prototype.getParameter = function(param) {
363
+ if (param === 0x9245) return 'Intel Inc.';
364
+ if (param === 0x9246) return 'Intel Iris OpenGL Engine';
365
+ return origGetParameter2.call(this, param);
366
+ };
367
+ }
368
+
369
+ // \u2500\u2500 9. Canvas fingerprint noise \u2500\u2500
370
+ // Adds subtle deterministic noise to canvas output based on domain
371
+ const seed = location.hostname.split('').reduce((a, c) => a + c.charCodeAt(0), 0);
372
+ const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
373
+ HTMLCanvasElement.prototype.toDataURL = function(type) {
374
+ const ctx = this.getContext('2d');
375
+ if (ctx && this.width > 0 && this.height > 0) {
376
+ try {
377
+ const imageData = ctx.getImageData(0, 0, 1, 1);
378
+ // Flip a single pixel with seeded noise
379
+ imageData.data[0] = (imageData.data[0] + seed) % 256;
380
+ ctx.putImageData(imageData, 0, 0);
381
+ } catch {}
382
+ }
383
+ return origToDataURL.apply(this, arguments);
384
+ };
385
+
386
+ // \u2500\u2500 10. Permissions API \u2500\u2500
387
+ // Headless returns 'denied' for notifications; real Chrome returns 'prompt'
388
+ const origQuery = navigator.permissions?.query?.bind(navigator.permissions);
389
+ if (origQuery) {
390
+ navigator.permissions.query = function(descriptor) {
391
+ if (descriptor.name === 'notifications') {
392
+ return Promise.resolve({ state: Notification.permission || 'prompt', onchange: null });
393
+ }
394
+ return origQuery(descriptor);
395
+ };
396
+ }
397
+
398
+ // \u2500\u2500 11. Notification constructor \u2500\u2500
399
+ if (!window.Notification) {
400
+ window.Notification = function() {};
401
+ window.Notification.permission = 'default';
402
+ window.Notification.requestPermission = () => Promise.resolve('default');
403
+ }
404
+
405
+ // \u2500\u2500 12. Connection type \u2500\u2500
406
+ if (navigator.connection) {
407
+ Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 });
408
+ }
409
+ })()
410
+ `;
411
+ async function injectStealth(page) {
412
+ await page.evaluateOnNewDocument(STEALTH_SCRIPT);
413
+ }
414
+ var STEALTH_ARGS = [
415
+ "--disable-blink-features=AutomationControlled",
416
+ "--disable-features=IsolateOrigins,site-per-process",
417
+ "--disable-infobars",
418
+ "--window-size=1920,1080"
419
+ ];
420
+
19
421
  // src/browser/manager.ts
20
422
  var BrowserManager = class {
21
423
  browser = null;
22
424
  config;
425
+ isAttached = false;
23
426
  constructor(config = {}) {
24
427
  this.config = config;
25
428
  }
26
429
  async connect() {
27
430
  if (this.browser?.connected) return this.browser;
431
+ if (this.config.attach) {
432
+ const wsEndpoint = await resolveAttachTarget(this.config.attach);
433
+ log.info(`Attaching to Chrome: ${wsEndpoint}`);
434
+ this.browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint });
435
+ this.isAttached = true;
436
+ return this.browser;
437
+ }
28
438
  if (this.config.cdpEndpoint) {
29
439
  log.debug(`Connecting to CDP endpoint: ${this.config.cdpEndpoint}`);
30
440
  this.browser = await puppeteer.connect({
31
441
  browserWSEndpoint: this.config.cdpEndpoint
32
442
  });
443
+ this.isAttached = true;
33
444
  return this.browser;
34
445
  }
35
446
  const executablePath = this.config.executablePath || findChrome();
@@ -38,27 +449,48 @@ var BrowserManager = class {
38
449
  "Chrome/Chromium not found. Set LOBSTER_BROWSER_PATH or config browser.executablePath"
39
450
  );
40
451
  }
452
+ const args = [
453
+ "--no-sandbox",
454
+ "--disable-setuid-sandbox",
455
+ "--disable-dev-shm-usage",
456
+ "--disable-gpu"
457
+ ];
458
+ if (this.config.stealth) {
459
+ args.push(...STEALTH_ARGS);
460
+ }
461
+ let userDataDir;
462
+ if (this.config.profile) {
463
+ userDataDir = getProfileDataDir(this.config.profile);
464
+ log.info(`Using profile "${this.config.profile}" \u2192 ${userDataDir}`);
465
+ }
41
466
  log.debug(`Launching Chrome: ${executablePath}`);
42
467
  this.browser = await puppeteer.launch({
43
468
  executablePath,
44
469
  headless: this.config.headless ?? true,
45
- args: [
46
- "--no-sandbox",
47
- "--disable-setuid-sandbox",
48
- "--disable-dev-shm-usage",
49
- "--disable-gpu"
50
- ]
470
+ userDataDir,
471
+ args
51
472
  });
473
+ this.isAttached = false;
52
474
  return this.browser;
53
475
  }
54
476
  async newPage() {
55
477
  const browser = await this.connect();
56
- return browser.newPage();
478
+ const page = await browser.newPage();
479
+ if (this.config.stealth) {
480
+ await injectStealth(page);
481
+ log.debug("Stealth mode enabled");
482
+ }
483
+ return page;
57
484
  }
58
485
  async close() {
59
486
  if (this.browser) {
60
- await this.browser.close().catch(() => {
61
- });
487
+ if (this.isAttached) {
488
+ this.browser.disconnect();
489
+ log.debug("Disconnected from Chrome (attached mode)");
490
+ } else {
491
+ await this.browser.close().catch(() => {
492
+ });
493
+ }
62
494
  this.browser = null;
63
495
  }
64
496
  }
@@ -78,7 +510,7 @@ function findChrome() {
78
510
  "/usr/bin/chromium",
79
511
  "/snap/bin/chromium"
80
512
  ];
81
- return paths.find((p) => existsSync(p));
513
+ return paths.find((p) => existsSync3(p));
82
514
  }
83
515
  export {
84
516
  BrowserManager
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/browser/manager.ts","../../src/utils/logger.ts"],"sourcesContent":["import puppeteer, { type Browser, type Page } from 'puppeteer-core';\nimport { existsSync } from 'node:fs';\nimport { log } from '../utils/logger.js';\n\nexport interface BrowserManagerConfig {\n executablePath?: string;\n headless?: boolean;\n cdpEndpoint?: string;\n connectTimeout?: number;\n}\n\nexport class BrowserManager {\n private browser: Browser | null = null;\n private config: BrowserManagerConfig;\n\n constructor(config: BrowserManagerConfig = {}) {\n this.config = config;\n }\n\n async connect(): Promise<Browser> {\n if (this.browser?.connected) return this.browser;\n\n if (this.config.cdpEndpoint) {\n log.debug(`Connecting to CDP endpoint: ${this.config.cdpEndpoint}`);\n this.browser = await puppeteer.connect({\n browserWSEndpoint: this.config.cdpEndpoint,\n });\n return this.browser;\n }\n\n const executablePath = this.config.executablePath || findChrome();\n if (!executablePath) {\n throw new Error(\n 'Chrome/Chromium not found. Set LOBSTER_BROWSER_PATH or config browser.executablePath'\n );\n }\n\n log.debug(`Launching Chrome: ${executablePath}`);\n this.browser = await puppeteer.launch({\n executablePath,\n headless: this.config.headless ?? true,\n args: [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n ],\n });\n\n return this.browser;\n }\n\n async newPage(): Promise<Page> {\n const browser = await this.connect();\n return browser.newPage();\n }\n\n async close(): Promise<void> {\n if (this.browser) {\n await this.browser.close().catch(() => {});\n this.browser = null;\n }\n }\n}\n\nfunction findChrome(): string | undefined {\n const paths =\n process.platform === 'darwin'\n ? [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Chromium.app/Contents/MacOS/Chromium',\n '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',\n ]\n : process.platform === 'win32'\n ? [\n 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n 'C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n ]\n : [\n '/usr/bin/google-chrome',\n '/usr/bin/google-chrome-stable',\n '/usr/bin/chromium-browser',\n '/usr/bin/chromium',\n '/snap/bin/chromium',\n ];\n\n return paths.find((p) => existsSync(p));\n}\n","import chalk from 'chalk';\n\nexport const log = {\n info: (msg: string) => console.log(chalk.blue('ℹ'), msg),\n success: (msg: string) => console.log(chalk.green('✓'), msg),\n warn: (msg: string) => console.log(chalk.yellow('⚠'), msg),\n error: (msg: string) => console.error(chalk.red('✗'), msg),\n debug: (msg: string) => {\n if (process.env.LOBSTER_DEBUG) console.log(chalk.gray('⋯'), msg);\n },\n step: (n: number, msg: string) => console.log(chalk.cyan(`[${n}]`), msg),\n dim: (msg: string) => console.log(chalk.dim(msg)),\n};\n"],"mappings":";AAAA,OAAO,eAA4C;AACnD,SAAS,kBAAkB;;;ACD3B,OAAO,WAAW;AAEX,IAAM,MAAM;AAAA,EACjB,MAAM,CAAC,QAAgB,QAAQ,IAAI,MAAM,KAAK,QAAG,GAAG,GAAG;AAAA,EACvD,SAAS,CAAC,QAAgB,QAAQ,IAAI,MAAM,MAAM,QAAG,GAAG,GAAG;AAAA,EAC3D,MAAM,CAAC,QAAgB,QAAQ,IAAI,MAAM,OAAO,QAAG,GAAG,GAAG;AAAA,EACzD,OAAO,CAAC,QAAgB,QAAQ,MAAM,MAAM,IAAI,QAAG,GAAG,GAAG;AAAA,EACzD,OAAO,CAAC,QAAgB;AACtB,QAAI,QAAQ,IAAI,cAAe,SAAQ,IAAI,MAAM,KAAK,QAAG,GAAG,GAAG;AAAA,EACjE;AAAA,EACA,MAAM,CAAC,GAAW,QAAgB,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,GAAG,GAAG,GAAG;AAAA,EACvE,KAAK,CAAC,QAAgB,QAAQ,IAAI,MAAM,IAAI,GAAG,CAAC;AAClD;;;ADDO,IAAM,iBAAN,MAAqB;AAAA,EAClB,UAA0B;AAAA,EAC1B;AAAA,EAER,YAAY,SAA+B,CAAC,GAAG;AAC7C,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,UAA4B;AAChC,QAAI,KAAK,SAAS,UAAW,QAAO,KAAK;AAEzC,QAAI,KAAK,OAAO,aAAa;AAC3B,UAAI,MAAM,+BAA+B,KAAK,OAAO,WAAW,EAAE;AAClE,WAAK,UAAU,MAAM,UAAU,QAAQ;AAAA,QACrC,mBAAmB,KAAK,OAAO;AAAA,MACjC,CAAC;AACD,aAAO,KAAK;AAAA,IACd;AAEA,UAAM,iBAAiB,KAAK,OAAO,kBAAkB,WAAW;AAChE,QAAI,CAAC,gBAAgB;AACnB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,MAAM,qBAAqB,cAAc,EAAE;AAC/C,SAAK,UAAU,MAAM,UAAU,OAAO;AAAA,MACpC;AAAA,MACA,UAAU,KAAK,OAAO,YAAY;AAAA,MAClC,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAED,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,UAAyB;AAC7B,UAAM,UAAU,MAAM,KAAK,QAAQ;AACnC,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACzC,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AACF;AAEA,SAAS,aAAiC;AACxC,QAAM,QACJ,QAAQ,aAAa,WACjB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF,IACA,QAAQ,aAAa,UACnB;AAAA,IACE;AAAA,IACA;AAAA,EACF,IACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAER,SAAO,MAAM,KAAK,CAAC,MAAM,WAAW,CAAC,CAAC;AACxC;","names":[]}
1
+ {"version":3,"sources":["../../src/browser/manager.ts","../../src/utils/logger.ts","../../src/browser/profiles.ts","../../src/config/index.ts","../../src/config/schema.ts","../../src/browser/chrome-attach.ts","../../src/browser/stealth.ts"],"sourcesContent":["import puppeteer, { type Browser, type Page } from 'puppeteer-core';\nimport { existsSync } from 'node:fs';\nimport { log } from '../utils/logger.js';\nimport { getProfileDataDir } from './profiles.js';\nimport { resolveAttachTarget } from './chrome-attach.js';\nimport { injectStealth, STEALTH_ARGS } from './stealth.js';\n\nexport interface BrowserManagerConfig {\n executablePath?: string;\n headless?: boolean;\n cdpEndpoint?: string;\n connectTimeout?: number;\n profile?: string;\n attach?: boolean | string;\n stealth?: boolean;\n}\n\nexport class BrowserManager {\n private browser: Browser | null = null;\n private config: BrowserManagerConfig;\n private isAttached = false;\n\n constructor(config: BrowserManagerConfig = {}) {\n this.config = config;\n }\n\n async connect(): Promise<Browser> {\n if (this.browser?.connected) return this.browser;\n\n // Priority 1: Attach to running Chrome\n if (this.config.attach) {\n const wsEndpoint = await resolveAttachTarget(this.config.attach);\n log.info(`Attaching to Chrome: ${wsEndpoint}`);\n this.browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint });\n this.isAttached = true;\n return this.browser;\n }\n\n // Priority 2: Connect to CDP endpoint\n if (this.config.cdpEndpoint) {\n log.debug(`Connecting to CDP endpoint: ${this.config.cdpEndpoint}`);\n this.browser = await puppeteer.connect({\n browserWSEndpoint: this.config.cdpEndpoint,\n });\n this.isAttached = true;\n return this.browser;\n }\n\n // Priority 3: Launch new Chrome\n const executablePath = this.config.executablePath || findChrome();\n if (!executablePath) {\n throw new Error(\n 'Chrome/Chromium not found. Set LOBSTER_BROWSER_PATH or config browser.executablePath'\n );\n }\n\n // Build launch args\n const args = [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n ];\n\n // Stealth args\n if (this.config.stealth) {\n args.push(...STEALTH_ARGS);\n }\n\n // Profile — set user data directory\n let userDataDir: string | undefined;\n if (this.config.profile) {\n userDataDir = getProfileDataDir(this.config.profile);\n log.info(`Using profile \"${this.config.profile}\" → ${userDataDir}`);\n }\n\n log.debug(`Launching Chrome: ${executablePath}`);\n this.browser = await puppeteer.launch({\n executablePath,\n headless: this.config.headless ?? true,\n userDataDir,\n args,\n });\n\n this.isAttached = false;\n return this.browser;\n }\n\n async newPage(): Promise<Page> {\n const browser = await this.connect();\n const page = await browser.newPage();\n\n // Inject stealth scripts before any navigation\n if (this.config.stealth) {\n await injectStealth(page);\n log.debug('Stealth mode enabled');\n }\n\n return page;\n }\n\n async close(): Promise<void> {\n if (this.browser) {\n if (this.isAttached) {\n // Don't close user's browser — just disconnect\n this.browser.disconnect();\n log.debug('Disconnected from Chrome (attached mode)');\n } else {\n await this.browser.close().catch(() => {});\n }\n this.browser = null;\n }\n }\n}\n\nfunction findChrome(): string | undefined {\n const paths =\n process.platform === 'darwin'\n ? [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Chromium.app/Contents/MacOS/Chromium',\n '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',\n ]\n : process.platform === 'win32'\n ? [\n 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n 'C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n ]\n : [\n '/usr/bin/google-chrome',\n '/usr/bin/google-chrome-stable',\n '/usr/bin/chromium-browser',\n '/usr/bin/chromium',\n '/snap/bin/chromium',\n ];\n\n return paths.find((p) => existsSync(p));\n}\n","import chalk from 'chalk';\n\nexport const log = {\n info: (msg: string) => console.log(chalk.blue('ℹ'), msg),\n success: (msg: string) => console.log(chalk.green('✓'), msg),\n warn: (msg: string) => console.log(chalk.yellow('⚠'), msg),\n error: (msg: string) => console.error(chalk.red('✗'), msg),\n debug: (msg: string) => {\n if (process.env.LOBSTER_DEBUG) console.log(chalk.gray('⋯'), msg);\n },\n step: (n: number, msg: string) => console.log(chalk.cyan(`[${n}]`), msg),\n dim: (msg: string) => console.log(chalk.dim(msg)),\n};\n","/**\n * Persistent Profiles — store Chrome user data dirs so cookies,\n * auth, and extensions survive across sessions.\n *\n * Inspired by PinchTab's profile management, built from scratch.\n *\n * Storage: ~/.lobster/profiles/<name>/\n * Metadata: ~/.lobster/profiles/<name>/.lobster-meta.json\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, rmSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getConfigDir } from '../config/index.js';\nimport { log } from '../utils/logger.js';\n\nexport interface ProfileMeta {\n name: string;\n createdAt: string;\n lastUsed: string;\n sizeMB?: number;\n}\n\nconst PROFILES_DIR = () => join(getConfigDir(), 'profiles');\nconst META_FILE = '.lobster-meta.json';\n\n// Name validation: safe across Windows/Mac/Linux, no path traversal\nconst VALID_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;\nconst RESERVED_NAMES = new Set([\n 'default', 'system', 'con', 'prn', 'aux', 'nul',\n 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',\n 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',\n]);\n\n// Directories to delete on cache reset (keep cookies, extensions, local storage)\nconst CACHE_DIRS = [\n 'Cache', 'Code Cache', 'GPUCache', 'GrShaderCache', 'ShaderCache',\n 'Service Worker', 'Sessions', 'Session Storage', 'blob_storage',\n];\n\nfunction ensureProfilesDir(): void {\n const dir = PROFILES_DIR();\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n}\n\nfunction validateName(name: string): void {\n if (!VALID_NAME.test(name)) {\n throw new Error(`Invalid profile name \"${name}\". Use only letters, numbers, hyphens, underscores (max 64 chars).`);\n }\n if (RESERVED_NAMES.has(name.toLowerCase())) {\n throw new Error(`\"${name}\" is a reserved name. Choose a different profile name.`);\n }\n}\n\nfunction getProfileDir(name: string): string {\n return join(PROFILES_DIR(), name);\n}\n\nfunction readMeta(profileDir: string): ProfileMeta | null {\n const metaPath = join(profileDir, META_FILE);\n if (!existsSync(metaPath)) return null;\n try {\n return JSON.parse(readFileSync(metaPath, 'utf-8'));\n } catch {\n return null;\n }\n}\n\nfunction writeMeta(profileDir: string, meta: ProfileMeta): void {\n writeFileSync(join(profileDir, META_FILE), JSON.stringify(meta, null, 2));\n}\n\nfunction getDirSizeMB(dirPath: string): number {\n let total = 0;\n try {\n const entries = readdirSync(dirPath, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n if (entry.isFile()) {\n total += statSync(fullPath).size;\n } else if (entry.isDirectory() && entry.name !== '.lobster-meta.json') {\n total += getDirSizeMB(fullPath) * 1024 * 1024; // recursive returns MB\n }\n }\n } catch {}\n return Math.round((total / (1024 * 1024)) * 10) / 10;\n}\n\n/**\n * Create a new profile.\n */\nexport function createProfile(name: string): ProfileMeta {\n validateName(name);\n ensureProfilesDir();\n\n const dir = getProfileDir(name);\n if (existsSync(dir)) {\n throw new Error(`Profile \"${name}\" already exists.`);\n }\n\n mkdirSync(dir, { recursive: true });\n\n const meta: ProfileMeta = {\n name,\n createdAt: new Date().toISOString(),\n lastUsed: new Date().toISOString(),\n };\n\n writeMeta(dir, meta);\n log.success(`Profile \"${name}\" created at ${dir}`);\n return meta;\n}\n\n/**\n * List all profiles.\n */\nexport function listProfiles(): ProfileMeta[] {\n ensureProfilesDir();\n const dir = PROFILES_DIR();\n const profiles: ProfileMeta[] = [];\n\n try {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const profileDir = join(dir, entry.name);\n const meta = readMeta(profileDir);\n if (meta) {\n meta.sizeMB = getDirSizeMB(profileDir);\n profiles.push(meta);\n } else {\n // Directory exists but no meta — create meta\n profiles.push({\n name: entry.name,\n createdAt: 'unknown',\n lastUsed: 'unknown',\n sizeMB: getDirSizeMB(profileDir),\n });\n }\n }\n } catch {}\n\n return profiles.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Remove a profile and all its data.\n */\nexport function removeProfile(name: string): void {\n const dir = getProfileDir(name);\n if (!existsSync(dir)) {\n throw new Error(`Profile \"${name}\" does not exist.`);\n }\n\n rmSync(dir, { recursive: true, force: true });\n log.success(`Profile \"${name}\" deleted.`);\n}\n\n/**\n * Get the Chrome user data directory for a profile.\n * Updates lastUsed timestamp.\n */\nexport function getProfileDataDir(name: string): string {\n validateName(name);\n const dir = getProfileDir(name);\n\n if (!existsSync(dir)) {\n // Auto-create if doesn't exist\n createProfile(name);\n } else {\n // Update lastUsed\n const meta = readMeta(dir) || { name, createdAt: 'unknown', lastUsed: '' };\n meta.lastUsed = new Date().toISOString();\n writeMeta(dir, meta);\n }\n\n return dir;\n}\n\n/**\n * Reset cache directories but keep cookies, extensions, and local storage.\n */\nexport function resetProfileCache(name: string): void {\n const dir = getProfileDir(name);\n if (!existsSync(dir)) {\n throw new Error(`Profile \"${name}\" does not exist.`);\n }\n\n let cleaned = 0;\n for (const cacheDir of CACHE_DIRS) {\n // Check both root and Default/ subdirectory\n for (const base of [dir, join(dir, 'Default')]) {\n const target = join(base, cacheDir);\n if (existsSync(target)) {\n rmSync(target, { recursive: true, force: true });\n cleaned++;\n }\n }\n }\n\n log.success(`Profile \"${name}\" cache reset (${cleaned} directories cleaned).`);\n}\n","import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport yaml from 'js-yaml';\nimport { configSchema, type LobsterConfig } from './schema.js';\nimport { DEFAULT_CONFIG } from './defaults.js';\n\nexport type { LobsterConfig };\n\nconst CONFIG_DIR = join(homedir(), '.lobster');\nconst CONFIG_FILE = join(CONFIG_DIR, 'config.yaml');\n\nfunction ensureConfigDir(): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\nexport function loadConfig(): LobsterConfig {\n ensureConfigDir();\n\n let fileConfig: Record<string, unknown> = {};\n if (existsSync(CONFIG_FILE)) {\n const raw = readFileSync(CONFIG_FILE, 'utf-8');\n fileConfig = (yaml.load(raw) as Record<string, unknown>) || {};\n }\n\n // Env var overrides\n const envOverrides: Record<string, unknown> = {};\n if (process.env.LOBSTER_API_KEY) {\n envOverrides.llm = { ...(fileConfig.llm as Record<string, unknown> || {}), apiKey: process.env.LOBSTER_API_KEY };\n }\n if (process.env.LOBSTER_MODEL) {\n envOverrides.llm = { ...(envOverrides.llm as Record<string, unknown> || fileConfig.llm as Record<string, unknown> || {}), model: process.env.LOBSTER_MODEL };\n }\n if (process.env.LOBSTER_BASE_URL) {\n envOverrides.llm = { ...(envOverrides.llm as Record<string, unknown> || fileConfig.llm as Record<string, unknown> || {}), baseURL: process.env.LOBSTER_BASE_URL };\n }\n if (process.env.LOBSTER_CDP_ENDPOINT) {\n envOverrides.browser = { ...(fileConfig.browser as Record<string, unknown> || {}), cdpEndpoint: process.env.LOBSTER_CDP_ENDPOINT };\n }\n if (process.env.LOBSTER_BROWSER_PATH) {\n envOverrides.browser = { ...(envOverrides.browser as Record<string, unknown> || fileConfig.browser as Record<string, unknown> || {}), executablePath: process.env.LOBSTER_BROWSER_PATH };\n }\n\n const merged = { ...fileConfig, ...envOverrides };\n return configSchema.parse(merged);\n}\n\nexport function saveConfig(config: Partial<LobsterConfig>): void {\n ensureConfigDir();\n const existing = loadConfig();\n const merged = deepMerge(existing, config);\n writeFileSync(CONFIG_FILE, yaml.dump(merged, { indent: 2 }), 'utf-8');\n}\n\nexport function setConfigValue(keyPath: string, value: string): void {\n const parts = keyPath.split('.');\n const obj: Record<string, unknown> = {};\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n current[parts[i]] = {};\n current = current[parts[i]] as Record<string, unknown>;\n }\n // Try to parse as number or boolean\n let parsed: unknown = value;\n if (value === 'true') parsed = true;\n else if (value === 'false') parsed = false;\n else if (!isNaN(Number(value)) && value !== '') parsed = Number(value);\n\n current[parts[parts.length - 1]] = parsed;\n saveConfig(obj as Partial<LobsterConfig>);\n}\n\nexport function getConfigDir(): string {\n return CONFIG_DIR;\n}\n\nfunction deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {\n const result = { ...target };\n for (const key of Object.keys(source)) {\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) &&\n target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) {\n result[key] = deepMerge(target[key] as Record<string, unknown>, source[key] as Record<string, unknown>);\n } else {\n result[key] = source[key];\n }\n }\n return result;\n}\n","import { z } from 'zod';\n\nexport const LLM_PROVIDERS = {\n openai: {\n name: 'OpenAI',\n baseURL: 'https://api.openai.com/v1',\n defaultModel: 'gpt-4o',\n keyPrefix: 'sk-',\n keyEnvHint: 'https://platform.openai.com/api-keys',\n models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1', 'o1-mini', 'o3-mini'],\n },\n anthropic: {\n name: 'Anthropic',\n baseURL: 'https://api.anthropic.com/v1',\n defaultModel: 'claude-sonnet-4-20250514',\n keyPrefix: 'sk-ant-',\n keyEnvHint: 'https://console.anthropic.com/settings/keys',\n models: ['claude-opus-4-20250514', 'claude-sonnet-4-20250514', 'claude-haiku-4-5-20251001'],\n },\n gemini: {\n name: 'Google Gemini',\n baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',\n defaultModel: 'gemini-2.5-flash',\n keyPrefix: 'AI',\n keyEnvHint: 'https://aistudio.google.com/apikey',\n models: ['gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-2.5-pro', 'gemini-3-flash-preview'],\n },\n ollama: {\n name: 'Ollama (local, free)',\n baseURL: 'http://localhost:11434/v1',\n defaultModel: 'llama3.1',\n keyPrefix: '',\n keyEnvHint: 'No API key needed — install from https://ollama.ai',\n models: ['llama3.1', 'llama3.2', 'mistral', 'codestral', 'qwen2.5', 'deepseek-r1'],\n },\n} as const;\n\nexport type LLMProvider = keyof typeof LLM_PROVIDERS;\n\nexport const configSchema = z.object({\n llm: z.object({\n provider: z.enum(['openai', 'anthropic', 'gemini', 'ollama']).default('openai'),\n baseURL: z.string().default('https://api.openai.com/v1'),\n model: z.string().default('gpt-4o'),\n apiKey: z.string().default(''),\n temperature: z.number().min(0).max(2).default(0.1),\n maxRetries: z.number().int().min(0).default(3),\n }).default({}),\n browser: z.object({\n executablePath: z.string().default(''),\n headless: z.boolean().default(true),\n connectTimeout: z.number().default(30),\n commandTimeout: z.number().default(60),\n cdpEndpoint: z.string().default(''),\n profile: z.string().default(''),\n stealth: z.boolean().default(false),\n }).default({}),\n agent: z.object({\n maxSteps: z.number().int().default(40),\n stepDelay: z.number().default(0.4),\n }).default({}),\n domains: z.object({\n allow: z.array(z.string()).default([]),\n block: z.array(z.string()).default([]),\n blockMessage: z.string().default(''),\n }).default({}),\n output: z.object({\n defaultFormat: z.enum(['table', 'json', 'yaml', 'markdown', 'csv']).default('table'),\n color: z.boolean().default(true),\n }).default({}),\n});\n\nexport type LobsterConfig = z.infer<typeof configSchema>;\n","/**\n * Chrome Attach — discover and connect to a running Chrome instance.\n *\n * Probes common debug ports for Chrome's /json/version endpoint\n * and returns the WebSocket debugger URL for Puppeteer.connect().\n *\n * Inspired by PinchTab's attach mode, built from scratch.\n */\n\nimport http from 'node:http';\nimport { log } from '../utils/logger.js';\n\nexport interface ChromeDiscoveryResult {\n wsEndpoint: string;\n port: number;\n version: string;\n browser: string;\n}\n\nconst DEFAULT_PORTS = [9222, 9229, 9333, 9515];\nconst PROBE_TIMEOUT = 1500; // ms\n\n/**\n * Probe a single port for Chrome's DevTools endpoint.\n */\nfunction probePort(port: number): Promise<ChromeDiscoveryResult | null> {\n return new Promise((resolve) => {\n const req = http.get(`http://127.0.0.1:${port}/json/version`, {\n timeout: PROBE_TIMEOUT,\n }, (res) => {\n let data = '';\n res.on('data', (chunk: string) => { data += chunk; });\n res.on('end', () => {\n try {\n const info = JSON.parse(data);\n if (info.webSocketDebuggerUrl) {\n resolve({\n wsEndpoint: info.webSocketDebuggerUrl,\n port,\n version: info['Protocol-Version'] || '',\n browser: info.Browser || '',\n });\n } else {\n resolve(null);\n }\n } catch {\n resolve(null);\n }\n });\n });\n\n req.on('error', () => resolve(null));\n req.on('timeout', () => { req.destroy(); resolve(null); });\n });\n}\n\n/**\n * Discover a running Chrome instance by probing common debug ports.\n * Returns the first responding instance, or null if none found.\n */\nexport async function discoverChrome(ports?: number[]): Promise<ChromeDiscoveryResult | null> {\n const portsToCheck = ports || DEFAULT_PORTS;\n log.debug(`Scanning ports for Chrome: ${portsToCheck.join(', ')}`);\n\n // Probe all ports in parallel for speed\n const results = await Promise.all(portsToCheck.map(probePort));\n const found = results.find(Boolean) || null;\n\n if (found) {\n log.info(`Found Chrome on port ${found.port}: ${found.browser}`);\n } else {\n log.debug('No running Chrome instance found on debug ports.');\n }\n\n return found;\n}\n\n/**\n * Get WebSocket debugger URL from a specific port.\n */\nexport async function getWebSocketDebuggerUrl(port: number): Promise<string | null> {\n const result = await probePort(port);\n return result?.wsEndpoint || null;\n}\n\n/**\n * Parse an attach target — could be:\n * - \"true\" / true → auto-discover\n * - \"ws://...\" → explicit WebSocket URL\n * - \"9222\" → specific port number\n */\nexport async function resolveAttachTarget(target: boolean | string): Promise<string> {\n if (target === true || target === 'true') {\n const result = await discoverChrome();\n if (!result) {\n throw new Error(\n 'No running Chrome found. Start Chrome with:\\n' +\n ' google-chrome --remote-debugging-port=9222\\n' +\n ' # or on Mac:\\n' +\n ' /Applications/Google\\\\ Chrome.app/Contents/MacOS/Google\\\\ Chrome --remote-debugging-port=9222'\n );\n }\n return result.wsEndpoint;\n }\n\n if (typeof target === 'string') {\n // Explicit WebSocket URL\n if (target.startsWith('ws://') || target.startsWith('wss://')) {\n return target;\n }\n\n // Port number\n const port = parseInt(target, 10);\n if (!isNaN(port) && port > 0 && port < 65536) {\n const url = await getWebSocketDebuggerUrl(port);\n if (!url) {\n throw new Error(`No Chrome found on port ${port}. Make sure Chrome is running with --remote-debugging-port=${port}`);\n }\n return url;\n }\n\n throw new Error(`Invalid attach target: \"${target}\". Use \"true\" for auto-discover, a port number, or a ws:// URL.`);\n }\n\n throw new Error('Invalid attach target.');\n}\n","/**\n * Stealth Mode — anti-bot detection scripts.\n *\n * Injected via page.evaluateOnNewDocument() so it runs before\n * any page JavaScript, on every navigation.\n *\n * Inspired by PinchTab's 3-tier stealth system, built from scratch.\n * This is a comprehensive single-tier implementation covering the\n * most critical detection vectors.\n */\n\nimport type { Page } from 'puppeteer-core';\n\n/**\n * Comprehensive stealth script that evades common bot detection.\n */\nexport const STEALTH_SCRIPT = `\n(() => {\n // ── 1. navigator.webdriver removal ──\n // Most important: this is the #1 detection vector\n Object.defineProperty(navigator, 'webdriver', {\n get: () => undefined,\n configurable: true,\n });\n\n // Also delete from prototype\n delete Object.getPrototypeOf(navigator).webdriver;\n\n // ── 2. CDP marker removal ──\n // Chrome DevTools Protocol injects cdc_* properties on window\n for (const key of Object.keys(window)) {\n if (/^cdc_|^__webdriver|^__selenium|^__driver/.test(key)) {\n try { delete window[key]; } catch {}\n }\n }\n\n // ── 3. Chrome runtime spoofing ──\n // Real Chrome has window.chrome with runtime, loadTimes, csi\n if (!window.chrome) {\n window.chrome = {};\n }\n if (!window.chrome.runtime) {\n window.chrome.runtime = {\n connect: function() {},\n sendMessage: function() {},\n onMessage: { addListener: function() {} },\n id: undefined,\n };\n }\n if (!window.chrome.loadTimes) {\n window.chrome.loadTimes = function() {\n return {\n commitLoadTime: Date.now() / 1000 - 0.5,\n connectionInfo: 'h2',\n finishDocumentLoadTime: Date.now() / 1000 - 0.1,\n finishLoadTime: Date.now() / 1000 - 0.05,\n firstPaintAfterLoadTime: 0,\n firstPaintTime: Date.now() / 1000 - 0.3,\n navigationType: 'Other',\n npnNegotiatedProtocol: 'h2',\n requestTime: Date.now() / 1000 - 1,\n startLoadTime: Date.now() / 1000 - 0.8,\n wasAlternateProtocolAvailable: false,\n wasFetchedViaSpdy: true,\n wasNpnNegotiated: true,\n };\n };\n }\n if (!window.chrome.csi) {\n window.chrome.csi = function() {\n return {\n onloadT: Date.now(),\n startE: Date.now() - 500,\n pageT: 500,\n tran: 15,\n };\n };\n }\n\n // ── 4. Plugin array spoofing ──\n // Headless Chrome reports empty plugins; real Chrome has at least 2\n const fakePlugins = [\n { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 1 },\n { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '', length: 1 },\n { name: 'Native Client', filename: 'internal-nacl-plugin', description: '', length: 2 },\n ];\n\n Object.defineProperty(navigator, 'plugins', {\n get: () => {\n const arr = fakePlugins.map(p => {\n const plugin = { ...p, item: (i) => plugin, namedItem: (n) => plugin };\n return plugin;\n });\n arr.item = (i) => arr[i];\n arr.namedItem = (n) => arr.find(p => p.name === n);\n arr.refresh = () => {};\n return arr;\n },\n });\n\n // ── 5. Languages ──\n Object.defineProperty(navigator, 'languages', {\n get: () => ['en-US', 'en'],\n });\n Object.defineProperty(navigator, 'language', {\n get: () => 'en-US',\n });\n\n // ── 6. Platform consistency ──\n // Ensure platform matches user agent\n const platform = navigator.userAgent.includes('Mac') ? 'MacIntel' :\n navigator.userAgent.includes('Win') ? 'Win32' :\n navigator.userAgent.includes('Linux') ? 'Linux x86_64' : navigator.platform;\n Object.defineProperty(navigator, 'platform', { get: () => platform });\n\n // ── 7. Hardware concurrency & device memory ──\n // Headless often reports unusual values\n if (navigator.hardwareConcurrency < 2) {\n Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });\n }\n if (!navigator.deviceMemory || navigator.deviceMemory < 2) {\n Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });\n }\n\n // ── 8. WebGL vendor/renderer spoofing ──\n // Headless reports \"Google SwiftShader\" which is a dead giveaway\n const origGetParameter = WebGLRenderingContext.prototype.getParameter;\n WebGLRenderingContext.prototype.getParameter = function(param) {\n // UNMASKED_VENDOR_WEBGL\n if (param === 0x9245) return 'Intel Inc.';\n // UNMASKED_RENDERER_WEBGL\n if (param === 0x9246) return 'Intel Iris OpenGL Engine';\n return origGetParameter.call(this, param);\n };\n\n // Also for WebGL2\n if (typeof WebGL2RenderingContext !== 'undefined') {\n const origGetParameter2 = WebGL2RenderingContext.prototype.getParameter;\n WebGL2RenderingContext.prototype.getParameter = function(param) {\n if (param === 0x9245) return 'Intel Inc.';\n if (param === 0x9246) return 'Intel Iris OpenGL Engine';\n return origGetParameter2.call(this, param);\n };\n }\n\n // ── 9. Canvas fingerprint noise ──\n // Adds subtle deterministic noise to canvas output based on domain\n const seed = location.hostname.split('').reduce((a, c) => a + c.charCodeAt(0), 0);\n const origToDataURL = HTMLCanvasElement.prototype.toDataURL;\n HTMLCanvasElement.prototype.toDataURL = function(type) {\n const ctx = this.getContext('2d');\n if (ctx && this.width > 0 && this.height > 0) {\n try {\n const imageData = ctx.getImageData(0, 0, 1, 1);\n // Flip a single pixel with seeded noise\n imageData.data[0] = (imageData.data[0] + seed) % 256;\n ctx.putImageData(imageData, 0, 0);\n } catch {}\n }\n return origToDataURL.apply(this, arguments);\n };\n\n // ── 10. Permissions API ──\n // Headless returns 'denied' for notifications; real Chrome returns 'prompt'\n const origQuery = navigator.permissions?.query?.bind(navigator.permissions);\n if (origQuery) {\n navigator.permissions.query = function(descriptor) {\n if (descriptor.name === 'notifications') {\n return Promise.resolve({ state: Notification.permission || 'prompt', onchange: null });\n }\n return origQuery(descriptor);\n };\n }\n\n // ── 11. Notification constructor ──\n if (!window.Notification) {\n window.Notification = function() {};\n window.Notification.permission = 'default';\n window.Notification.requestPermission = () => Promise.resolve('default');\n }\n\n // ── 12. Connection type ──\n if (navigator.connection) {\n Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 });\n }\n})()\n`;\n\n/**\n * Inject stealth script into a Puppeteer page.\n * Must be called before first navigation for full effectiveness.\n */\nexport async function injectStealth(page: Page): Promise<void> {\n await page.evaluateOnNewDocument(STEALTH_SCRIPT);\n}\n\n/**\n * Chrome launch args for stealth mode.\n */\nexport const STEALTH_ARGS = [\n '--disable-blink-features=AutomationControlled',\n '--disable-features=IsolateOrigins,site-per-process',\n '--disable-infobars',\n '--window-size=1920,1080',\n];\n"],"mappings":"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","names":["existsSync","existsSync","mkdirSync","readFileSync","writeFileSync","join","join","existsSync","mkdirSync","join","readFileSync","writeFileSync","existsSync","mkdirSync","existsSync","existsSync"]}