@duckmind/dm-darwin-arm64 0.52.3 → 0.53.1

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 (56) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +15 -8
  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 -10
  11. package/extensions/greedysearch-dm/bin/kill-visible.mjs +1 -1
  12. package/extensions/greedysearch-dm/bin/launch-visible.mjs +1 -4
  13. package/extensions/greedysearch-dm/bin/launch.mjs +9 -5
  14. package/extensions/greedysearch-dm/bin/mcp.mjs +375 -0
  15. package/extensions/greedysearch-dm/bin/search.mjs +310 -156
  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 +225 -92
  21. package/extensions/greedysearch-dm/extractors/consent.mjs +44 -17
  22. package/extensions/greedysearch-dm/extractors/gemini.mjs +275 -136
  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 +20 -17
  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 -9
  35. package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +48 -0
  36. package/extensions/greedysearch-dm/src/search/chrome.mjs +129 -53
  37. package/extensions/greedysearch-dm/src/search/constants.mjs +8 -8
  38. package/extensions/greedysearch-dm/src/search/engines.mjs +9 -9
  39. package/extensions/greedysearch-dm/src/search/fetch-source.mjs +154 -77
  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 -0
  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 +11 -11
  50. package/extensions/greedysearch-dm/src/search/synthesis.mjs +11 -11
  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/utils/browser-executable.mjs +0 -1
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import{randomInt as F}from"node:crypto";import{spawn as J}from"node:child_process";import{dirname as W,join as B}from"node:path";import{fileURLToPath as V}from"node:url";import{basename as U}from"node:path";function X(e=process.env,r=process.execPath){let t=e.GREEDY_SEARCH_NODE||e.NODE_BINARY||e.NODE;if(t?.trim())return t.trim();let n=U(r||"").toLowerCase();if(n==="node"||n==="node.exe")return r;return"node"}var Y=W(V(import.meta.url)),Q=B(Y,"..","bin","cdp.mjs");function c(e,r=30000){return Z(e,null,r)}function Z(e,r=null,t=30000){return new Promise((n,a)=>{let i=J(X(),[Q,...e],{stdio:[r==null?"ignore":"pipe","pipe","pipe"]});if(r!=null)i.stdin.write(r),i.stdin.end();let o="",l="";i.stdout.on("data",(u)=>o+=u),i.stderr.on("data",(u)=>l+=u);let s=setTimeout(()=>{i.kill(),a(Error(`cdp timeout: ${e[0]}`))},t);i.on("close",(u)=>{if(clearTimeout(s),u===0)n(o.trim());else a(Error(l.trim()||`cdp exit ${u}`))})})}async function S(e){if(e)return e;let r=(await c(["list"])).split(`
3
- `)[0]?.slice(0,8);if(!r)throw Error("No Chrome tabs found. Is Chrome running with --remote-debugging-port=9222?");let t=await c(["evalraw",r,"Target.createTarget",'{"url":"about:blank"}']),{targetId:n}=JSON.parse(t);await c(["list"]);let a=n.slice(0,8);try{await G(a)}catch(i){process.stderr.write(`[getOrOpenTab] stealth injection failed: ${i.message}
4
- `)}return a}async function T(e,r){let t=`
2
+ import{randomInt as U}from"node:crypto";import{spawn as Y}from"node:child_process";import{basename as X}from"node:path";import{dirname as G,join as z}from"node:path";import{fileURLToPath as Q}from"node:url";function Z(e=process.env,t=process.execPath){let r=e.GREEDY_SEARCH_NODE||e.NODE_BINARY||e.NODE;if(r?.trim())return r.trim();let n=X(t||"").toLowerCase();if(n==="node"||n==="node.exe")return t;return"node"}var K=G(Q(import.meta.url)),ee=z(K,"..","bin","cdp.mjs"),te=new Set(["list","snap","eval","shot","html","nav","net","click","clickxy","type","loadall","evalraw","browse","stop","--tab"]);function re(e){if(!Array.isArray(e)||e.length===0)throw Error("cdp: args must be a non-empty array");if(e[0]==="test")return e.map((t,r)=>O(t,r));if(!te.has(e[0]))throw Error(`cdp: unknown subcommand '${e[0]}'`);return e.map((t,r)=>O(t,r))}function O(e,t){if(typeof e!=="string")throw Error(`cdp: argv[${t}] must be a string (got ${typeof e})`);if(e.includes("\x00"))throw Error(`cdp: argv[${t}] contains a null byte`);return e}function c(e,t=30000){return ne(e,null,t)}function ne(e,t=null,r=30000){let n=re(e);return new Promise((a,l)=>{let i=Y(Z(),[ee,...n],{stdio:[t==null?"ignore":"pipe","pipe","pipe"]});if(t!=null)i.stdin.write(t),i.stdin.end();let o="",s="";i.stdout.on("data",(u)=>o+=u),i.stderr.on("data",(u)=>s+=u);let f=setTimeout(()=>{i.kill(),l(Error(`cdp timeout: ${e[0]}`))},r);i.on("close",(u)=>{if(clearTimeout(f),u===0)a(o.trim());else l(Error(s.trim()||`cdp exit ${u}`))})})}async function C(e){if(e)return e;let t=(await c(["list"])).split(`
3
+ `)[0]?.slice(0,8);if(!t)throw Error("No Chrome tabs found. Is Chrome running with --remote-debugging-port=9222?");let r=await c(["evalraw",t,"Target.createTarget",'{"url":"about:blank"}']),n;try{n=JSON.parse(r)}catch(i){throw Error(`Target.createTarget returned invalid JSON: ${i.message}`)}let{targetId:a}=n;if(!a)throw Error("Target.createTarget did not return a targetId");await c(["list"]);let l=a.slice(0,8);try{await ie(l)}catch(i){process.stderr.write(`[getOrOpenTab] stealth injection failed: ${i.message}
4
+ `)}return l}async function S(e,t){let r=`
5
5
  (() => {
6
- window.${r} = null;
6
+ window.${t} = 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 F}from"node:crypto";import{spawn as J}from"node:child_proces
14
14
  : null;
15
15
 
16
16
  _clipboard.writeText = function(text) {
17
- window.${r} = String(text ?? '');
17
+ window.${t} = 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 F}from"node:crypto";import{spawn as J}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.${r} = await blob.text();
31
+ window.${t} = await blob.text();
32
32
  break;
33
33
  }
34
34
  }
