@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.
- package/dm.exe +0 -0
- package/extensions/.dm-extensions.json +43 -183
- package/extensions/dm-context/src/context.js +1 -2
- package/extensions/dm-context/src/index.js +1 -2
- package/extensions/{greedysearch-dm/bin/cdp.mjs → dm-cua/bin/browser-cdp.mjs} +3 -3
- package/extensions/dm-cua/bin/browser-cua.mjs +1 -1
- package/extensions/{greedysearch-dm/bin/launch.mjs → dm-cua/bin/browser-launch.mjs} +12 -11
- package/extensions/dm-cua/index.js +1 -1
- package/extensions/dm-cua/src/browser-cua-lib.mjs +17 -12
- package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/constants.mjs +11 -11
- package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/node-runtime.mjs +1 -1
- package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/port-pid.mjs +1 -1
- package/extensions/dm-ponytail/extensions/ponytail.mjs +186 -0
- package/extensions/dm-ponytail/hooks/claude-codex-hooks.json +41 -0
- package/extensions/dm-ponytail/hooks/copilot-hooks.json +21 -0
- package/extensions/dm-ponytail/hooks/ponytail-activate.js +59 -0
- package/extensions/dm-ponytail/hooks/ponytail-config.js +116 -0
- package/extensions/dm-ponytail/hooks/ponytail-instructions.js +81 -0
- package/extensions/dm-ponytail/hooks/ponytail-mode-tracker.js +96 -0
- package/extensions/dm-ponytail/hooks/ponytail-runtime.js +79 -0
- package/extensions/dm-ponytail/hooks/ponytail-statusline.ps1 +24 -0
- package/extensions/dm-ponytail/hooks/ponytail-statusline.sh +18 -0
- package/extensions/dm-ponytail/hooks/ponytail-subagent.js +50 -0
- package/extensions/dm-ponytail/hooks/qoder-hooks.json +26 -0
- package/extensions/dm-ponytail/package.json +19 -0
- package/extensions/dm-ponytail/pi-extension/index.js +186 -0
- package/extensions/dm-ponytail/pi-extension/package.json +8 -0
- package/extensions/dm-ponytail/skills/ponytail/SKILL.md +120 -0
- package/extensions/dm-ponytail/skills/ponytail-audit/SKILL.md +41 -0
- package/extensions/dm-ponytail/skills/ponytail-debt/SKILL.md +44 -0
- package/extensions/dm-ponytail/skills/ponytail-gain/SKILL.md +50 -0
- package/extensions/dm-ponytail/skills/ponytail-help/SKILL.md +71 -0
- package/extensions/dm-ponytail/skills/ponytail-review/SKILL.md +57 -0
- package/package.json +2 -6
- package/extensions/dm-caveman/extensions/caveman.js +0 -300
- package/extensions/dm-caveman/package.json +0 -20
- package/extensions/dm-goal/package.json +0 -23
- package/extensions/dm-goal/src/goal.js +0 -914
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +0 -40
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +0 -5
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +0 -5
- package/extensions/greedysearch-dm/bin/gschrome.mjs +0 -30
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +0 -7
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +0 -13
- package/extensions/greedysearch-dm/bin/mcp.mjs +0 -400
- package/extensions/greedysearch-dm/bin/search.mjs +0 -635
- package/extensions/greedysearch-dm/bin/visible.mjs +0 -22
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +0 -344
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +0 -354
- package/extensions/greedysearch-dm/extractors/common.mjs +0 -794
- package/extensions/greedysearch-dm/extractors/consensus.mjs +0 -527
- package/extensions/greedysearch-dm/extractors/consent.mjs +0 -449
- package/extensions/greedysearch-dm/extractors/gemini.mjs +0 -291
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +0 -86
- package/extensions/greedysearch-dm/extractors/logically.mjs +0 -589
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +0 -370
- package/extensions/greedysearch-dm/extractors/selectors.mjs +0 -32
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +0 -199
- package/extensions/greedysearch-dm/index.js +0 -123
- package/extensions/greedysearch-dm/package.json +0 -27
- package/extensions/greedysearch-dm/skills/greedy-search/skill.md +0 -18
- package/extensions/greedysearch-dm/src/fetcher.mjs +0 -577
- package/extensions/greedysearch-dm/src/formatters/results.js +0 -95
- package/extensions/greedysearch-dm/src/formatters/sources.js +0 -57
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +0 -49
- package/extensions/greedysearch-dm/src/github.mjs +0 -228
- package/extensions/greedysearch-dm/src/reddit.mjs +0 -154
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +0 -340
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +0 -156
- package/extensions/greedysearch-dm/src/search/chrome.mjs +0 -510
- package/extensions/greedysearch-dm/src/search/defaults.mjs +0 -10
- package/extensions/greedysearch-dm/src/search/engines.mjs +0 -79
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +0 -470
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +0 -31
- package/extensions/greedysearch-dm/src/search/output.mjs +0 -51
- package/extensions/greedysearch-dm/src/search/paths.mjs +0 -48
- package/extensions/greedysearch-dm/src/search/pdf.mjs +0 -64
- package/extensions/greedysearch-dm/src/search/progress.mjs +0 -109
- package/extensions/greedysearch-dm/src/search/query.mjs +0 -21
- package/extensions/greedysearch-dm/src/search/recovery.mjs +0 -49
- package/extensions/greedysearch-dm/src/search/research.mjs +0 -2255
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +0 -61
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +0 -418
- package/extensions/greedysearch-dm/src/search/sources.mjs +0 -412
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +0 -127
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +0 -202
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +0 -209
- package/extensions/greedysearch-dm/src/tools/shared.js +0 -226
- package/extensions/greedysearch-dm/src/types.js +0 -0
- package/extensions/greedysearch-dm/src/utils/content.mjs +0 -38
- package/extensions/greedysearch-dm/src/utils/helpers.js +0 -22
- /package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/minimize.mjs +0 -0
- /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
|
-
}
|