@duckmind/dm-windows-x64 0.62.1 → 0.62.9

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,577 +0,0 @@
1
- let _deps = null;
2
- async function loadDeps() {
3
- if (_deps)
4
- return _deps;
5
- const [{ Readability }, { JSDOM }, { default: TurndownService }] = await Promise.all([
6
- import("@mozilla/readability"),
7
- import("jsdom"),
8
- import("turndown")
9
- ]);
10
- const turndown = new TurndownService({
11
- headingStyle: "atx",
12
- bulletListMarker: "-",
13
- codeBlockStyle: "fenced"
14
- });
15
- turndown.addRule("removeDataUrls", {
16
- filter: (node) => node.tagName === "IMG" && node.getAttribute("src")?.startsWith("data:"),
17
- replacement: () => ""
18
- });
19
- _deps = { Readability, JSDOM, turndown };
20
- return _deps;
21
- }
22
- const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
23
- const DEFAULT_HEADERS = {
24
- "user-agent": DEFAULT_USER_AGENT,
25
- accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
26
- "accept-language": "en-US,en;q=0.9",
27
- "accept-encoding": "gzip, deflate, br",
28
- "cache-control": "no-cache",
29
- pragma: "no-cache",
30
- "sec-ch-ua": '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
31
- "sec-ch-ua-mobile": "?0",
32
- "sec-ch-ua-platform": '"Windows"',
33
- "sec-fetch-dest": "document",
34
- "sec-fetch-mode": "navigate",
35
- "sec-fetch-site": "none",
36
- "sec-fetch-user": "?1",
37
- "upgrade-insecure-requests": "1"
38
- };
39
- const PRIVATE_URL_PATTERNS = [
40
- /^localhost$/i,
41
- /^127\.\d+\.\d+\.\d+$/,
42
- /^0\.0\.0\.0$/,
43
- /^\[::1\]$/,
44
- /^10\./,
45
- /^172\.(1[6-9]|2\d|3[01])\./,
46
- /^192\.168\./,
47
- /^169\.254\./,
48
- /^fc00:/i,
49
- /^fe80:/i,
50
- /\.local$/i,
51
- /\.internal$/i,
52
- /\.localhost$/i
53
- ];
54
- const IPV4_MAPPED_DOTTED_RE = /^::ffff:(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/i;
55
- const IPV4_MAPPED_HEX_RE = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
56
- const INET_ATON_SEGMENT_RE = /^(?:0x[0-9a-f]+|0[0-7]*|[1-9][0-9]*)$/i;
57
- function normalizeNumericIPv4(hostname) {
58
- if (!hostname || hostname.includes(":"))
59
- return null;
60
- const parts = hostname.split(".");
61
- if (parts.length < 1 || parts.length > 4)
62
- return null;
63
- if (!parts.every((p) => INET_ATON_SEGMENT_RE.test(p)))
64
- return null;
65
- const nums = parts.map((p) => {
66
- if (/^0x/i.test(p))
67
- return parseInt(p, 16);
68
- if (/^0[0-7]+$/.test(p))
69
- return parseInt(p, 8);
70
- return parseInt(p, 10);
71
- });
72
- if (nums.some((n) => !Number.isFinite(n) || n < 0))
73
- return null;
74
- let bytes;
75
- if (nums.length === 1) {
76
- if (nums[0] > 4294967295)
77
- return null;
78
- bytes = [
79
- nums[0] >>> 24 & 255,
80
- nums[0] >>> 16 & 255,
81
- nums[0] >>> 8 & 255,
82
- nums[0] & 255
83
- ];
84
- } else if (nums.length === 2) {
85
- if (nums[0] > 255 || nums[1] > 16777215)
86
- return null;
87
- bytes = [nums[0], nums[1] >>> 16 & 255, nums[1] >>> 8 & 255, nums[1] & 255];
88
- } else if (nums.length === 3) {
89
- if (nums[0] > 255 || nums[1] > 255 || nums[2] > 65535)
90
- return null;
91
- bytes = [nums[0], nums[1], nums[2] >>> 8 & 255, nums[2] & 255];
92
- } else {
93
- if (nums.some((n) => n > 255))
94
- return null;
95
- bytes = nums;
96
- }
97
- return bytes.join(".");
98
- }
99
- function extractMappedIPv4(core) {
100
- const dottedMatch = core.match(IPV4_MAPPED_DOTTED_RE);
101
- if (dottedMatch) {
102
- return `${dottedMatch[1]}.${dottedMatch[2]}.${dottedMatch[3]}.${dottedMatch[4]}`;
103
- }
104
- const hexMatch = core.match(IPV4_MAPPED_HEX_RE);
105
- if (hexMatch) {
106
- const hi = parseInt(hexMatch[1], 16);
107
- const lo = parseInt(hexMatch[2], 16);
108
- if (hi > 65535 || lo > 65535)
109
- return null;
110
- return [
111
- hi >>> 8 & 255,
112
- hi & 255,
113
- lo >>> 8 & 255,
114
- lo & 255
115
- ].join(".");
116
- }
117
- return null;
118
- }
119
- function isPrivateIPv4(ip) {
120
- return PRIVATE_URL_PATTERNS.some((pattern) => pattern.test(ip));
121
- }
122
- export function defaultFetchHeaders(overrides = {}) {
123
- return { ...DEFAULT_HEADERS, ...overrides };
124
- }
125
- export function isPrivateUrl(url) {
126
- try {
127
- if (typeof url !== "string" || !url.trim()) {
128
- return { blocked: true, reason: "URL must be a non-empty string" };
129
- }
130
- const parsed = new URL(url);
131
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
132
- return {
133
- blocked: true,
134
- reason: `Protocol not allowed: ${parsed.protocol}`
135
- };
136
- }
137
- const hostname = parsed.hostname.toLowerCase();
138
- for (const pattern of PRIVATE_URL_PATTERNS) {
139
- if (pattern.test(hostname)) {
140
- return {
141
- blocked: true,
142
- reason: `Private/internal address: ${hostname}`
143
- };
144
- }
145
- }
146
- if (hostname.startsWith("[") && hostname.endsWith("]")) {
147
- const core = hostname.slice(1, -1);
148
- const mapped = extractMappedIPv4(core);
149
- if (mapped && isPrivateIPv4(mapped)) {
150
- return {
151
- blocked: true,
152
- reason: `Private/internal address: ${hostname} (maps to ${mapped})`
153
- };
154
- }
155
- }
156
- const normalized = normalizeNumericIPv4(hostname);
157
- if (normalized && isPrivateIPv4(normalized)) {
158
- return {
159
- blocked: true,
160
- reason: `Private/internal address: ${hostname} (normalizes to ${normalized})`
161
- };
162
- }
163
- return { blocked: false };
164
- } catch (error) {
165
- return { blocked: true, reason: `Invalid URL: ${error.message}` };
166
- }
167
- }
168
- export function rewriteGitHubUrl(url) {
169
- try {
170
- const parsed = new URL(url);
171
- if (!(parsed.hostname === "github.com" || parsed.hostname.endsWith(".github.com"))) {
172
- return url;
173
- }
174
- const parts = parsed.pathname.split("/").filter(Boolean);
175
- if (parts.length < 5) {
176
- return url;
177
- }
178
- const [owner, repo, type, ref, ...fileParts] = parts;
179
- if (type !== "blob") {
180
- return url;
181
- }
182
- const rawPath = fileParts.join("/");
183
- const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${rawPath}`;
184
- return rawUrl;
185
- } catch {
186
- return url;
187
- }
188
- }
189
- export async function fetchSourceHttp(url, options = {}) {
190
- const privateCheck = isPrivateUrl(url);
191
- if (privateCheck.blocked) {
192
- return {
193
- ok: false,
194
- url,
195
- finalUrl: url,
196
- status: 403,
197
- error: `Blocked: ${privateCheck.reason}`,
198
- needsBrowser: false
199
- };
200
- }
201
- const originalUrl = url;
202
- url = rewriteGitHubUrl(url);
203
- if (url !== originalUrl) {
204
- console.error(`[fetcher] Rewrote GitHub URL: ${originalUrl.slice(0, 60)}... → raw.githubusercontent.com`);
205
- }
206
- const { timeoutMs = 15000, userAgent, signal } = options;
207
- const controller = new AbortController;
208
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
209
- if (signal) {
210
- signal.addEventListener("abort", () => controller.abort(), { once: true });
211
- }
212
- try {
213
- const response = await fetch(url, {
214
- method: "GET",
215
- headers: {
216
- ...DEFAULT_HEADERS,
217
- "user-agent": userAgent || DEFAULT_USER_AGENT
218
- },
219
- redirect: "follow",
220
- signal: controller.signal
221
- });
222
- clearTimeout(timeoutId);
223
- const contentType = response.headers.get("content-type") || "";
224
- const finalUrl = response.url;
225
- const lastModified = response.headers.get("last-modified") || "";
226
- const finalPrivateCheck = isPrivateUrl(finalUrl);
227
- if (finalPrivateCheck.blocked) {
228
- return {
229
- ok: false,
230
- url,
231
- finalUrl,
232
- status: response.status,
233
- error: `Blocked: ${finalPrivateCheck.reason}`,
234
- needsBrowser: false
235
- };
236
- }
237
- let isRawGitHub = false;
238
- try {
239
- const finalHost = new URL(finalUrl).hostname.toLowerCase();
240
- isRawGitHub = finalHost === "raw.githubusercontent.com";
241
- } catch {}
242
- if (contentType.includes("text/plain") && isRawGitHub) {
243
- const text = await response.text();
244
- return {
245
- ok: true,
246
- url: originalUrl,
247
- finalUrl,
248
- status: response.status,
249
- title: finalUrl.split("/").pop() || "GitHub File",
250
- byline: "",
251
- siteName: "GitHub",
252
- lang: "",
253
- publishedTime: lastModified,
254
- lastModified,
255
- markdown: text,
256
- contentLength: text.length,
257
- excerpt: text.slice(0, 300).replaceAll(/\n/g, " "),
258
- needsBrowser: false
259
- };
260
- }
261
- if (!contentType.includes("text/html") && !contentType.includes("application/xhtml")) {
262
- return {
263
- ok: false,
264
- url,
265
- finalUrl,
266
- status: response.status,
267
- error: `Unsupported content type: ${contentType}`,
268
- needsBrowser: false
269
- };
270
- }
271
- const html = await response.text();
272
- const quickCheck = detectBotBlock(response.status, html, finalUrl, url);
273
- if (quickCheck.blocked) {
274
- return {
275
- ok: false,
276
- url,
277
- finalUrl,
278
- status: response.status,
279
- error: `Blocked: ${quickCheck.reason}`,
280
- needsBrowser: true
281
- };
282
- }
283
- const extracted = await extractContent(html, finalUrl);
284
- const quality = checkContentQuality(extracted);
285
- if (!quality.ok) {
286
- return {
287
- ok: false,
288
- url,
289
- finalUrl,
290
- status: response.status,
291
- error: `Low quality content: ${quality.reason}`,
292
- needsBrowser: true
293
- };
294
- }
295
- return {
296
- ok: true,
297
- url,
298
- finalUrl,
299
- status: response.status,
300
- title: extracted.title,
301
- byline: extracted.byline,
302
- siteName: extracted.siteName,
303
- lang: extracted.lang,
304
- publishedTime: extracted.publishedTime || lastModified,
305
- lastModified,
306
- markdown: extracted.markdown,
307
- excerpt: extracted.excerpt,
308
- contentLength: extracted.markdown.length,
309
- needsBrowser: false
310
- };
311
- } catch (error) {
312
- clearTimeout(timeoutId);
313
- const needsBrowser = isNetworkErrorRetryableWithBrowser(error);
314
- return {
315
- ok: false,
316
- url,
317
- finalUrl: url,
318
- status: 0,
319
- error: error.message,
320
- needsBrowser
321
- };
322
- }
323
- }
324
- export function detectBotBlock(status, html, finalUrl, originalUrl) {
325
- const title = html.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1]?.toLowerCase() || "";
326
- const sample = html.slice(0, 30000).toLowerCase();
327
- const combined = `${title} ${sample}`;
328
- if (status === 403 || status === 429 || status === 503) {
329
- return { blocked: true, reason: `HTTP ${status}` };
330
- }
331
- const blockSignals = [
332
- {
333
- pattern: /class=["'][^"']*captcha["']|<div[^>]*id=["']captcha/i,
334
- reason: "captcha"
335
- },
336
- {
337
- pattern: /g-recaptcha|data-sitekey|i['"]m not a robot/i,
338
- reason: "captcha"
339
- },
340
- {
341
- pattern: /checking your browser.{0,100}please wait|cf-browser-verification/i,
342
- reason: "cloudflare challenge"
343
- },
344
- {
345
- pattern: /just a moment.{0,50}security check|ddos protection by cloudflare/i,
346
- reason: "cloudflare challenge"
347
- },
348
- {
349
- pattern: /unusual traffic.{0,50}from your computer network/i,
350
- reason: "unusual traffic"
351
- },
352
- {
353
- pattern: /bot detected|automated.{0,20}request/i,
354
- reason: "bot detection"
355
- },
356
- {
357
- pattern: /enable\s+javascript\s+to\s+view|javascript\s+is\s+required.{0,50}enabled/i,
358
- reason: "requires javascript"
359
- },
360
- { pattern: /access denied|accessdenied/i, reason: "access denied" },
361
- {
362
- pattern: /protected by anubis|anubis uses a proof-of-work/i,
363
- reason: "anubis challenge"
364
- }
365
- ];
366
- for (const signal of blockSignals) {
367
- if (signal.pattern.test(combined)) {
368
- return { blocked: true, reason: signal.reason };
369
- }
370
- }
371
- const loginRedirect = detectLoginRedirect(originalUrl, finalUrl, html);
372
- if (loginRedirect) {
373
- return { blocked: true, reason: loginRedirect };
374
- }
375
- return { blocked: false };
376
- }
377
- const AUTH_DOMAINS = [
378
- "accounts.google.com",
379
- "login.microsoftonline.com",
380
- "login.live.com",
381
- "auth0.com",
382
- "okta.com",
383
- "auth.mozilla.auth0.com",
384
- "id.atlassian.com"
385
- ];
386
- const AUTH_HOSTNAME_PREFIXES = [
387
- "login.",
388
- "signin.",
389
- "auth.",
390
- "sso.",
391
- "accounts.",
392
- "idp."
393
- ];
394
- const LOGIN_CONTENT_PATTERNS = [
395
- "sign in to continue",
396
- "log in to continue",
397
- "authentication required",
398
- "create an account to continue",
399
- "subscribe to continue reading",
400
- "members only"
401
- ];
402
- function detectLoginRedirect(requestedUrl, finalUrl, html) {
403
- try {
404
- const requested = new URL(requestedUrl);
405
- const final = new URL(finalUrl);
406
- if (requested.hostname.toLowerCase() === final.hostname.toLowerCase()) {
407
- return;
408
- }
409
- const finalHost = final.hostname.toLowerCase();
410
- if (AUTH_DOMAINS.some((d) => finalHost === d || finalHost.endsWith(`.${d}`))) {
411
- return `redirected to login (${final.hostname})`;
412
- }
413
- if (AUTH_HOSTNAME_PREFIXES.some((p) => finalHost.startsWith(p))) {
414
- return `redirected to login (${final.hostname})`;
415
- }
416
- const sample = html.slice(0, 20000).toLowerCase();
417
- if (LOGIN_CONTENT_PATTERNS.some((p) => sample.includes(p))) {
418
- return `redirected to login page (${final.hostname})`;
419
- }
420
- } catch {}
421
- return;
422
- }
423
- function isNetworkErrorRetryableWithBrowser(error) {
424
- const message = error.message.toLowerCase();
425
- return message.includes("fetch failed") || message.includes("unable to verify") || message.includes("certificate") || message.includes("timeout");
426
- }
427
- function extractMetaDate(document) {
428
- const selectors = [
429
- 'meta[property="article:published_time"]',
430
- 'meta[name="article:published_time"]',
431
- 'meta[property="og:published_time"]',
432
- 'meta[name="publication_date"]',
433
- 'meta[name="date"]',
434
- 'meta[itemprop="datePublished"]',
435
- 'time[itemprop="datePublished"]',
436
- 'meta[name="DC.date"]'
437
- ];
438
- for (const sel of selectors) {
439
- const el = document.querySelector(sel);
440
- const val = el?.getAttribute("content") || el?.getAttribute("datetime") || "";
441
- if (val)
442
- return val;
443
- }
444
- return "";
445
- }
446
- export async function extractContent(html, url) {
447
- const { Readability, JSDOM, turndown } = await loadDeps();
448
- const dom = new JSDOM(html, { url });
449
- try {
450
- const document = dom.window.document;
451
- const reader = new Readability(document);
452
- const article = reader.parse();
453
- if (article && article.content) {
454
- const markdown = turndown.turndown(article.content);
455
- const cleanMarkdown = markdown.replaceAll(/\n{3,}/g, `
456
-
457
- `).trim();
458
- const publishedTime = article.publishedTime || extractMetaDate(document) || "";
459
- return {
460
- title: article.title || document.title || url,
461
- byline: article.byline || "",
462
- siteName: article.siteName || "",
463
- lang: article.lang || "",
464
- publishedTime,
465
- markdown: cleanMarkdown,
466
- excerpt: cleanMarkdown.slice(0, 300).replaceAll(/\n/g, " ")
467
- };
468
- }
469
- const body = document.body;
470
- if (body) {
471
- const clone = body.cloneNode(true);
472
- clone.querySelectorAll("script, style, nav, footer, header, aside").forEach((el) => el.remove());
473
- const text = clone.textContent || "";
474
- const cleanText = text.replaceAll(/\s+/g, " ").trim();
475
- return {
476
- title: document.title || url,
477
- byline: "",
478
- siteName: "",
479
- lang: "",
480
- publishedTime: extractMetaDate(document),
481
- markdown: cleanText,
482
- excerpt: cleanText.slice(0, 300)
483
- };
484
- }
485
- return {
486
- title: url,
487
- byline: "",
488
- siteName: "",
489
- lang: "",
490
- publishedTime: "",
491
- markdown: "",
492
- excerpt: ""
493
- };
494
- } finally {
495
- dom.window.close();
496
- }
497
- }
498
- export function checkContentQuality(extracted) {
499
- const markdown = extracted.markdown.trim().toLowerCase();
500
- const title = (extracted.title || "").toLowerCase();
501
- if (extracted.markdown.trim().length < 100) {
502
- return { ok: false, reason: "content too short (< 100 chars)" };
503
- }
504
- const lc = markdown.toLowerCase();
505
- const suspiciousChecks = [
506
- {
507
- check: () => lc.includes("loading") && lc.includes("please wait"),
508
- desc: "loading page"
509
- },
510
- {
511
- check: () => lc.includes("please ensure javascript is enabled"),
512
- desc: "requires javascript"
513
- },
514
- {
515
- check: () => lc.includes("enable javascript to view"),
516
- desc: "requires javascript"
517
- },
518
- {
519
- check: () => lc.includes("just a moment"),
520
- desc: "cloudflare challenge detected in content"
521
- },
522
- {
523
- check: () => lc.includes("verify you are human"),
524
- desc: "human verification"
525
- },
526
- {
527
- check: () => lc.includes("captcha required"),
528
- desc: "captcha in extracted content"
529
- },
530
- {
531
- check: () => lc.includes("access denied"),
532
- desc: "access denied in content"
533
- },
534
- {
535
- check: () => /^\s{0,10}sign\s{1,5}in\s{0,10}$|^\s{0,10}log\s{1,5}in\s{0,10}$/im.test(markdown),
536
- desc: "login form only"
537
- }
538
- ];
539
- for (const { check, desc } of suspiciousChecks) {
540
- if (check()) {
541
- return { ok: false, reason: desc };
542
- }
543
- }
544
- if (title.includes("just a moment") || title.includes("checking your browser")) {
545
- return { ok: false, reason: "cloudflare challenge page detected in title" };
546
- }
547
- return { ok: true };
548
- }
549
- export function shouldUseBrowser(url) {
550
- try {
551
- const parsed = new URL(url);
552
- const hostname = parsed.hostname.toLowerCase();
553
- const pathname = parsed.pathname.toLowerCase();
554
- const jsHeavyDomains = [
555
- "react.dev",
556
- "nextjs.org",
557
- "vuejs.org",
558
- "angular.io",
559
- "svelte.dev",
560
- "docs.expo.dev",
561
- "tailwindcss.com",
562
- "storybook.js.org"
563
- ];
564
- if (jsHeavyDomains.some((d) => hostname === d || hostname.endsWith(`.${d}`))) {
565
- return true;
566
- }
567
- if (pathname.includes("/playground") || pathname.includes("/demo") || pathname.includes("/app")) {
568
- return true;
569
- }
570
- if (parsed.hash && parsed.hash.length > 1) {
571
- return true;
572
- }
573
- return false;
574
- } catch {
575
- return false;
576
- }
577
- }
@@ -1,95 +0,0 @@
1
- import { formatEngineName } from "../utils/helpers.js";
2
- import { renderSynthesis } from "./synthesis.js";
3
- const MAX_LINE_WIDTH = 800;
4
- function _truncateLongLines(text) {
5
- return text.split(`
6
- `).map((line) => line.length > MAX_LINE_WIDTH ? line.slice(0, MAX_LINE_WIDTH - 1) + "…" : line).join(`
7
- `);
8
- }
9
- export function formatResults(engine, data) {
10
- const lines = [];
11
- if (engine === "all") {
12
- return _truncateLongLines(formatAllEnginesResult(data, lines));
13
- }
14
- return _truncateLongLines(formatSingleEngineResult(data, lines));
15
- }
16
- function formatAllEnginesResult(data, lines) {
17
- const synthesis = data._synthesis;
18
- const dedupedSources = data._sources;
19
- const needsHuman = data._needsHumanVerification;
20
- const research = data._research;
21
- if (needsHuman) {
22
- const engines = Array.isArray(needsHuman.engines) ? needsHuman.engines.join(", ") : "one or more engines";
23
- lines.push("## Manual verification required");
24
- lines.push(String(needsHuman.message || "Visible Chrome is open. Solve the verification challenge, then rerun the same search."));
25
- lines.push(`Engines: ${engines}`);
26
- lines.push("");
27
- }
28
- if (synthesis?.answer) {
29
- if (research?.mode === "iterative")
30
- renderResearchHeader(lines, research);
31
- renderSynthesis(lines, synthesis, dedupedSources || [], 6);
32
- const synthesizedBy = String(synthesis.synthesizedBy || "configured synthesizer");
33
- lines.push(research?.mode === "iterative" ? `*Research mode: iterative planning, source fetching, citation audit, and bundle output*
34
- ` : `*Synthesized by ${synthesizedBy} from multi-engine results and fetched sources*
35
- `);
36
- return lines.join(`
37
- `).trim();
38
- }
39
- for (const [eng, result] of Object.entries(data)) {
40
- if (eng.startsWith("_"))
41
- continue;
42
- lines.push(`
43
- ## ${formatEngineName(eng)}`);
44
- formatEngineResult(result, lines, 3);
45
- }
46
- return lines.join(`
47
- `).trim();
48
- }
49
- function renderResearchHeader(lines, research) {
50
- const floor = research.floor;
51
- const metrics = floor?.metrics;
52
- const bundle = research.bundle;
53
- const manifest = research.manifest;
54
- lines.push("## Research Run");
55
- lines.push(`- Status: ${floor?.floorMet ? "floor met" : "partial / floor unmet"}`);
56
- if (manifest?.terminationReason)
57
- lines.push(`- Stop reason: ${String(manifest.terminationReason)}`);
58
- if (metrics) {
59
- lines.push(`- Evidence: ${metrics.fetchedOk || 0} fetched sources, ${metrics.primarySources || 0} primary/official, ${metrics.claims || 0} claims, ${metrics.cited || 0} citations`);
60
- lines.push(`- Questions: ${metrics.closedQuestions || 0}/${metrics.totalQuestions || 0} closed${metrics.openQuestions ? `, ${metrics.openQuestions} open` : ""}`);
61
- }
62
- if (bundle?.dir)
63
- lines.push(`- Bundle: ${String(bundle.dir)}`);
64
- lines.push("");
65
- }
66
- function formatSingleEngineResult(data, lines) {
67
- const needsHuman = data._needsHumanVerification;
68
- if (needsHuman) {
69
- const engines = Array.isArray(needsHuman.engines) ? needsHuman.engines.join(", ") : "this engine";
70
- lines.push("## Manual verification required");
71
- lines.push(String(needsHuman.message || "Visible Chrome is open. Solve the verification challenge, then rerun the same search."));
72
- lines.push(`Engines: ${engines}`);
73
- lines.push("");
74
- }
75
- formatEngineResult(data, lines, 5);
76
- return lines.join(`
77
- `).trim();
78
- }
79
- function formatEngineResult(data, lines, maxSources) {
80
- if (data.error) {
81
- lines.push(`Error: ${data.error}`);
82
- return;
83
- }
84
- if (data.answer) {
85
- lines.push(String(data.answer));
86
- }
87
- const sources = data.sources;
88
- if (Array.isArray(sources) && sources.length > 0) {
89
- lines.push(`
90
- Sources:`);
91
- for (const s of sources.slice(0, maxSources)) {
92
- lines.push(`- [${s.title || s.url}](${s.url})`);
93
- }
94
- }
95
- }