@@ -38,7 +38,26 @@ import{randomInt as F}from"node:crypto";import{spawn as J}from"node:child_proces
38
38
  catch (_) { return undefined; }
39
39
  };
40
40
  })();
41
- `;await c(["eval",e,t])}async function G(e){await c(["evalraw",e,"Page.addScriptToEvaluateOnNewDocument",JSON.stringify({source:`
41
+ `;await c(["eval",e,r])}async function D(e,t,r,n={}){let{timeoutMs:a=2600}=n,l=n.retryClick!=null?n.retryClick:Math.floor(a*0.4),i=String.raw`
42
+ new Promise((resolve) => {
43
+ const _deadline = Date.now() + ${a};
44
+ const _retryAt = Date.now() + ${l};
45
+ let _retried = false;
46
+ function _click() { try { ${t}; } catch(_) {} }
47
+ function _poll() {
48
+ const val = window.${r};
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.${r} || ''); }
56
+ }
57
+ _click();
58
+ _poll();
59
+ })
60
+ `;return await c(["eval",e,i],a+5000).catch(()=>"")}async function ie(e){await c(["evalraw",e,"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 F}from"node:crypto";import{spawn as J}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 F}from"node:crypto";import{spawn as J}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 F}from"node:crypto";import{spawn as J}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 F}from"node:crypto";import{spawn as J}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 F}from"node:crypto";import{spawn as J}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 F}from"node:crypto";import{spawn as J}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 F}from"node:crypto";import{spawn as J}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 F}from"node:crypto";import{spawn as J}from"node:child_proces
246
342
  };
247
343
  } catch(_) {}
248
344
  })();
249
- `})])}function C(e){if(!e)return[];let r=[],t=0;while(t<e.length&&r.length<10){let n=e.indexOf("[",t);if(n===-1)break;let a=e.indexOf("](",n);if(a===-1)break;let i=a+2,o=-1;for(let l=i;l<e.length;l++){let s=e[l];if(s===")"){o=l;break}if(/\s/.test(s))break}if(o!==-1){let l=e.slice(n+1,a),s=e.slice(i,o);if(/^https?:\/\//i.test(s)&&l){if(!r.some((u)=>u.url===s))r.push({title:l,url:s})}t=o+1}else t=n+1}return r}var O={postNav:800,postNavSlow:1200,postClick:300,postType:300,inputPoll:400,copyPoll:600,afterVerify:1500};function v(e){let r=e*0.4,t=F(-Math.floor(r),Math.floor(r)+1);return Math.max(50,Math.round(e+t))}async function N(e,r={}){let{timeout:t=20000,interval:n=600,stableRounds:a=3,selector:i="document.body",minLength:o=0}=r,l=String.raw`
345
+ `})])}function N(e){if(!e)return[];let t=[],r=0;while(r<e.length&&t.length<10){let n=e.indexOf("[",r);if(n===-1)break;let a=e.indexOf("](",n);if(a===-1)break;let l=a+2,i=-1;for(let o=l;o<e.length;o++){let s=e[o];if(s===")"){i=o;break}if(/\s/.test(s))break}if(i!==-1){let o=e.slice(n+1,a),s=e.slice(l,i);if(/^https?:\/\//i.test(s)&&o){if(!t.some((f)=>f.url===s))t.push({title:o,url:s})}r=i+1}else r=n+1}return t}var $={postNav:800,postNavSlow:1200,postClick:300,postType:300,inputPoll:400,copyPoll:600,afterVerify:1500};function P(e){let t=e*0.2,r=U(-Math.floor(t),Math.floor(t)+1);return Math.max(50,Math.round(e+r))}async function M(e,t={}){let{timeout:r=20000,interval:n=600,stableRounds:a=3,selector:l="document.body",minLength:i=0,isStreamingExpr:o="false"}=t,s=String.raw`
250
346
  new Promise((resolve, reject) => {
251
- const _deadline = Date.now() + ${t};
347
+ const _deadline = Date.now() + ${r};
252
348
  const _baseInterval = ${n};
253
349
  const _stableRounds = ${a};
254
- const _minLength = ${o};
350
+ const _minLength = ${i};
255
351
  let _lastLen = -1;
256
352
  let _stableCount = 0;
257
353
 
@@ -262,9 +358,13 @@ import{randomInt as F}from"node:crypto";import{spawn as J}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 = ${i};
266
- const cur = el?.innerText?.length ?? 0;
267
- if (cur >= _minLength) {
361
+ const el = ${l};
362
+ const cur = el?.textContent?.length ?? 0;
363
+ const streaming = ${o};
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,17 +376,17 @@ import{randomInt as F}from"node:crypto";import{spawn as J}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 ${t}ms')); }
379
+ if (_lastLen >= _minLength && !streaming) { resolve(_lastLen); }
380
+ else { reject(new Error('Generation did not stabilise within ${r}ms')); }
281
381
  }
282
382
  } catch(e) { reject(e); }
283
383
  }
284
384
 
285
385
  _poll();
286
386
  })
287
- `,s=await c(["eval",e,l],t+1e4),u=parseInt(s,10)||0;if(u>=o)return u;throw Error(`Generation did not stabilise within ${t}ms`)}async function D(e,r,t=15000,n=500){let a=String.raw`
387
+ `,f=await c(["eval",e,s],r+1e4),u=parseInt(f,10)||0;if(u>=i)return u;throw Error(`Generation did not stabilise within ${r}ms`)}async function I(e,t,r=15000,n=500){let a=String.raw`
288
388
  new Promise((resolve) => {
289
- const _deadline = Date.now() + ${t};
389
+ const _deadline = Date.now() + ${r};
290
390
  const _baseInterval = ${n};
291
391
 
292
392
  function _jitter(ms) {
@@ -295,7 +395,7 @@ import{randomInt as F}from"node:crypto";import{spawn as J}from"node:child_proces
295
395
 
296
396
  function _poll() {
297
397
  try {
298
- if (document.querySelector('${r}')) { resolve(true); return; }
398
+ if (document.querySelector('${t}')) { 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 F}from"node:crypto";import{spawn as J}from"node:child_proces
303
403
 
304
404
  _poll();
305
405
  })
306
- `;return await c(["eval",e,a],t+5000)==="true"}async function A(e){let r=e.indexOf("--stdin");if(r===-1)return e;let t=await new Promise((a)=>{let i="";process.stdin.setEncoding("utf8"),process.stdin.on("data",(o)=>i+=o),process.stdin.on("end",()=>a(i.trim()))}),n=[...e];return n[r]=t,n}function I(e){let r=e.includes("--short"),t=e.filter((l)=>l!=="--short"),n=t.indexOf("--tab"),a=n===-1?null:t[n+1];if(n!==-1)t=t.filter((l,s)=>s!==n&&s!==n+1);let i=t.indexOf("--locale"),o=i===-1?null:t[i+1];if(i!==-1)t=t.filter((l,s)=>s!==i&&s!==i+1);return{query:t.join(" "),tabPrefix:a,short:r,locale:o}}function $(e,r){if(!e.length||e[0]==="--help")process.stderr.write(r),process.exit(1)}function E(e,r,t=300){if(!r||e.length<=t)return e;let n=e.slice(0,t),a=n.lastIndexOf(" ");return a>0?`${n.slice(0,a)}…`:`${n}…`}function M(e){process.stdout.write(`${JSON.stringify(e,null,2)}
307
- `)}function _({engine:e,mode:r="headless",clipboardEmpty:t=null,fallbackUsed:n=null,blockedBy:a=null,verificationResult:i=null,inputReady:o=null,durationMs:l=null,lastStage:s=null,stages:u=null}={}){return{engine:e,mode:r,clipboardEmpty:t,fallbackUsed:n,blockedBy:a,verificationResult:i,inputReady:o,durationMs:l,lastStage:s,stages:u}}function R(e,r=null){if(r){let t=JSON.stringify({_envelope:r,error:e.message});process.stdout.write(`${t}
406
+ `;return await c(["eval",e,a],r+5000)==="true"}async function A(e){let t=e.indexOf("--stdin");if(t===-1)return e;let r=await new Promise((a)=>{let l="";process.stdin.setEncoding("utf8"),process.stdin.on("data",(i)=>l+=i),process.stdin.on("end",()=>a(l.trim()))}),n=[...e];return n[t]=r,n}function E(e){let t=e.includes("--short"),r=e.filter((o)=>o!=="--short"),n=r.indexOf("--tab"),a=n===-1?null:r[n+1];if(n!==-1)r=r.filter((o,s)=>s!==n&&s!==n+1);let l=r.indexOf("--locale"),i=l===-1?null:r[l+1];if(l!==-1)r=r.filter((o,s)=>s!==l&&s!==l+1);return{query:r.join(" "),tabPrefix:a,short:t,locale:i}}function j(e,t){if(!e.length||e[0]==="--help")process.stderr.write(t),process.exit(1)}function L(e,t,r=300){if(!t||e.length<=r)return e;let n=e.slice(0,r),a=n.lastIndexOf(" ");return a>0?`${n.slice(0,a)}…`:`${n}…`}function R(e){process.stdout.write(`${JSON.stringify(e,null,2)}
407
+ `)}function k({engine:e,mode:t="headless",clipboardEmpty:r=null,fallbackUsed:n=null,blockedBy:a=null,verificationResult:l=null,inputReady:i=null,durationMs:o=null,lastStage:s=null,stages:f=null}={}){return{engine:e,mode:t,clipboardEmpty:r,fallbackUsed:n,blockedBy:a,verificationResult:l,inputReady:i,durationMs:o,lastStage:s,stages:f}}function F(e,t=null){if(t){let r=JSON.stringify({_envelope:t,error:e.message});process.stdout.write(`${r}
308
408
  `)}process.stderr.write(`Error: ${e.message}
309
- `),process.exit(1)}import{randomInt as K}from"node:crypto";import{existsSync as z,readFileSync as ee}from"node:fs";import te from"node:http";var re=`
409
+ `),process.exit(1)}import{randomInt as ae}from"node:crypto";import{existsSync as oe,readFileSync as le}from"node:fs";import se from"node:http";var ce=`
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 F}from"node:crypto";import{spawn as J}from"node:child_proces
323
423
 
324
424
  return null;
325
425
  })()
326
- `,ne=`
426
+ `,ue=`
327
427
  (function() {
328
428
  var url = document.location.href;
329
429
 
@@ -354,12 +454,24 @@ import{randomInt as F}from"node:crypto";import{spawn as J}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 F}from"node:crypto";import{spawn as J}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 F}from"node:crypto";import{spawn as J}from"node:child_proces
393
518
 
394
519
  return null;
395
520
  })()
