@duckmind/dm-darwin-x64 0.52.6 → 0.53.4

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 (58) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +14 -10
  3. package/extensions/dm-cua/bin/browser-cua.mjs +6 -6
  4. package/extensions/dm-cua/index.js +5 -5
  5. package/extensions/dm-cua/src/browser-cua-lib.mjs +2 -2
  6. package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +8 -1
  7. package/extensions/greedysearch-dm/bin/cdp-headless.mjs +1 -1
  8. package/extensions/greedysearch-dm/bin/cdp-visible.mjs +1 -1
  9. package/extensions/greedysearch-dm/bin/cdp.mjs +29 -20
  10. package/extensions/greedysearch-dm/bin/gschrome.mjs +1 -8
  11. package/extensions/greedysearch-dm/bin/kill-visible.mjs +1 -1
  12. package/extensions/greedysearch-dm/bin/launch-visible.mjs +1 -2
  13. package/extensions/greedysearch-dm/bin/launch.mjs +9 -11
  14. package/extensions/greedysearch-dm/bin/mcp.mjs +375 -0
  15. package/extensions/greedysearch-dm/bin/search.mjs +309 -157
  16. package/extensions/greedysearch-dm/bin/visible.mjs +1 -1
  17. package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +205 -68
  18. package/extensions/greedysearch-dm/extractors/chatgpt.mjs +229 -140
  19. package/extensions/greedysearch-dm/extractors/common.mjs +160 -50
  20. package/extensions/greedysearch-dm/extractors/consensus.mjs +226 -93
  21. package/extensions/greedysearch-dm/extractors/consent.mjs +44 -17
  22. package/extensions/greedysearch-dm/extractors/gemini.mjs +277 -138
  23. package/extensions/greedysearch-dm/extractors/google-ai.mjs +172 -64
  24. package/extensions/greedysearch-dm/extractors/logically.mjs +183 -69
  25. package/extensions/greedysearch-dm/extractors/perplexity.mjs +331 -65
  26. package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +118 -41
  27. package/extensions/greedysearch-dm/index.js +23 -20
  28. package/extensions/greedysearch-dm/package.json +8 -3
  29. package/extensions/greedysearch-dm/skills/greedy-search/skill.md +8 -8
  30. package/extensions/greedysearch-dm/src/fetcher.mjs +2 -2
  31. package/extensions/greedysearch-dm/src/formatters/results.js +8 -6
  32. package/extensions/greedysearch-dm/src/github.mjs +7 -7
  33. package/extensions/greedysearch-dm/src/reddit.mjs +1 -1
  34. package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +10 -8
  35. package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +48 -0
  36. package/extensions/greedysearch-dm/src/search/chrome.mjs +130 -54
  37. package/extensions/greedysearch-dm/src/search/constants.mjs +6 -6
  38. package/extensions/greedysearch-dm/src/search/engines.mjs +9 -9
  39. package/extensions/greedysearch-dm/src/search/fetch-source.mjs +160 -83
  40. package/extensions/greedysearch-dm/src/search/minimize.mjs +1 -0
  41. package/extensions/greedysearch-dm/src/search/paths.mjs +1 -1
  42. package/extensions/greedysearch-dm/src/search/pdf.mjs +1 -1
  43. package/extensions/greedysearch-dm/src/search/port-pid.mjs +1 -1
  44. package/extensions/greedysearch-dm/src/search/progress.mjs +2 -0
  45. package/extensions/greedysearch-dm/src/search/recovery.mjs +1 -1
  46. package/extensions/greedysearch-dm/src/search/research.mjs +262 -167
  47. package/extensions/greedysearch-dm/src/search/scale-aware.mjs +11 -0
  48. package/extensions/greedysearch-dm/src/search/simple-research.mjs +827 -0
  49. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +9 -9
  50. package/extensions/greedysearch-dm/src/search/synthesis.mjs +10 -10
  51. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +23 -20
  52. package/extensions/greedysearch-dm/src/tools/shared.js +10 -9
  53. package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +1 -1
  54. package/package.json +1 -1
  55. package/extensions/greedysearch-dm/src/search/cdp-endpoint.mjs +0 -3
  56. package/extensions/greedysearch-dm/src/search/launcher-paths.mjs +0 -3
  57. package/extensions/greedysearch-dm/src/search/partial-output.mjs +0 -1
  58. package/extensions/greedysearch-dm/src/utils/browser-executable.mjs +0 -1
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_process";import{dirname as n,join as s}from"node:path";import{fileURLToPath as a}from"node:url";import{basename as l}from"node:path";function M(z=process.env,Z=process.execPath){let K=z.GREEDY_SEARCH_NODE||z.NODE_BINARY||z.NODE;if(K?.trim())return K.trim();let Q=l(Z||"").toLowerCase();if(Q==="node"||Q==="node.exe")return Z;return"node"}var r=n(a(import.meta.url)),t=s(r,"..","bin","cdp.mjs");function Y(z,Z=30000){return I(z,null,Z)}function I(z,Z=null,K=30000){return new Promise((Q,W)=>{let H=o(M(),[t,...z],{stdio:[Z==null?"ignore":"pipe","pipe","pipe"]});if(Z!=null)H.stdin.write(Z),H.stdin.end();let $="",X="";H.stdout.on("data",(U)=>$+=U),H.stderr.on("data",(U)=>X+=U);let J=setTimeout(()=>{H.kill(),W(Error(`cdp timeout: ${z[0]}`))},K);H.on("close",(U)=>{if(clearTimeout(J),U===0)Q($.trim());else W(Error(X.trim()||`cdp exit ${U}`))})})}async function L(z){if(z)return z;let K=(await Y(["list"])).split(`
3
- `)[0]?.slice(0,8);if(!K)throw Error("No Chrome tabs found. Is Chrome running with --remote-debugging-port=9222?");let Q=await Y(["evalraw",K,"Target.createTarget",'{"url":"about:blank"}']),{targetId:W}=JSON.parse(Q);await Y(["list"]);let H=W.slice(0,8);try{await e(H)}catch($){process.stderr.write(`[getOrOpenTab] stealth injection failed: ${$.message}
4
- `)}return H}async function P(z,Z){let K=`
2
+ import{randomInt as r}from"node:crypto";import{spawn as t}from"node:child_process";import{basename as a}from"node:path";function k(Q=process.env,$=process.execPath){let Z=Q.GREEDY_SEARCH_NODE||Q.NODE_BINARY||Q.NODE;if(Z?.trim())return Z.trim();let K=a($||"").toLowerCase();if(K==="node"||K==="node.exe")return $;return"node"}import{dirname as e,join as QQ}from"node:path";import{fileURLToPath as ZQ}from"node:url";var $Q=e(ZQ(import.meta.url)),KQ=QQ($Q,"..","bin","cdp.mjs"),WQ=new Set(["list","snap","eval","shot","html","nav","net","click","clickxy","type","loadall","evalraw","browse","stop","--tab"]);function JQ(Q){if(!Array.isArray(Q)||Q.length===0)throw Error("cdp: args must be a non-empty array");if(Q[0]==="test")return Q.map(($,Z)=>y($,Z));if(!WQ.has(Q[0]))throw Error(`cdp: unknown subcommand '${Q[0]}'`);return Q.map(($,Z)=>y($,Z))}function y(Q,$){if(typeof Q!=="string")throw Error(`cdp: argv[${$}] must be a string (got ${typeof Q})`);if(Q.includes("\x00"))throw Error(`cdp: argv[${$}] contains a null byte`);return Q}function B(Q,$=30000){return UQ(Q,null,$)}function UQ(Q,$=null,Z=30000){let K=JQ(Q);return new Promise((J,X)=>{let W=t(k(),[KQ,...K],{stdio:[$==null?"ignore":"pipe","pipe","pipe"]});if($!=null)W.stdin.write($),W.stdin.end();let U="",Y="";W.stdout.on("data",(z)=>U+=z),W.stderr.on("data",(z)=>Y+=z);let q=setTimeout(()=>{W.kill(),X(Error(`cdp timeout: ${Q[0]}`))},Z);W.on("close",(z)=>{if(clearTimeout(q),z===0)J(U.trim());else X(Error(Y.trim()||`cdp exit ${z}`))})})}async function E(Q){if(Q)return Q;let Z=(await B(["list"])).split(`
3
+ `)[0]?.slice(0,8);if(!Z)throw Error("No Chrome tabs found. Is Chrome running with --remote-debugging-port=9222?");let K=await B(["evalraw",Z,"Target.createTarget",'{"url":"about:blank"}']),J;try{J=JSON.parse(K)}catch(U){throw Error(`Target.createTarget returned invalid JSON: ${U.message}`)}let{targetId:X}=J;if(!X)throw Error("Target.createTarget did not return a targetId");await B(["list"]);let W=X.slice(0,8);try{await XQ(W)}catch(U){process.stderr.write(`[getOrOpenTab] stealth injection failed: ${U.message}
4
+ `)}return W}async function T(Q,$){let Z=`
5
5
  (() => {
6
- window.${Z} = null;
6
+ window.${$} = null;
7
7
  const _clipboard = navigator.clipboard;
8
8
  if (!_clipboard) return;
9
9
  const _origWriteText = typeof _clipboard.writeText === 'function'
@@ -14,7 +14,7 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
14
14
  : null;
15
15
 
16
16
  _clipboard.writeText = function(text) {
17
- window.${Z} = String(text ?? '');
17
+ window.${$} = String(text ?? '');
18
18
  if (!_origWriteText) return Promise.resolve();
19
19
  // The OS/browser clipboard write may be denied in automated Chrome or
20
20
  // when the tab is not focused. We only need the captured text; returning
@@ -28,7 +28,7 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
28
28
  for (const item of items || []) {
29
29
  if (item.types && item.types.includes('text/plain')) {
30
30
  const blob = await item.getType('text/plain');
31
- window.${Z} = await blob.text();
31
+ window.${$} = await blob.text();
32
32
  break;
33
33
  }
34
34
  }
@@ -38,7 +38,26 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
38
38
  catch (_) { return undefined; }
39
39
  };
40
40
  })();
41
- `;await Y(["eval",z,K])}async function e(z){await Y(["evalraw",z,"Page.addScriptToEvaluateOnNewDocument",JSON.stringify({source:`
41
+ `;await B(["eval",Q,Z])}async function S(Q,$,Z,K={}){let{timeoutMs:J=2600}=K,X=K.retryClick!=null?K.retryClick:Math.floor(J*0.4),W=String.raw`
42
+ new Promise((resolve) => {
43
+ const _deadline = Date.now() + ${J};
44
+ const _retryAt = Date.now() + ${X};
45
+ let _retried = false;
46
+ function _click() { try { ${$}; } catch(_) {} }
47
+ function _poll() {
48
+ const val = window.${Z};
49
+ if (val) { resolve(val); return; }
50
+ if (!_retried && Date.now() >= _retryAt) {
51
+ _retried = true;
52
+ _click();
53
+ }
54
+ if (Date.now() < _deadline) { setTimeout(_poll, 100); }
55
+ else { resolve(window.${Z} || ''); }
56
+ }
57
+ _click();
58
+ _poll();
59
+ })
60
+ `;return await B(["eval",Q,W],J+5000).catch(()=>"")}async function XQ(Q){await B(["evalraw",Q,"Page.addScriptToEvaluateOnNewDocument",JSON.stringify({source:`
42
61
  (function() {
43
62
  // ── Runtime.enable / CDP detection masking ──────────────
44
63
  try { delete window.__REBROWSER_RUNTIME_ENABLE; } catch(_) {}
@@ -49,40 +68,62 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
49
68
  try { delete window._phantom; } catch(_) {}
50
69
  try { delete window.Buffer; } catch(_) {}
51
70
 
52
- // Real Chrome without automation does not expose a useful webdriver value.
53
- // A literal false value is itself a common stealth tell; prefer undefined and
54
- // make the descriptor configurable like native browser properties.
55
- Object.defineProperty(navigator, 'webdriver', { get: () => undefined, configurable: true });
71
+ // Real Chrome without automation should not expose navigator.webdriver at all.
72
+ // A literal false or an own-property getter returning undefined is itself a
73
+ // common stealth tell; remove both instance and prototype properties when the
74
+ // descriptor is configurable (as it is with --disable-blink-features).
75
+ try { delete navigator.webdriver; } catch(_) {}
76
+ try { delete Navigator.prototype.webdriver; } catch(_) {}
56
77
  Object.defineProperty(navigator, 'vendor', { get: () => 'Google Inc.', configurable: true });
57
78
  Object.defineProperty(navigator, 'platform', { get: () => 'Win32', configurable: true });
58
79
  Object.defineProperty(navigator, 'maxTouchPoints', { get: () => 0, configurable: true });
59
80
  Object.defineProperty(navigator, 'pdfViewerEnabled', { get: () => true, configurable: true });
81
+ Object.defineProperty(navigator, 'productSub', { get: () => '20030107', configurable: true });
82
+ Object.defineProperty(navigator, 'product', { get: () => 'Gecko', configurable: true });
83
+ var __greedyMimeTypes = null;
84
+ function __makeMimeTypes() {
85
+ var pdf = { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: null };
86
+ var textPdf = { type: 'text/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: null };
87
+ try { Object.setPrototypeOf(pdf, MimeType.prototype); } catch(_) {}
88
+ try { Object.setPrototypeOf(textPdf, MimeType.prototype); } catch(_) {}
89
+ var m = [pdf, textPdf];
90
+ try { Object.setPrototypeOf(m, MimeTypeArray.prototype); } catch(_) {}
91
+ m.item = function item(i) { return this[i] || null; };
92
+ m.namedItem = function namedItem(name) { return Array.prototype.find.call(this, function(x) { return x && x.type === name; }) || null; };
93
+ return m;
94
+ }
60
95
  Object.defineProperty(navigator, 'plugins', {
61
96
  get: () => {
62
- var p = [
63
- { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
64
- { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
65
- { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
66
- ];
67
- p.length = 3;
97
+ __greedyMimeTypes = __greedyMimeTypes || __makeMimeTypes();
98
+ var plugin0 = { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' };
99
+ var plugin1 = { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' };
100
+ var plugin2 = { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' };
101
+ try { Object.setPrototypeOf(plugin0, Plugin.prototype); } catch(_) {}
102
+ try { Object.setPrototypeOf(plugin1, Plugin.prototype); } catch(_) {}
103
+ try { Object.setPrototypeOf(plugin2, Plugin.prototype); } catch(_) {}
104
+ var p = [plugin0, plugin1, plugin2];
105
+ p.item = function item(i) { return this[i] || null; };
106
+ p.namedItem = function namedItem(name) { return Array.prototype.find.call(this, function(x) { return x && x.name === name; }) || null; };
107
+ p.refresh = function refresh() {};
108
+ try { Object.setPrototypeOf(p, PluginArray.prototype); } catch(_) {}
109
+ try {
110
+ __greedyMimeTypes[0].enabledPlugin = p[0];
111
+ __greedyMimeTypes[1].enabledPlugin = p[0];
112
+ } catch(_) {}
68
113
  return p;
69
114
  },
115
+ configurable: true,
70
116
  });
71
117
  Object.defineProperty(navigator, 'mimeTypes', {
72
118
  get: () => {
73
- var m = [
74
- { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: null },
75
- { type: 'text/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: null },
76
- ];
77
- m.item = function(i) { return m[i] || null; };
78
- m.namedItem = function(name) { return m.find(function(x) { return x.type === name; }) || null; };
79
- return m;
119
+ __greedyMimeTypes = __greedyMimeTypes || __makeMimeTypes();
120
+ return __greedyMimeTypes;
80
121
  },
81
122
  configurable: true,
82
123
  });
83
124
  Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'], configurable: true });
84
125
  try {
85
- Object.defineProperty(navigator, 'connection', { get: () => ({ effectiveType: '4g', rtt: 50, downlink: 10, saveData: false }), configurable: true });
126
+ Object.defineProperty(navigator, 'connection', { get: () => ({ effectiveType: '4g', rtt: 50, downlink: 10, downlinkMax: Infinity, saveData: false }), configurable: true });
86
127
  } catch(_) {}
87
128
  if (!navigator.mediaDevices) {
88
129
  Object.defineProperty(navigator, 'mediaDevices', {
@@ -98,6 +139,18 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
98
139
  configurable: true,
99
140
  });
100
141
  }
142
+ // ── Missing platform APIs (headless often lacks these) ─
143
+ try {
144
+ if (!navigator.share) {
145
+ navigator.share = function() { return Promise.reject(new Error('NotAllowedError')); };
146
+ }
147
+ } catch(_) {}
148
+ try {
149
+ if (!navigator.contentIndex) {
150
+ Object.defineProperty(navigator, 'contentIndex', { get: () => ({ add: function() {}, delete: function() {}, getAll: function() { return Promise.resolve([]); } }), configurable: true });
151
+ }
152
+ } catch(_) {}
153
+
101
154
  if (!window.chrome) {
102
155
  window.chrome = {
103
156
  app: { isInstalled: false, InstallState: {}, RunningState: {} },
@@ -105,8 +158,8 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
105
158
  OnInstalledReason: {}, OnRestartRequiredReason: {}, PlatformArch: {}, PlatformNaclArch: {}, PlatformOs: {}, RequestUpdateCheckStatus: {},
106
159
  connect: () => ({}), sendMessage: () => {}, onMessage: { addListener: () => {} }
107
160
  },
108
- loadTimes: () => ({}),
109
- csi: () => ({}),
161
+ loadTimes: function() { return { requestTime: 0, startLoadTime: Date.now() - 5000, commitLoadTime: Date.now() - 3000, finishDocumentLoadTime: Date.now() - 2000, finishLoadTime: Date.now() - 1000, firstPaintTime: Date.now() - 800, navigationType: 'Other', wasFetchedViaSpdy: true, wasNpnNegotiated: true, npnNegotiatedProtocol: 'h2', wasAlternateProtocolAvailable: false, connectionInfo: 'http/2' }; },
162
+ csi: function() { var t = Date.now(); return { onloadT: t - 2000, startE: t - 5000, pageT: 'back', tran: 2 }; },
110
163
  };
111
164
  }
112
165
  var __greedyNativeFns = [];
@@ -127,6 +180,19 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
127
180
  return getParam.call(this, p);
128
181
  });
129
182
  } catch(_) {}
183
+ // ── WebGL readPixels noise ──────────────────────────
184
+ // CreepJS and other fingerprinters draw content with WebGL and read back the
185
+ // rendered pixels. Adding subtle noise breaks rendering-based fingerprinting.
186
+ try {
187
+ var origReadPixels = WebGLRenderingContext.prototype.readPixels;
188
+ WebGLRenderingContext.prototype.readPixels = __markNative(function readPixels(x, y, width, height, format, type, pixels) {
189
+ var result = origReadPixels.call(this, x, y, width, height, format, type, pixels);
190
+ if (pixels && pixels.length > 0) {
191
+ pixels[0] ^= 1;
192
+ }
193
+ return result;
194
+ });
195
+ } catch(_) {}
130
196
  Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8, configurable: true });
131
197
  Object.defineProperty(navigator, 'deviceMemory', { get: () => 8, configurable: true });
132
198
 
@@ -134,7 +200,7 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
134
200
  // Headless rendering engines produce slightly different canvas output
135
201
  // than headed Chrome. Subtle noise breaks hash-based fingerprinting.
136
202
  try {
137
- var __canvasNoise = ((Date.now() % 997) + Math.floor(Math.random() * 997)) & 1;
203
+ var __canvasNoise = ((Date.now() & 0xFF) | 1);
138
204
  var origFill = CanvasRenderingContext2D.prototype.fillText;
139
205
  CanvasRenderingContext2D.prototype.fillText = __markNative(function fillText() {
140
206
  this.globalAlpha = 0.9995;
@@ -153,15 +219,39 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
153
219
  HTMLCanvasElement.prototype.toDataURL = __markNative(function toDataURL() {
154
220
  var ctx = this.getContext('2d');
155
221
  if (ctx) {
156
- // Add 1px noise pixel in corner (invisible but changes hash)
157
- var imgData = ctx.getImageData(0, 0, 1, 1);
158
- if (imgData) imgData.data[0] ^= __canvasNoise;
159
- ctx.putImageData(imgData, 0, 0);
222
+ // Spread noise across canvas to break hash-based fingerprinting.
223
+ // Uses a deterministic pattern so it's consistent per page load
224
+ // but varies between sessions.
225
+ var w = this.width, h = this.height;
226
+ if (w > 0 && h > 0) {
227
+ var imgData = ctx.getImageData(0, 0, Math.min(w, 4), Math.min(h, 4));
228
+ if (imgData && imgData.data) {
229
+ for (var __i = 0; __i < imgData.data.length; __i += 4) {
230
+ imgData.data[__i] ^= (__canvasNoise + __i) & 0xFF;
231
+ }
232
+ ctx.putImageData(imgData, 0, 0);
233
+ }
234
+ }
160
235
  }
161
236
  return origToDataURL.apply(this, arguments);
162
237
  });
163
238
  } catch(_) {}
164
239
 
240
+ // ── AudioContext fingerprint noise ────────────────────
241
+ // Headless Chrome's AudioContext produces slightly different output.
242
+ // Subtle noise breaks audio-based fingerprinting.
243
+ try {
244
+ var __audioSeed = ((Date.now() & 0x1F) | 1);
245
+ var origGetChannelData = AudioBuffer.prototype.getChannelData;
246
+ AudioBuffer.prototype.getChannelData = __markNative(function getChannelData(channel) {
247
+ var data = origGetChannelData.call(this, channel);
248
+ for (var __i = 0; __i < data.length; __i += 64) {
249
+ data[__i] *= 0.99999;
250
+ }
251
+ return data;
252
+ });
253
+ } catch(_) {}
254
+
165
255
  // ── window outer dimensions ──────────────────────────
166
256
  // outerWidth/Height = 0 in headless — a well-known bot signal.
167
257
  // Mirror innerWidth/Height (set by --window-size flag) so the ratio is sane.
@@ -171,9 +261,15 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
171
261
  } catch(_) {}
172
262
 
173
263
  // ── screen properties ─────────────────────────────────
264
+ // Headless Chrome often reports an 800x600 screen even when the viewport is
265
+ // 1920x1080. Keep screen metrics internally consistent with our launch flags.
174
266
  try {
175
- if (!screen.colorDepth) Object.defineProperty(screen, 'colorDepth', { get: () => 24, configurable: true });
176
- if (!screen.pixelDepth) Object.defineProperty(screen, 'pixelDepth', { get: () => 24, configurable: true });
267
+ Object.defineProperty(screen, 'width', { get: () => 1920, configurable: true });
268
+ Object.defineProperty(screen, 'height', { get: () => 1080, configurable: true });
269
+ Object.defineProperty(screen, 'availWidth', { get: () => 1920, configurable: true });
270
+ Object.defineProperty(screen, 'availHeight', { get: () => 1040, configurable: true });
271
+ Object.defineProperty(screen, 'colorDepth', { get: () => 24, configurable: true });
272
+ Object.defineProperty(screen, 'pixelDepth', { get: () => 24, configurable: true });
177
273
  } catch(_) {}
178
274
 
179
275
  // ── navigator.userAgentData (UA Client Hints) ─────────
@@ -246,12 +342,12 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
246
342
  };
247
343
  } catch(_) {}
248
344
  })();
249
- `})])}function k(z){if(!z)return[];let Z=[],K=0;while(K<z.length&&Z.length<10){let Q=z.indexOf("[",K);if(Q===-1)break;let W=z.indexOf("](",Q);if(W===-1)break;let H=W+2,$=-1;for(let X=H;X<z.length;X++){let J=z[X];if(J===")"){$=X;break}if(/\s/.test(J))break}if($!==-1){let X=z.slice(Q+1,W),J=z.slice(H,$);if(/^https?:\/\//i.test(J)&&X){if(!Z.some((U)=>U.url===J))Z.push({title:X,url:J})}K=$+1}else K=Q+1}return Z}function zz(z){if(!z)return!1;for(let Z=0;Z<z.length;Z++){let K=z.charCodeAt(Z);if(K<48||K>57)return!1}return!0}function E(z){if(!z)return[];let Z=[],K=new Map,Q=/^\[(\d+)\]:\s*(https?:\/\/[^\s"]+)(?:\s+"([^"]*)")?/gm,W;while((W=Q.exec(z))!==null){let $=W[1],X=W[2],J=W[3]||"";K.set($,{url:X,title:J})}let H=0;while(H<z.length){let $=z.indexOf("[",H);if($===-1)break;let X=z.indexOf("]",$+1);if(X===-1)break;if(z[X+1]!=="["){H=$+1;continue}let J=z.indexOf("]",X+2);if(J===-1)break;let U=z.slice($+1,X),D=z.slice(X+2,J);if(zz(D)){let q=K.get(D);if(q&&!Z.some((N)=>N.url===q.url))Z.push({title:U.trim()||q.title||"",url:q.url})}H=J+1}return Z}function j(z){let Z=z*0.4,K=c(-Math.floor(Z),Math.floor(Z)+1);return Math.max(50,Math.round(z+K))}async function T(z,Z={}){let{timeout:K=20000,interval:Q=600,stableRounds:W=3,selector:H="document.body",minLength:$=0}=Z,X=String.raw`
345
+ `})])}function f(Q){if(!Q)return[];let $=[],Z=0;while(Z<Q.length&&$.length<10){let K=Q.indexOf("[",Z);if(K===-1)break;let J=Q.indexOf("](",K);if(J===-1)break;let X=J+2,W=-1;for(let U=X;U<Q.length;U++){let Y=Q[U];if(Y===")"){W=U;break}if(/\s/.test(Y))break}if(W!==-1){let U=Q.slice(K+1,J),Y=Q.slice(X,W);if(/^https?:\/\//i.test(Y)&&U){if(!$.some((q)=>q.url===Y))$.push({title:U,url:Y})}Z=W+1}else Z=K+1}return $}function YQ(Q){if(!Q)return!1;for(let $=0;$<Q.length;$++){let Z=Q.charCodeAt($);if(Z<48||Z>57)return!1}return!0}function h(Q){if(!Q)return[];let $=[],Z=new Map,K=/^\[(\d+)\]:\s*(https?:\/\/[^\s"]+)(?:\s+"([^"]*)")?/gm,J;while((J=K.exec(Q))!==null){let W=J[1],U=J[2],Y=J[3]||"";Z.set(W,{url:U,title:Y})}let X=0;while(X<Q.length){let W=Q.indexOf("[",X);if(W===-1)break;let U=Q.indexOf("]",W+1);if(U===-1)break;if(Q[U+1]!=="["){X=W+1;continue}let Y=Q.indexOf("]",U+2);if(Y===-1)break;let q=Q.slice(W+1,U),z=Q.slice(U+2,Y);if(YQ(z)){let D=Z.get(z);if(D&&!$.some((H)=>H.url===D.url))$.push({title:q.trim()||D.title||"",url:D.url})}X=Y+1}return $}function L(Q){let $=Q*0.2,Z=r(-Math.floor($),Math.floor($)+1);return Math.max(50,Math.round(Q+Z))}async function R(Q,$={}){let{timeout:Z=20000,interval:K=600,stableRounds:J=3,selector:X="document.body",minLength:W=0,isStreamingExpr:U="false"}=$,Y=String.raw`
250
346
  new Promise((resolve, reject) => {
251
- const _deadline = Date.now() + ${K};
252
- const _baseInterval = ${Q};
253
- const _stableRounds = ${W};
254
- const _minLength = ${$};
347
+ const _deadline = Date.now() + ${Z};
348
+ const _baseInterval = ${K};
349
+ const _stableRounds = ${J};
350
+ const _minLength = ${W};
255
351
  let _lastLen = -1;
256
352
  let _stableCount = 0;
257
353
 
@@ -262,9 +358,13 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
262
358
  function _poll() {
263
359
  try {
264
360
  // Re-query DOM each tick — element may not exist at eval start
265
- const el = ${H};
266
- const cur = el?.innerText?.length ?? 0;
267
- if (cur >= _minLength) {
361
+ const el = ${X};
362
+ const cur = el?.textContent?.length ?? 0;
363
+ const streaming = ${U};
364
+ if (streaming) {
365
+ if (cur !== _lastLen) _lastLen = cur;
366
+ _stableCount = 0;
367
+ } else if (cur >= _minLength) {
268
368
  if (cur === _lastLen) {
269
369
  _stableCount++;
270
370
  if (_stableCount >= _stableRounds) { resolve(cur); return; }
@@ -276,18 +376,18 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
276
376
  if (Date.now() < _deadline) {
277
377
  setTimeout(_poll, _jitter(_baseInterval));
278
378
  } else {
279
- if (_lastLen >= _minLength) { resolve(_lastLen); }
280
- else { reject(new Error('Generation did not stabilise within ${K}ms')); }
379
+ if (_lastLen >= _minLength && !streaming) { resolve(_lastLen); }
380
+ else { reject(new Error('Generation did not stabilise within ${Z}ms')); }
281
381
  }
282
382
  } catch(e) { reject(e); }
283
383
  }
284
384
 
285
385
  _poll();
286
386
  })
287
- `,J=await Y(["eval",z,X],K+1e4),U=parseInt(J,10)||0;if(U>=$)return U;throw Error(`Generation did not stabilise within ${K}ms`)}async function w(z,Z,K=15000,Q=500){let W=String.raw`
387
+ `,q=await B(["eval",Q,Y],Z+1e4),z=parseInt(q,10)||0;if(z>=W)return z;throw Error(`Generation did not stabilise within ${Z}ms`)}async function v(Q,$,Z=15000,K=500){let J=String.raw`
288
388
  new Promise((resolve) => {
289
- const _deadline = Date.now() + ${K};
290
- const _baseInterval = ${Q};
389
+ const _deadline = Date.now() + ${Z};
390
+ const _baseInterval = ${K};
291
391
 
292
392
  function _jitter(ms) {
293
393
  return Math.max(50, ms + (Math.random() * ms * 0.4 - ms * 0.2));
@@ -295,7 +395,7 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
295
395
 
296
396
  function _poll() {
297
397
  try {
298
- if (document.querySelector('${Z}')) { resolve(true); return; }
398
+ if (document.querySelector('${$}')) { resolve(true); return; }
299
399
  if (Date.now() < _deadline) { setTimeout(_poll, _jitter(_baseInterval)); }
300
400
  else { resolve(false); }
301
401
  } catch(_) { resolve(false); }
@@ -303,10 +403,10 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
303
403
 
304
404
  _poll();
305
405
  })
306
- `;return await Y(["eval",z,W],K+5000)==="true"}async function S(z){let Z=z.indexOf("--stdin");if(Z===-1)return z;let K=await new Promise((W)=>{let H="";process.stdin.setEncoding("utf8"),process.stdin.on("data",($)=>H+=$),process.stdin.on("end",()=>W(H.trim()))}),Q=[...z];return Q[Z]=K,Q}function f(z){let Z=z.includes("--short"),K=z.filter((J)=>J!=="--short"),Q=K.indexOf("--tab"),W=Q===-1?null:K[Q+1];if(Q!==-1)K=K.filter((J,U)=>U!==Q&&U!==Q+1);let H=K.indexOf("--locale"),$=H===-1?null:K[H+1];if(H!==-1)K=K.filter((J,U)=>U!==H&&U!==H+1);return{query:K.join(" "),tabPrefix:W,short:Z,locale:$}}function h(z,Z){if(!z.length||z[0]==="--help")process.stderr.write(Z),process.exit(1)}function y(z,Z,K=300){if(!Z||z.length<=K)return z;let Q=z.slice(0,K),W=Q.lastIndexOf(" ");return W>0?`${Q.slice(0,W)}…`:`${Q}…`}function R(z){process.stdout.write(`${JSON.stringify(z,null,2)}
307
- `)}function V(z,Z,K=null){if(!z||typeof z!=="object")return;let Q=K?` (+${Date.now()-K}ms)`:"";if(z.lastStage=Z,!Array.isArray(z.stages))z.stages=[];z.stages.push({stage:Z,at:Date.now()});let W=z.engine||"extractor";console.error(`[${W}] stage: ${Z}${Q}`)}function C({engine:z,mode:Z="headless",clipboardEmpty:K=null,fallbackUsed:Q=null,blockedBy:W=null,verificationResult:H=null,inputReady:$=null,durationMs:X=null,lastStage:J=null,stages:U=null}={}){return{engine:z,mode:Z,clipboardEmpty:K,fallbackUsed:Q,blockedBy:W,verificationResult:H,inputReady:$,durationMs:X,lastStage:J,stages:U}}function v(z,Z=null){if(Z){let K=JSON.stringify({_envelope:Z,error:z.message});process.stdout.write(`${K}
308
- `)}process.stderr.write(`Error: ${z.message}
309
- `),process.exit(1)}import{randomInt as Kz}from"node:crypto";import{existsSync as Zz,readFileSync as $z}from"node:fs";import Qz from"node:http";var Wz=`
406
+ `;return await B(["eval",Q,J],Z+5000)==="true"}async function x(Q){let $=Q.indexOf("--stdin");if($===-1)return Q;let Z=await new Promise((J)=>{let X="";process.stdin.setEncoding("utf8"),process.stdin.on("data",(W)=>X+=W),process.stdin.on("end",()=>J(X.trim()))}),K=[...Q];return K[$]=Z,K}function g(Q){let $=Q.includes("--short"),Z=Q.filter((Y)=>Y!=="--short"),K=Z.indexOf("--tab"),J=K===-1?null:Z[K+1];if(K!==-1)Z=Z.filter((Y,q)=>q!==K&&q!==K+1);let X=Z.indexOf("--locale"),W=X===-1?null:Z[X+1];if(X!==-1)Z=Z.filter((Y,q)=>q!==X&&q!==X+1);return{query:Z.join(" "),tabPrefix:J,short:$,locale:W}}function b(Q,$){if(!Q.length||Q[0]==="--help")process.stderr.write($),process.exit(1)}function m(Q,$,Z=300){if(!$||Q.length<=Z)return Q;let K=Q.slice(0,Z),J=K.lastIndexOf(" ");return J>0?`${K.slice(0,J)}…`:`${K}…`}function u(Q){process.stdout.write(`${JSON.stringify(Q,null,2)}
407
+ `)}function O(Q,$,Z=null){if(!Q||typeof Q!=="object")return;let K=Z?` (+${Date.now()-Z}ms)`:"";if(Q.lastStage=$,!Array.isArray(Q.stages))Q.stages=[];Q.stages.push({stage:$,at:Date.now()});let J=Q.engine||"extractor";console.error(`[${J}] stage: ${$}${K}`)}function C({engine:Q,mode:$="headless",clipboardEmpty:Z=null,fallbackUsed:K=null,blockedBy:J=null,verificationResult:X=null,inputReady:W=null,durationMs:U=null,lastStage:Y=null,stages:q=null}={}){return{engine:Q,mode:$,clipboardEmpty:Z,fallbackUsed:K,blockedBy:J,verificationResult:X,inputReady:W,durationMs:U,lastStage:Y,stages:q}}function p(Q,$=null){if($){let Z=JSON.stringify({_envelope:$,error:Q.message});process.stdout.write(`${Z}
408
+ `)}process.stderr.write(`Error: ${Q.message}
409
+ `),process.exit(1)}import{randomInt as qQ}from"node:crypto";import{existsSync as zQ,readFileSync as DQ}from"node:fs";import HQ from"node:http";var BQ=`
310
410
  (function() {
311
411
  // Google consent page (consent.google.com)
312
412
  var g = document.querySelector('#L2AGLb, button[jsname="b3VHJd"], .tHlp8d');
@@ -323,7 +423,7 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
323
423
 
324
424
  return null;
325
425
  })()
326
- `,Xz=`
426
+ `,GQ=`
327
427
  (function() {
328
428
  var url = document.location.href;
329
429
 
@@ -354,12 +454,24 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
354
454
 
355
455
  // --- Cloudflare Turnstile widget inside closed shadow DOM (Copilot, etc.) ---
356
456
  // The iframe is not queryable from main document, but the host container
357
- // (#cf-turnstile) and the hidden response input are.
358
- var cfTurnstileHost = document.querySelector('#cf-turnstile, [id^="cf-chl-widget-"]');
457
+ // (#cf-turnstile) and the hidden response input are. When only the
458
+ // hidden response input matches (no #cf-turnstile host and no visible
459
+ // iframe), the actual challenge widget is rendered inside a closed
460
+ // shadow DOM and cannot be auto-clicked. Return a sentinel so callers
461
+ // know to surface this as needs-human verification instead of wasting
462
+ // time on a doomed waitForSelector.
463
+ var cfTurnstileHost = document.querySelector('#cf-turnstile');
359
464
  if (cfTurnstileHost) {
360
465
  var r2 = cfTurnstileHost.getBoundingClientRect();
361
466
  return JSON.stringify({t:'xy',x:r2.left+r2.width/2,y:r2.top+r2.height/2});
362
467
  }
468
+ // Hidden cf-chl-widget-*_response input present but no visible host:
469
+ // the widget is in closed shadow DOM. Signal this so handleVerification
470
+ // can return 'needs-human' rather than 'clear'.
471
+ var cfResponseInput = document.querySelector('input[name="cf-turnstile-response"], [id^="cf-chl-widget-"][id$="_response"]');
472
+ if (cfResponseInput && cfResponseInput.value === '') {
473
+ return 'cf-closed-shadow-dom';
474
+ }
363
475
 
364
476
  // --- Cloudflare challenge page ---
365
477
  var cfCheckbox = document.querySelector('#cf-stage input[type="checkbox"], .ctp-checkbox-container input');
@@ -374,15 +486,28 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
374
486
  }
375
487
 
376
488
  // --- Generic verify/continue/proceed buttons (catch-all) ---
377
- // IMPORTANT: exclude sign-in / OAuth buttons (e.g. "Continue with Google")
489
+ // IMPORTANT: exclude sign-in / OAuth buttons (e.g. "Continue with Google",
490
+ // "Continue with email", "Login or sign up for free"). These appear on
491
+ // many sites (Perplexity, ChatGPT, etc.) when the user isn't logged in,
492
+ // and clicking them triggers a sign-in flow that takes us to a login
493
+ // wall — a much worse outcome than the original search failure we were
494
+ // trying to recover from. The exclusion list must cover both OAuth
495
+ // providers AND generic "sign in / log in / with email" patterns.
378
496
  var btns = Array.from(document.querySelectorAll('button, input[type=submit], a[role=button]'));
379
497
  var verify = btns.find(b => {
380
498
  var t = (b.innerText?.trim() || b.value || '').toLowerCase();
381
- var isVerifyLike = (t.includes('verify') || t.includes('human') || t.includes('robot') || t.includes('continue') || t.includes('proceed')) &&
499
+ var isVerifyLike = (t === 'continue' || t === 'proceed' || t === 'next' ||
500
+ t.startsWith('verify ') || t.startsWith('human ') || t === 'i am human' || t.includes('robot check')) &&
382
501
  !t.includes('verified') && !document.querySelector('iframe[src*="recaptcha"]');
383
502
  if (!isVerifyLike) return false;
384
503
  // Exclude OAuth / sign-in buttons to prevent accidental login flows
385
- var isSignIn = /sign.in|log.in|google|microsoft|apple|facebook|github|auth/i.test(t);
504
+ // covers "Continue with Google", "Continue with Apple", "Continue
505
+ // with email", "Login or sign up", "Log in", "Sign in", "Sign up",
506
+ // "Single sign-on", and the visible panel "Login or sign up for free"
507
+ // text. The previous list missed "email" and "sso" which let the
508
+ // auto-click land on the email/SSO sign-in buttons on Perplexity's
509
+ // anonymous-mode homepage, navigating us into a login flow.
510
+ var isSignIn = new RegExp("sign.?in|log.?in|sign.?up|with\\s+(google|apple|email|github|facebook|microsoft|sso)|sso|auth", "i").test(t);
386
511
  return !isSignIn;
387
512
  });
388
513
  if (verify) { verify.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:verify.innerText?.trim()||verify.value}); }
@@ -393,7 +518,7 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
393
518
 
394
519
  return null;
395
520
  })()
396
- `,Hz=`
521
+ `,VQ=`
397
522
  (function() {
398
523
  var url = document.location.href;
399
524
  var isVerifyPage = url.includes('/sorry/') ||
@@ -427,25 +552,20 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
427
552
 
428
553
  return 'still-verifying';
429
554
  })()
430
- `;async function g(z,Z){let K=await Z(["eval",z,Wz]).catch(()=>null);if(K&&K!=="null")await new Promise((Q)=>setTimeout(Q,1500))}function G(z,Z){return Kz(z*1000,Z*1000)/1000}async function Jz(z,Z){if(!globalThis.WebSocket)return;let K=process.env.CDP_PROFILE_DIR;if(!K)return;let Q=`${K.replaceAll("\\","/")}/DevToolsActivePort`;if(!Zz(Q))return;let W=$z(Q,"utf8").trim().split(`
431
- `)[0],H=await new Promise((J,U)=>{let D=Qz.get(`http://localhost:${W}/json/version`,(q)=>{let N="";q.on("data",(B)=>N+=B),q.on("end",()=>{try{J(JSON.parse(N))}catch{U(Error("bad JSON"))}})});D.on("error",U),D.setTimeout(1000,()=>{D.destroy(),U(Error("timeout"))})}),$=new globalThis.WebSocket(H.webSocketDebuggerUrl),X=0;await new Promise((J)=>{$.onopen=async()=>{let U=(N,B)=>new Promise((_)=>{let O=++X,A=(d)=>{if(JSON.parse(d.data).id===O)$.removeEventListener("message",A),_()};$.addEventListener("message",A),$.send(JSON.stringify({id:O,method:N,params:B}))}),D=z+G(-2,2),q=Z+G(-2,2);await U("Input.dispatchMouseEvent",{type:"mouseMoved",x:D,y:q,button:"none"}),await new Promise((N)=>setTimeout(N,G(80,160))),await U("Input.dispatchMouseEvent",{type:"mousePressed",x:D,y:q,button:"left",clickCount:1}),await new Promise((N)=>setTimeout(N,G(30,80))),await U("Input.dispatchMouseEvent",{type:"mouseReleased",x:D+G(-1,1),y:q+G(-1,1),button:"left",clickCount:1}),setTimeout(()=>{$.close(),J()},200)},$.onerror=()=>J(),setTimeout(J,3000)})}async function m(z,Z,K,Q){let W=Number.parseFloat(K),H=Number.parseFloat(Q);if(Number.isNaN(W)||Number.isNaN(H))throw Error(`humanClickXY: invalid coordinates (${K}, ${Q})`);let $={button:"left",clickCount:1,modifiers:0},X=W+G(-3,3),J=H+G(-3,3);await Z(["evalraw",z,"Input.dispatchMouseEvent",JSON.stringify({...$,type:"mouseMoved",x:X,y:J})]),await new Promise((B)=>setTimeout(B,G(80,180)));let U=W+G(-2,2),D=H+G(-2,2);await Z(["evalraw",z,"Input.dispatchMouseEvent",JSON.stringify({...$,type:"mousePressed",x:U,y:D})]),await new Promise((B)=>setTimeout(B,G(30,90)));let q=U+G(-1,1),N=D+G(-1,1);return await Z(["evalraw",z,"Input.dispatchMouseEvent",JSON.stringify({...$,type:"mouseReleased",x:q,y:N})]),await Jz(W,H).catch(()=>{}),await new Promise((B)=>setTimeout(B,G(100,300))),`human-clicked at (${W.toFixed(0)}, ${H.toFixed(0)})`}async function Uz(z,Z,K){let Q=await Z(["eval",z,`(function() {
432
- var el = document.querySelector('${K.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}');
555
+ `;async function c(Q,$){let Z=await $(["eval",Q,BQ]).catch(()=>null);if(Z&&Z!=="null")await new Promise((K)=>setTimeout(K,1500))}function j(Q,$){return qQ(Q*1000,$*1000)/1000}async function jQ(Q,$){if(!globalThis.WebSocket)return;let Z=process.env.CDP_PROFILE_DIR;if(!Z)return;let K=`${Z.replaceAll("\\","/")}/DevToolsActivePort`;if(!zQ(K))return;let J=DQ(K,"utf8").trim().split(`
556
+ `)[0],X=await new Promise((Y,q)=>{let z=HQ.get(`http://localhost:${J}/json/version`,(D)=>{let H="";D.on("data",(G)=>H+=G),D.on("end",()=>{try{Y(JSON.parse(H))}catch{q(Error("bad JSON"))}})});z.on("error",q),z.setTimeout(1000,()=>{z.destroy(),q(Error("timeout"))})}),W=new globalThis.WebSocket(X.webSocketDebuggerUrl),U=0;await new Promise((Y)=>{W.onopen=async()=>{let q=(H,G)=>new Promise((M)=>{let _=++U,N=(A)=>{if(JSON.parse(A.data).id===_)W.removeEventListener("message",N),M()};W.addEventListener("message",N),W.send(JSON.stringify({id:_,method:H,params:G}))}),z=Q+j(-2,2),D=$+j(-2,2);await q("Input.dispatchMouseEvent",{type:"mouseMoved",x:z,y:D,button:"none"}),await new Promise((H)=>setTimeout(H,j(80,160))),await q("Input.dispatchMouseEvent",{type:"mousePressed",x:z,y:D,button:"left",clickCount:1}),await new Promise((H)=>setTimeout(H,j(30,80))),await q("Input.dispatchMouseEvent",{type:"mouseReleased",x:z+j(-1,1),y:D+j(-1,1),button:"left",clickCount:1}),setTimeout(()=>{W.close(),Y()},200)},W.onerror=()=>Y(),setTimeout(Y,3000)})}async function l(Q,$,Z,K){let J=Number.parseFloat(Z),X=Number.parseFloat(K);if(Number.isNaN(J)||Number.isNaN(X))throw Error(`humanClickXY: invalid coordinates (${Z}, ${K})`);let W={button:"left",clickCount:1,modifiers:0},U=J+j(-3,3),Y=X+j(-3,3);await $(["evalraw",Q,"Input.dispatchMouseEvent",JSON.stringify({...W,type:"mouseMoved",x:U,y:Y})]),await new Promise((G)=>setTimeout(G,j(80,180)));let q=J+j(-2,2),z=X+j(-2,2);await $(["evalraw",Q,"Input.dispatchMouseEvent",JSON.stringify({...W,type:"mousePressed",x:q,y:z})]),await new Promise((G)=>setTimeout(G,j(30,90)));let D=q+j(-1,1),H=z+j(-1,1);return await $(["evalraw",Q,"Input.dispatchMouseEvent",JSON.stringify({...W,type:"mouseReleased",x:D,y:H})]),await jQ(J,X).catch(()=>{}),await new Promise((G)=>setTimeout(G,j(100,300))),`human-clicked at (${J.toFixed(0)}, ${X.toFixed(0)})`}async function NQ(Q,$,Z){let K=await $(["eval",Q,`(function() {
557
+ var el = document.querySelector('${Z.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}');
433
558
  if (!el) return 'null';
434
559
  var r = el.getBoundingClientRect();
435
560
  return JSON.stringify({x: r.left + r.width / 2, y: r.top + r.height / 2, w: r.width, h: r.height});
436
- })()`]).catch(()=>"null");if(!Q||Q==="null")return null;let W=JSON.parse(Q);if(W.w===0||W.h===0||W.x===0&&W.y===0)return null;let{x:H,y:$}=W;return m(z,Z,H,$)}async function x(z,Z,K){if(!K||K==="null"||K==="cleared"||K==="still-verifying")return!1;try{let Q=JSON.parse(K);if(Q.t==="sel"&&Q.s)return process.stderr.write(`[greedysearch] Human-clicking "${Q.txt}" via CDP...
437
- `),await Uz(z,Z,Q.s)!==null;if(Q.t==="xy"){if(!Q.x&&!Q.y)return!1;return process.stderr.write(`[greedysearch] Human-clicking at (${Q.x.toFixed(0)}, ${Q.y.toFixed(0)})...
438
- `),await m(z,Z,Q.x,Q.y),!0}}catch{}return!1}async function Yz(z,Z){let K=await Z(["eval",z,Xz]).catch(()=>null);return K&&K!=="null"?K:null}async function b(z,Z,K=30000){let Q=await Yz(z,Z);if(!Q)return"clear";if(Q==="sorry-page"){process.stderr.write(`[greedysearch] Google CAPTCHA detected please solve it in the browser window (waiting up to ${Math.floor(K/1000)}s)...
439
- `);let H=Date.now()+K;while(Date.now()<H)if(await new Promise((X)=>setTimeout(X,2000)),!(await Z(["eval",z,"document.location.href"]).catch(()=>"")).includes("/sorry/"))return"cleared-by-user";return"needs-human"}if(await x(z,Z,Q)){await new Promise(($)=>setTimeout($,2000));let H=Date.now()+K;while(Date.now()<H){let $=await Z(["eval",z,Hz]).catch(()=>null);if($==="cleared"||!$||$==="null")return process.stderr.write(`[greedysearch] Verification cleared.
440
- `),"clicked";if($!=="still-verifying")await x(z,Z,$),await new Promise((X)=>setTimeout(X,2000));else await new Promise((X)=>setTimeout(X,1500))}return process.stderr.write(`[greedysearch] Verification may require manual intervention.
441
- `),"needs-human"}return"clear"}var F="__chatgptClipboard",p="div.ProseMirror",qz='button[data-testid="send-button"]',u='button[data-testid="copy-turn-action-button"]';async function Dz(z,Z){await Y(["click",z,p]),await new Promise((W)=>setTimeout(W,j(200))),await I(["type",z,"--stdin"],Z),await new Promise((W)=>setTimeout(W,j(300)));let K=`
442
- (() => {
443
- const btn = document.querySelector('${qz}');
444
- if (!btn) return 'no-send';
445
- btn.click();
446
- return 'ok';
447
- })()
448
- `;if(await Y(["eval",z,K])==="no-send")throw Error("ChatGPT send button not found");await new Promise((W)=>setTimeout(W,j(300)))}var i=String.raw`(() => {
561
+ })()`]).catch(()=>"null");if(!K||K==="null")return null;let J=JSON.parse(K);if(J.w===0||J.h===0||J.x===0&&J.y===0)return null;let{x:X,y:W}=J;return l(Q,$,X,W)}function i(Q,$,Z){if(!Z||Z==="null"||Z==="cleared"||Z==="still-verifying"||Z==="cf-closed-shadow-dom")return Promise.resolve("no-challenge");try{let K=JSON.parse(Z);if(K.t==="sel"&&K.s)return process.stderr.write(`[greedysearch] Human-clicking "${K.txt}" via CDP...
562
+ `),NQ(Q,$,K.s).then((J)=>J!==null?"clicked":"cant-click");if(K.t==="xy"){if(!K.x&&!K.y)return Promise.resolve("cant-click");return process.stderr.write(`[greedysearch] Human-clicking at (${K.x.toFixed(0)}, ${K.y.toFixed(0)})...
563
+ `),l(Q,$,K.x,K.y).then(()=>"clicked")}}catch{}return Promise.resolve("no-challenge")}async function OQ(Q,$){let Z=await $(["eval",Q,GQ]).catch(()=>null);if(Z==="cf-closed-shadow-dom"){let K=await AQ(Q,$).catch(()=>null);if(K)return K;return Z}if(Z&&Z!=="null")return Z;return null}async function AQ(Q,$){if(typeof $!=="function")return null;await $(["evalraw",Q,"DOM.enable","{}"]).catch(()=>{});let Z=await $(["evalraw",Q,"DOM.getDocument",JSON.stringify({depth:-1,pierce:!0})]).catch(()=>null);if(!Z)return null;let K;try{K=JSON.parse(Z)}catch{return null}if(K.error||!K.root)return null;let J=K.root;return await d(J,Q,$)}async function d(Q,$,Z){if(!Q)return null;let K=[];if(Q.shadowRoots&&Q.shadowRoots.length>0)for(let J of Q.shadowRoots)K.push(J);if(Q.children)for(let J of Q.children)K.push(J);for(let J of K){if(J.nodeName==="IFRAME"){let W=J.attributes||[],U=W.indexOf("src"),Y=U>=0?W[U+1]:"";if(Y&&/challenges\.cloudflare\.com|turnstile/i.test(Y)&&J.backendNodeId){let q=await Z(["evalraw",$,"DOM.getBoxModel",JSON.stringify({backendNodeId:J.backendNodeId})]).catch(()=>null);if(!q)continue;let z;try{z=JSON.parse(q)}catch{continue}let D=z?.model?.content||z?.result?.model?.content;if(!D||D.length<8)continue;let H=D[0],G=D[1],M=D[4],_=D[5],N=M-H,A=_-G;if(N<50||A<20)continue;let V=H+N*0.25,I=G+A*0.5;return process.stderr.write(`[greedysearch] Found CF iframe via CDP pierce at (${H.toFixed(0)}, ${G.toFixed(0)}) ${N.toFixed(0)}x${A.toFixed(0)}, clicking checkbox at (${V.toFixed(0)}, ${I.toFixed(0)})
564
+ `),JSON.stringify({t:"xy",x:V,y:I})}}let X=await d(J,$,Z);if(X)return X}return null}async function n(Q,$,Z=30000){let K=await OQ(Q,$);if(!K)return"clear";if(K==="sorry-page"){process.stderr.write(`[greedysearch] Google CAPTCHA detected — please solve it in the browser window (waiting up to ${Math.floor(Z/1000)}s)...
565
+ `);let X=Date.now()+Z;while(Date.now()<X)if(await new Promise((U)=>setTimeout(U,2000)),!(await $(["eval",Q,"document.location.href"]).catch(()=>"")).includes("/sorry/"))return"cleared-by-user";return"needs-human"}let J=await i(Q,$,K);if(J==="clicked"){await new Promise((W)=>setTimeout(W,2000));let X=Date.now()+Z;while(Date.now()<X){let W=await $(["eval",Q,VQ]).catch(()=>null);if(W==="cleared"||!W||W==="null")return process.stderr.write(`[greedysearch] Verification cleared.
566
+ `),"clicked";if(W!=="still-verifying")await i(Q,$,W),await new Promise((U)=>setTimeout(U,2000));else await new Promise((U)=>setTimeout(U,1500))}return process.stderr.write(`[greedysearch] Verification may require manual intervention.
567
+ `),"needs-human"}if(J==="cant-click")return process.stderr.write(`[greedysearch] Verification challenge detected but cannot be auto-clicked — please solve it manually in the visible browser window.
568
+ `),"needs-human";return"clear"}var w="__chatgptClipboard",P="div.ProseMirror",MQ='button[data-testid="send-button"]',_Q='button[data-testid="copy-turn-action-button"]',F=String.raw`(() => {
449
569
  const all = document.querySelectorAll('[data-message-author-role]');
450
570
  let lastUserIdx = -1;
451
571
  for (let i = 0; i < all.length; i++) {
@@ -461,43 +581,44 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
461
581
  }
462
582
  }
463
583
  return bestEl;
464
- })()`;async function Nz(z,Z=20000){return T(z,{timeout:Z,interval:600,stableRounds:3,minLength:1,selector:i})}async function Gz(z,Z=15000){let K=Date.now()+Z,Q=0,W=0;while(Date.now()<K){let H=await Y(["eval",z,`${i}?.innerText?.length ?? 0`],4000).catch(()=>"0"),$=parseInt(H,10)||0;if($>=1&&$===Q){if(W++,W>=3)return $}else Q=$,W=0;await new Promise((X)=>setTimeout(X,1200))}return Q}async function Bz(z){let Z=await Y(["eval",z,String.raw`
584
+ })()`,FQ=`(() => {
585
+ window.${w} = '';
586
+ const assistant = ${F};
587
+ const assistantCopy = assistant?.querySelector('${_Q}');
588
+ if (assistantCopy) assistantCopy.click();
589
+ })()`;async function IQ(Q,$){await B(["click",Q,P]),await new Promise((X)=>setTimeout(X,L(200)));let Z=await B(["eval",Q,`(() => {
590
+ const editor = document.querySelector('${P}');
591
+ if (!editor) return 'no-editor';
592
+ editor.focus();
593
+ const ok = document.execCommand('insertText', false, ${JSON.stringify($)});
594
+ return ok ? 'ok' : 'exec-failed';
595
+ })()`],5000);if(Z!=="ok")throw Error(`ChatGPT type failed: ${Z}`);await new Promise((X)=>setTimeout(X,L(300)));let K=`
465
596
  (() => {
466
- // Find the assistant message that comes AFTER the last user message,
467
- // not the absolute last assistant element. The chatgpt.com homepage
468
- // has a static pre-rendered greeting card that renders as a
469
- // [data-message-author-role="assistant"] element with
470
- // data-turn-start-message="true" — it must be skipped or the
471
- // static "Hello! How can I help you today?" placeholder gets
472
- // returned as the answer to a query the assistant never answered.
473
- const all = Array.from(document.querySelectorAll('[data-message-author-role]'));
474
- let lastUserIdx = -1;
475
- for (let i = 0; i < all.length; i++) {
476
- if (all[i].getAttribute('data-message-author-role') === 'user') {
477
- lastUserIdx = i;
478
- }
479
- }
480
- if (lastUserIdx < 0) {
481
- // No user message at all — page is still on the homepage.
482
- return JSON.stringify({
483
- answer: '',
484
- sources: [],
485
- skipped: 'no-user-message',
486
- });
487
- }
488
- let assistant = null;
489
- for (let i = lastUserIdx + 1; i < all.length; i++) {
490
- if (all[i].getAttribute('data-message-author-role') === 'assistant') {
491
- assistant = all[i];
492
- }
493
- }
597
+ const btn = document.querySelector('${MQ}');
598
+ if (!btn) return 'no-send';
599
+ if (btn.disabled) return 'send-disabled';
600
+ btn.click();
601
+ return 'ok';
602
+ })()
603
+ `,J=await B(["eval",Q,K]);if(J==="no-send")throw Error("ChatGPT send button not found");if(J==="send-disabled")throw Error("ChatGPT send button disabled — query was not registered");await new Promise((X)=>setTimeout(X,L(300)))}var LQ=String.raw`(() => {
604
+ const el = ${F};
605
+ return !!el?.querySelector('.streaming-animation,[data-is-streaming="true"]') ||
606
+ !!document.querySelector('button[data-testid="stop-button"], button[aria-label="Stop generating"], button[aria-label*="Stop"]');
607
+ })()`;async function o(Q,{required:$=!1}={}){try{return await B(["evalraw",Q,"Page.bringToFront","{}"],5000),!0}catch(Z){if(console.error(`[chatgpt] bringToFront failed: ${Z.message}`),$)throw Z;return!1}}async function s(Q,$=35000){await o(Q,{required:!0});let Z=0,K=!1,J=setInterval(()=>{if(K)return;if(Z>=8){console.error("[chatgpt] stopping foreground keepalive after 8 attempts"),clearInterval(J);return}Z++,K=!0,o(Q).finally(()=>{K=!1})},4000);try{return await R(Q,{timeout:$,interval:700,stableRounds:5,minLength:1,selector:F,isStreamingExpr:LQ})}finally{clearInterval(J)}}async function CQ(Q,$=15000){let Z=Date.now()+$,K=0,J=0;while(Date.now()<Z){let X=await B(["eval",Q,`${F}?.innerText?.length ?? 0`],4000).catch(()=>"0"),W=parseInt(X,10)||0;if(W>=1&&W===K){if(J++,J>=3)return W}else K=W,J=0;await new Promise((U)=>setTimeout(U,1200))}return K}async function PQ(Q){let $=await B(["eval",Q,String.raw`
608
+ (() => {
609
+ const assistant = ${F};
494
610
  if (!assistant) {
611
+ const skipped = document.querySelector('[data-message-author-role="user"]')
612
+ ? 'no-assistant-response'
613
+ : 'no-user-message';
495
614
  return JSON.stringify({
496
615
  answer: '',
497
616
  sources: [],
498
- skipped: 'no-assistant-response',
617
+ skipped,
499
618
  });
500
619
  }
620
+ const streaming = !!assistant.querySelector('.streaming-animation,[data-is-streaming="true"]') ||
621
+ !!document.querySelector('button[data-testid="stop-button"], button[aria-label="Stop generating"], button[aria-label*="Stop"]');
501
622
  const answer = (assistant.innerText || assistant.textContent || '').trim();
502
623
  const seen = new Set();
503
624
  const sources = [];
@@ -509,39 +630,7 @@ import{randomInt as c}from"node:crypto";import{spawn as o}from"node:child_proces
509
630
  sources.push({ title, url });
510
631
  if (sources.length >= 10) break;
511
632
  }
512
- return JSON.stringify({ answer, sources });
633
+ return JSON.stringify({ answer, sources, streaming });
513
634
  })()
514
- `]);try{return JSON.parse(Z)}catch{return{answer:"",sources:[],skipped:"parse-error"}}}async function Vz(z,Z){await Y(["eval",z,`(() => {
515
- const all = document.querySelectorAll('[data-message-author-role]');
516
- let lastUserIdx = -1;
517
- for (let i = 0; i < all.length; i++) {
518
- if (all[i].getAttribute('data-message-author-role') === 'user') lastUserIdx = i;
519
- }
520
- if (lastUserIdx < 0) return 'no-user';
521
- let assistantCopy = null;
522
- for (let i = lastUserIdx + 1; i < all.length; i++) {
523
- if (all[i].getAttribute('data-message-author-role') === 'assistant') {
524
- const btn = all[i].querySelector('${u}');
525
- if (btn) assistantCopy = btn;
526
- }
527
- }
528
- if (assistantCopy) { assistantCopy.click(); return 'clicked'; }
529
- return 'no-assistant-copy';
530
- })()`]),await new Promise((J)=>setTimeout(J,600));let K=await Y(["eval",z,`window.${F} || ''`]);if(Z.clipboardEmpty=!K,!K)console.error("[chatgpt] Clipboard empty, retrying in 2s..."),await Y(["eval",z,`(() => {
531
- const all = document.querySelectorAll('[data-message-author-role]');
532
- let lastUserIdx = -1;
533
- for (let i = 0; i < all.length; i++) {
534
- if (all[i].getAttribute('data-message-author-role') === 'user') lastUserIdx = i;
535
- }
536
- if (lastUserIdx < 0) return 'no-user';
537
- let assistantCopy = null;
538
- for (let i = lastUserIdx + 1; i < all.length; i++) {
539
- if (all[i].getAttribute('data-message-author-role') === 'assistant') {
540
- const btn = all[i].querySelector('${u}');
541
- if (btn) assistantCopy = btn;
542
- }
543
- }
544
- if (assistantCopy) { assistantCopy.click(); return 'clicked'; }
545
- return 'no-assistant-copy';
546
- })()`]),await new Promise((J)=>setTimeout(J,2000)),K=await Y(["eval",z,`window.${F} || ''`]),Z.clipboardEmpty=!K;let Q=null;if(!K)Q=await Bz(z),K=Q.answer,Z.fallbackUsed=K?"dom":null;if(!K)throw Error("Clipboard interceptor returned empty text");let W=k(K),H=E(K),$=new Map;for(let J of[...Q?.sources||[],...H,...W])if(J?.url&&!$.has(J.url))$.set(J.url,J);let X=Array.from($.values()).slice(0,10);return{answer:K.trim(),sources:X}}var Az=`Usage: node extractors/chatgpt.mjs "<query>" [--tab <prefix>]
547
- `;async function _z(){let z=await S(process.argv.slice(2));h(z,Az);let{query:Z,tabPrefix:K,short:Q}=f(z),W=Date.now(),$={engine:"chatgpt",mode:process.env.GREEDY_SEARCH_VISIBLE==="1"?"visible":"headless",clipboardEmpty:null,fallbackUsed:null,blockedBy:null,verificationResult:null,inputReady:null};try{if(!K)await Y(["list"]);let X=await L(K),J=await Y(["eval",X,"document.location.href"]).catch(()=>""),U=!1;try{U=new URL(J).hostname.toLowerCase()==="chatgpt.com"}catch{}if(!U)V($,"nav",W),await Y(["nav",X,"https://chatgpt.com"],20000),await new Promise((A)=>setTimeout(A,600));V($,"consent",W),await g(X,Y),V($,"verification",W),await b(X,Y,1e4),V($,"input-wait",W);let D=await w(X,p,8000,400);if($.inputReady=D,!D){let A=await Y(["eval",X,"document.body?.innerText || ''"]).catch(()=>"");if(/sign in|log in|sign up|\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7|login/i.test(A))throw Error("ChatGPT requires sign-in — please sign in in the visible browser window");throw Error("ChatGPT input not found — page may be blocked or in unexpected state")}V($,"clipboard-inject",W),await P(X,F),V($,"type-and-submit",W),await Dz(X,Z),V($,"stream-wait",W);let q=0;try{q=await Nz(X,20000)}catch(A){V($,"stream-poll-fallback",W),q=await Gz(X,15000)}if($.assistantTextLen=q,q<1)console.error("[chatgpt] Warning: assistant response may not have completed");V($,"extract",W);let{answer:N,sources:B,skipped:_}=await Vz(X,$);if(!N)throw $.blockedBy="no-response",$.skipped=_||null,Error(_==="no-user-message"?"ChatGPT still on homepage — query was not submitted":_==="no-assistant-response"?"ChatGPT did not return an assistant response after submit":"ChatGPT returned no answer — assistant never responded");V($,"done",W);let O=await Y(["eval",X,"document.location.href"]).catch(()=>"https://chatgpt.com");$.durationMs=Date.now()-W,R({query:Z,url:O,answer:y(N,Q),sources:B,_envelope:C($)})}catch(X){$.durationMs=Date.now()-W,console.error(`[chatgpt] error during stage '${$.lastStage||"unknown"}': ${X.message}`),v(X,C($))}}_z();
635
+ `]);try{return JSON.parse($)}catch(Z){return console.error(`[chatgpt] DOM fallback JSON parse failed (${$.length} chars): ${Z.message}`),{answer:"",sources:[],skipped:"parse-error"}}}async function wQ(Q,$){let Z=await S(Q,FQ,w,{timeoutMs:2600,retryClick:600});$.clipboardEmpty=!Z;let K=null;if(!Z){if(K=await PQ(Q),K.streaming)return{answer:"",sources:[],skipped:"still-streaming"};Z=K.answer,$.fallbackUsed=Z?"dom":null}if(Z){let Y=Z.trim(),q=Y.length>=5&&Y.length<=50&&/\s|[.,!?;:]/.test(Y),z=Y.length>50,D=Y.split(/\s+/).filter(Boolean),H=(Y.match(/\b[a-z0-9-]+\.(?:com|org|net|dev|io)\b/gi)||[]).length,G=H>=4&&H>=Math.max(3,Math.floor(D.length/3));if(!q&&!z||G)return console.error(`[chatgpt] DOM fallback answer suspicious (${Y.length} chars, domainRepeats=${H}: ${JSON.stringify(Y.slice(0,80))}) — returning empty for caller to retry`),$.fallbackUsed=null,{answer:"",sources:[],skipped:G?"citation-stub":"header-stub"}}if(!Z)return{answer:"",sources:[],skipped:"no-answer"};let J=f(Z),X=h(Z),W=new Map;for(let Y of[...K?.sources||[],...X,...J])if(Y?.url&&!W.has(Y.url))W.set(Y.url,Y);let U=Array.from(W.values()).slice(0,10);return{answer:Z.trim(),sources:U}}var kQ=`Usage: node extractors/chatgpt.mjs "<query>" [--tab <prefix>]
636
+ `;async function yQ(){let Q=await x(process.argv.slice(2));b(Q,kQ);let{query:$,tabPrefix:Z,short:K}=g(Q),J=Date.now(),W={engine:"chatgpt",mode:process.env.GREEDY_SEARCH_VISIBLE==="1"?"visible":"headless",clipboardEmpty:null,fallbackUsed:null,blockedBy:null,verificationResult:null,inputReady:null};try{if(!Z)await B(["list"]);let U=await E(Z),Y=await B(["eval",U,"document.location.href"]).catch(()=>""),q=!1;try{q=new URL(Y).hostname.toLowerCase()==="chatgpt.com"}catch{}if(!q)O(W,"nav",J),await B(["nav",U,"https://chatgpt.com"],20000),await new Promise((V)=>setTimeout(V,600));O(W,"consent",J),await c(U,B),O(W,"verification",J);let z=await n(U,B,1e4);if(W.verificationResult=z,z==="needs-human")throw W.blockedBy="cloudflare-closed-shadow-dom",Error("ChatGPT is showing a Cloudflare Turnstile challenge that auto-clicking could not clear — please solve it in the visible browser window");if(z==="clicked")await new Promise((V)=>setTimeout(V,2500));O(W,"input-wait",J);let D=await v(U,P,8000,400);if(W.inputReady=D,!D){let V=await B(["eval",U,"document.body?.innerText || ''"]).catch(()=>"");if(/sign in|log in|sign up|\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7|login/i.test(V))throw Error("ChatGPT requires sign-in — please sign in in the visible browser window");throw Error("ChatGPT input not found — page may be blocked or in unexpected state")}O(W,"clipboard-inject",J),await T(U,w),O(W,"type-and-submit",J),await IQ(U,$),O(W,"stream-wait",J);let H=0;try{H=await s(U,20000)}catch(V){O(W,"stream-poll-fallback",J),H=await CQ(U,15000)}if(W.assistantTextLen=H,H<1)console.error("[chatgpt] Warning: assistant response may not have completed");O(W,"extract",J);let G;for(let V=0;V<3;V++){if(V>0)try{await s(U,1e4)}catch{}if(G=await wQ(U,W),G.answer)break;if(V<2)console.error(`[chatgpt] Extract attempt ${V+1} returned empty, retrying in 2s...`),await new Promise((I)=>setTimeout(I,2000))}let{answer:M,sources:_,skipped:N}=G;if(!M){W.blockedBy="no-response",W.skipped=N||null;let V="ChatGPT returned no answer — assistant never responded";if(N==="no-user-message")V="ChatGPT still on homepage — query was not submitted";else if(N==="no-assistant-response")V="ChatGPT did not return an assistant response after submit";else if(N==="header-stub"||N==="citation-stub"||N==="still-streaming")V="ChatGPT response did not finish rendering after 3 retries — assistant never rendered the body";throw Error(V)}O(W,"done",J);let A=await B(["eval",U,"document.location.href"]).catch(()=>"https://chatgpt.com");W.durationMs=Date.now()-J,u({query:$,url:A,answer:m(M,K),sources:_,_envelope:C(W)})}catch(U){W.durationMs=Date.now()-J,console.error(`[chatgpt] error during stage '${W.lastStage||"unknown"}': ${U.message}`),p(U,C(W))}}yQ();