@duckmind/dm-windows-x64 0.62.3 → 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,412 +0,0 @@
1
- export const TRACKING_PARAMS = [
2
- "fbclid",
3
- "gclid",
4
- "ref",
5
- "ref_src",
6
- "ref_url",
7
- "source",
8
- "utm_campaign",
9
- "utm_content",
10
- "utm_medium",
11
- "utm_source",
12
- "utm_term"
13
- ];
14
- export const COMMUNITY_HOSTS = [
15
- "dev.to",
16
- "hashnode.com",
17
- "medium.com",
18
- "reddit.com",
19
- "stackoverflow.com",
20
- "stackexchange.com",
21
- "substack.com"
22
- ];
23
- export const NEWS_HOSTS = [
24
- "arstechnica.com",
25
- "techcrunch.com",
26
- "theverge.com",
27
- "venturebeat.com",
28
- "wired.com",
29
- "zdnet.com"
30
- ];
31
- export const SOCIAL_HOSTS = [
32
- "facebook.com",
33
- "instagram.com",
34
- "linkedin.com",
35
- "pinterest.com",
36
- "tiktok.com",
37
- "twitter.com",
38
- "x.com"
39
- ];
40
- export function trimText(text = "", maxChars = 240) {
41
- const clean = String(text).replaceAll(/\s+/g, " ").trim();
42
- if (clean.length <= maxChars)
43
- return clean;
44
- const truncated = clean.slice(0, maxChars);
45
- const lastSpace = truncated.lastIndexOf(" ");
46
- return lastSpace > 0 ? `${truncated.slice(0, lastSpace)}...` : `${truncated}...`;
47
- }
48
- export function normalizeSourceTitle(title = "") {
49
- const clean = trimText(title, 180);
50
- if (!clean)
51
- return "";
52
- if (/^https?:\/\//i.test(clean))
53
- return "";
54
- const wordCount = clean.split(/\s+/).filter(Boolean).length;
55
- const hasUppercase = /[A-Z]/.test(clean);
56
- const hasDigit = /\d/.test(clean);
57
- const looksLikeFragment = clean === clean.toLowerCase() && wordCount <= 4 && !hasUppercase && !hasDigit;
58
- return looksLikeFragment ? "" : clean;
59
- }
60
- export function pickPreferredTitle(currentTitle = "", nextTitle = "") {
61
- const current = normalizeSourceTitle(currentTitle);
62
- const next = normalizeSourceTitle(nextTitle);
63
- if (!next)
64
- return current;
65
- if (!current)
66
- return next;
67
- const currentLooksLikeUrl = /^https?:\/\//i.test(current);
68
- const nextLooksLikeUrl = /^https?:\/\//i.test(next);
69
- if (currentLooksLikeUrl && !nextLooksLikeUrl)
70
- return next;
71
- if (!currentLooksLikeUrl && nextLooksLikeUrl)
72
- return current;
73
- return next.length > current.length ? next : current;
74
- }
75
- export function normalizeUrl(rawUrl) {
76
- if (!rawUrl)
77
- return null;
78
- try {
79
- const url = new URL(rawUrl);
80
- if (!["http:", "https:"].includes(url.protocol))
81
- return null;
82
- url.hash = "";
83
- url.hostname = url.hostname.toLowerCase();
84
- if (url.protocol === "https:" && url.port === "443" || url.protocol === "http:" && url.port === "80") {
85
- url.port = "";
86
- }
87
- for (const key of [...url.searchParams.keys()]) {
88
- const lower = key.toLowerCase();
89
- if (TRACKING_PARAMS.includes(lower) || lower.startsWith("utm_")) {
90
- url.searchParams.delete(key);
91
- }
92
- }
93
- url.searchParams.sort();
94
- const normalizedPath = url.pathname.replace(/\/{1,10}$/, "") || "/";
95
- url.pathname = normalizedPath;
96
- const normalized = url.toString();
97
- return normalizedPath === "/" ? normalized.replace(/\/$/, "") : normalized;
98
- } catch {
99
- return null;
100
- }
101
- }
102
- export function getDomain(rawUrl) {
103
- try {
104
- const domain = new URL(rawUrl).hostname.toLowerCase();
105
- return domain.replace(/^www\./, "");
106
- } catch {
107
- return "";
108
- }
109
- }
110
- export function matchesDomain(domain, hosts) {
111
- return hosts.some((host) => domain === host || domain.endsWith(`.${host}`));
112
- }
113
- export function classifySourceType(domain, title = "", rawUrl = "") {
114
- const lowerTitle = title.toLowerCase();
115
- const lowerUrl = rawUrl.toLowerCase();
116
- if (domain === "github.com" || domain === "gitlab.com")
117
- return "repo";
118
- if (domain === "arxiv.org" || domain === "doi.org" || domain === "semanticscholar.org" || domain.endsWith(".semanticscholar.org") || lowerUrl.includes("/paper/") || lowerUrl.includes("/pdf/")) {
119
- return "academic";
120
- }
121
- if (matchesDomain(domain, SOCIAL_HOSTS))
122
- return "social";
123
- if (matchesDomain(domain, COMMUNITY_HOSTS))
124
- return "community";
125
- if (matchesDomain(domain, NEWS_HOSTS))
126
- return "news";
127
- if (domain.startsWith("docs.") || domain.startsWith("developer.") || domain.startsWith("developers.") || domain.startsWith("api.") || lowerTitle.includes("documentation") || lowerTitle.includes("docs") || lowerTitle.includes("reference") || lowerUrl.includes("/docs/") || lowerUrl.includes("/reference/") || lowerUrl.includes("/api/")) {
128
- return "official-docs";
129
- }
130
- if (domain.startsWith("blog.") || lowerUrl.includes("/blog/"))
131
- return "maintainer-blog";
132
- return "website";
133
- }
134
- export function sourceTypePriority(sourceType) {
135
- switch (sourceType) {
136
- case "official-docs":
137
- return 5;
138
- case "repo":
139
- return 4;
140
- case "academic":
141
- return 4;
142
- case "maintainer-blog":
143
- return 3;
144
- case "website":
145
- return 2;
146
- case "community":
147
- return 1;
148
- case "news":
149
- return 0;
150
- case "social":
151
- return -6;
152
- default:
153
- return 0;
154
- }
155
- }
156
- export function bestRank(source) {
157
- const ranks = Object.values(source.perEngine || {}).map((v) => v?.rank || 99);
158
- return ranks.length ? Math.min(...ranks) : 99;
159
- }
160
- const DISCUSSION_HOSTS = ["reddit.com", "news.ycombinator.com", "lobste.rs"];
161
- export function computeCompositeScore(source) {
162
- return source.smartScore * 3 + source.engineCount * 5 + sourceTypePriority(source.sourceType) * 2 + Math.max(0, 7 - bestRank(source));
163
- }
164
- export function inferPreferredDomains(query) {
165
- const normalized = query.toLowerCase();
166
- const matches = [];
167
- if (normalized.includes("openai") || normalized.includes("gpt") || normalized.includes("chatgpt")) {
168
- matches.push("openai.com", "platform.openai.com", "help.openai.com");
169
- }
170
- if (normalized.includes("anthropic") || normalized.includes("claude")) {
171
- matches.push("anthropic.com", "docs.anthropic.com");
172
- }
173
- if (normalized.includes("bun")) {
174
- matches.push("bun.sh", "bun.com");
175
- }
176
- if (normalized.includes("next.js") || normalized.includes("nextjs")) {
177
- matches.push("nextjs.org", "vercel.com");
178
- }
179
- if (normalized.includes("playwright")) {
180
- matches.push("playwright.dev");
181
- }
182
- if (normalized.includes("supabase")) {
183
- matches.push("supabase.com", "supabase.io");
184
- }
185
- if (normalized.includes("prisma")) {
186
- matches.push("prisma.io");
187
- }
188
- if (normalized.includes("tailwind")) {
189
- matches.push("tailwindcss.com");
190
- }
191
- if (normalized.includes("vite")) {
192
- matches.push("vitejs.dev", "vite.dev");
193
- }
194
- if (normalized.includes("astro")) {
195
- matches.push("astro.build");
196
- }
197
- if (normalized.includes("svelte")) {
198
- matches.push("svelte.dev");
199
- }
200
- if (normalized.includes("solid")) {
201
- matches.push("solidjs.com");
202
- }
203
- if (normalized.includes("vue") || normalized.includes("nuxt")) {
204
- matches.push("vuejs.org", "nuxt.com");
205
- }
206
- if (normalized.includes("react") || normalized.includes("react native")) {
207
- matches.push("react.dev", "reactnative.dev");
208
- }
209
- if (normalized.includes("angular")) {
210
- matches.push("angular.io", "angular.dev");
211
- }
212
- if (normalized.includes("node.js") || normalized.includes("nodejs")) {
213
- matches.push("nodejs.org", "nodejs.dev", "npmjs.com");
214
- }
215
- if (/\bgo\b/.test(normalized) || normalized.includes("golang")) {
216
- matches.push("go.dev", "golang.org", "pkg.go.dev");
217
- }
218
- if (normalized.includes("deno")) {
219
- matches.push("deno.land", "deno.com");
220
- }
221
- if (normalized.includes("fresh")) {
222
- matches.push("fresh.deno.dev");
223
- }
224
- if (normalized.includes("typescript") || normalized.includes("ts")) {
225
- matches.push("typescriptlang.org");
226
- }
227
- if (normalized.includes("python")) {
228
- matches.push("python.org", "docs.python.org");
229
- }
230
- if (normalized.includes("rust")) {
231
- matches.push("rust-lang.org", "docs.rs", "crates.io");
232
- }
233
- if (normalized.includes("zig")) {
234
- matches.push("ziglang.org");
235
- }
236
- if (normalized.includes("docker")) {
237
- matches.push("docker.com", "docs.docker.com", "hub.docker.com");
238
- }
239
- if (normalized.includes("kubernetes") || normalized.includes("k8s")) {
240
- matches.push("kubernetes.io", "k8s.io");
241
- }
242
- if (normalized.includes("postgres") || normalized.includes("postgresql")) {
243
- matches.push("postgresql.org", "neon.tech", "supabase.com");
244
- }
245
- if (normalized.includes("redis")) {
246
- matches.push("redis.io");
247
- }
248
- if (normalized.includes("sqlite")) {
249
- matches.push("sqlite.org");
250
- }
251
- if (normalized.includes("cloudflare")) {
252
- matches.push("developers.cloudflare.com", "cloudflare.com");
253
- }
254
- if (normalized.includes("vercel")) {
255
- matches.push("vercel.com", "nextjs.org");
256
- }
257
- if (normalized.includes("netlify")) {
258
- matches.push("netlify.com", "docs.netlify.com");
259
- }
260
- if (normalized.includes("stripe")) {
261
- matches.push("stripe.com", "docs.stripe.com");
262
- }
263
- if (normalized.includes("github")) {
264
- matches.push("github.com", "docs.github.com");
265
- }
266
- if (normalized.includes("gitlab")) {
267
- matches.push("gitlab.com", "docs.gitlab.com");
268
- }
269
- if (normalized.includes("aws")) {
270
- matches.push("aws.amazon.com", "docs.aws.amazon.com");
271
- }
272
- if (normalized.includes("azure")) {
273
- matches.push("azure.microsoft.com", "learn.microsoft.com");
274
- }
275
- if (normalized.includes("gcp") || normalized.includes("google cloud")) {
276
- matches.push("cloud.google.com", "developers.google.com");
277
- }
278
- if (normalized.includes("gemini") || normalized.includes("google ai")) {
279
- matches.push("ai.google.dev", "developers.google.com");
280
- }
281
- for (const socialHost of SOCIAL_HOSTS) {
282
- const bareName = socialHost.replace(/\.com$/, "");
283
- if (normalized.includes(bareName))
284
- matches.push(socialHost);
285
- }
286
- return [...new Set(matches)];
287
- }
288
- export function domainMatches(hostname, candidate) {
289
- return hostname === candidate || hostname.endsWith(`.${candidate}`);
290
- }
291
- export function buildSourceRegistry(out, query = "") {
292
- const seen = new Map;
293
- const engineOrder = Object.keys(out || {}).filter((key) => !key.startsWith("_"));
294
- const preferredDomains = inferPreferredDomains(query);
295
- for (const engine of engineOrder) {
296
- const result = out[engine];
297
- if (!result?.sources)
298
- continue;
299
- for (let i = 0;i < result.sources.length; i++) {
300
- const source = result.sources[i];
301
- const canonicalUrl = normalizeUrl(source.url);
302
- if (!canonicalUrl || canonicalUrl.length < 10)
303
- continue;
304
- const title = normalizeSourceTitle(source.title || "");
305
- const domain = getDomain(canonicalUrl);
306
- const sourceType = classifySourceType(domain, title, canonicalUrl);
307
- let smartScore = 0;
308
- if (preferredDomains.some((pd) => domainMatches(domain, pd))) {
309
- smartScore += 10;
310
- }
311
- if (sourceType === "official-docs") {
312
- smartScore += 3;
313
- }
314
- const lowerUrl = canonicalUrl.toLowerCase();
315
- if (/\/docs\/|\/documentation\/|\.dev\/|\/api\/|\/reference\//.test(lowerUrl)) {
316
- smartScore += 2;
317
- }
318
- const queryTargetsSocialHost = preferredDomains.some((pd) => domainMatches(domain, pd));
319
- if (sourceType === "social" && !queryTargetsSocialHost) {
320
- smartScore -= 20;
321
- }
322
- if (preferredDomains.length > 0) {
323
- if (matchesDomain(domain, DISCUSSION_HOSTS)) {
324
- smartScore -= 3;
325
- } else if (sourceType === "community" && !matchesDomain(domain, ["stackoverflow.com", "stackexchange.com"])) {
326
- smartScore -= 1;
327
- }
328
- }
329
- const existing = seen.get(canonicalUrl) || {
330
- id: "",
331
- canonicalUrl,
332
- displayUrl: source.url || canonicalUrl,
333
- domain,
334
- title: "",
335
- engines: [],
336
- engineCount: 0,
337
- perEngine: {},
338
- sourceType,
339
- isOfficial: sourceType === "official-docs",
340
- smartScore: 0
341
- };
342
- existing.title = pickPreferredTitle(existing.title, title);
343
- existing.displayUrl = existing.displayUrl || source.url || canonicalUrl;
344
- existing.sourceType = existing.sourceType || sourceType;
345
- existing.isOfficial = existing.isOfficial || sourceType === "official-docs";
346
- existing.smartScore = Math.max(existing.smartScore, smartScore);
347
- if (!existing.engines.includes(engine)) {
348
- existing.engines.push(engine);
349
- }
350
- existing.perEngine[engine] = {
351
- rank: i + 1,
352
- title: pickPreferredTitle(existing.perEngine[engine]?.title || "", title)
353
- };
354
- seen.set(canonicalUrl, existing);
355
- }
356
- }
357
- const sources = Array.from(seen.values()).map((source) => ({
358
- ...source,
359
- engineCount: source.engines.length
360
- }));
361
- const nonSocial = sources.filter((s) => s.sourceType !== "social");
362
- const socialSources = sources.filter((s) => s.sourceType === "social");
363
- nonSocial.sort((a, b) => {
364
- const diff = computeCompositeScore(b) - computeCompositeScore(a);
365
- if (diff !== 0)
366
- return diff;
367
- return a.domain.localeCompare(b.domain);
368
- });
369
- socialSources.sort((a, b) => {
370
- const diff = computeCompositeScore(b) - computeCompositeScore(a);
371
- if (diff !== 0)
372
- return diff;
373
- return a.domain.localeCompare(b.domain);
374
- });
375
- const ordered = [...nonSocial, ...socialSources];
376
- return ordered.slice(0, 12).map((source, index) => ({
377
- ...source,
378
- id: `S${index + 1}`,
379
- title: source.title || source.domain || source.canonicalUrl
380
- }));
381
- }
382
- export function mergeFetchDataIntoSources(sources, fetchedSources) {
383
- const byId = new Map(fetchedSources.map((source) => [source.id, source]));
384
- return sources.map((source) => {
385
- const fetched = byId.get(source.id);
386
- if (!fetched)
387
- return source;
388
- const title = pickPreferredTitle(source.title, fetched.title || "");
389
- return {
390
- ...source,
391
- title: title || source.title,
392
- fetch: {
393
- attempted: true,
394
- ok: !fetched.error && fetched.contentChars > 100,
395
- status: fetched.status || null,
396
- finalUrl: fetched.finalUrl || fetched.url || source.canonicalUrl,
397
- contentType: fetched.contentType || "",
398
- lastModified: fetched.lastModified || "",
399
- publishedTime: fetched.publishedTime || "",
400
- byline: fetched.byline || "",
401
- siteName: fetched.siteName || "",
402
- lang: fetched.lang || "",
403
- title: fetched.title || "",
404
- snippet: fetched.snippet || "",
405
- contentChars: fetched.contentChars || 0,
406
- source: fetched.source || "unknown",
407
- duration: fetched.duration || 0,
408
- error: fetched.error || ""
409
- }
410
- };
411
- });
412
- }
@@ -1,127 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { nodeRuntimeCommand } from "../utils/node-runtime.mjs";
3
- import { GREEDY_PROFILE_DIR, SUPPORTED_SYNTHESIZERS } from "./constants.mjs";
4
- import {
5
- moduleDirectory,
6
- resolveGreedySearchExtractorScript
7
- } from "./paths.mjs";
8
- import {
9
- buildSynthesisPrompt,
10
- normalizeSynthesisPayload,
11
- parseStructuredJson
12
- } from "./synthesis.mjs";
13
- import { buildSourceRegistry } from "./sources.mjs";
14
- const __dir = moduleDirectory(import.meta.url);
15
- const SYNTHESIS_EXTRACTORS = {
16
- gemini: "gemini.mjs",
17
- chatgpt: "chatgpt.mjs"
18
- };
19
- const SYNTHESIS_START_URLS = {
20
- gemini: "https://gemini.google.com/app",
21
- chatgpt: "https://chatgpt.com/"
22
- };
23
- export function normalizeSynthesizer(synthesizer = "gemini") {
24
- const normalized = String(synthesizer || "gemini").toLowerCase();
25
- if (normalized === "gem")
26
- return "gemini";
27
- if (normalized === "gpt")
28
- return "chatgpt";
29
- return normalized;
30
- }
31
- export function getSynthesisStartUrl(synthesizer = "gemini") {
32
- return SYNTHESIS_START_URLS[normalizeSynthesizer(synthesizer)] || "about:blank";
33
- }
34
- export async function runSynthesisPrompt(synthesizer, prompt, { tabPrefix = null, timeoutMs = 180000, visible = null } = {}) {
35
- const normalizedSynthesizer = normalizeSynthesizer(synthesizer);
36
- const script = SYNTHESIS_EXTRACTORS[normalizedSynthesizer];
37
- if (!script || !SUPPORTED_SYNTHESIZERS.includes(normalizedSynthesizer)) {
38
- throw new Error(`Unsupported synthesizer "${synthesizer}". Supported: ${SUPPORTED_SYNTHESIZERS.join(", ")}`);
39
- }
40
- return new Promise((resolve, reject) => {
41
- const extractorScript = resolveGreedySearchExtractorScript(script, {
42
- moduleDir: __dir
43
- });
44
- const extraArgs = tabPrefix ? ["--tab", String(tabPrefix)] : [];
45
- const childEnv = {
46
- ...process.env,
47
- CDP_PROFILE_DIR: GREEDY_PROFILE_DIR
48
- };
49
- if (visible !== true) {
50
- delete childEnv.GREEDY_SEARCH_VISIBLE;
51
- delete childEnv.GREEDY_SEARCH_ALWAYS_VISIBLE;
52
- } else {
53
- childEnv.GREEDY_SEARCH_VISIBLE = "1";
54
- childEnv.GREEDY_SEARCH_ALWAYS_VISIBLE = "1";
55
- }
56
- const proc = spawn(nodeRuntimeCommand(), [extractorScript, "--stdin", ...extraArgs], {
57
- stdio: ["pipe", "pipe", "pipe"],
58
- env: childEnv
59
- });
60
- proc.stdin.write(prompt);
61
- proc.stdin.end();
62
- let out = "";
63
- let err = "";
64
- proc.stdout.on("data", (d) => out += d);
65
- proc.stderr.on("data", (d) => err += d);
66
- const t = setTimeout(() => {
67
- proc.kill();
68
- reject(new Error(`${normalizedSynthesizer} prompt timed out after ${timeoutMs / 1000}s`));
69
- }, timeoutMs);
70
- proc.on("close", (code) => {
71
- clearTimeout(t);
72
- if (code !== 0) {
73
- reject(new Error(err.trim() || `${normalizedSynthesizer} extractor failed`));
74
- return;
75
- }
76
- try {
77
- resolve(JSON.parse(out.trim()));
78
- } catch {
79
- reject(new Error(`bad JSON from ${normalizedSynthesizer}: ${out.slice(0, 100)}`));
80
- }
81
- });
82
- });
83
- }
84
- export async function runGeminiPrompt(prompt, options = {}) {
85
- return runSynthesisPrompt("gemini", prompt, options);
86
- }
87
- export async function synthesizeResults(query, results, {
88
- grounded = false,
89
- tabPrefix = null,
90
- visible = null,
91
- synthesizer = "gemini"
92
- } = {}) {
93
- const normalizedSynthesizer = normalizeSynthesizer(synthesizer);
94
- const sources = Array.isArray(results._sources) ? results._sources : buildSourceRegistry(results);
95
- const prompt = buildSynthesisPrompt(query, results, sources, { grounded });
96
- const raw = await runSynthesisPrompt(normalizedSynthesizer, prompt, {
97
- tabPrefix,
98
- timeoutMs: 180000,
99
- visible
100
- });
101
- let structured = parseStructuredJson(raw.answer || "");
102
- const SYNTHESIS_FIELDS = [
103
- "answer",
104
- "agreement",
105
- "claims",
106
- "differences",
107
- "caveats"
108
- ];
109
- const hasSynthesisFields = structured && SYNTHESIS_FIELDS.some((f) => (f in structured));
110
- const hasEngineKeys = structured && ["perplexity", "bing", "google", "chatgpt", "gemini"].some((e) => (e in structured));
111
- if (hasEngineKeys && !hasSynthesisFields) {
112
- structured = null;
113
- }
114
- return {
115
- ...normalizeSynthesisPayload(structured, sources, raw.answer || ""),
116
- rawAnswer: raw.answer || "",
117
- synthesizedBy: normalizedSynthesizer,
118
- synthesizerSources: raw.sources || [],
119
- geminiSources: normalizedSynthesizer === "gemini" ? raw.sources || [] : []
120
- };
121
- }
122
- export async function synthesizeWithGemini(query, results, options = {}) {
123
- return synthesizeResults(query, results, {
124
- ...options,
125
- synthesizer: "gemini"
126
- });
127
- }