396
- `,ie=`
521
+ `,de=`
397
522
  (function() {
398
523
  var url = document.location.href;
399
524
  var isVerifyPage = url.includes('/sorry/') ||
@@ -427,16 +552,157 @@ import{randomInt as F}from"node:crypto";import{spawn as J}from"node:child_proces
427
552
 
428
553
  return 'still-verifying';
429
554
  })()
430
- `;async function x(e,r){let t=await r(["eval",e,re]).catch(()=>null);if(t&&t!=="null")await new Promise((n)=>setTimeout(n,1500))}function d(e,r){return K(e*1000,r*1000)/1000}async function ae(e,r){if(!globalThis.WebSocket)return;let t=process.env.CDP_PROFILE_DIR;if(!t)return;let n=`${t.replaceAll("\\","/")}/DevToolsActivePort`;if(!z(n))return;let a=ee(n,"utf8").trim().split(`
431
- `)[0],i=await new Promise((s,u)=>{let m=te.get(`http://localhost:${a}/json/version`,(p)=>{let g="";p.on("data",(y)=>g+=y),p.on("end",()=>{try{s(JSON.parse(g))}catch{u(Error("bad JSON"))}})});m.on("error",u),m.setTimeout(1000,()=>{m.destroy(),u(Error("timeout"))})}),o=new globalThis.WebSocket(i.webSocketDebuggerUrl),l=0;await new Promise((s)=>{o.onopen=async()=>{let u=(g,y)=>new Promise((w)=>{let f=++l,b=(h)=>{if(JSON.parse(h.data).id===f)o.removeEventListener("message",b),w()};o.addEventListener("message",b),o.send(JSON.stringify({id:f,method:g,params:y}))}),m=e+d(-2,2),p=r+d(-2,2);await u("Input.dispatchMouseEvent",{type:"mouseMoved",x:m,y:p,button:"none"}),await new Promise((g)=>setTimeout(g,d(80,160))),await u("Input.dispatchMouseEvent",{type:"mousePressed",x:m,y:p,button:"left",clickCount:1}),await new Promise((g)=>setTimeout(g,d(30,80))),await u("Input.dispatchMouseEvent",{type:"mouseReleased",x:m+d(-1,1),y:p+d(-1,1),button:"left",clickCount:1}),setTimeout(()=>{o.close(),s()},200)},o.onerror=()=>s(),setTimeout(s,3000)})}async function q(e,r,t,n){let a=Number.parseFloat(t),i=Number.parseFloat(n);if(Number.isNaN(a)||Number.isNaN(i))throw Error(`humanClickXY: invalid coordinates (${t}, ${n})`);let o={button:"left",clickCount:1,modifiers:0},l=a+d(-3,3),s=i+d(-3,3);await r(["evalraw",e,"Input.dispatchMouseEvent",JSON.stringify({...o,type:"mouseMoved",x:l,y:s})]),await new Promise((y)=>setTimeout(y,d(80,180)));let u=a+d(-2,2),m=i+d(-2,2);await r(["evalraw",e,"Input.dispatchMouseEvent",JSON.stringify({...o,type:"mousePressed",x:u,y:m})]),await new Promise((y)=>setTimeout(y,d(30,90)));let p=u+d(-1,1),g=m+d(-1,1);return await r(["evalraw",e,"Input.dispatchMouseEvent",JSON.stringify({...o,type:"mouseReleased",x:p,y:g})]),await ae(a,i).catch(()=>{}),await new Promise((y)=>setTimeout(y,d(100,300))),`human-clicked at (${a.toFixed(0)}, ${i.toFixed(0)})`}async function oe(e,r,t){let n=await r(["eval",e,`(function() {
432
- var el = document.querySelector('${t.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}');
555
+ `;async function T(e,t){let r=await t(["eval",e,ce]).catch(()=>null);if(r&&r!=="null")await new Promise((n)=>setTimeout(n,1500))}function y(e,t){return ae(e*1000,t*1000)/1000}async function fe(e,t){if(!globalThis.WebSocket)return;let r=process.env.CDP_PROFILE_DIR;if(!r)return;let n=`${r.replaceAll("\\","/")}/DevToolsActivePort`;if(!oe(n))return;let a=le(n,"utf8").trim().split(`
556
+ `)[0],l=await new Promise((s,f)=>{let u=se.get(`http://localhost:${a}/json/version`,(m)=>{let g="";m.on("data",(p)=>g+=p),m.on("end",()=>{try{s(JSON.parse(g))}catch{f(Error("bad JSON"))}})});u.on("error",f),u.setTimeout(1000,()=>{u.destroy(),f(Error("timeout"))})}),i=new globalThis.WebSocket(l.webSocketDebuggerUrl),o=0;await new Promise((s)=>{i.onopen=async()=>{let f=(g,p)=>new Promise((_)=>{let v=++o,d=(h)=>{if(JSON.parse(h.data).id===v)i.removeEventListener("message",d),_()};i.addEventListener("message",d),i.send(JSON.stringify({id:v,method:g,params:p}))}),u=e+y(-2,2),m=t+y(-2,2);await f("Input.dispatchMouseEvent",{type:"mouseMoved",x:u,y:m,button:"none"}),await new Promise((g)=>setTimeout(g,y(80,160))),await f("Input.dispatchMouseEvent",{type:"mousePressed",x:u,y:m,button:"left",clickCount:1}),await new Promise((g)=>setTimeout(g,y(30,80))),await f("Input.dispatchMouseEvent",{type:"mouseReleased",x:u+y(-1,1),y:m+y(-1,1),button:"left",clickCount:1}),setTimeout(()=>{i.close(),s()},200)},i.onerror=()=>s(),setTimeout(s,3000)})}async function W(e,t,r,n){let a=Number.parseFloat(r),l=Number.parseFloat(n);if(Number.isNaN(a)||Number.isNaN(l))throw Error(`humanClickXY: invalid coordinates (${r}, ${n})`);let i={button:"left",clickCount:1,modifiers:0},o=a+y(-3,3),s=l+y(-3,3);await t(["evalraw",e,"Input.dispatchMouseEvent",JSON.stringify({...i,type:"mouseMoved",x:o,y:s})]),await new Promise((p)=>setTimeout(p,y(80,180)));let f=a+y(-2,2),u=l+y(-2,2);await t(["evalraw",e,"Input.dispatchMouseEvent",JSON.stringify({...i,type:"mousePressed",x:f,y:u})]),await new Promise((p)=>setTimeout(p,y(30,90)));let m=f+y(-1,1),g=u+y(-1,1);return await t(["evalraw",e,"Input.dispatchMouseEvent",JSON.stringify({...i,type:"mouseReleased",x:m,y:g})]),await fe(a,l).catch(()=>{}),await new Promise((p)=>setTimeout(p,y(100,300))),`human-clicked at (${a.toFixed(0)}, ${l.toFixed(0)})`}async function ge(e,t,r){let n=await t(["eval",e,`(function() {
557
+ var el = document.querySelector('${r.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(!n||n==="null")return null;let a=JSON.parse(n);if(a.w===0||a.h===0||a.x===0&&a.y===0)return null;let{x:i,y:o}=a;return q(e,r,i,o)}async function j(e,r,t){if(!t||t==="null"||t==="cleared"||t==="still-verifying")return!1;try{let n=JSON.parse(t);if(n.t==="sel"&&n.s)return process.stderr.write(`[greedysearch] Human-clicking "${n.txt}" via CDP...
437
- `),await oe(e,r,n.s)!==null;if(n.t==="xy"){if(!n.x&&!n.y)return!1;return process.stderr.write(`[greedysearch] Human-clicking at (${n.x.toFixed(0)}, ${n.y.toFixed(0)})...
438
- `),await q(e,r,n.x,n.y),!0}}catch{}return!1}async function le(e,r){let t=await r(["eval",e,ne]).catch(()=>null);return t&&t!=="null"?t:null}async function L(e,r,t=30000){let n=await le(e,r);if(!n)return"clear";if(n==="sorry-page"){process.stderr.write(`[greedysearch] Google CAPTCHA detected please solve it in the browser window (waiting up to ${Math.floor(t/1000)}s)...
439
- `);let a=Date.now()+t;while(Date.now()<a)if(await new Promise((i)=>setTimeout(i,2000)),!(await r(["eval",e,"document.location.href"]).catch(()=>"")).includes("/sorry/"))return"cleared-by-user";return"needs-human"}if(await j(e,r,n)){await new Promise((i)=>setTimeout(i,2000));let a=Date.now()+t;while(Date.now()<a){let i=await r(["eval",e,ie]).catch(()=>null);if(i==="cleared"||!i||i==="null")return process.stderr.write(`[greedysearch] Verification cleared.
440
- `),"clicked";if(i!=="still-verifying")await j(e,r,i),await new Promise((o)=>setTimeout(o,2000));else await new Promise((o)=>setTimeout(o,1500))}return process.stderr.write(`[greedysearch] Verification may require manual intervention.
441
- `),"needs-human"}return"clear"}var H={perplexity:{input:"#ask-input",copyButton:null,sourceItem:"[data-pplx-citation-url]",sourceLink:"a",consent:"#onetrust-accept-btn-handler"},bing:{input:"#userInput",copyButton:'button[data-testid="copy-ai-message-button"]',sourceLink:'a[href^="http"][target="_blank"]',sourceExclude:"copilot.microsoft.com",consent:"#onetrust-accept-btn-handler"},google:{answerContainer:".pWvJNd",sourceLink:'a[href^="http"]',sourceExclude:["google.","gstatic","googleapis"],sourceHeadingParent:"[data-snhf]",consent:'#L2AGLb, button[jsname="b3VHJd"], .tHlp8d'},gemini:{input:"rich-textarea .ql-editor",copyButton:'button:has(mat-icon[data-mat-icon-name="copy"])',sendButton:'button:has(mat-icon[data-mat-icon-name="arrow_upward"]), [data-test-id="send-button"], .send-button',sourcesSidebarButton:"button.legacy-sources-sidebar-button",sourcesExclude:["gemini.google","gstatic","google.com/search"],citationButtonPattern:'button[aria-label*="citation from"]',citationNameRegex:/from\s{1,20}([^.]{1,200})\.\s/}};var k=H.perplexity,P="__pplxClipboard";function se(){return"Array.from(document.querySelectorAll('button')).filter(b => b.innerHTML.includes('#pplx-icon-copy')).pop()"}async function ue(e,r){let t=se();await c(["eval",e,`${t}?.click()`]),await new Promise((i)=>setTimeout(i,400));let n=await c(["eval",e,`window.${P} || ''`]);if(r.clipboardEmpty=!n,!n)console.error("[perplexity] Clipboard empty, retrying in 2s..."),await c(["eval",e,`${t}?.click()`]),await new Promise((i)=>setTimeout(i,2000)),n=await c(["eval",e,`window.${P} || ''`]),r.clipboardEmpty=!n;if(!n)throw Error("Clipboard interceptor returned empty text");let a=C(n);return{answer:n.trim(),sources:a}}var ce=`Usage: node extractors/perplexity.mjs "<query>" [--tab <prefix>]
442
- `;async function fe(){let e=await A(process.argv.slice(2));$(e,ce);let{query:r,tabPrefix:t,short:n}=I(e),a=Date.now(),o={engine:"perplexity",mode:process.env.GREEDY_SEARCH_VISIBLE==="1"?"visible":"headless",clipboardEmpty:null,fallbackUsed:null,blockedBy:null,verificationResult:null,inputReady:null};try{if(!t)await c(["list"]);let l=await S(t),s=await c(["eval",l,"document.location.href"]).catch(()=>""),u=!1;try{let f=new URL(s).hostname.toLowerCase();u=f==="perplexity.ai"||f.endsWith(".perplexity.ai")}catch{}if(!u)await c(["nav",l,"https://www.perplexity.ai/"],20000),await new Promise((f)=>setTimeout(f,800));let m=await L(l,c,1e4);if(o.verificationResult=m,m==="needs-human")throw Error("Perplexity verification required — please solve it manually in the browser window");if(await x(l,c),m==="clicked"){await new Promise((h)=>setTimeout(h,O.afterVerify));let f=await c(["eval",l,"document.location.href"]).catch(()=>""),b=!1;try{let h=new URL(f).hostname.toLowerCase();b=h==="perplexity.ai"||h.endsWith(".perplexity.ai")}catch{}if(!b)await c(["nav",l,"https://www.perplexity.ai/"],20000),await new Promise((h)=>setTimeout(h,800)),await x(l,c)}if(process.env.GREEDY_SEARCH_HEADLESS==="1"){let f=await c(["snap",l]).catch(()=>"");if(/cloudflare|challenge|security check/i.test(f))throw console.error("[perplexity] Cloudflare challenge in snap — fast-failing to visible retry"),o.blockedBy="cloudflare",Error("Cloudflare challenge detected — headless blocked")}let p=await D(l,k.input,15000,400);if(o.inputReady=p,!p)throw Error("Perplexity input not found — page may not have loaded or is in unexpected state");await new Promise((f)=>setTimeout(f,v(300))),await T(l,P),await c(["click",l,k.input]),await new Promise((f)=>setTimeout(f,v(400))),await c(["type",l,r]),await new Promise((f)=>setTimeout(f,v(400))),await c(["eval",l,`document.querySelector('${k.input}')?.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',bubbles:true,keyCode:13})), 'ok'`]),await N(l,{timeout:20000,interval:600,stableRounds:3,selector:"document.body"});let{answer:g,sources:y}=await ue(l,o);if(!g)throw Error("No answer extracted — Perplexity may not have responded");let w=await c(["eval",l,"document.location.href"]).catch(()=>"");o.durationMs=Date.now()-a,M({query:r,url:w,answer:E(g,n),sources:y,_envelope:_(o)})}catch(l){o.durationMs=Date.now()-a,R(l,_(o))}}fe();
561
+ })()`]).catch(()=>"null");if(!n||n==="null")return null;let a=JSON.parse(n);if(a.w===0||a.h===0||a.x===0&&a.y===0)return null;let{x:l,y:i}=a;return W(e,t,l,i)}function J(e,t,r){if(!r||r==="null"||r==="cleared"||r==="still-verifying"||r==="cf-closed-shadow-dom")return Promise.resolve("no-challenge");try{let n=JSON.parse(r);if(n.t==="sel"&&n.s)return process.stderr.write(`[greedysearch] Human-clicking "${n.txt}" via CDP...
562
+ `),ge(e,t,n.s).then((a)=>a!==null?"clicked":"cant-click");if(n.t==="xy"){if(!n.x&&!n.y)return Promise.resolve("cant-click");return process.stderr.write(`[greedysearch] Human-clicking at (${n.x.toFixed(0)}, ${n.y.toFixed(0)})...
563
+ `),W(e,t,n.x,n.y).then(()=>"clicked")}}catch{}return Promise.resolve("no-challenge")}async function me(e,t){let r=await t(["eval",e,ue]).catch(()=>null);if(r==="cf-closed-shadow-dom"){let n=await pe(e,t).catch(()=>null);if(n)return n;return r}if(r&&r!=="null")return r;return null}async function pe(e,t){if(typeof t!=="function")return null;await t(["evalraw",e,"DOM.enable","{}"]).catch(()=>{});let r=await t(["evalraw",e,"DOM.getDocument",JSON.stringify({depth:-1,pierce:!0})]).catch(()=>null);if(!r)return null;let n;try{n=JSON.parse(r)}catch{return null}if(n.error||!n.root)return null;let a=n.root;return await q(a,e,t)}async function q(e,t,r){if(!e)return null;let n=[];if(e.shadowRoots&&e.shadowRoots.length>0)for(let a of e.shadowRoots)n.push(a);if(e.children)for(let a of e.children)n.push(a);for(let a of n){if(a.nodeName==="IFRAME"){let i=a.attributes||[],o=i.indexOf("src"),s=o>=0?i[o+1]:"";if(s&&/challenges\.cloudflare\.com|turnstile/i.test(s)&&a.backendNodeId){let f=await r(["evalraw",t,"DOM.getBoxModel",JSON.stringify({backendNodeId:a.backendNodeId})]).catch(()=>null);if(!f)continue;let u;try{u=JSON.parse(f)}catch{continue}let m=u?.model?.content||u?.result?.model?.content;if(!m||m.length<8)continue;let g=m[0],p=m[1],_=m[4],v=m[5],d=_-g,h=v-p;if(d<50||h<20)continue;let w=g+d*0.25,x=p+h*0.5;return process.stderr.write(`[greedysearch] Found CF iframe via CDP pierce at (${g.toFixed(0)}, ${p.toFixed(0)}) ${d.toFixed(0)}x${h.toFixed(0)}, clicking checkbox at (${w.toFixed(0)}, ${x.toFixed(0)})
564
+ `),JSON.stringify({t:"xy",x:w,y:x})}}let l=await q(a,t,r);if(l)return l}return null}async function H(e,t,r=30000){let n=await me(e,t);if(!n)return"clear";if(n==="sorry-page"){process.stderr.write(`[greedysearch] Google CAPTCHA detected — please solve it in the browser window (waiting up to ${Math.floor(r/1000)}s)...
565
+ `);let l=Date.now()+r;while(Date.now()<l)if(await new Promise((i)=>setTimeout(i,2000)),!(await t(["eval",e,"document.location.href"]).catch(()=>"")).includes("/sorry/"))return"cleared-by-user";return"needs-human"}let a=await J(e,t,n);if(a==="clicked"){await new Promise((i)=>setTimeout(i,2000));let l=Date.now()+r;while(Date.now()<l){let i=await t(["eval",e,de]).catch(()=>null);if(i==="cleared"||!i||i==="null")return process.stderr.write(`[greedysearch] Verification cleared.
566
+ `),"clicked";if(i!=="still-verifying")await J(e,t,i),await new Promise((o)=>setTimeout(o,2000));else await new Promise((o)=>setTimeout(o,1500))}return process.stderr.write(`[greedysearch] Verification may require manual intervention.
567
+ `),"needs-human"}if(a==="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 B={perplexity:{input:"#ask-input",copyButton:null,sourceItem:"[data-pplx-citation-url]",sourceLink:"a",consent:"#onetrust-accept-btn-handler"},bing:{input:"#userInput",copyButton:'button[data-testid="copy-ai-message-button"]',sourceLink:'a[href^="http"][target="_blank"]',sourceExclude:"copilot.microsoft.com",consent:"#onetrust-accept-btn-handler"},google:{answerContainer:".pWvJNd",sourceLink:'a[href^="http"]',sourceExclude:["google.","gstatic","googleapis"],sourceHeadingParent:"[data-snhf]",consent:'#L2AGLb, button[jsname="b3VHJd"], .tHlp8d'},gemini:{input:"rich-textarea .ql-editor",copyButton:'button:has(mat-icon[data-mat-icon-name="copy"])',sendButton:'button:has(mat-icon[data-mat-icon-name="arrow_upward"]), [data-test-id="send-button"], .send-button',sourcesSidebarButton:"button.legacy-sources-sidebar-button",sourcesExclude:["gemini.google","gstatic","google.com/search"],citationButtonPattern:'button[aria-label*="citation from"]',citationNameRegex:/from\s{1,20}([^.]{1,200})\.\s/}};var b=B.perplexity,V="__pplxClipboard";function he(){return"Array.from(document.querySelectorAll('button')).filter(b => b.innerHTML.includes('#pplx-icon-copy')).pop()"}async function ye(e,t){function r(l){let i=(l||"").trim();if(i.length>50)return!0;return i.length>=5&&/\s|[.,!?;:]/.test(i)}if(await c(["eval",e,`new Promise((resolve) => {
569
+ const _deadline = Date.now() + 8000;
570
+ function _checkNav() {
571
+ const url = document.location.href;
572
+ if (url.includes('/search/') || url.includes('/thread/') || url.match(/perplexity.ai\\/[^/]+/)) {
573
+ resolve('navigated');
574
+ } else if (Date.now() < _deadline) {
575
+ setTimeout(_checkNav, 300);
576
+ } else {
577
+ resolve('timeout');
578
+ }
579
+ }
580
+ _checkNav();
581
+ })`],1e4).catch(()=>"timeout")==="timeout")return null;let a=await c(["eval",e,`new Promise((resolve) => {
582
+ const _deadline = Date.now() + 5000;
583
+ function _looksLikeAnswerText(text) {
584
+ const t = (text || '').trim();
585
+ if (t.length > 50) return true;
586
+ return t.length >= 5 && /\\s|[.,!?;:]/.test(t);
587
+ }
588
+ function _tryExtract() {
589
+ try {
590
+ // Strategy 1: Find .prose block that's NOT the question
591
+ // and NOT in the sidebar/nav. The answer is the last .prose
592
+ // that contains substantial text and is in the main content area.
593
+ const proseBlocks = Array.from(document.querySelectorAll('.prose, [class*="prose"]'));
594
+ const candidates = proseBlocks.filter(el => {
595
+ const text = el.innerText?.trim() || '';
596
+ if (!_looksLikeAnswerText(text)) return false;
597
+ // Exclude sidebar/nav (they're usually in <nav> or <aside> or have specific classes)
598
+ if (el.closest('nav, aside, [role="navigation"], [class*="sidebar"], [class*="nav-"]')) return false;
599
+ return true;
600
+ });
601
+ if (candidates.length > 0) {
602
+ const last = candidates[candidates.length - 1];
603
+ return resolve(JSON.stringify({ answer: last.innerText.trim(), method: 'prose' }));
604
+ }
605
+
606
+ // Strategy 2: Look for the answer container by data attributes
607
+ // Perplexity uses [data-testid*="answer"] or [class*="answer-content"]
608
+ const answerContainer = document.querySelector('[data-testid*="answer"], [class*="answer-content"], [class*="response-content"]');
609
+ if (answerContainer && _looksLikeAnswerText(answerContainer.innerText?.trim())) {
610
+ return resolve(JSON.stringify({ answer: answerContainer.innerText.trim(), method: 'answer-container' }));
611
+ }
612
+
613
+ // Strategy 3: Find the largest text block in the main content area
614
+ // (not in nav/aside/sidebar), positioned after the input.
615
+ const input = document.querySelector('${b.input}');
616
+ if (!input) return resolve(null);
617
+ const inputRect = input.getBoundingClientRect();
618
+ const main = document.querySelector('main, [role="main"], [class*="main-content"]') || document.body;
619
+ const blocks = Array.from(main.querySelectorAll('div, article, section'))
620
+ .filter(d => {
621
+ const r = d.getBoundingClientRect();
622
+ if (r.top <= inputRect.bottom) return false; // not below input
623
+ if (r.width === 0 || r.height === 0) return false; // not visible
624
+ if (d.closest('nav, aside, [role="navigation"], [class*="sidebar"]')) return false; // not in nav
625
+ const text = d.innerText?.trim() || '';
626
+ return _looksLikeAnswerText(text) && d.children.length < 20;
627
+ })
628
+ .sort((a, b) => (b.innerText?.length || 0) - (a.innerText?.length || 0));
629
+ if (blocks.length > 0) {
630
+ return resolve(JSON.stringify({ answer: blocks[0].innerText.trim(), method: 'main-content' }));
631
+ }
632
+
633
+ // Retry if we haven't found anything yet
634
+ if (Date.now() < _deadline) {
635
+ setTimeout(_tryExtract, 400);
636
+ } else {
637
+ resolve(null);
638
+ }
639
+ } catch(e) { resolve(null); }
640
+ }
641
+ _tryExtract();
642
+ })`],8000).catch(()=>null);if(!a||a==="null")return null;try{let{answer:l,method:i}=JSON.parse(a);if(l&&r(l)){t.fallbackUsed=`dom:${i}`,t.clipboardEmpty=!0;let o=await c(["eval",e,`(() => {
643
+ const links = Array.from(document.querySelectorAll('a[href^="https://"]'))
644
+ .filter(a => {
645
+ const href = a.href || '';
646
+ return !href.includes('perplexity.ai') && !href.includes('google.com') && !href.includes('gstatic');
647
+ })
648
+ .slice(0, 10)
649
+ .map(a => ({ title: a.innerText?.trim() || a.href, url: a.href }));
650
+ return JSON.stringify(links);
651
+ })()`],3000).catch(()=>"[]"),s=[];try{s=JSON.parse(o||"[]")}catch{}return{answer:l,sources:s}}}catch{}return null}async function we(e,t){let r=he(),n=await D(e,`(${r})?.click()`,V,{timeoutMs:2400,retryClick:400});if(t.clipboardEmpty=!n,t.query&&n){let l=t.query.toLowerCase().trim();if(n.toLowerCase().trim()===l||n.trim().length<Math.max(20,l.length*0.5))console.error(`[perplexity] Clipboard contains query echo or stub (${n.length} chars), retrying with longer wait...`),t.clipboardEmpty=!0,n=""}if(!n){console.error("[perplexity] Clipboard empty — trying DOM fallback...");let l=await ye(e,t);if(l)return console.error(`[perplexity] DOM fallback succeeded (${t.fallbackUsed})`),l;throw Error("Clipboard interceptor returned empty text")}let a=N(n);return{answer:n.trim(),sources:a}}var be=`Usage: node extractors/perplexity.mjs "<query>" [--tab <prefix>]
652
+ `;async function ve(){let e=await A(process.argv.slice(2));j(e,be);let{query:t,tabPrefix:r,short:n}=E(e),a=Date.now(),i={engine:"perplexity",mode:process.env.GREEDY_SEARCH_VISIBLE==="1"?"visible":"headless",clipboardEmpty:null,fallbackUsed:null,blockedBy:null,verificationResult:null,inputReady:null,query:t};try{if(!r)await c(["list"]);let o=await C(r),s=await c(["eval",o,"document.location.href"]).catch(()=>""),f=!1;try{let d=new URL(s).hostname.toLowerCase();f=d==="perplexity.ai"||d.endsWith(".perplexity.ai")}catch{}if(!f)if(await c(["nav",o,"https://www.perplexity.ai/"],20000),await c(["eval",o,`new Promise((resolve) => {
653
+ const _deadline = Date.now() + 15000;
654
+ function _check() {
655
+ const input = document.querySelector('${b.input}');
656
+ if (input) {
657
+ // Force visibility on all parents up to body —
658
+ // Perplexity hides the first 5 wrapper DIVs until
659
+ // the user interacts with the page
660
+ let el = input;
661
+ while (el && el !== document.body) {
662
+ if (window.getComputedStyle(el).visibility === 'hidden') {
663
+ el.style.visibility = 'visible';
664
+ }
665
+ el = el.parentElement;
666
+ }
667
+ input.focus();
668
+ if (document.activeElement === input) return resolve('ready');
669
+ }
670
+ if (Date.now() < _deadline) setTimeout(_check, 500);
671
+ else resolve('timeout');
672
+ }
673
+ _check();
674
+ })`],18000).catch(()=>"timeout")!=="ready"){for(let h=0;h<2;h++)if(await c(["nav",o,"https://www.perplexity.ai/"],20000),await new Promise((x)=>setTimeout(x,2000)),await c(["eval",o,`(() => {
675
+ const input = document.querySelector('${b.input}');
676
+ if (!input) return false;
677
+ let el = input;
678
+ while (el && el !== document.body) {
679
+ if (window.getComputedStyle(el).visibility === 'hidden') {
680
+ el.style.visibility = 'visible';
681
+ }
682
+ el = el.parentElement;
683
+ }
684
+ input.focus();
685
+ return document.activeElement === input;
686
+ })()`],5000).catch(()=>!1)==="true")break}else await new Promise((h)=>setTimeout(h,600));let u=await H(o,c,1e4);if(i.verificationResult=u,u==="needs-human")throw Error("Perplexity verification required — please solve it manually in the browser window");if(await T(o,c),u==="clicked"){await new Promise((w)=>setTimeout(w,$.afterVerify));let d=await c(["eval",o,"document.location.href"]).catch(()=>""),h=!1;try{let w=new URL(d).hostname.toLowerCase();h=w==="perplexity.ai"||w.endsWith(".perplexity.ai")}catch{}if(!h)await c(["nav",o,"https://www.perplexity.ai/"],20000),await new Promise((w)=>setTimeout(w,800)),await T(o,c)}let m=await I(o,b.input,15000,400);if(i.inputReady=m,!m)throw Error("Perplexity input not found — page may not have loaded or is in unexpected state");await new Promise((d)=>setTimeout(d,P(300))),await S(o,V),await c(["click",o,b.input]),await new Promise((d)=>setTimeout(d,P(400)));let g;for(let d=0;d<3;d++){if(g=await c(["eval",o,`(() => {
687
+ try {
688
+ const input = document.querySelector('${b.input}');
689
+ if (!input) return 'no-input';
690
+ input.focus();
691
+ if (document.activeElement !== input) {
692
+ const activeTag = document.activeElement?.tagName || 'none';
693
+ const activeClass = (document.activeElement?.className || '').slice(0, 80);
694
+ return 'not-focused:active=' + activeTag + '.' + activeClass;
695
+ }
696
+ // execCommand('insertText') dispatches the proper input
697
+ // event that React's onChange listens for
698
+ const ok = document.execCommand('insertText', false, ${JSON.stringify(t)});
699
+ return ok ? 'ok' : 'exec-failed';
700
+ } catch (e) { return 'err:' + e.message; }
701
+ })()`],5000),g==="ok")break;if(String(g).startsWith("not-focused"))await c(["click",o,b.input]).catch(()=>{});await new Promise((h)=>setTimeout(h,800))}if(g!=="ok")throw Error(`Perplexity type failed: ${g}`);if(await new Promise((d)=>setTimeout(d,P(400))),await c(["eval",o,`(() => {
702
+ const input = document.querySelector('${b.input}');
703
+ if (!input) return 'no-input';
704
+ input.focus();
705
+ const ev = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true });
706
+ input.dispatchEvent(ev);
707
+ return 'ok';
708
+ })()`]),await M(o,{timeout:20000,interval:600,stableRounds:5,minLength:50,selector:`Array.from(document.querySelectorAll('.prose, [class*="prose"]')).pop() || document.querySelector('[data-testid*="answer"], [class*="answer-content"], [class*="response-content"]') || document.body`}),process.env.GREEDY_SEARCH_HEADLESS==="1"){let d=await c(["snap",o]).catch(()=>"");if(/\[dialog\]/i.test(d)&&/Pro|αναβάθμιση|upgrade/i.test(d)&&!/\.prose|\[article\]/i.test(d))throw console.error("[perplexity] Rate Limited — skipping (visible retry won't help)"),i.blockedBy="rate-limit",Error("Rate Limited — Perplexity free search limit reached. Wait a few hours.")}let{answer:p,sources:_}=await we(o,i);if(!p)throw Error("No answer extracted — Perplexity may not have responded");let v=await c(["eval",o,"document.location.href"]).catch(()=>"");i.durationMs=Date.now()-a,R({query:t,url:v,answer:L(p,n),sources:_,_envelope:k(i)})}catch(o){i.durationMs=Date.now()-a,F(o,k(i))}}ve();