@duckmind/dm-windows-x64 0.62.3 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +43 -183
  3. package/extensions/dm-context/src/context.js +1 -2
  4. package/extensions/dm-context/src/index.js +1 -2
  5. package/extensions/{greedysearch-dm/bin/cdp.mjs → dm-cua/bin/browser-cdp.mjs} +3 -3
  6. package/extensions/dm-cua/bin/browser-cua.mjs +1 -1
  7. package/extensions/{greedysearch-dm/bin/launch.mjs → dm-cua/bin/browser-launch.mjs} +12 -11
  8. package/extensions/dm-cua/index.js +1 -1
  9. package/extensions/dm-cua/src/browser-cua-lib.mjs +17 -12
  10. package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/constants.mjs +11 -11
  11. package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/node-runtime.mjs +1 -1
  12. package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/port-pid.mjs +1 -1
  13. package/extensions/dm-ponytail/extensions/ponytail.mjs +186 -0
  14. package/extensions/dm-ponytail/hooks/claude-codex-hooks.json +41 -0
  15. package/extensions/dm-ponytail/hooks/copilot-hooks.json +21 -0
  16. package/extensions/dm-ponytail/hooks/ponytail-activate.js +59 -0
  17. package/extensions/dm-ponytail/hooks/ponytail-config.js +116 -0
  18. package/extensions/dm-ponytail/hooks/ponytail-instructions.js +81 -0
  19. package/extensions/dm-ponytail/hooks/ponytail-mode-tracker.js +96 -0
  20. package/extensions/dm-ponytail/hooks/ponytail-runtime.js +79 -0
  21. package/extensions/dm-ponytail/hooks/ponytail-statusline.ps1 +24 -0
  22. package/extensions/dm-ponytail/hooks/ponytail-statusline.sh +18 -0
  23. package/extensions/dm-ponytail/hooks/ponytail-subagent.js +50 -0
  24. package/extensions/dm-ponytail/hooks/qoder-hooks.json +26 -0
  25. package/extensions/dm-ponytail/package.json +19 -0
  26. package/extensions/dm-ponytail/pi-extension/index.js +186 -0
  27. package/extensions/dm-ponytail/pi-extension/package.json +8 -0
  28. package/extensions/dm-ponytail/skills/ponytail/SKILL.md +120 -0
  29. package/extensions/dm-ponytail/skills/ponytail-audit/SKILL.md +41 -0
  30. package/extensions/dm-ponytail/skills/ponytail-debt/SKILL.md +44 -0
  31. package/extensions/dm-ponytail/skills/ponytail-gain/SKILL.md +50 -0
  32. package/extensions/dm-ponytail/skills/ponytail-help/SKILL.md +71 -0
  33. package/extensions/dm-ponytail/skills/ponytail-review/SKILL.md +57 -0
  34. package/package.json +2 -6
  35. package/extensions/dm-caveman/extensions/caveman.js +0 -300
  36. package/extensions/dm-caveman/package.json +0 -20
  37. package/extensions/dm-goal/package.json +0 -23
  38. package/extensions/dm-goal/src/goal.js +0 -914
  39. package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +0 -40
  40. package/extensions/greedysearch-dm/bin/cdp-headless.mjs +0 -5
  41. package/extensions/greedysearch-dm/bin/cdp-visible.mjs +0 -5
  42. package/extensions/greedysearch-dm/bin/gschrome.mjs +0 -30
  43. package/extensions/greedysearch-dm/bin/kill-visible.mjs +0 -7
  44. package/extensions/greedysearch-dm/bin/launch-visible.mjs +0 -13
  45. package/extensions/greedysearch-dm/bin/mcp.mjs +0 -400
  46. package/extensions/greedysearch-dm/bin/search.mjs +0 -635
  47. package/extensions/greedysearch-dm/bin/visible.mjs +0 -22
  48. package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +0 -344
  49. package/extensions/greedysearch-dm/extractors/chatgpt.mjs +0 -354
  50. package/extensions/greedysearch-dm/extractors/common.mjs +0 -794
  51. package/extensions/greedysearch-dm/extractors/consensus.mjs +0 -527
  52. package/extensions/greedysearch-dm/extractors/consent.mjs +0 -449
  53. package/extensions/greedysearch-dm/extractors/gemini.mjs +0 -291
  54. package/extensions/greedysearch-dm/extractors/google-ai.mjs +0 -86
  55. package/extensions/greedysearch-dm/extractors/logically.mjs +0 -589
  56. package/extensions/greedysearch-dm/extractors/perplexity.mjs +0 -370
  57. package/extensions/greedysearch-dm/extractors/selectors.mjs +0 -32
  58. package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +0 -199
  59. package/extensions/greedysearch-dm/index.js +0 -123
  60. package/extensions/greedysearch-dm/package.json +0 -27
  61. package/extensions/greedysearch-dm/skills/greedy-search/skill.md +0 -18
  62. package/extensions/greedysearch-dm/src/fetcher.mjs +0 -577
  63. package/extensions/greedysearch-dm/src/formatters/results.js +0 -95
  64. package/extensions/greedysearch-dm/src/formatters/sources.js +0 -57
  65. package/extensions/greedysearch-dm/src/formatters/synthesis.js +0 -49
  66. package/extensions/greedysearch-dm/src/github.mjs +0 -228
  67. package/extensions/greedysearch-dm/src/reddit.mjs +0 -154
  68. package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +0 -340
  69. package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +0 -156
  70. package/extensions/greedysearch-dm/src/search/chrome.mjs +0 -510
  71. package/extensions/greedysearch-dm/src/search/defaults.mjs +0 -10
  72. package/extensions/greedysearch-dm/src/search/engines.mjs +0 -79
  73. package/extensions/greedysearch-dm/src/search/fetch-source.mjs +0 -470
  74. package/extensions/greedysearch-dm/src/search/file-sources.mjs +0 -31
  75. package/extensions/greedysearch-dm/src/search/output.mjs +0 -51
  76. package/extensions/greedysearch-dm/src/search/paths.mjs +0 -48
  77. package/extensions/greedysearch-dm/src/search/pdf.mjs +0 -64
  78. package/extensions/greedysearch-dm/src/search/progress.mjs +0 -109
  79. package/extensions/greedysearch-dm/src/search/query.mjs +0 -21
  80. package/extensions/greedysearch-dm/src/search/recovery.mjs +0 -49
  81. package/extensions/greedysearch-dm/src/search/research.mjs +0 -2255
  82. package/extensions/greedysearch-dm/src/search/scale-aware.mjs +0 -61
  83. package/extensions/greedysearch-dm/src/search/simple-research.mjs +0 -418
  84. package/extensions/greedysearch-dm/src/search/sources.mjs +0 -412
  85. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +0 -127
  86. package/extensions/greedysearch-dm/src/search/synthesis.mjs +0 -202
  87. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +0 -209
  88. package/extensions/greedysearch-dm/src/tools/shared.js +0 -226
  89. package/extensions/greedysearch-dm/src/types.js +0 -0
  90. package/extensions/greedysearch-dm/src/utils/content.mjs +0 -38
  91. package/extensions/greedysearch-dm/src/utils/helpers.js +0 -22
  92. /package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/minimize.mjs +0 -0
  93. /package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/system-cmds.mjs +0 -0
