@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,470 +0,0 @@
1
- import {
2
- fetchSourceHttp,
3
- extractContent,
4
- detectBotBlock,
5
- checkContentQuality,
6
- defaultFetchHeaders,
7
- isPrivateUrl
8
- } from "../fetcher.mjs";
9
- import { fetchGitHubContent, parseGitHubUrl } from "../github.mjs";
10
- import { fetchRedditContent, parseRedditUrl } from "../reddit.mjs";
11
- import { trimContentHeadTail } from "../utils/content.mjs";
12
- import { cdp, closeTab, openNewTab } from "./chrome.mjs";
13
- import { SOURCE_FETCH_CONCURRENCY } from "./constants.mjs";
14
- import { extractPdfMarkdown } from "./pdf.mjs";
15
- import { trimText } from "./sources.mjs";
16
- async function fetchSourceViaChrome(tab, url, maxChars = 8000) {
17
- const start = Date.now();
18
- try {
19
- const frames = await cdp(["evalraw", tab, "Page.getFrameTree", "{}"]).then((r) => JSON.parse(r)).catch(() => null);
20
- const frameId = frames?.frameTree?.frame?.id || undefined;
21
- const raw = await cdp([
22
- "evalraw",
23
- tab,
24
- "Network.loadNetworkResource",
25
- JSON.stringify({
26
- frameId,
27
- url,
28
- options: { disableCache: true, includeCredentials: false }
29
- })
30
- ], 20000);
31
- const result = JSON.parse(raw);
32
- const resource = result.resource;
33
- if (!resource?.success || !resource.httpStatusCode) {
34
- return {
35
- url,
36
- error: resource?.netErrorName || resource?.netError || "loadNetworkResource failed",
37
- source: "chrome",
38
- duration: Date.now() - start,
39
- needsFallback: true
40
- };
41
- }
42
- let body = "";
43
- if (resource.stream) {
44
- try {
45
- const ioRaw = await cdp([
46
- "evalraw",
47
- tab,
48
- "IO.read",
49
- JSON.stringify({ handle: resource.stream })
50
- ], 1e4);
51
- const ioResult = JSON.parse(ioRaw);
52
- body = ioResult.data || "";
53
- await cdp([
54
- "evalraw",
55
- tab,
56
- "IO.close",
57
- JSON.stringify({ handle: resource.stream })
58
- ]).catch(() => {});
59
- } catch {}
60
- }
61
- if (!body || body.length < 100) {
62
- return {
63
- url,
64
- error: "Empty response body from Network.loadNetworkResource",
65
- source: "chrome",
66
- duration: Date.now() - start,
67
- needsFallback: true
68
- };
69
- }
70
- const botCheck = detectBotBlock(resource.httpStatusCode, body, url, url);
71
- if (botCheck.blocked) {
72
- return {
73
- url,
74
- status: resource.httpStatusCode,
75
- error: `Blocked: ${botCheck.reason}`,
76
- source: "chrome",
77
- duration: Date.now() - start,
78
- needsBrowser: true
79
- };
80
- }
81
- const extracted = await extractContent(body, url);
82
- const quality = checkContentQuality(extracted);
83
- if (!quality.ok) {
84
- return {
85
- url,
86
- status: resource.httpStatusCode,
87
- error: `Low quality: ${quality.reason}`,
88
- source: "chrome",
89
- duration: Date.now() - start,
90
- needsBrowser: true
91
- };
92
- }
93
- const content = trimContentHeadTail(extracted.markdown, maxChars);
94
- return {
95
- url,
96
- finalUrl: url,
97
- status: resource.httpStatusCode,
98
- contentType: "text/markdown",
99
- lastModified: "",
100
- publishedTime: extracted.publishedTime || "",
101
- byline: extracted.byline || "",
102
- siteName: extracted.siteName || "",
103
- lang: extracted.lang || "",
104
- title: extracted.title || url,
105
- snippet: extracted.excerpt,
106
- content,
107
- contentChars: content.length,
108
- source: "chrome",
109
- duration: Date.now() - start
110
- };
111
- } catch (error) {
112
- return {
113
- url,
114
- error: error.message,
115
- source: "chrome",
116
- duration: Date.now() - start,
117
- needsFallback: true
118
- };
119
- }
120
- }
121
- function isLikelyPdfUrl(url) {
122
- try {
123
- const parsed = new URL(url);
124
- return parsed.pathname.toLowerCase().endsWith(".pdf");
125
- } catch {
126
- return false;
127
- }
128
- }
129
- async function fetchPdfSourceHttp(url, maxChars = 8000) {
130
- const privateCheck = isPrivateUrl(url);
131
- if (privateCheck.blocked) {
132
- return {
133
- url,
134
- finalUrl: url,
135
- status: 403,
136
- error: `Blocked: ${privateCheck.reason}`,
137
- source: "pdf-http"
138
- };
139
- }
140
- const controller = new AbortController;
141
- const timeoutId = setTimeout(() => controller.abort(), 20000);
142
- const start = Date.now();
143
- try {
144
- const response = await fetch(url, {
145
- method: "GET",
146
- redirect: "follow",
147
- signal: controller.signal,
148
- headers: defaultFetchHeaders({
149
- accept: "application/pdf,application/octet-stream;q=0.9,*/*;q=0.5"
150
- })
151
- });
152
- clearTimeout(timeoutId);
153
- const contentType = response.headers.get("content-type") || "";
154
- const finalUrl = response.url || url;
155
- const contentLength = Number.parseInt(response.headers.get("content-length") || "0", 10);
156
- if (response.status >= 400) {
157
- return {
158
- url,
159
- finalUrl,
160
- status: response.status,
161
- error: `HTTP ${response.status}`,
162
- source: "pdf-http",
163
- duration: Date.now() - start
164
- };
165
- }
166
- if (!contentType.toLowerCase().includes("application/pdf") && !isLikelyPdfUrl(finalUrl)) {
167
- return null;
168
- }
169
- if (contentLength > 30 * 1024 * 1024) {
170
- return {
171
- url,
172
- finalUrl,
173
- status: response.status,
174
- error: `PDF too large: ${contentLength} bytes`,
175
- source: "pdf-http",
176
- duration: Date.now() - start
177
- };
178
- }
179
- const buffer = Buffer.from(await response.arrayBuffer());
180
- const pdf = await extractPdfMarkdown(buffer, finalUrl);
181
- if (!pdf || pdf.error) {
182
- return {
183
- url,
184
- finalUrl,
185
- status: response.status,
186
- error: pdf?.error || "PDF text extraction failed",
187
- source: "pdf-http",
188
- duration: Date.now() - start
189
- };
190
- }
191
- const content = trimContentHeadTail(pdf.content, maxChars);
192
- return {
193
- url,
194
- finalUrl,
195
- status: response.status,
196
- contentType: "application/pdf",
197
- lastModified: response.headers.get("last-modified") || "",
198
- title: pdf.title,
199
- snippet: trimText(content, 320),
200
- content,
201
- contentChars: content.length,
202
- pages: pdf.pages,
203
- source: "pdf-http",
204
- duration: Date.now() - start
205
- };
206
- } catch (error) {
207
- clearTimeout(timeoutId);
208
- return {
209
- url,
210
- finalUrl: url,
211
- error: error.message || String(error),
212
- source: "pdf-http",
213
- duration: Date.now() - start
214
- };
215
- }
216
- }
217
- export async function fetchSourceContent(url, maxChars = 8000) {
218
- const start = Date.now();
219
- if (isLikelyPdfUrl(url)) {
220
- const pdfResult = await fetchPdfSourceHttp(url, maxChars);
221
- if (pdfResult?.content || pdfResult?.status === 403)
222
- return pdfResult;
223
- }
224
- if (parseGitHubUrl(url)) {
225
- const parsed = parseGitHubUrl(url);
226
- if (parsed && (parsed.type === "root" || parsed.type === "tree" || parsed.type === "blob" && !parsed.path?.includes("."))) {
227
- const ghResult = await fetchGitHubContent(url);
228
- if (ghResult.ok) {
229
- const content = trimContentHeadTail(ghResult.content, maxChars);
230
- return {
231
- url,
232
- finalUrl: url,
233
- status: 200,
234
- contentType: "text/markdown",
235
- lastModified: "",
236
- title: ghResult.title,
237
- snippet: content.slice(0, 320),
238
- content,
239
- contentChars: content.length,
240
- source: "github-api",
241
- ...ghResult.tree && { tree: ghResult.tree },
242
- duration: Date.now() - start
243
- };
244
- }
245
- process.stderr.write(`[greedysearch] GitHub API fetch failed, trying HTTP: ${ghResult.error}
246
- `);
247
- }
248
- }
249
- const redditInfo = parseRedditUrl(url);
250
- if (redditInfo?.type === "post") {
251
- process.stderr.write(`[greedysearch] Using Reddit JSON API for: ${url.slice(0, 60)}...
252
- `);
253
- const redditResult = await fetchRedditContent(url, maxChars);
254
- if (redditResult.ok) {
255
- const content = trimContentHeadTail(redditResult.markdown, maxChars);
256
- return {
257
- url,
258
- finalUrl: redditResult.finalUrl,
259
- status: redditResult.status,
260
- contentType: "text/markdown",
261
- lastModified: redditResult.lastModified || "",
262
- publishedTime: redditResult.publishedTime || "",
263
- byline: redditResult.byline || "",
264
- siteName: redditResult.siteName || "",
265
- lang: redditResult.lang || "",
266
- title: redditResult.title,
267
- snippet: redditResult.excerpt,
268
- content,
269
- contentChars: content.length,
270
- source: "reddit-api",
271
- duration: Date.now() - start
272
- };
273
- }
274
- process.stderr.write(`[greedysearch] Reddit API fetch failed, falling back to HTTP: ${redditResult.error}
275
- `);
276
- }
277
- const httpResult = await fetchSourceHttp(url, { timeoutMs: 1e4 });
278
- if (httpResult.ok) {
279
- const content = trimContentHeadTail(httpResult.markdown, maxChars);
280
- return {
281
- url,
282
- finalUrl: httpResult.finalUrl,
283
- status: httpResult.status,
284
- contentType: "text/markdown",
285
- lastModified: httpResult.lastModified || "",
286
- publishedTime: httpResult.publishedTime || "",
287
- byline: httpResult.byline || "",
288
- siteName: httpResult.siteName || "",
289
- lang: httpResult.lang || "",
290
- title: httpResult.title,
291
- snippet: httpResult.excerpt,
292
- content,
293
- contentChars: content.length,
294
- source: "http",
295
- duration: Date.now() - start
296
- };
297
- }
298
- if (httpResult.needsBrowser) {
299
- try {
300
- const chromeTab = await openNewTab();
301
- try {
302
- const chromeResult = await fetchSourceViaChrome(chromeTab, url, maxChars);
303
- if (chromeResult.content && chromeResult.content.length > 100) {
304
- return chromeResult;
305
- }
306
- } finally {
307
- await closeTab(chromeTab);
308
- }
309
- } catch {}
310
- }
311
- process.stderr.write(`[greedysearch] HTTP failed for ${url.slice(0, 60)}, trying browser...
312
- `);
313
- return await fetchSourceContentBrowser(url, maxChars);
314
- }
315
- async function waitForPageReady(tab, timeoutMs = 4000, intervalMs = 200) {
316
- const deadline = Date.now() + timeoutMs;
317
- while (Date.now() < deadline) {
318
- try {
319
- const ready = await cdp([
320
- "eval",
321
- tab,
322
- `document.readyState === "complete" && !!document.body && document.body.innerText.length > 500`
323
- ]);
324
- if (ready.trim() === "true")
325
- return;
326
- } catch {}
327
- await new Promise((r) => setTimeout(r, intervalMs));
328
- }
329
- }
330
- async function fetchSourceContentBrowser(url, maxChars = 8000) {
331
- const start = Date.now();
332
- let tab;
333
- try {
334
- tab = await openNewTab();
335
- } catch (e) {
336
- return {
337
- url,
338
- title: "",
339
- content: null,
340
- snippet: "",
341
- contentChars: 0,
342
- error: `openNewTab failed: ${e.message}`,
343
- source: "browser",
344
- duration: Date.now() - start
345
- };
346
- }
347
- try {
348
- await cdp(["nav", tab, url], 30000);
349
- await waitForPageReady(tab);
350
- const content = await cdp([
351
- "eval",
352
- tab,
353
- String.raw`
354
- (function(){
355
- var el = document.querySelector('article, [role="main"], main, .post-content, .article-body, #content, .content');
356
- var text = (el || document.body).innerText;
357
- return JSON.stringify({
358
- title: document.title,
359
- content: text.replace(/\s+/g, ' ').trim(),
360
- url: location.href
361
- });
362
- })()
363
- `
364
- ]);
365
- const parsed = JSON.parse(content);
366
- const finalContent = trimContentHeadTail(parsed.content, maxChars);
367
- return {
368
- url,
369
- finalUrl: parsed.url || url,
370
- status: 200,
371
- contentType: "text/plain",
372
- lastModified: "",
373
- title: parsed.title,
374
- snippet: trimText(finalContent, 320),
375
- content: finalContent,
376
- contentChars: finalContent.length,
377
- source: "browser",
378
- duration: Date.now() - start
379
- };
380
- } catch (error) {
381
- return {
382
- url,
383
- title: "",
384
- content: null,
385
- snippet: "",
386
- contentChars: 0,
387
- error: error.message,
388
- source: "browser",
389
- duration: Date.now() - start
390
- };
391
- } finally {
392
- await closeTab(tab);
393
- }
394
- }
395
- export async function fetchMultipleSources(sources, maxSources = 5, maxChars = 8000, concurrency = SOURCE_FETCH_CONCURRENCY) {
396
- const toFetch = sources.slice(0, maxSources);
397
- if (toFetch.length === 0)
398
- return [];
399
- const workerCount = Math.min(toFetch.length, Math.max(1, Number.parseInt(String(concurrency), 10) || SOURCE_FETCH_CONCURRENCY));
400
- process.stderr.write(`[greedysearch] Fetching content from ${toFetch.length} sources via HTTP (concurrency ${workerCount})...
401
- `);
402
- const fetched = new Array(toFetch.length);
403
- let nextIndex = 0;
404
- let completed = 0;
405
- async function worker() {
406
- while (true) {
407
- const index = nextIndex++;
408
- if (index >= toFetch.length)
409
- return;
410
- const s = toFetch[index];
411
- const url = s.canonicalUrl || s.url;
412
- process.stderr.write(`[greedysearch] [${index + 1}/${toFetch.length}] Fetching: ${url.slice(0, 60)}...
413
- `);
414
- const result = await fetchSourceContent(url, maxChars).catch((e) => ({
415
- url,
416
- title: "",
417
- content: null,
418
- snippet: "",
419
- contentChars: 0,
420
- error: e.message,
421
- source: "error",
422
- duration: 0
423
- }));
424
- fetched[index] = {
425
- id: s.id,
426
- ...result
427
- };
428
- if (result.content && result.content.length > 100) {
429
- process.stderr.write(`[greedysearch] ✓ ${result.source}: ${result.content.length} chars
430
- `);
431
- } else if (result.error) {
432
- process.stderr.write(`[greedysearch] ✗ ${result.error.slice(0, 80)}
433
- `);
434
- }
435
- completed += 1;
436
- process.stderr.write(`PROGRESS:fetch:${completed}/${toFetch.length}
437
- `);
438
- }
439
- }
440
- await Promise.all(Array.from({ length: workerCount }, () => worker()));
441
- const successful = fetched.filter((f) => f.content && f.content.length > 100);
442
- const httpCount = fetched.filter((f) => f.source === "http").length;
443
- const browserCount = fetched.filter((f) => f.source === "browser").length;
444
- process.stderr.write(`[greedysearch] Fetched ${successful.length}/${fetched.length} sources ` + `(HTTP: ${httpCount}, Browser: ${browserCount})
445
- `);
446
- return fetched;
447
- }
448
- export async function fetchTopSource(url) {
449
- const tab = await openNewTab();
450
- try {
451
- await cdp(["nav", tab, url], 30000);
452
- await waitForPageReady(tab);
453
- const content = await cdp([
454
- "eval",
455
- tab,
456
- String.raw`
457
- (function(){
458
- var el = document.querySelector('article, [role="main"], main, .post-content, .article-body, #content, .content');
459
- var text = (el || document.body).innerText;
460
- return text.replace(/\s+/g, ' ').trim();
461
- })()
462
- `
463
- ]);
464
- return { url, content };
465
- } catch (e) {
466
- return { url, content: null, error: e.message };
467
- } finally {
468
- await closeTab(tab);
469
- }
470
- }
@@ -1,31 +0,0 @@
1
- import { mkdirSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- const DEFAULT_DIR = join(process.cwd(), ".dm", "greedysearch-sources");
4
- export function writeSourcesToFiles(fetchedSources, dir = DEFAULT_DIR) {
5
- mkdirSync(dir, { recursive: true });
6
- return fetchedSources.map((source) => {
7
- if (!source.content || source.content.length < 10) {
8
- return source;
9
- }
10
- const safeId = String(source.id || "unknown").replace(/[^a-zA-Z0-9_-]/g, "");
11
- const urlSlug = (source.canonicalUrl || source.url || "").replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "-").slice(0, 40);
12
- const filename = `${safeId}-${urlSlug}.md`;
13
- const filepath = join(dir, filename);
14
- const header = `---
15
- url: ${source.finalUrl || source.url}
16
- title: ${source.title || ""}
17
- source: ${source.source || "unknown"}
18
- status: ${source.status || ""}
19
- chars: ${source.contentChars || source.content.length}
20
- ---
21
-
22
- `;
23
- writeFileSync(filepath, header + source.content, "utf8");
24
- const { content, ...rest } = source;
25
- return {
26
- ...rest,
27
- contentPath: filepath,
28
- contentChars: source.contentChars || content.length
29
- };
30
- });
31
- }
@@ -1,51 +0,0 @@
1
- import { mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- const __dir = import.meta.dirname || new URL(".", import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1");
4
- export function slugify(query) {
5
- return query.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-|-$/g, "").slice(0, 60);
6
- }
7
- const RESULTS_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
8
- const RESULTS_MIN_KEEP = 10;
9
- function purgeOldResults(dir) {
10
- try {
11
- const files = readdirSync(dir).filter((f) => f.endsWith(".json") || f.endsWith(".md")).map((f) => ({ f, mtime: statSync(join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
12
- const cutoff = Date.now() - RESULTS_MAX_AGE_MS;
13
- for (let i = RESULTS_MIN_KEEP;i < files.length; i++) {
14
- if (files[i].mtime < cutoff) {
15
- rmSync(join(dir, files[i].f), { force: true });
16
- }
17
- }
18
- } catch {}
19
- }
20
- export function resultsDir() {
21
- const dir = join(__dir, "..", "..", "results");
22
- mkdirSync(dir, { recursive: true });
23
- purgeOldResults(dir);
24
- return dir;
25
- }
26
- export function writeOutput(data, outFile, { inline = false, synthesize = false, query = "" } = {}) {
27
- const json = `${JSON.stringify(data, null, 2)}
28
- `;
29
- if (outFile) {
30
- writeFileSync(outFile, json, "utf8");
31
- process.stderr.write(`Results written to ${outFile}
32
- `);
33
- return;
34
- }
35
- if (inline) {
36
- process.stdout.write(json);
37
- return;
38
- }
39
- const ts = new Date().toISOString().replaceAll("T", "_").replaceAll(/[:.]/g, "-").slice(0, 19);
40
- const slug = slugify(query);
41
- const base = join(resultsDir(), `${ts}_${slug}`);
42
- writeFileSync(`${base}.json`, json, "utf8");
43
- if (synthesize && data._synthesis?.answer) {
44
- writeFileSync(`${base}-synthesis.md`, data._synthesis.answer, "utf8");
45
- process.stdout.write(`${base}-synthesis.md
46
- `);
47
- } else {
48
- process.stdout.write(`${base}.json
49
- `);
50
- }
51
- }
@@ -1,48 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- export function moduleDirectory(metaUrl) {
5
- return fileURLToPath(new URL(".", metaUrl)).replace(/^\/([A-Z]:)/, "$1");
6
- }
7
- function uniqueExisting(candidates, exists = existsSync) {
8
- const seen = new Set;
9
- for (const candidate of candidates.filter(Boolean)) {
10
- if (seen.has(candidate))
11
- continue;
12
- seen.add(candidate);
13
- if (exists(candidate))
14
- return candidate;
15
- }
16
- return candidates.filter(Boolean).at(-1);
17
- }
18
- export function resolveGreedySearchExtractorScript(script, {
19
- moduleDir,
20
- entrypoint = process.argv[1],
21
- env = process.env,
22
- exists = existsSync
23
- } = {}) {
24
- const entryDir = entrypoint ? dirname(entrypoint) : null;
25
- const extensionDir = env.GREEDY_SEARCH_EXTENSION_DIR?.trim() || null;
26
- return uniqueExisting([
27
- moduleDir ? join(moduleDir, "..", "..", "extractors", script) : null,
28
- moduleDir ? join(moduleDir, "..", "extractors", script) : null,
29
- extensionDir ? join(extensionDir, "extractors", script) : null,
30
- entryDir ? join(entryDir, "..", "extractors", script) : null
31
- ], exists);
32
- }
33
- export function resolveGreedySearchLaunchScript({
34
- moduleDir,
35
- entrypoint = process.argv[1],
36
- env = process.env,
37
- exists = existsSync
38
- } = {}) {
39
- const entryDir = entrypoint ? dirname(entrypoint) : null;
40
- const extensionDir = env.GREEDY_SEARCH_EXTENSION_DIR?.trim() || null;
41
- return uniqueExisting([
42
- moduleDir ? join(moduleDir, "..", "..", "bin", "launch.mjs") : null,
43
- moduleDir ? join(moduleDir, "launch.mjs") : null,
44
- extensionDir ? join(extensionDir, "bin", "launch.mjs") : null,
45
- entryDir ? join(entryDir, "launch.mjs") : null,
46
- entryDir ? join(entryDir, "..", "bin", "launch.mjs") : null
47
- ], exists);
48
- }
@@ -1,64 +0,0 @@
1
- function ensurePdfDomPolyfills() {
2
- if (typeof globalThis.DOMMatrix === "undefined") {
3
- globalThis.DOMMatrix = class DOMMatrix {
4
- constructor(_init = undefined) {}
5
- multiplySelf() {
6
- return this;
7
- }
8
- preMultiplySelf() {
9
- return this;
10
- }
11
- translateSelf() {
12
- return this;
13
- }
14
- scaleSelf() {
15
- return this;
16
- }
17
- rotateSelf() {
18
- return this;
19
- }
20
- };
21
- }
22
- if (typeof globalThis.ImageData === "undefined") {
23
- globalThis.ImageData = class ImageData {
24
- constructor(data = undefined, width = 0, height = 0) {
25
- this.data = data;
26
- this.width = width;
27
- this.height = height;
28
- }
29
- };
30
- }
31
- if (typeof globalThis.Path2D === "undefined") {
32
- globalThis.Path2D = class Path2D {
33
- constructor(_path = undefined) {}
34
- };
35
- }
36
- }
37
- async function loadPdfParseCtor() {
38
- ensurePdfDomPolyfills();
39
- const mod = await import("pdf-parse");
40
- const ctor = mod.PDFParse ?? mod.default;
41
- if (!ctor)
42
- throw new Error("pdf-parse did not export PDFParse");
43
- return ctor;
44
- }
45
- export async function extractPdfMarkdown(buffer, url) {
46
- try {
47
- const PDFParseCtor = await loadPdfParseCtor();
48
- const parser = new PDFParseCtor({ data: new Uint8Array(buffer) });
49
- await parser.load();
50
- const data = await parser.getText();
51
- const text = data.text?.trim();
52
- if (!text)
53
- return null;
54
- return {
55
- title: new URL(url).pathname.split("/").pop() || "Document.pdf",
56
- content: `## PDF Content (${data.total} pages)
57
-
58
- ${text}`,
59
- pages: data.total
60
- };
61
- } catch (error) {
62
- return { error: error.message || String(error) };
63
- }
64
- }