omnikey-cli 1.0.23 → 1.0.25

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.
@@ -0,0 +1,613 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.isAnyBrowserRunning = isAnyBrowserRunning;
40
+ exports.isBrowserOpenWithUrl = isBrowserOpenWithUrl;
41
+ exports.fetchWithPlaywright = fetchWithPlaywright;
42
+ const axios_1 = __importDefault(require("axios"));
43
+ // Utility: Promise with timeout
44
+ async function withTimeout(promise, ms, label, log) {
45
+ let timeoutId;
46
+ return Promise.race([
47
+ promise,
48
+ new Promise((resolve) => {
49
+ timeoutId = setTimeout(() => {
50
+ log.warn('browser-playwright: fetch timed out', { label, ms });
51
+ resolve(null);
52
+ }, ms);
53
+ }),
54
+ ]).then((result) => {
55
+ clearTimeout(timeoutId);
56
+ return result;
57
+ });
58
+ }
59
+ /**
60
+ * Playwright-based web fetching using the user's installed browser profile.
61
+ *
62
+ * Key design decisions:
63
+ * 1. Detects which Chromium browsers are currently RUNNING and tries those
64
+ * first — the active browser is where the authenticated session lives.
65
+ * 2. Discovers the actual profile directory dynamically (Default, Profile 1,
66
+ * Profile 2 …) rather than hardcoding "Default".
67
+ * 3. Checks multiple executable locations (system /Applications and
68
+ * user ~/Applications).
69
+ * 4. Firefox is intentionally excluded from Playwright — headless Firefox
70
+ * on macOS has a known RenderCompositorSWGL rendering bug that causes
71
+ * 30-second timeouts. Cookies from Firefox are still extracted separately
72
+ * by browser-cookies.ts for the plain-HTTP fallback.
73
+ *
74
+ * macOS only. Returns null on other platforms.
75
+ */
76
+ const child_process_1 = require("child_process");
77
+ const fs = __importStar(require("fs"));
78
+ const os = __importStar(require("os"));
79
+ const path = __importStar(require("path"));
80
+ const playwright_core_1 = __importDefault(require("playwright-core"));
81
+ const home = os.homedir();
82
+ const BROWSER_CATALOGUE = [
83
+ {
84
+ name: 'Chrome',
85
+ executablePaths: [
86
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
87
+ `${home}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,
88
+ ],
89
+ userDataDir: path.join(home, 'Library/Application Support/Google/Chrome'),
90
+ },
91
+ {
92
+ name: 'Brave',
93
+ executablePaths: [
94
+ '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
95
+ `${home}/Applications/Brave Browser.app/Contents/MacOS/Brave Browser`,
96
+ ],
97
+ userDataDir: path.join(home, 'Library/Application Support/BraveSoftware/Brave-Browser'),
98
+ },
99
+ {
100
+ name: 'Edge',
101
+ executablePaths: [
102
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
103
+ `${home}/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge`,
104
+ ],
105
+ userDataDir: path.join(home, 'Library/Application Support/Microsoft Edge'),
106
+ },
107
+ {
108
+ name: 'Arc',
109
+ executablePaths: [
110
+ '/Applications/Arc.app/Contents/MacOS/Arc',
111
+ `${home}/Applications/Arc.app/Contents/MacOS/Arc`,
112
+ ],
113
+ userDataDir: path.join(home, 'Library/Application Support/Arc/User Data'),
114
+ },
115
+ {
116
+ name: 'Vivaldi',
117
+ executablePaths: [
118
+ '/Applications/Vivaldi.app/Contents/MacOS/Vivaldi',
119
+ `${home}/Applications/Vivaldi.app/Contents/MacOS/Vivaldi`,
120
+ ],
121
+ userDataDir: path.join(home, 'Library/Application Support/Vivaldi'),
122
+ },
123
+ {
124
+ name: 'Opera',
125
+ executablePaths: [
126
+ '/Applications/Opera.app/Contents/MacOS/Opera',
127
+ `${home}/Applications/Opera.app/Contents/MacOS/Opera`,
128
+ ],
129
+ userDataDir: path.join(home, 'Library/Application Support/com.operasoftware.Opera'),
130
+ },
131
+ {
132
+ name: 'Chromium',
133
+ executablePaths: [
134
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
135
+ `${home}/Applications/Chromium.app/Contents/MacOS/Chromium`,
136
+ ],
137
+ userDataDir: path.join(home, 'Library/Application Support/Chromium'),
138
+ },
139
+ ];
140
+ // ─── Running browser detection ────────────────────────────────────────────────
141
+ /**
142
+ * Returns the names of browsers that are currently running.
143
+ * Used to sort the browser list so the active browser (with a live session)
144
+ * is tried first.
145
+ */
146
+ function getRunningBrowserNames() {
147
+ const running = new Set();
148
+ try {
149
+ // ps -axco command lists only the process name (no path, no args)
150
+ const output = (0, child_process_1.execSync)('ps -axco command', {
151
+ encoding: 'utf8',
152
+ stdio: ['pipe', 'pipe', 'pipe'],
153
+ });
154
+ const lines = output.toLowerCase().split('\n');
155
+ const processMap = {
156
+ 'google chrome': 'Chrome',
157
+ 'brave browser': 'Brave',
158
+ 'microsoft edge': 'Edge',
159
+ arc: 'Arc',
160
+ vivaldi: 'Vivaldi',
161
+ opera: 'Opera',
162
+ chromium: 'Chromium',
163
+ safari: 'Safari',
164
+ };
165
+ for (const [processName, browserName] of Object.entries(processMap)) {
166
+ if (processName === 'safari') {
167
+ // Only match the main Safari process exactly (case-insensitive, trimmed)
168
+ if (lines.some((l) => l.trim() === 'safari')) {
169
+ running.add(browserName);
170
+ }
171
+ }
172
+ else {
173
+ // For other browsers, allow exact match or substring match
174
+ if (lines.some((l) => l.trim() === processName || l.includes(processName))) {
175
+ running.add(browserName);
176
+ }
177
+ }
178
+ }
179
+ }
180
+ catch {
181
+ // ps failed — proceed without running-browser info
182
+ }
183
+ return running;
184
+ }
185
+ // ─── Strategy -1: CDP via DevToolsActivePort ─────────────────────────────────
186
+ //
187
+ // When Chrome is launched with --remote-debugging-port (or --remote-debugging-port=0
188
+ // to let it pick a free port), it writes a DevToolsActivePort file to the user data
189
+ // directory containing the actual port. Connecting via CDP gives us direct access
190
+ // to the live, JS-rendered tab content without AppleScript permissions or cookie
191
+ // decryption. This is the fastest and most reliable path when available.
192
+ async function fetchWithCDP(url, browsersWithUrl, log) {
193
+ const targetBase = url.split('?')[0]; // strip query for prefix match
194
+ // Collect candidate ports:
195
+ // 1. DevToolsActivePort file (written when Chrome was started with --remote-debugging-port)
196
+ // 2. Well-known default ports developers commonly use
197
+ const candidatePorts = [];
198
+ for (const candidate of BROWSER_CATALOGUE) {
199
+ if (!browsersWithUrl.has(candidate.name))
200
+ continue;
201
+ if (candidate.name === 'Safari')
202
+ continue; // CDP is Chromium-only
203
+ const portFile = path.join(candidate.userDataDir, 'DevToolsActivePort');
204
+ if (fs.existsSync(portFile)) {
205
+ try {
206
+ const raw = fs.readFileSync(portFile, 'utf8');
207
+ const port = parseInt(raw.split('\n')[0].trim(), 10);
208
+ if (!isNaN(port) && port > 0 && !candidatePorts.includes(port)) {
209
+ candidatePorts.push(port);
210
+ }
211
+ }
212
+ catch { }
213
+ }
214
+ }
215
+ // Always probe the most common debug ports — many developers run Chrome with
216
+ // --remote-debugging-port=9222 and these checks are cheap (instant refusal if closed).
217
+ for (const p of [9222, 9229, 9333]) {
218
+ if (!candidatePorts.includes(p))
219
+ candidatePorts.push(p);
220
+ }
221
+ for (const port of candidatePorts) {
222
+ // Quick HTTP probe: /json/version returns immediately if the debug endpoint is up.
223
+ let endpointUp = false;
224
+ try {
225
+ const probe = await axios_1.default.get(`http://localhost:${port}/json/version`, { timeout: 800 });
226
+ endpointUp = probe.status === 200;
227
+ }
228
+ catch {
229
+ // Port not listening — skip without logging noise
230
+ continue;
231
+ }
232
+ if (!endpointUp)
233
+ continue;
234
+ log.info('browser-playwright: CDP — debug endpoint found, connecting', { port });
235
+ let cdpBrowser = null;
236
+ try {
237
+ cdpBrowser = await playwright_core_1.default.chromium.connectOverCDP(`http://localhost:${port}`, {
238
+ timeout: 5000,
239
+ });
240
+ let matchedPage = null;
241
+ for (const context of cdpBrowser.contexts()) {
242
+ for (const page of context.pages()) {
243
+ if (page.url().startsWith(targetBase)) {
244
+ matchedPage = page;
245
+ break;
246
+ }
247
+ }
248
+ if (matchedPage)
249
+ break;
250
+ }
251
+ if (!matchedPage) {
252
+ log.debug('browser-playwright: CDP — no tab found matching URL', { port, url });
253
+ continue;
254
+ }
255
+ log.info('browser-playwright: CDP — tab found, extracting content', {
256
+ port,
257
+ tabUrl: matchedPage.url(),
258
+ });
259
+ try {
260
+ await matchedPage.waitForFunction(() => (document.body?.innerText ?? '').trim().length > 200, { timeout: 5000 });
261
+ }
262
+ catch {
263
+ // Best-effort — extract whatever is rendered so far
264
+ }
265
+ const content = await matchedPage.evaluate(() => document.body.innerText ?? document.body.textContent ?? '');
266
+ log.info('browser-playwright: CDP — content extracted', {
267
+ port,
268
+ contentLength: content.trim().length,
269
+ });
270
+ const trimmed = content.trim();
271
+ return trimmed ? { content: trimmed, finalUrl: matchedPage.url() } : null;
272
+ }
273
+ catch (err) {
274
+ log.warn('browser-playwright: CDP — connection failed', {
275
+ port,
276
+ error: err instanceof Error ? err.message.split('\n')[0] : String(err),
277
+ });
278
+ }
279
+ finally {
280
+ if (cdpBrowser) {
281
+ try {
282
+ await cdpBrowser.close();
283
+ }
284
+ catch { }
285
+ }
286
+ }
287
+ }
288
+ return null;
289
+ }
290
+ // ─── Public API ───────────────────────────────────────────────────────────────
291
+ /**
292
+ * Returns true if any supported Chromium browser is currently running.
293
+ */
294
+ function isAnyBrowserRunning() {
295
+ return getRunningBrowserNames().size > 0;
296
+ }
297
+ const BROWSER_APPLESCRIPT = {
298
+ Chrome: { appName: 'Google Chrome', jsVerb: 'execute javascript' },
299
+ Brave: { appName: 'Brave Browser', jsVerb: 'execute javascript' },
300
+ Edge: { appName: 'Microsoft Edge', jsVerb: 'execute javascript' },
301
+ Arc: { appName: 'Arc', jsVerb: 'execute javascript' },
302
+ Vivaldi: { appName: 'Vivaldi', jsVerb: 'execute javascript' },
303
+ Opera: { appName: 'Opera', jsVerb: 'execute javascript' },
304
+ Chromium: { appName: 'Chromium', jsVerb: 'execute javascript' },
305
+ Safari: { appName: 'Safari', jsVerb: 'do JavaScript' },
306
+ };
307
+ // ─── Tab detection ────────────────────────────────────────────────────────────
308
+ /**
309
+ * Returns the names of running browsers that are confirmed to have the given
310
+ * URL's hostname open in a tab, via AppleScript.
311
+ * Only browsers where AppleScript succeeds AND the hostname is found are included.
312
+ * Browsers where AppleScript fails are silently skipped (not assumed to have it open).
313
+ */
314
+ function getBrowsersWithUrlOpen(url, log) {
315
+ const confirmed = new Set();
316
+ let targetHostname;
317
+ try {
318
+ targetHostname = new URL(url).hostname;
319
+ }
320
+ catch {
321
+ return confirmed;
322
+ }
323
+ const runningBrowsers = getRunningBrowserNames();
324
+ if (runningBrowsers.size === 0)
325
+ return confirmed;
326
+ for (const browserName of runningBrowsers) {
327
+ const info = BROWSER_APPLESCRIPT[browserName];
328
+ if (!info)
329
+ continue;
330
+ try {
331
+ const script = `tell application "${info.appName}" to get URL of every tab of every window`;
332
+ const output = (0, child_process_1.execSync)(`osascript -e '${script}'`, {
333
+ encoding: 'utf8',
334
+ timeout: 5000,
335
+ stdio: ['pipe', 'pipe', 'pipe'],
336
+ });
337
+ const found = output
338
+ .split(/[,\n]/)
339
+ .map((u) => u.trim())
340
+ .some((u) => {
341
+ try {
342
+ return new URL(u).hostname === targetHostname;
343
+ }
344
+ catch {
345
+ return false;
346
+ }
347
+ });
348
+ log.debug('browser-playwright: tab check', { browser: browserName, targetHostname, found });
349
+ if (found)
350
+ confirmed.add(browserName);
351
+ }
352
+ catch {
353
+ log.debug('browser-playwright: AppleScript tab check failed — skipping browser', {
354
+ browser: browserName,
355
+ });
356
+ }
357
+ }
358
+ return confirmed;
359
+ }
360
+ /**
361
+ * Returns true if the given URL's hostname is confirmed open in any running
362
+ * browser tab via AppleScript. Returns false if the check cannot be performed
363
+ * or if no browser has the URL open.
364
+ */
365
+ function isBrowserOpenWithUrl(url, log) {
366
+ return getBrowsersWithUrlOpen(url, log).size > 0;
367
+ }
368
+ // ─── Strategy 0: Live-tab AppleScript extraction ──────────────────────────────
369
+ //
370
+ // When the user already has the URL open in a browser we can pull the rendered
371
+ // page text directly via AppleScript — no cookie decryption, no profile copying,
372
+ // no headless browser launch needed. This is the most reliable strategy for
373
+ // authenticated pages because the live tab already holds the valid session.
374
+ /**
375
+ * Writes an AppleScript to a temp file, executes it with `osascript`, then
376
+ * deletes the file. Using a temp file avoids heredoc parsing issues that arise
377
+ * when multi-line scripts are passed inline to execSync.
378
+ *
379
+ * On failure, the thrown Error includes the osascript stderr so callers can
380
+ * log the actual reason (e.g. "Allow JavaScript from Apple Events is not enabled").
381
+ */
382
+ function runAppleScript(script, timeoutMs) {
383
+ const tmpPath = path.join(os.tmpdir(), `omnikey-as-${Date.now()}-${Math.random().toString(36).slice(2)}.applescript`);
384
+ fs.writeFileSync(tmpPath, script, 'utf8');
385
+ try {
386
+ return (0, child_process_1.execSync)(`osascript "${tmpPath}"`, {
387
+ encoding: 'utf8',
388
+ timeout: timeoutMs,
389
+ stdio: ['pipe', 'pipe', 'pipe'],
390
+ });
391
+ }
392
+ catch (err) {
393
+ // Enrich the error with osascript's stderr so callers get the real reason.
394
+ const stderr = err?.stderr?.toString?.().trim() ?? '';
395
+ const base = err instanceof Error ? err.message : String(err);
396
+ const enriched = new Error(stderr ? `${base}\n${stderr}` : base);
397
+ throw enriched;
398
+ }
399
+ finally {
400
+ try {
401
+ fs.unlinkSync(tmpPath);
402
+ }
403
+ catch { }
404
+ }
405
+ }
406
+ /**
407
+ * Finds the window/tab index of `url` inside `appName` via AppleScript.
408
+ * Returns { winIdx, tabIdx } (1-based) or null if not found.
409
+ */
410
+ function findTabLocation(appName, url) {
411
+ // Strip query-string for the prefix match so deep links still resolve.
412
+ const urlBase = url.split('?')[0].replace(/"/g, ''); // remove double-quotes to avoid breaking AppleScript string
413
+ const script = [
414
+ `tell application "${appName}"`,
415
+ ` repeat with wIdx from 1 to count of windows`,
416
+ ` repeat with tIdx from 1 to count of tabs of window wIdx`,
417
+ ` if URL of tab tIdx of window wIdx starts with "${urlBase}" then`,
418
+ ` return (wIdx as string) & ":" & (tIdx as string)`,
419
+ ` end if`,
420
+ ` end repeat`,
421
+ ` end repeat`,
422
+ ` return ""`,
423
+ `end tell`,
424
+ ].join('\n');
425
+ try {
426
+ const result = runAppleScript(script, 5000).trim();
427
+ if (!result)
428
+ return null;
429
+ const [w, t] = result.split(':').map(Number);
430
+ if (!w || !t || isNaN(w) || isNaN(t))
431
+ return null;
432
+ return { winIdx: w, tabIdx: t };
433
+ }
434
+ catch {
435
+ return null;
436
+ }
437
+ }
438
+ /**
439
+ * Attempts to extract the rendered text of `url` directly from an open browser
440
+ * tab using AppleScript JS execution. Only tries browsers confirmed to have the
441
+ * URL open. Returns null if the URL is not open or extraction fails.
442
+ */
443
+ async function fetchFromRunningBrowserTab(url, browsersWithUrl, log) {
444
+ if (process.platform !== 'darwin' || browsersWithUrl.size === 0)
445
+ return null;
446
+ for (const browserName of browsersWithUrl) {
447
+ const info = BROWSER_APPLESCRIPT[browserName];
448
+ if (!info)
449
+ continue;
450
+ const location = findTabLocation(info.appName, url);
451
+ if (!location) {
452
+ log.debug('browser-playwright: tab location not found', { browser: browserName, url });
453
+ continue;
454
+ }
455
+ const { winIdx, tabIdx } = location;
456
+ log.info('browser-playwright: extracting content from live tab', {
457
+ browser: browserName,
458
+ winIdx,
459
+ tabIdx,
460
+ url,
461
+ });
462
+ // ── Attempt A: execute JavaScript to get the JS-rendered innerText ────────
463
+ // Requires "Allow JavaScript from Apple Events" in Chrome (View → Developer)
464
+ // or Safari (Develop → Allow JavaScript from Apple Events).
465
+ //
466
+ // Chrome ONLY allows execute javascript on the ACTIVE tab of a window, even
467
+ // with "Allow JavaScript from Apple Events" enabled. We must set the active
468
+ // tab index first, then use the `tell tab` block form (not the `in tab` form)
469
+ // which is more reliably dispatched by Chrome's Apple Event handler.
470
+ const extractJsScript = browserName === 'Safari'
471
+ ? [
472
+ `tell application "${info.appName}"`,
473
+ ` ${info.jsVerb} "document.body.innerText || document.body.textContent || ''" in tab ${tabIdx} of window ${winIdx}`,
474
+ `end tell`,
475
+ ].join('\n')
476
+ : [
477
+ `tell application "${info.appName}"`,
478
+ ` set active tab index of window ${winIdx} to ${tabIdx}`,
479
+ ` tell tab ${tabIdx} of window ${winIdx}`,
480
+ ` execute javascript "document.body.innerText || document.body.textContent || ''"`,
481
+ ` end tell`,
482
+ `end tell`,
483
+ ].join('\n');
484
+ try {
485
+ const content = runAppleScript(extractJsScript, 10000).trim();
486
+ if (content && content.length > 100) {
487
+ log.info('browser-playwright: live tab JS content extracted', {
488
+ browser: browserName,
489
+ url,
490
+ contentLength: content.length,
491
+ });
492
+ return content;
493
+ }
494
+ log.debug('browser-playwright: live tab JS content too short or empty', {
495
+ browser: browserName,
496
+ url,
497
+ contentLength: content.length,
498
+ });
499
+ }
500
+ catch (err) {
501
+ // The first line of err.message is "Command failed: osascript ...".
502
+ // The second line (from stderr) is the real reason, e.g.:
503
+ // "Google Chrome got an error: Allow JavaScript from Apple Events is not enabled"
504
+ const lines = (err instanceof Error ? err.message : String(err)).split('\n');
505
+ const detail = lines.find((l) => l.trim() && !l.startsWith('Command failed')) ?? lines[0];
506
+ log.warn('browser-playwright: live tab JS extraction failed — falling back to page source', {
507
+ browser: browserName,
508
+ url,
509
+ reason: detail.trim(),
510
+ });
511
+ }
512
+ // ── Attempt B: get source of tab (Safari only) ───────────────────────────
513
+ // Chrome-family does NOT expose a `source` property on tab objects via
514
+ // AppleScript — the only content-extraction path is `execute javascript`
515
+ // (Attempt A), which requires "Allow JavaScript from Apple Events".
516
+ // Safari exposes `source` on `document` objects (not `tab`), so we compute
517
+ // the global document index by counting tabs across all windows in order.
518
+ if (browserName !== 'Safari') {
519
+ log.info('browser-playwright: live tab JS execution failed — ensure "Allow JavaScript from Apple Events" is enabled (Chrome: View → Developer → Allow JavaScript from Apple Events) and restart Chrome after enabling it', {
520
+ browser: browserName,
521
+ url,
522
+ });
523
+ continue;
524
+ }
525
+ const getSourceScript = [
526
+ `tell application "${info.appName}"`,
527
+ ` set docIdx to 0`,
528
+ ` repeat with w from 1 to count of windows`,
529
+ ` repeat with t from 1 to count of tabs of window w`,
530
+ ` set docIdx to docIdx + 1`,
531
+ ` if w = ${winIdx} and t = ${tabIdx} then`,
532
+ ` return source of document docIdx`,
533
+ ` end if`,
534
+ ` end repeat`,
535
+ ` end repeat`,
536
+ ` return ""`,
537
+ `end tell`,
538
+ ].join('\n');
539
+ try {
540
+ const html = runAppleScript(getSourceScript, 10000).trim();
541
+ if (html && html.length > 200) {
542
+ const text = html
543
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
544
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
545
+ .replace(/<[^>]+>/g, ' ')
546
+ .replace(/\s+/g, ' ')
547
+ .trim();
548
+ if (text.length > 100) {
549
+ log.info('browser-playwright: live tab page source extracted', {
550
+ browser: browserName,
551
+ url,
552
+ contentLength: text.length,
553
+ });
554
+ return text;
555
+ }
556
+ }
557
+ log.debug('browser-playwright: live tab page source too short or empty', {
558
+ browser: browserName,
559
+ url,
560
+ });
561
+ }
562
+ catch (err) {
563
+ const lines = (err instanceof Error ? err.message : String(err)).split('\n');
564
+ const detail = lines.find((l) => l.trim() && !l.startsWith('Command failed')) ?? lines[0];
565
+ log.warn('browser-playwright: live tab page source extraction failed', {
566
+ browser: browserName,
567
+ url,
568
+ reason: detail.trim(),
569
+ });
570
+ }
571
+ }
572
+ return null;
573
+ }
574
+ /**
575
+ * Fetches a URL using the user's browser session.
576
+ *
577
+ * Only browsers that are confirmed (via AppleScript) to have the URL open are
578
+ * tried — this avoids wasting time on browsers or profiles that don't hold the
579
+ * active session.
580
+ *
581
+ * Strategies in order:
582
+ * 0. Live-tab extraction — reads content directly from the open tab via
583
+ * AppleScript JS execution. No cookie decryption required.
584
+ * 1. Cookie injection — decrypts cookies and injects into a fresh headless
585
+ * Chromium context (handles cookie-based auth when live tab unavailable).
586
+ * 2. Profile copy — copies Local Storage + IndexedDB to a temp dir (handles
587
+ * localStorage/sessionStorage token auth flows).
588
+ * 3. Safari Playwright — WebKit with injected Safari cookies (Safari only).
589
+ */
590
+ async function fetchWithPlaywright(url, log) {
591
+ // Determine which browsers have the URL open right now.
592
+ const browsersWithUrl = getBrowsersWithUrlOpen(url, log);
593
+ log.info('browser-playwright: browsers with URL open', {
594
+ url,
595
+ browsers: [...browsersWithUrl],
596
+ });
597
+ // ── Strategy -1: CDP via DevToolsActivePort ──────────────────────────────
598
+ // Fastest path — connects directly to the live browser's JS-rendered tab.
599
+ // Only works when Chrome was launched with --remote-debugging-port.
600
+ if (browsersWithUrl.size > 0) {
601
+ const cdpResult = await fetchWithCDP(url, browsersWithUrl, log);
602
+ if (cdpResult) {
603
+ return cdpResult.content;
604
+ }
605
+ }
606
+ // ── Strategy 0: extract from the live tab directly ────────────────────────
607
+ const liveContent = await fetchFromRunningBrowserTab(url, browsersWithUrl, log);
608
+ if (liveContent) {
609
+ return liveContent;
610
+ }
611
+ log.warn('browser-playwright: all strategies exhausted', { url });
612
+ return null;
613
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./web-search-provider"), exports);