@@ -1,449 +0,0 @@
1
- import { randomInt } from "node:crypto";
2
- import { existsSync, readFileSync } from "node:fs";
3
- import http from "node:http";
4
- const CONSENT_JS = `
5
- (function() {
6
- // Google consent page (consent.google.com)
7
- var g = document.querySelector('#L2AGLb, button[jsname="b3VHJd"], .tHlp8d');
8
- if (g) { g.click(); return 'google'; }
9
-
10
- // OneTrust (used by many sites including Stack Overflow)
11
- var ot = document.querySelector('#onetrust-accept-btn-handler, .onetrust-accept-btn-handler');
12
- if (ot) { ot.click(); return 'onetrust'; }
13
-
14
- // Generic "accept all" / "agree" buttons
15
- var btns = Array.from(document.querySelectorAll('button, a[role=button]'));
16
- var accept = btns.find(b => /^(accept all|accept cookies|agree|i agree|got it|allow all|allow cookies)$/i.test(b.innerText?.trim()));
17
- if (accept) { accept.click(); return 'generic:' + accept.innerText.trim(); }
18
-
19
- return null;
20
- })()
21
- `;
22
- const VERIFY_DETECT_JS = `
23
- (function() {
24
- var url = document.location.href;
25
-
26
- // --- Google "sorry" page (hard CAPTCHA, can't auto-solve) ---
27
- if (url.includes('/sorry/') || url.includes('sorry.google')) return 'sorry-page';
28
-
29
- // --- Microsoft account verification page ---
30
- if (url.includes('login.microsoftonline.com') || url.includes('login.live.com') || url.includes('account.microsoft.com')) {
31
- var msBtns = Array.from(document.querySelectorAll('button, input[type=submit], a'));
32
- var msVerify = msBtns.find(b => /verify|continue|next/i.test(b.innerText?.trim() || b.value || ''));
33
- if (msVerify) { msVerify.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:msVerify.innerText?.trim()||msVerify.value}); }
34
- }
35
-
36
- // --- Copilot / modal verification ---
37
- var modal = document.querySelector('[role="dialog"], .b_modal, [class*="verify"], [class*="challenge"]');
38
- if (modal) {
39
- var modalBtns = Array.from(modal.querySelectorAll('button, a[role="button"], input[type="submit"]'));
40
- var actionBtn = modalBtns.find(b => /^(continue|verify|submit|next|i agree|accept|got it)$/i.test(b.innerText?.trim() || b.value || ''));
41
- if (actionBtn) { actionBtn.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:actionBtn.innerText?.trim()}); }
42
- }
43
-
44
- // --- Turnstile / Cloudflare challenge iframe (return coordinates for humanClickXY) ---
45
- var turnstileIframe = document.querySelector('iframe[src*="challenges.cloudflare.com"], iframe[src*="turnstile"], iframe[title*="challenge"]');
46
- if (turnstileIframe) {
47
- var r = turnstileIframe.getBoundingClientRect();
48
- return JSON.stringify({t:'xy',x:r.left+30,y:r.top+r.height/2});
49
- }
50
-
51
- // --- Cloudflare Turnstile widget inside closed shadow DOM (Copilot, etc.) ---
52
- // The iframe is not queryable from main document, but the host container
53
- // (#cf-turnstile) and the hidden response input are. When only the
54
- // hidden response input matches (no #cf-turnstile host and no visible
55
- // iframe), the actual challenge widget is rendered inside a closed
56
- // shadow DOM and cannot be auto-clicked. Return a sentinel so callers
57
- // know to surface this as needs-human verification instead of wasting
58
- // time on a doomed waitForSelector.
59
- var cfTurnstileHost = document.querySelector('#cf-turnstile');
60
- if (cfTurnstileHost) {
61
- var r2 = cfTurnstileHost.getBoundingClientRect();
62
- return JSON.stringify({t:'xy',x:r2.left+r2.width/2,y:r2.top+r2.height/2});
63
- }
64
- // Hidden cf-chl-widget-*_response input present but no visible host:
65
- // the widget is in closed shadow DOM. Signal this so handleVerification
66
- // can return 'needs-human' rather than 'clear'.
67
- var cfResponseInput = document.querySelector('input[name="cf-turnstile-response"], [id^="cf-chl-widget-"][id$="_response"]');
68
- if (cfResponseInput && cfResponseInput.value === '') {
69
- return 'cf-closed-shadow-dom';
70
- }
71
-
72
- // --- Cloudflare challenge page ---
73
- var cfCheckbox = document.querySelector('#cf-stage input[type="checkbox"], .ctp-checkbox-container input');
74
- if (cfCheckbox) { cfCheckbox.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:'cloudflare-checkbox'}); }
75
- var cfBtn = document.querySelector('#challenge-form button, .cf-challenge button');
76
- if (cfBtn) { cfBtn.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:cfBtn.innerText?.trim()}); }
77
-
78
- // --- Microsoft "I am human" button ---
79
- var msHumanBtn = document.querySelector('button[id*="i0"], button[id*="id__"]');
80
- if (msHumanBtn && /verify|human|robot|continue/i.test(msHumanBtn.innerText?.trim())) {
81
- msHumanBtn.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:msHumanBtn.innerText.trim()});
82
- }
83
-
84
- // --- Generic verify/continue/proceed buttons (catch-all) ---
85
- // IMPORTANT: exclude sign-in / OAuth buttons (e.g. "Continue with Google",
86
- // "Continue with email", "Login or sign up for free"). These appear on
87
- // many sites (Perplexity, ChatGPT, etc.) when the user isn't logged in,
88
- // and clicking them triggers a sign-in flow that takes us to a login
89
- // wall — a much worse outcome than the original search failure we were
90
- // trying to recover from. The exclusion list must cover both OAuth
91
- // providers AND generic "sign in / log in / with email" patterns.
92
- var btns = Array.from(document.querySelectorAll('button, input[type=submit], a[role=button]'));
93
- var verify = btns.find(b => {
94
- var t = (b.innerText?.trim() || b.value || '').toLowerCase();
95
- var isVerifyLike = (t === 'continue' || t === 'proceed' || t === 'next' ||
96
- t.startsWith('verify ') || t.startsWith('human ') || t === 'i am human' || t.includes('robot check')) &&
97
- !t.includes('verified') && !document.querySelector('iframe[src*="recaptcha"]');
98
- if (!isVerifyLike) return false;
99
- // Exclude OAuth / sign-in buttons to prevent accidental login flows
100
- // — covers "Continue with Google", "Continue with Apple", "Continue
101
- // with email", "Login or sign up", "Log in", "Sign in", "Sign up",
102
- // "Single sign-on", and the visible panel "Login or sign up for free"
103
- // text. The previous list missed "email" and "sso" which let the
104
- // auto-click land on the email/SSO sign-in buttons on Perplexity's
105
- // anonymous-mode homepage, navigating us into a login flow.
106
- var isSignIn = new RegExp("sign.?in|log.?in|sign.?up|with\\s+(google|apple|email|github|facebook|microsoft|sso)|sso|auth", "i").test(t);
107
- return !isSignIn;
108
- });
109
- if (verify) { verify.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:verify.innerText?.trim()||verify.value}); }
110
-
111
- // --- Google reCAPTCHA checkbox ---
112
- var recaptchaCheckbox = document.querySelector('.recaptcha-checkbox-unchecked, input[type=checkbox][id*="recaptcha"]');
113
- if (recaptchaCheckbox) { recaptchaCheckbox.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:'recaptcha'}); }
114
-
115
- return null;
116
- })()
117
- `;
118
- const VERIFY_RETRY_JS = `
119
- (function() {
120
- var url = document.location.href;
121
- var isVerifyPage = url.includes('/sorry/') ||
122
- url.includes('challenges.cloudflare.com') ||
123
- url.includes('login.microsoftonline.com') ||
124
- document.querySelector('#challenge-running, #challenge-stage, .cf-turnstile, [role="dialog"]');
125
- if (!isVerifyPage) return 'cleared';
126
-
127
- var btns = Array.from(document.querySelectorAll('button, input[type=submit], a[role=button]'));
128
- var btn = btns.find(b => {
129
- var t = (b.innerText?.trim() || b.value || '').toLowerCase();
130
- var isVerifyLike = t.includes('verify') || t.includes('human') || t.includes('robot') || t.includes('continue') || t.includes('next') || t.includes('submit');
131
- if (!isVerifyLike) return false;
132
- var isSignIn = /sign.in|log.in|google|microsoft|apple|facebook|github|auth/i.test(t);
133
- return !isSignIn;
134
- });
135
- if (btn) { btn.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:btn.innerText?.trim()||btn.value}); }
136
-
137
- var cf = document.querySelector('#cf-stage input[type="checkbox"], .cf-turnstile input');
138
- if (cf) { cf.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:'turnstile'}); }
139
-
140
- // Cloudflare Turnstile widget inside closed shadow DOM (detected via host container)
141
- var cfTurnstileHost = document.querySelector('#cf-turnstile, [id^="cf-chl-widget-"]');
142
- if (cfTurnstileHost) { return 'still-verifying'; }
143
-
144
- var modal = document.querySelector('[role="dialog"], .b_modal, [class*="verify"]');
145
- if (modal) {
146
- var modalBtn = modal.querySelector('button, a[role="button"]');
147
- if (modalBtn) { modalBtn.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:modalBtn.innerText?.trim()}); }
148
- }
149
-
150
- return 'still-verifying';
151
- })()
152
- `;
153
- export async function dismissConsent(tab, cdp) {
154
- const result = await cdp(["eval", tab, CONSENT_JS]).catch(() => null);
155
- if (result && result !== "null") {
156
- await new Promise((r) => setTimeout(r, 1500));
157
- }
158
- }
159
- function rng(min, max) {
160
- return randomInt(min * 1000, max * 1000) / 1000;
161
- }
162
- async function browserLevelClick(x, y) {
163
- if (!globalThis.WebSocket)
164
- return;
165
- const profileDir = process.env.CDP_PROFILE_DIR;
166
- if (!profileDir)
167
- return;
168
- const portFile = `${profileDir.replaceAll("\\", "/")}/DevToolsActivePort`;
169
- if (!existsSync(portFile))
170
- return;
171
- const port = readFileSync(portFile, "utf8").trim().split(`
172
- `)[0];
173
- const version = await new Promise((resolve, reject) => {
174
- const req = http.get(`http://localhost:${port}/json/version`, (res) => {
175
- let body = "";
176
- res.on("data", (d) => body += d);
177
- res.on("end", () => {
178
- try {
179
- resolve(JSON.parse(body));
180
- } catch {
181
- reject(new Error("bad JSON"));
182
- }
183
- });
184
- });
185
- req.on("error", reject);
186
- req.setTimeout(1000, () => {
187
- req.destroy();
188
- reject(new Error("timeout"));
189
- });
190
- });
191
- const ws = new globalThis.WebSocket(version.webSocketDebuggerUrl);
192
- let msgId = 0;
193
- await new Promise((resolve) => {
194
- ws.onopen = async () => {
195
- const send = (method, params) => new Promise((r) => {
196
- const id = ++msgId;
197
- const handler = (evt) => {
198
- if (JSON.parse(evt.data).id === id) {
199
- ws.removeEventListener("message", handler);
200
- r();
201
- }
202
- };
203
- ws.addEventListener("message", handler);
204
- ws.send(JSON.stringify({ id, method, params }));
205
- });
206
- const cx = x + rng(-2, 2);
207
- const cy = y + rng(-2, 2);
208
- await send("Input.dispatchMouseEvent", {
209
- type: "mouseMoved",
210
- x: cx,
211
- y: cy,
212
- button: "none"
213
- });
214
- await new Promise((r) => setTimeout(r, rng(80, 160)));
215
- await send("Input.dispatchMouseEvent", {
216
- type: "mousePressed",
217
- x: cx,
218
- y: cy,
219
- button: "left",
220
- clickCount: 1
221
- });
222
- await new Promise((r) => setTimeout(r, rng(30, 80)));
223
- await send("Input.dispatchMouseEvent", {
224
- type: "mouseReleased",
225
- x: cx + rng(-1, 1),
226
- y: cy + rng(-1, 1),
227
- button: "left",
228
- clickCount: 1
229
- });
230
- setTimeout(() => {
231
- ws.close();
232
- resolve();
233
- }, 200);
234
- };
235
- ws.onerror = () => resolve();
236
- setTimeout(resolve, 3000);
237
- });
238
- }
239
- export async function humanClickXY(tab, cdpFn, x, y) {
240
- const cx = Number.parseFloat(x);
241
- const cy = Number.parseFloat(y);
242
- if (Number.isNaN(cx) || Number.isNaN(cy)) {
243
- throw new Error(`humanClickXY: invalid coordinates (${x}, ${y})`);
244
- }
245
- const base = { button: "left", clickCount: 1, modifiers: 0 };
246
- const jx = cx + rng(-3, 3);
247
- const jy = cy + rng(-3, 3);
248
- await cdpFn([
249
- "evalraw",
250
- tab,
251
- "Input.dispatchMouseEvent",
252
- JSON.stringify({ ...base, type: "mouseMoved", x: jx, y: jy })
253
- ]);
254
- await new Promise((r) => setTimeout(r, rng(80, 180)));
255
- const px = cx + rng(-2, 2);
256
- const py = cy + rng(-2, 2);
257
- await cdpFn([
258
- "evalraw",
259
- tab,
260
- "Input.dispatchMouseEvent",
261
- JSON.stringify({ ...base, type: "mousePressed", x: px, y: py })
262
- ]);
263
- await new Promise((r) => setTimeout(r, rng(30, 90)));
264
- const rx = px + rng(-1, 1);
265
- const ry = py + rng(-1, 1);
266
- await cdpFn([
267
- "evalraw",
268
- tab,
269
- "Input.dispatchMouseEvent",
270
- JSON.stringify({ ...base, type: "mouseReleased", x: rx, y: ry })
271
- ]);
272
- await browserLevelClick(cx, cy).catch(() => {});
273
- await new Promise((r) => setTimeout(r, rng(100, 300)));
274
- return `human-clicked at (${cx.toFixed(0)}, ${cy.toFixed(0)})`;
275
- }
276
- export async function humanClickElement(tab, cdpFn, selector) {
277
- const rect = await cdpFn([
278
- "eval",
279
- tab,
280
- `(function() {
281
- var el = document.querySelector('${selector.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}');
282
- if (!el) return 'null';
283
- var r = el.getBoundingClientRect();
284
- return JSON.stringify({x: r.left + r.width / 2, y: r.top + r.height / 2, w: r.width, h: r.height});
285
- })()`
286
- ]).catch(() => "null");
287
- if (!rect || rect === "null") {
288
- return null;
289
- }
290
- const parsed = JSON.parse(rect);
291
- if (parsed.w === 0 || parsed.h === 0 || parsed.x === 0 && parsed.y === 0) {
292
- return null;
293
- }
294
- const { x, y } = parsed;
295
- return humanClickXY(tab, cdpFn, x, y);
296
- }
297
- function tryHumanClick(tab, cdp, detectResult) {
298
- if (!detectResult || detectResult === "null" || detectResult === "cleared" || detectResult === "still-verifying" || detectResult === "cf-closed-shadow-dom")
299
- return Promise.resolve("no-challenge");
300
- try {
301
- const info = JSON.parse(detectResult);
302
- if (info.t === "sel" && info.s) {
303
- process.stderr.write(`[greedysearch] Human-clicking "${info.txt}" via CDP...
304
- `);
305
- return humanClickElement(tab, cdp, info.s).then((r) => r !== null ? "clicked" : "cant-click");
306
- }
307
- if (info.t === "xy") {
308
- if (!info.x && !info.y)
309
- return Promise.resolve("cant-click");
310
- process.stderr.write(`[greedysearch] Human-clicking at (${info.x.toFixed(0)}, ${info.y.toFixed(0)})...
311
- `);
312
- return humanClickXY(tab, cdp, info.x, info.y).then(() => "clicked");
313
- }
314
- } catch {}
315
- return Promise.resolve("no-challenge");
316
- }
317
- export async function detectVerificationChallenge(tab, cdp) {
318
- const result = await cdp(["eval", tab, VERIFY_DETECT_JS]).catch(() => null);
319
- if (result === "cf-closed-shadow-dom") {
320
- const cfIframe = await findCloudflareIframeViaPierce(tab, cdp).catch(() => null);
321
- if (cfIframe)
322
- return cfIframe;
323
- return result;
324
- }
325
- if (result && result !== "null")
326
- return result;
327
- return null;
328
- }
329
- async function findCloudflareIframeViaPierce(tab, cdp) {
330
- if (typeof cdp !== "function")
331
- return null;
332
- await cdp(["evalraw", tab, "DOM.enable", "{}"]).catch(() => {});
333
- const doc = await cdp(["evalraw", tab, "DOM.getDocument", JSON.stringify({ depth: -1, pierce: true })]).catch(() => null);
334
- if (!doc)
335
- return null;
336
- let docParsed;
337
- try {
338
- docParsed = JSON.parse(doc);
339
- } catch {
340
- return null;
341
- }
342
- if (docParsed.error || !docParsed.root)
343
- return null;
344
- const root = docParsed.root;
345
- const found = await walkForCfIframe(root, tab, cdp);
346
- return found;
347
- }
348
- async function walkForCfIframe(node, tab, cdp) {
349
- if (!node)
350
- return null;
351
- const children = [];
352
- if (node.shadowRoots && node.shadowRoots.length > 0) {
353
- for (const s of node.shadowRoots) {
354
- children.push(s);
355
- }
356
- }
357
- if (node.children) {
358
- for (const c of node.children)
359
- children.push(c);
360
- }
361
- for (const child of children) {
362
- if (child.nodeName === "IFRAME") {
363
- const attrs = child.attributes || [];
364
- const srcIdx = attrs.indexOf("src");
365
- const src = srcIdx >= 0 ? attrs[srcIdx + 1] : "";
366
- if (src && /challenges\.cloudflare\.com|turnstile/i.test(src) && child.backendNodeId) {
367
- const boxRes = await cdp([
368
- "evalraw",
369
- tab,
370
- "DOM.getBoxModel",
371
- JSON.stringify({ backendNodeId: child.backendNodeId })
372
- ]).catch(() => null);
373
- if (!boxRes)
374
- continue;
375
- let boxParsed;
376
- try {
377
- boxParsed = JSON.parse(boxRes);
378
- } catch {
379
- continue;
380
- }
381
- const content = boxParsed?.model?.content || boxParsed?.result?.model?.content;
382
- if (!content || content.length < 8)
383
- continue;
384
- const x1 = content[0];
385
- const y1 = content[1];
386
- const x3 = content[4];
387
- const y3 = content[5];
388
- const width = x3 - x1;
389
- const height = y3 - y1;
390
- if (width < 50 || height < 20)
391
- continue;
392
- const checkboxX = x1 + width * 0.25;
393
- const checkboxY = y1 + height * 0.5;
394
- process.stderr.write(`[greedysearch] Found CF iframe via CDP pierce at (${x1.toFixed(0)}, ${y1.toFixed(0)}) ${width.toFixed(0)}x${height.toFixed(0)}, clicking checkbox at (${checkboxX.toFixed(0)}, ${checkboxY.toFixed(0)})
395
- `);
396
- return JSON.stringify({ t: "xy", x: checkboxX, y: checkboxY });
397
- }
398
- }
399
- const deeper = await walkForCfIframe(child, tab, cdp);
400
- if (deeper)
401
- return deeper;
402
- }
403
- return null;
404
- }
405
- export async function handleVerification(tab, cdp, waitMs = 30000) {
406
- const result = await detectVerificationChallenge(tab, cdp);
407
- if (!result)
408
- return "clear";
409
- if (result === "sorry-page") {
410
- process.stderr.write(`[greedysearch] Google CAPTCHA detected — please solve it in the browser window (waiting up to ${Math.floor(waitMs / 1000)}s)...
411
- `);
412
- const deadline = Date.now() + waitMs;
413
- while (Date.now() < deadline) {
414
- await new Promise((r) => setTimeout(r, 2000));
415
- const url = await cdp(["eval", tab, "document.location.href"]).catch(() => "");
416
- if (!url.includes("/sorry/"))
417
- return "cleared-by-user";
418
- }
419
- return "needs-human";
420
- }
421
- const clickResult = await tryHumanClick(tab, cdp, result);
422
- if (clickResult === "clicked") {
423
- await new Promise((r) => setTimeout(r, 2000));
424
- const deadline = Date.now() + waitMs;
425
- while (Date.now() < deadline) {
426
- const retryResult = await cdp(["eval", tab, VERIFY_RETRY_JS]).catch(() => null);
427
- if (retryResult === "cleared" || !retryResult || retryResult === "null") {
428
- process.stderr.write(`[greedysearch] Verification cleared.
429
- `);
430
- return "clicked";
431
- }
432
- if (retryResult !== "still-verifying") {
433
- await tryHumanClick(tab, cdp, retryResult);
434
- await new Promise((r) => setTimeout(r, 2000));
435
- } else {
436
- await new Promise((r) => setTimeout(r, 1500));
437
- }
438
- }
439
- process.stderr.write(`[greedysearch] Verification may require manual intervention.
440
- `);
441
- return "needs-human";
442
- }
443
- if (clickResult === "cant-click") {
444
- process.stderr.write(`[greedysearch] Verification challenge detected but cannot be auto-clicked — please solve it manually in the visible browser window.
445
- `);
446
- return "needs-human";
447
- }
448
- return "clear";
449
- }