@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,794 +0,0 @@
1
- import { randomInt } from "node:crypto";
2
- import { spawn } from "node:child_process";
3
- import { nodeRuntimeCommand } from "../src/utils/node-runtime.mjs";
4
- import { dirname, join } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- const __dir = dirname(fileURLToPath(import.meta.url));
7
- const CDP = join(__dir, "..", "bin", "cdp.mjs");
8
- const VALID_CDP_COMMANDS = new Set([
9
- "list",
10
- "snap",
11
- "eval",
12
- "shot",
13
- "html",
14
- "nav",
15
- "net",
16
- "click",
17
- "clickxy",
18
- "type",
19
- "loadall",
20
- "evalraw",
21
- "browse",
22
- "stop",
23
- "--tab"
24
- ]);
25
- function cdpSafeArgv(args) {
26
- if (!Array.isArray(args) || args.length === 0) {
27
- throw new Error("cdp: args must be a non-empty array");
28
- }
29
- if (args[0] === "test")
30
- return args.map((v, i) => validateArg(v, i));
31
- if (!VALID_CDP_COMMANDS.has(args[0])) {
32
- throw new Error(`cdp: unknown subcommand '${args[0]}'`);
33
- }
34
- return args.map((v, i) => validateArg(v, i));
35
- }
36
- function validateArg(value, index) {
37
- if (typeof value !== "string") {
38
- throw new Error(`cdp: argv[${index}] must be a string (got ${typeof value})`);
39
- }
40
- if (value.includes("\x00")) {
41
- throw new Error(`cdp: argv[${index}] contains a null byte`);
42
- }
43
- return value;
44
- }
45
- export function cdp(args, timeoutMs = 30000) {
46
- return cdpWithInput(args, null, timeoutMs);
47
- }
48
- export function cdpWithInput(args, input = null, timeoutMs = 30000) {
49
- const safeArgs = cdpSafeArgv(args);
50
- return new Promise((resolve, reject) => {
51
- const proc = spawn(nodeRuntimeCommand(), [CDP, ...safeArgs], {
52
- stdio: [input == null ? "ignore" : "pipe", "pipe", "pipe"]
53
- });
54
- if (input != null) {
55
- proc.stdin.write(input);
56
- proc.stdin.end();
57
- }
58
- let out = "";
59
- let err = "";
60
- proc.stdout.on("data", (d) => out += d);
61
- proc.stderr.on("data", (d) => err += d);
62
- const timer = setTimeout(() => {
63
- proc.kill();
64
- reject(new Error(`cdp timeout: ${args[0]}`));
65
- }, timeoutMs);
66
- proc.on("close", (code) => {
67
- clearTimeout(timer);
68
- if (code === 0)
69
- resolve(out.trim());
70
- else
71
- reject(new Error(err.trim() || `cdp exit ${code}`));
72
- });
73
- });
74
- }
75
- export function verifyInputText(actual, expected) {
76
- const normalize = (value) => String(value ?? "").replace(/\s+/g, " ").trim();
77
- const actualNormalized = normalize(actual);
78
- const expectedNormalized = normalize(expected);
79
- if (!expectedNormalized)
80
- return false;
81
- if (actualNormalized.length < Math.ceil(expectedNormalized.length * 0.8)) {
82
- return false;
83
- }
84
- const sampleLength = Math.min(40, expectedNormalized.length);
85
- return actualNormalized.includes(expectedNormalized.slice(0, sampleLength)) && actualNormalized.includes(expectedNormalized.slice(-sampleLength));
86
- }
87
- export async function getOrOpenTab(tabPrefix) {
88
- if (tabPrefix)
89
- return tabPrefix;
90
- const list = await cdp(["list"]);
91
- const anchor = list.split(`
92
- `)[0]?.slice(0, 8);
93
- if (!anchor)
94
- throw new Error("No Chrome tabs found. Is Chrome running with --remote-debugging-port=9222?");
95
- const raw = await cdp([
96
- "evalraw",
97
- anchor,
98
- "Target.createTarget",
99
- '{"url":"about:blank"}'
100
- ]);
101
- let createdTarget;
102
- try {
103
- createdTarget = JSON.parse(raw);
104
- } catch (error) {
105
- throw new Error(`Target.createTarget returned invalid JSON: ${error.message}`);
106
- }
107
- const { targetId } = createdTarget;
108
- if (!targetId) {
109
- throw new Error("Target.createTarget did not return a targetId");
110
- }
111
- await cdp(["list"]);
112
- const tid = targetId.slice(0, 8);
113
- try {
114
- await injectHeadlessStealth(tid);
115
- } catch (e) {
116
- process.stderr.write(`[getOrOpenTab] stealth injection failed: ${e.message}
117
- `);
118
- }
119
- return tid;
120
- }
121
- export async function injectClipboardInterceptor(tab, globalVar) {
122
- const code = `
123
- (() => {
124
- window.${globalVar} = null;
125
- const _clipboard = navigator.clipboard;
126
- if (!_clipboard) return;
127
- const _origWriteText = typeof _clipboard.writeText === 'function'
128
- ? _clipboard.writeText.bind(_clipboard)
129
- : null;
130
- const _origWrite = typeof _clipboard.write === 'function'
131
- ? _clipboard.write.bind(_clipboard)
132
- : null;
133
-
134
- _clipboard.writeText = function(text) {
135
- window.${globalVar} = String(text ?? '');
136
- if (!_origWriteText) return Promise.resolve();
137
- // The OS/browser clipboard write may be denied in automated Chrome or
138
- // when the tab is not focused. We only need the captured text; returning
139
- // a resolved promise prevents the page from surfacing a misleading
140
- // "failed to copy" toast after our interceptor already succeeded.
141
- return Promise.resolve(_origWriteText(text)).catch(() => undefined);
142
- };
143
-
144
- _clipboard.write = async function(items) {
145
- try {
146
- for (const item of items || []) {
147
- if (item.types && item.types.includes('text/plain')) {
148
- const blob = await item.getType('text/plain');
149
- window.${globalVar} = await blob.text();
150
- break;
151
- }
152
- }
153
- } catch(e) {}
154
- if (!_origWrite) return undefined;
155
- try { return await _origWrite(items); }
156
- catch (_) { return undefined; }
157
- };
158
- })();
159
- `;
160
- await cdp(["eval", tab, code]);
161
- }
162
- export async function clickCopyAndAwaitClipboard(tab, clickExpr, globalVar, options = {}) {
163
- const { timeoutMs = 2600 } = options;
164
- const retryClick = options.retryClick != null ? options.retryClick : Math.floor(timeoutMs * 0.4);
165
- const code = String.raw`
166
- new Promise((resolve) => {
167
- const _deadline = Date.now() + ${timeoutMs};
168
- const _retryAt = Date.now() + ${retryClick};
169
- let _retried = false;
170
- function _click() { try { ${clickExpr}; } catch(_) {} }
171
- function _poll() {
172
- const val = window.${globalVar};
173
- if (val) { resolve(val); return; }
174
- if (!_retried && Date.now() >= _retryAt) {
175
- _retried = true;
176
- _click();
177
- }
178
- if (Date.now() < _deadline) { setTimeout(_poll, 100); }
179
- else { resolve(window.${globalVar} || ''); }
180
- }
181
- _click();
182
- _poll();
183
- })
184
- `;
185
- return await cdp(["eval", tab, code], timeoutMs + 5000).catch(() => "");
186
- }
187
- export async function injectHeadlessStealth(tab) {
188
- const code = `
189
- (function() {
190
- // ── Runtime.enable / CDP detection masking ──────────────
191
- try { delete window.__REBROWSER_RUNTIME_ENABLE; } catch(_) {}
192
- try { delete window.__REBROWSER_DEVTOOLS; } catch(_) {}
193
- try { delete window.__nightmare; } catch(_) {}
194
- try { delete window.__phantom; } catch(_) {}
195
- try { delete window.callPhantom; } catch(_) {}
196
- try { delete window._phantom; } catch(_) {}
197
- try { delete window.Buffer; } catch(_) {}
198
-
199
- // Real Chrome without automation should not expose navigator.webdriver at all.
200
- // A literal false or an own-property getter returning undefined is itself a
201
- // common stealth tell; remove both instance and prototype properties when the
202
- // descriptor is configurable (as it is with --disable-blink-features).
203
- try { delete navigator.webdriver; } catch(_) {}
204
- try { delete Navigator.prototype.webdriver; } catch(_) {}
205
- Object.defineProperty(navigator, 'vendor', { get: () => 'Google Inc.', configurable: true });
206
- Object.defineProperty(navigator, 'platform', { get: () => 'Win32', configurable: true });
207
- Object.defineProperty(navigator, 'maxTouchPoints', { get: () => 0, configurable: true });
208
- Object.defineProperty(navigator, 'pdfViewerEnabled', { get: () => true, configurable: true });
209
- Object.defineProperty(navigator, 'productSub', { get: () => '20030107', configurable: true });
210
- Object.defineProperty(navigator, 'product', { get: () => 'Gecko', configurable: true });
211
- var __greedyMimeTypes = null;
212
- function __makeMimeTypes() {
213
- var pdf = { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: null };
214
- var textPdf = { type: 'text/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: null };
215
- try { Object.setPrototypeOf(pdf, MimeType.prototype); } catch(_) {}
216
- try { Object.setPrototypeOf(textPdf, MimeType.prototype); } catch(_) {}
217
- var m = [pdf, textPdf];
218
- try { Object.setPrototypeOf(m, MimeTypeArray.prototype); } catch(_) {}
219
- m.item = function item(i) { return this[i] || null; };
220
- m.namedItem = function namedItem(name) { return Array.prototype.find.call(this, function(x) { return x && x.type === name; }) || null; };
221
- return m;
222
- }
223
- Object.defineProperty(navigator, 'plugins', {
224
- get: () => {
225
- __greedyMimeTypes = __greedyMimeTypes || __makeMimeTypes();
226
- var plugin0 = { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' };
227
- var plugin1 = { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' };
228
- var plugin2 = { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' };
229
- try { Object.setPrototypeOf(plugin0, Plugin.prototype); } catch(_) {}
230
- try { Object.setPrototypeOf(plugin1, Plugin.prototype); } catch(_) {}
231
- try { Object.setPrototypeOf(plugin2, Plugin.prototype); } catch(_) {}
232
- var p = [plugin0, plugin1, plugin2];
233
- p.item = function item(i) { return this[i] || null; };
234
- p.namedItem = function namedItem(name) { return Array.prototype.find.call(this, function(x) { return x && x.name === name; }) || null; };
235
- p.refresh = function refresh() {};
236
- try { Object.setPrototypeOf(p, PluginArray.prototype); } catch(_) {}
237
- try {
238
- __greedyMimeTypes[0].enabledPlugin = p[0];
239
- __greedyMimeTypes[1].enabledPlugin = p[0];
240
- } catch(_) {}
241
- return p;
242
- },
243
- configurable: true,
244
- });
245
- Object.defineProperty(navigator, 'mimeTypes', {
246
- get: () => {
247
- __greedyMimeTypes = __greedyMimeTypes || __makeMimeTypes();
248
- return __greedyMimeTypes;
249
- },
250
- configurable: true,
251
- });
252
- Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'], configurable: true });
253
- try {
254
- Object.defineProperty(navigator, 'connection', { get: () => ({ effectiveType: '4g', rtt: 50, downlink: 10, downlinkMax: Infinity, saveData: false }), configurable: true });
255
- } catch(_) {}
256
- if (!navigator.mediaDevices) {
257
- Object.defineProperty(navigator, 'mediaDevices', {
258
- get: () => ({
259
- enumerateDevices: () => Promise.resolve([
260
- { deviceId: 'default', kind: 'audioinput', label: '', groupId: 'default' },
261
- { deviceId: 'default', kind: 'audiooutput', label: '', groupId: 'default' },
262
- { deviceId: '', kind: 'videoinput', label: '', groupId: '' },
263
- ]),
264
- getUserMedia: () => Promise.reject(new DOMException('NotAllowedError')),
265
- getDisplayMedia: () => Promise.reject(new DOMException('NotAllowedError')),
266
- }),
267
- configurable: true,
268
- });
269
- }
270
- // ── Missing platform APIs (headless often lacks these) ─
271
- try {
272
- if (!navigator.share) {
273
- navigator.share = function() { return Promise.reject(new Error('NotAllowedError')); };
274
- }
275
- } catch(_) {}
276
- try {
277
- if (!navigator.contentIndex) {
278
- Object.defineProperty(navigator, 'contentIndex', { get: () => ({ add: function() {}, delete: function() {}, getAll: function() { return Promise.resolve([]); } }), configurable: true });
279
- }
280
- } catch(_) {}
281
-
282
- if (!window.chrome) {
283
- window.chrome = {
284
- app: { isInstalled: false, InstallState: {}, RunningState: {} },
285
- runtime: {
286
- OnInstalledReason: {}, OnRestartRequiredReason: {}, PlatformArch: {}, PlatformNaclArch: {}, PlatformOs: {}, RequestUpdateCheckStatus: {},
287
- connect: () => ({}), sendMessage: () => {}, onMessage: { addListener: () => {} }
288
- },
289
- 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' }; },
290
- csi: function() { var t = Date.now(); return { onloadT: t - 2000, startE: t - 5000, pageT: 'back', tran: 2 }; },
291
- };
292
- }
293
- var __greedyNativeFns = [];
294
- function __markNative(fn) { try { __greedyNativeFns.push(fn); } catch(_) {} return fn; }
295
-
296
- var origQuery = navigator.permissions?.query;
297
- if (origQuery) {
298
- navigator.permissions.query = __markNative(function query(params) {
299
- if (params && params.name === 'notifications') return Promise.resolve({ state: Notification.permission || 'default', onchange: null });
300
- return origQuery.apply(this, arguments);
301
- });
302
- }
303
- try {
304
- var getParam = WebGLRenderingContext.prototype.getParameter;
305
- WebGLRenderingContext.prototype.getParameter = __markNative(function getParameter(p) {
306
- if (p === 37445) return 'Intel Inc.';
307
- if (p === 37446) return 'Intel Iris OpenGL Engine';
308
- return getParam.call(this, p);
309
- });
310
- } catch(_) {}
311
- // ── WebGL readPixels noise ──────────────────────────
312
- // CreepJS and other fingerprinters draw content with WebGL and read back the
313
- // rendered pixels. Adding subtle noise breaks rendering-based fingerprinting.
314
- try {
315
- var origReadPixels = WebGLRenderingContext.prototype.readPixels;
316
- WebGLRenderingContext.prototype.readPixels = __markNative(function readPixels(x, y, width, height, format, type, pixels) {
317
- var result = origReadPixels.call(this, x, y, width, height, format, type, pixels);
318
- if (pixels && pixels.length > 0) {
319
- pixels[0] ^= 1;
320
- }
321
- return result;
322
- });
323
- } catch(_) {}
324
- Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8, configurable: true });
325
- Object.defineProperty(navigator, 'deviceMemory', { get: () => 8, configurable: true });
326
-
327
- // ── Canvas fingerprint noise ─────────────────────────
328
- // Headless rendering engines produce slightly different canvas output
329
- // than headed Chrome. Subtle noise breaks hash-based fingerprinting.
330
- try {
331
- var __canvasNoise = ((Date.now() & 0xFF) | 1);
332
- var origFill = CanvasRenderingContext2D.prototype.fillText;
333
- CanvasRenderingContext2D.prototype.fillText = __markNative(function fillText() {
334
- this.globalAlpha = 0.9995;
335
- return origFill.apply(this, arguments);
336
- });
337
- } catch(_) {}
338
- try {
339
- var origStroke = CanvasRenderingContext2D.prototype.strokeText;
340
- CanvasRenderingContext2D.prototype.strokeText = __markNative(function strokeText() {
341
- this.globalAlpha = 0.9995;
342
- return origStroke.apply(this, arguments);
343
- });
344
- } catch(_) {}
345
- try {
346
- var origToDataURL = HTMLCanvasElement.prototype.toDataURL;
347
- HTMLCanvasElement.prototype.toDataURL = __markNative(function toDataURL() {
348
- var ctx = this.getContext('2d');
349
- if (ctx) {
350
- // Spread noise across canvas to break hash-based fingerprinting.
351
- // Uses a deterministic pattern so it's consistent per page load
352
- // but varies between sessions.
353
- var w = this.width, h = this.height;
354
- if (w > 0 && h > 0) {
355
- var imgData = ctx.getImageData(0, 0, Math.min(w, 4), Math.min(h, 4));
356
- if (imgData && imgData.data) {
357
- for (var __i = 0; __i < imgData.data.length; __i += 4) {
358
- imgData.data[__i] ^= (__canvasNoise + __i) & 0xFF;
359
- }
360
- ctx.putImageData(imgData, 0, 0);
361
- }
362
- }
363
- }
364
- return origToDataURL.apply(this, arguments);
365
- });
366
- } catch(_) {}
367
-
368
- // ── AudioContext fingerprint noise ────────────────────
369
- // Headless Chrome's AudioContext produces slightly different output.
370
- // Subtle noise breaks audio-based fingerprinting.
371
- try {
372
- var __audioSeed = ((Date.now() & 0x1F) | 1);
373
- var origGetChannelData = AudioBuffer.prototype.getChannelData;
374
- AudioBuffer.prototype.getChannelData = __markNative(function getChannelData(channel) {
375
- var data = origGetChannelData.call(this, channel);
376
- for (var __i = 0; __i < data.length; __i += 64) {
377
- data[__i] *= 0.99999;
378
- }
379
- return data;
380
- });
381
- } catch(_) {}
382
-
383
- // ── window outer dimensions ──────────────────────────
384
- // outerWidth/Height = 0 in headless — a well-known bot signal.
385
- // Mirror innerWidth/Height (set by --window-size flag) so the ratio is sane.
386
- try {
387
- if (!window.outerWidth) Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth || 1920, configurable: true });
388
- if (!window.outerHeight) Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight || 1080, configurable: true });
389
- } catch(_) {}
390
-
391
- // ── screen properties ─────────────────────────────────
392
- // Headless Chrome often reports an 800x600 screen even when the viewport is
393
- // 1920x1080. Keep screen metrics internally consistent with our launch flags.
394
- try {
395
- Object.defineProperty(screen, 'width', { get: () => 1920, configurable: true });
396
- Object.defineProperty(screen, 'height', { get: () => 1080, configurable: true });
397
- Object.defineProperty(screen, 'availWidth', { get: () => 1920, configurable: true });
398
- Object.defineProperty(screen, 'availHeight', { get: () => 1040, configurable: true });
399
- Object.defineProperty(screen, 'colorDepth', { get: () => 24, configurable: true });
400
- Object.defineProperty(screen, 'pixelDepth', { get: () => 24, configurable: true });
401
- } catch(_) {}
402
-
403
- // ── navigator.userAgentData (UA Client Hints) ─────────
404
- // Derive version from the UA string already set by --user-agent flag so the
405
- // two APIs are always consistent. Removes any "HeadlessChrome" brand entry.
406
- try {
407
- var _uaMajor = (navigator.userAgent.match(new RegExp('Chrome/([0-9]+)')) || [])[1] || '136';
408
- var _uaFull = (navigator.userAgent.match(new RegExp('Chrome/([0-9.]+)')) || [])[1] || (_uaMajor + '.0.0.0');
409
- var _brands = [
410
- { brand: 'Not)A;Brand', version: '99' },
411
- { brand: 'Google Chrome', version: _uaMajor },
412
- { brand: 'Chromium', version: _uaMajor },
413
- ];
414
- Object.defineProperty(navigator, 'userAgentData', {
415
- get: function() {
416
- return {
417
- brands: _brands, mobile: false, platform: 'Windows',
418
- getHighEntropyValues: function() {
419
- return Promise.resolve({
420
- architecture: 'x86', bitness: '64',
421
- brands: _brands,
422
- fullVersionList: [
423
- { brand: 'Not)A;Brand', version: '99.0.0.0' },
424
- { brand: 'Google Chrome', version: _uaFull },
425
- { brand: 'Chromium', version: _uaFull },
426
- ],
427
- mobile: false, model: '', platform: 'Windows',
428
- platformVersion: '15.0.0', uaFullVersion: _uaFull, wow64: false,
429
- });
430
- },
431
- toJSON: function() { return { brands: _brands, mobile: false, platform: 'Windows' }; },
432
- };
433
- },
434
- configurable: true,
435
- });
436
- } catch(_) {}
437
-
438
- // ── CDP Runtime serialization guard ──────────────────
439
- // Sites detect CDP by putting a getter on Error.prototype.stack
440
- // and checking if console.log triggers it (only happens when
441
- // Runtime domain is enabled). We monkey-patch console methods to
442
- // strip custom getters from arguments before they reach CDP.
443
- try {
444
- var _origLog = console.log, _origError = console.error,
445
- _origWarn = console.warn, _origDebug = console.debug,
446
- _origInfo = console.info;
447
- var _safeArg = function(a) {
448
- if (a instanceof Error) {
449
- try { return new Error(a.message); } catch(_) { return a; }
450
- }
451
- return a;
452
- };
453
- console.log = __markNative(function log() { return _origLog.apply(console, Array.prototype.map.call(arguments, _safeArg)); });
454
- console.error = __markNative(function error() { return _origError.apply(console, Array.prototype.map.call(arguments, _safeArg)); });
455
- console.warn = __markNative(function warn() { return _origWarn.apply(console, Array.prototype.map.call(arguments, _safeArg)); });
456
- console.debug = __markNative(function debug() { return _origDebug.apply(console, Array.prototype.map.call(arguments, _safeArg)); });
457
- console.info = __markNative(function info() { return _origInfo.apply(console, Array.prototype.map.call(arguments, _safeArg)); });
458
- } catch(_) {}
459
-
460
- // ── Native function masking ──────────────────────────
461
- // Patched APIs should not stringify as user-defined stealth code.
462
- try {
463
- var __nativeToString = Function.prototype.toString;
464
- Function.prototype.toString = function toString() {
465
- if (__greedyNativeFns.indexOf(this) !== -1) {
466
- var name = this.name || '';
467
- return 'function ' + name + '() { [native code] }';
468
- }
469
- return __nativeToString.call(this);
470
- };
471
- } catch(_) {}
472
- })();
473
- `;
474
- await cdp([
475
- "evalraw",
476
- tab,
477
- "Page.addScriptToEvaluateOnNewDocument",
478
- JSON.stringify({ source: code })
479
- ]);
480
- }
481
- export function parseSourcesFromMarkdown(text) {
482
- if (!text)
483
- return [];
484
- const results = [];
485
- let idx = 0;
486
- while (idx < text.length && results.length < 10) {
487
- const openBracket = text.indexOf("[", idx);
488
- if (openBracket === -1)
489
- break;
490
- const closeBracket = text.indexOf("](", openBracket);
491
- if (closeBracket === -1)
492
- break;
493
- const openParen = closeBracket + 2;
494
- let closeParen = -1;
495
- for (let p = openParen;p < text.length; p++) {
496
- const ch = text[p];
497
- if (ch === ")") {
498
- closeParen = p;
499
- break;
500
- }
501
- if (/\s/.test(ch))
502
- break;
503
- }
504
- if (closeParen !== -1) {
505
- const title = text.slice(openBracket + 1, closeBracket);
506
- const url = text.slice(openParen, closeParen);
507
- if (/^https?:\/\//i.test(url) && title) {
508
- if (!results.some((r) => r.url === url)) {
509
- results.push({ title, url });
510
- }
511
- }
512
- idx = closeParen + 1;
513
- } else {
514
- idx = openBracket + 1;
515
- }
516
- }
517
- return results;
518
- }
519
- function isAllDigits(s) {
520
- if (!s)
521
- return false;
522
- for (let k = 0;k < s.length; k++) {
523
- const c = s.charCodeAt(k);
524
- if (c < 48 || c > 57)
525
- return false;
526
- }
527
- return true;
528
- }
529
- export function parseSourcesFromMarkdownRefStyle(text) {
530
- if (!text)
531
- return [];
532
- const results = [];
533
- const refMap = new Map;
534
- const refRegex = /^\[(\d+)\]:\s*(https?:\/\/[^\s"]+)(?:\s+"([^"]*)")?/gm;
535
- let m;
536
- while ((m = refRegex.exec(text)) !== null) {
537
- const num = m[1];
538
- const url = m[2];
539
- const title = m[3] || "";
540
- refMap.set(num, { url, title });
541
- }
542
- let cursor = 0;
543
- while (cursor < text.length) {
544
- const open = text.indexOf("[", cursor);
545
- if (open === -1)
546
- break;
547
- const close = text.indexOf("]", open + 1);
548
- if (close === -1)
549
- break;
550
- if (text[close + 1] !== "[") {
551
- cursor = open + 1;
552
- continue;
553
- }
554
- const close2 = text.indexOf("]", close + 2);
555
- if (close2 === -1)
556
- break;
557
- const inner = text.slice(open + 1, close);
558
- const numStr = text.slice(close + 2, close2);
559
- if (isAllDigits(numStr)) {
560
- const ref = refMap.get(numStr);
561
- if (ref && !results.some((r) => r.url === ref.url)) {
562
- results.push({
563
- title: inner.trim() || ref.title || "",
564
- url: ref.url
565
- });
566
- }
567
- }
568
- cursor = close2 + 1;
569
- }
570
- return results;
571
- }
572
- export const TIMING = {
573
- postNav: 800,
574
- postNavSlow: 1200,
575
- postClick: 300,
576
- postType: 300,
577
- inputPoll: 400,
578
- copyPoll: 600,
579
- afterVerify: 1500
580
- };
581
- export async function waitForCopyButton(tab, selector, options = {}) {
582
- const { timeout = 60000, onPoll } = options;
583
- if (!onPoll) {
584
- const code = String.raw`
585
- new Promise((resolve) => {
586
- const _deadline = Date.now() + ${timeout};
587
- function _poll() {
588
- if (document.querySelector('${selector}')) { resolve(true); return; }
589
- if (Date.now() < _deadline) { setTimeout(_poll, 150); }
590
- else { resolve(false); }
591
- }
592
- _poll();
593
- })
594
- `;
595
- const found = await cdp(["eval", tab, code], timeout + 5000).catch(() => "false");
596
- if (found === "true")
597
- return;
598
- throw new Error(`Copy button ('${selector}') did not appear within ${timeout}ms`);
599
- }
600
- const deadline = Date.now() + timeout;
601
- let tick = 0;
602
- while (Date.now() < deadline) {
603
- const found = await cdp([
604
- "eval",
605
- tab,
606
- `!!document.querySelector('${selector}')`
607
- ]).catch(() => "false");
608
- if (found === "true")
609
- return;
610
- await onPoll(++tick).catch(() => null);
611
- await new Promise((r) => setTimeout(r, jitter(TIMING.copyPoll)));
612
- }
613
- throw new Error(`Copy button ('${selector}') did not appear within ${timeout}ms`);
614
- }
615
- export function jitter(ms) {
616
- const variance = ms * 0.2;
617
- const offset = randomInt(-Math.floor(variance), Math.floor(variance) + 1);
618
- return Math.max(50, Math.round(ms + offset));
619
- }
620
- export async function waitForStreamComplete(tab, options = {}) {
621
- const {
622
- timeout = 20000,
623
- interval = 600,
624
- stableRounds = 3,
625
- selector = "document.body",
626
- minLength = 0,
627
- isStreamingExpr = "false"
628
- } = options;
629
- const code = String.raw`
630
- new Promise((resolve, reject) => {
631
- const _deadline = Date.now() + ${timeout};
632
- const _baseInterval = ${interval};
633
- const _stableRounds = ${stableRounds};
634
- const _minLength = ${minLength};
635
- let _lastLen = -1;
636
- let _stableCount = 0;
637
-
638
- function _jitter(ms) {
639
- return Math.max(50, ms + (Math.random() * ms * 0.4 - ms * 0.2));
640
- }
641
-
642
- function _poll() {
643
- try {
644
- // Re-query DOM each tick — element may not exist at eval start
645
- const el = ${selector};
646
- const cur = el?.textContent?.length ?? 0;
647
- const streaming = ${isStreamingExpr};
648
- if (streaming) {
649
- if (cur !== _lastLen) _lastLen = cur;
650
- _stableCount = 0;
651
- } else if (cur >= _minLength) {
652
- if (cur === _lastLen) {
653
- _stableCount++;
654
- if (_stableCount >= _stableRounds) { resolve(cur); return; }
655
- } else {
656
- _lastLen = cur;
657
- _stableCount = 0;
658
- }
659
- }
660
- if (Date.now() < _deadline) {
661
- setTimeout(_poll, _jitter(_baseInterval));
662
- } else {
663
- if (_lastLen >= _minLength && !streaming) { resolve(_lastLen); }
664
- else { reject(new Error('Generation did not stabilise within ${timeout}ms')); }
665
- }
666
- } catch(e) { reject(e); }
667
- }
668
-
669
- _poll();
670
- })
671
- `;
672
- const lenStr = await cdp(["eval", tab, code], timeout + 1e4);
673
- const currentLen = parseInt(lenStr, 10) || 0;
674
- if (currentLen >= minLength)
675
- return currentLen;
676
- throw new Error(`Generation did not stabilise within ${timeout}ms`);
677
- }
678
- export async function waitForSelector(tab, selector, timeoutMs = 15000, interval = 500) {
679
- const code = String.raw`
680
- new Promise((resolve) => {
681
- const _deadline = Date.now() + ${timeoutMs};
682
- const _baseInterval = ${interval};
683
-
684
- function _jitter(ms) {
685
- return Math.max(50, ms + (Math.random() * ms * 0.4 - ms * 0.2));
686
- }
687
-
688
- function _poll() {
689
- try {
690
- if (document.querySelector('${selector}')) { resolve(true); return; }
691
- if (Date.now() < _deadline) { setTimeout(_poll, _jitter(_baseInterval)); }
692
- else { resolve(false); }
693
- } catch(_) { resolve(false); }
694
- }
695
-
696
- _poll();
697
- })
698
- `;
699
- const result = await cdp(["eval", tab, code], timeoutMs + 5000);
700
- return result === "true";
701
- }
702
- export async function prepareArgs(args) {
703
- const stdinIdx = args.indexOf("--stdin");
704
- if (stdinIdx === -1)
705
- return args;
706
- const query = await new Promise((resolve) => {
707
- let data = "";
708
- process.stdin.setEncoding("utf8");
709
- process.stdin.on("data", (chunk) => data += chunk);
710
- process.stdin.on("end", () => resolve(data.trim()));
711
- });
712
- const modified = [...args];
713
- modified[stdinIdx] = query;
714
- return modified;
715
- }
716
- export function parseArgs(args) {
717
- const short = args.includes("--short");
718
- let rest = args.filter((a) => a !== "--short");
719
- const tabFlagIdx = rest.indexOf("--tab");
720
- const tabPrefix = tabFlagIdx === -1 ? null : rest[tabFlagIdx + 1];
721
- if (tabFlagIdx !== -1) {
722
- rest = rest.filter((_, i) => i !== tabFlagIdx && i !== tabFlagIdx + 1);
723
- }
724
- const localeIdx = rest.indexOf("--locale");
725
- const locale = localeIdx === -1 ? null : rest[localeIdx + 1];
726
- if (localeIdx !== -1) {
727
- rest = rest.filter((_, i) => i !== localeIdx && i !== localeIdx + 1);
728
- }
729
- const query = rest.join(" ");
730
- return { query, tabPrefix, short, locale };
731
- }
732
- export function validateQuery(args, usage) {
733
- if (!args.length || args[0] === "--help") {
734
- process.stderr.write(usage);
735
- process.exit(1);
736
- }
737
- }
738
- export function formatAnswer(answer, short, maxLen = 300) {
739
- if (!short || answer.length <= maxLen)
740
- return answer;
741
- const truncated = answer.slice(0, maxLen);
742
- const lastSpace = truncated.lastIndexOf(" ");
743
- return lastSpace > 0 ? `${truncated.slice(0, lastSpace)}…` : `${truncated}…`;
744
- }
745
- export function outputJson(data) {
746
- process.stdout.write(`${JSON.stringify(data, null, 2)}
747
- `);
748
- }
749
- export function logStage(env, stage, startTime = null) {
750
- if (!env || typeof env !== "object")
751
- return;
752
- const elapsed = startTime ? ` (+${Date.now() - startTime}ms)` : "";
753
- env.lastStage = stage;
754
- if (!Array.isArray(env.stages))
755
- env.stages = [];
756
- env.stages.push({ stage, at: Date.now() });
757
- const engine = env.engine || "extractor";
758
- console.error(`[${engine}] stage: ${stage}${elapsed}`);
759
- }
760
- export function buildEnvelope({
761
- engine,
762
- mode = "headless",
763
- clipboardEmpty = null,
764
- fallbackUsed = null,
765
- blockedBy = null,
766
- verificationResult = null,
767
- inputReady = null,
768
- durationMs = null,
769
- lastStage = null,
770
- stages = null
771
- } = {}) {
772
- return {
773
- engine,
774
- mode,
775
- clipboardEmpty,
776
- fallbackUsed,
777
- blockedBy,
778
- verificationResult,
779
- inputReady,
780
- durationMs,
781
- lastStage,
782
- stages
783
- };
784
- }
785
- export function handleError(error, envelope = null) {
786
- if (envelope) {
787
- const out = JSON.stringify({ _envelope: envelope, error: error.message });
788
- process.stdout.write(`${out}
789
- `);
790
- }
791
- process.stderr.write(`Error: ${error.message}
792
- `);
793
- process.exit(1);
794
- }