@duckmind/dm-windows-x64 0.63.0 → 0.63.4

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 (71) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +138 -2
  3. package/extensions/dm-web-access/SECURITY.md +5 -0
  4. package/extensions/dm-web-access/activity.js +65 -0
  5. package/extensions/dm-web-access/anysearch.js +158 -0
  6. package/extensions/dm-web-access/auth-fetch.js +131 -0
  7. package/extensions/dm-web-access/bocha.js +214 -0
  8. package/extensions/dm-web-access/brave.js +196 -0
  9. package/extensions/dm-web-access/brightdata-unlocker.js +202 -0
  10. package/extensions/dm-web-access/brightdata.js +334 -0
  11. package/extensions/dm-web-access/chrome-cookies.js +627 -0
  12. package/extensions/dm-web-access/content-find.js +114 -0
  13. package/extensions/dm-web-access/credential-source.js +150 -0
  14. package/extensions/dm-web-access/curator-page.js +3559 -0
  15. package/extensions/dm-web-access/curator-server.js +691 -0
  16. package/extensions/dm-web-access/data-uri-sanitize.js +312 -0
  17. package/extensions/dm-web-access/datalab-pdf-extract.js +346 -0
  18. package/extensions/dm-web-access/declared-web-links.js +167 -0
  19. package/extensions/dm-web-access/dm-web-fetch-demo.mp4 +0 -0
  20. package/extensions/dm-web-access/duckduckgo.js +118 -0
  21. package/extensions/dm-web-access/exa.js +401 -0
  22. package/extensions/dm-web-access/extract.js +1220 -0
  23. package/extensions/dm-web-access/feature-config.js +24 -0
  24. package/extensions/dm-web-access/fetch-params.js +81 -0
  25. package/extensions/dm-web-access/firecrawl.js +378 -0
  26. package/extensions/dm-web-access/gemini-adc.js +241 -0
  27. package/extensions/dm-web-access/gemini-api.js +258 -0
  28. package/extensions/dm-web-access/gemini-pdf-extract.js +74 -0
  29. package/extensions/dm-web-access/gemini-search.js +889 -0
  30. package/extensions/dm-web-access/gemini-url-context.js +97 -0
  31. package/extensions/dm-web-access/gemini-web-config.js +84 -0
  32. package/extensions/dm-web-access/gemini-web.js +351 -0
  33. package/extensions/dm-web-access/github-api.js +166 -0
  34. package/extensions/dm-web-access/github-extract.js +991 -0
  35. package/extensions/dm-web-access/github-issue-pr.js +750 -0
  36. package/extensions/dm-web-access/index.js +3117 -0
  37. package/extensions/dm-web-access/jina-search.js +242 -0
  38. package/extensions/dm-web-access/kagi.js +255 -0
  39. package/extensions/dm-web-access/kimi-search.js +214 -0
  40. package/extensions/dm-web-access/ollama.js +209 -0
  41. package/extensions/dm-web-access/openai-search.js +510 -0
  42. package/extensions/dm-web-access/package.json +34 -0
  43. package/extensions/dm-web-access/page-query.js +124 -0
  44. package/extensions/dm-web-access/parallel-mcp.js +223 -0
  45. package/extensions/dm-web-access/parallel.js +345 -0
  46. package/extensions/dm-web-access/pdf-extract.js +257 -0
  47. package/extensions/dm-web-access/perplexity.js +151 -0
  48. package/extensions/dm-web-access/querit.js +327 -0
  49. package/extensions/dm-web-access/query-rewrite.js +40 -0
  50. package/extensions/dm-web-access/render-search-error.js +80 -0
  51. package/extensions/dm-web-access/rsc-extract.js +347 -0
  52. package/extensions/dm-web-access/search1api.js +245 -0
  53. package/extensions/dm-web-access/searchinfinity.js +221 -0
  54. package/extensions/dm-web-access/searxng.js +223 -0
  55. package/extensions/dm-web-access/serpbase.js +205 -0
  56. package/extensions/dm-web-access/serpdive.js +238 -0
  57. package/extensions/dm-web-access/serper.js +200 -0
  58. package/extensions/dm-web-access/source-check.js +198 -0
  59. package/extensions/dm-web-access/ssrf-protection.js +436 -0
  60. package/extensions/dm-web-access/storage.js +451 -0
  61. package/extensions/dm-web-access/summary-model-scope.js +83 -0
  62. package/extensions/dm-web-access/summary-review.js +364 -0
  63. package/extensions/dm-web-access/tavily.js +199 -0
  64. package/extensions/dm-web-access/tinyfish.js +325 -0
  65. package/extensions/dm-web-access/utils.js +476 -0
  66. package/extensions/dm-web-access/valyu.js +189 -0
  67. package/extensions/dm-web-access/video-extract.js +336 -0
  68. package/extensions/dm-web-access/xai-search.js +285 -0
  69. package/extensions/dm-web-access/xcrawl.js +221 -0
  70. package/extensions/dm-web-access/youtube-extract.js +279 -0
  71. package/package.json +9 -1
@@ -0,0 +1,257 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { writeFile, mkdir } from "node:fs/promises";
3
+ import { join, basename } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { CredentialResolutionError } from "./credential-source.js";
6
+ import {
7
+ DATALAB_MODE_VALUES,
8
+ DEFAULT_DATALAB_TIMEOUT_MS,
9
+ isDatalabApiAvailable,
10
+ normalizeDatalabMode,
11
+ extractPDFViaDatalab
12
+ } from "./datalab-pdf-extract.js";
13
+ import { isGeminiApiAvailable } from "./gemini-api.js";
14
+ import { extractPDFViaGemini } from "./gemini-pdf-extract.js";
15
+ import { getWebSearchConfigPath } from "./utils.js";
16
+ export const PDF_PROVIDER_VALUES = new Set([
17
+ "auto",
18
+ "gemini",
19
+ "datalab",
20
+ "unpdf"
21
+ ]);
22
+ export const DEFAULT_PDF_MAX_SIZE_MB = 20;
23
+ export const MAX_PDF_MAX_SIZE_MB = 50;
24
+ export const MAX_DATALAB_TIMEOUT_MS = 300000;
25
+ const DEFAULT_MAX_PAGES = 100;
26
+ const DEFAULT_OUTPUT_DIR = join(tmpdir(), "pi-web-pdf");
27
+ const CONFIG_PATH = getWebSearchConfigPath();
28
+ const PAGE_MARKER_PATTERN = /^<!-- Page (\d+) -->$/gm;
29
+ export function loadPDFConfig() {
30
+ if (!existsSync(CONFIG_PATH)) {
31
+ return {
32
+ enabled: true,
33
+ maxSizeMB: DEFAULT_PDF_MAX_SIZE_MB,
34
+ maxPages: DEFAULT_MAX_PAGES,
35
+ provider: "auto",
36
+ datalabMode: normalizeDatalabMode(process.env.DATALAB_MODE),
37
+ datalabTimeoutMs: DEFAULT_DATALAB_TIMEOUT_MS
38
+ };
39
+ }
40
+ const rawText = readFileSync(CONFIG_PATH, "utf-8");
41
+ let raw;
42
+ try {
43
+ raw = JSON.parse(rawText);
44
+ } catch (err) {
45
+ const message = err instanceof Error ? err.message : String(err);
46
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
47
+ }
48
+ const root = raw && typeof raw === "object" ? raw : {};
49
+ const pdf = root.pdf && typeof root.pdf === "object" ? root.pdf : {};
50
+ const enabled = pdf.enabled !== false;
51
+ const configured = pdf.maxSizeMB;
52
+ const normalized = typeof configured === "number" && Number.isFinite(configured) && configured > 0 ? Math.min(configured, MAX_PDF_MAX_SIZE_MB) : DEFAULT_PDF_MAX_SIZE_MB;
53
+ const configuredMaxPages = pdf.maxPages;
54
+ const maxPages = typeof configuredMaxPages === "number" && Number.isFinite(configuredMaxPages) && configuredMaxPages > 0 ? Math.max(1, Math.floor(configuredMaxPages)) : DEFAULT_MAX_PAGES;
55
+ const provider = typeof pdf.provider === "string" && PDF_PROVIDER_VALUES.has(pdf.provider) ? pdf.provider : "auto";
56
+ const datalabMode = typeof pdf.datalabMode === "string" && DATALAB_MODE_VALUES.has(pdf.datalabMode) ? pdf.datalabMode : normalizeDatalabMode(process.env.DATALAB_MODE);
57
+ const configuredTimeout = pdf.datalabTimeoutMs;
58
+ const datalabTimeoutMs = typeof configuredTimeout === "number" && Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? Math.min(configuredTimeout, MAX_DATALAB_TIMEOUT_MS) : DEFAULT_DATALAB_TIMEOUT_MS;
59
+ return {
60
+ enabled,
61
+ maxSizeMB: normalized,
62
+ maxPages,
63
+ provider,
64
+ datalabMode,
65
+ datalabTimeoutMs
66
+ };
67
+ }
68
+ async function getUnpdf() {
69
+ if (typeof Promise.try !== "function") {
70
+ const { default: promiseTry } = await import("promise.try");
71
+ promiseTry.shim();
72
+ }
73
+ const [unpdf, pdfjs] = await Promise.all([
74
+ import("unpdf"),
75
+ import("unpdf/pdfjs")
76
+ ]);
77
+ const { VerbosityLevel } = pdfjs;
78
+ return { getDocumentProxy: unpdf.getDocumentProxy, VerbosityLevel };
79
+ }
80
+ export async function extractPDFToMarkdown(buffer, url, options = {}) {
81
+ const {
82
+ maxPages,
83
+ outputDir = DEFAULT_OUTPUT_DIR,
84
+ filename,
85
+ signal,
86
+ geminiTimeoutMs
87
+ } = options;
88
+ const pdfConfig = loadPDFConfig();
89
+ const safeMaxPages = maxPages === undefined ? pdfConfig.maxPages : Number.isFinite(maxPages) ? Math.max(1, Math.floor(maxPages)) : DEFAULT_MAX_PAGES;
90
+ const urlTitle = extractTitleFromURL(url);
91
+ const provider = pdfConfig.provider;
92
+ if (provider === "auto" || provider === "datalab") {
93
+ try {
94
+ if (isDatalabApiAvailable()) {
95
+ const result = await extractPDFViaDatalab(buffer, {
96
+ maxPages: safeMaxPages,
97
+ title: urlTitle,
98
+ mode: pdfConfig.datalabMode,
99
+ timeoutMs: pdfConfig.datalabTimeoutMs,
100
+ ...signal ? { signal } : {}
101
+ });
102
+ return writeMarkdownResult({
103
+ markdownBody: result.markdown,
104
+ title: urlTitle,
105
+ pages: result.pages,
106
+ outputDir,
107
+ filename,
108
+ url
109
+ });
110
+ }
111
+ } catch (err) {
112
+ if (shouldRethrowExtractionError(err, signal))
113
+ throw err;
114
+ }
115
+ }
116
+ if (provider === "auto" || provider === "gemini") {
117
+ try {
118
+ if (isGeminiApiAvailable()) {
119
+ const markdownBody = await extractPDFViaGemini(buffer, {
120
+ maxPages: safeMaxPages,
121
+ title: urlTitle,
122
+ ...signal ? { signal } : {},
123
+ ...geminiTimeoutMs !== undefined ? { timeoutMs: geminiTimeoutMs } : {}
124
+ });
125
+ return writeMarkdownResult({
126
+ markdownBody,
127
+ title: urlTitle,
128
+ pages: countPageMarkers(markdownBody),
129
+ outputDir,
130
+ filename,
131
+ url
132
+ });
133
+ }
134
+ } catch (err) {
135
+ if (shouldRethrowExtractionError(err, signal))
136
+ throw err;
137
+ }
138
+ }
139
+ const { getDocumentProxy, VerbosityLevel } = await getUnpdf();
140
+ const pdf = await getDocumentProxy(new Uint8Array(buffer), {
141
+ verbosity: VerbosityLevel.ERRORS
142
+ });
143
+ const metadata = await pdf.getMetadata();
144
+ const metadataInfo = metadata.info && typeof metadata.info === "object" ? metadata.info : null;
145
+ const metaTitle = typeof metadataInfo?.Title === "string" ? metadataInfo.Title : undefined;
146
+ const metaAuthor = typeof metadataInfo?.Author === "string" ? metadataInfo.Author : undefined;
147
+ const title = metaTitle?.trim() || urlTitle;
148
+ const pagesToExtract = Math.min(pdf.numPages, safeMaxPages);
149
+ const truncated = pdf.numPages > safeMaxPages;
150
+ const pages = [];
151
+ for (let i = 1;i <= pagesToExtract; i++) {
152
+ const page = await pdf.getPage(i);
153
+ const textContent = await page.getTextContent();
154
+ const pageText = textContent.items.map((item) => {
155
+ const textItem = item;
156
+ return textItem.str || "";
157
+ }).join(" ").replace(/\s+/g, " ").trim();
158
+ if (pageText) {
159
+ pages.push({ pageNum: i, text: pageText });
160
+ }
161
+ }
162
+ const bodyLines = [];
163
+ for (let i = 0;i < pages.length; i++) {
164
+ if (i > 0) {
165
+ bodyLines.push("");
166
+ bodyLines.push(`<!-- Page ${pages[i].pageNum} -->`);
167
+ bodyLines.push("");
168
+ }
169
+ bodyLines.push(pages[i].text);
170
+ }
171
+ return writeMarkdownResult({
172
+ markdownBody: bodyLines.join(`
173
+ `),
174
+ title,
175
+ pages: pdf.numPages,
176
+ outputDir,
177
+ filename,
178
+ url,
179
+ metaAuthor,
180
+ truncated,
181
+ pagesToExtract
182
+ });
183
+ }
184
+ async function writeMarkdownResult(options) {
185
+ const lines = [];
186
+ lines.push(`# ${options.title}`);
187
+ lines.push("");
188
+ lines.push(`> Source: ${options.url}`);
189
+ lines.push(`> Pages: ${options.pages}${options.truncated ? ` (extracted first ${options.pagesToExtract})` : ""}`);
190
+ if (options.metaAuthor)
191
+ lines.push(`> Author: ${options.metaAuthor}`);
192
+ lines.push("");
193
+ lines.push("---");
194
+ lines.push("");
195
+ if (options.markdownBody)
196
+ lines.push(options.markdownBody);
197
+ if (options.truncated) {
198
+ lines.push("");
199
+ lines.push("---");
200
+ lines.push("");
201
+ lines.push(`*[Truncated: Only first ${options.pagesToExtract} of ${options.pages} pages extracted]*`);
202
+ }
203
+ const content = lines.join(`
204
+ `);
205
+ const outputFilename = options.filename || sanitizeFilename(options.title) + ".md";
206
+ const outputPath = join(options.outputDir, outputFilename);
207
+ await mkdir(options.outputDir, { recursive: true });
208
+ await writeFile(outputPath, content, "utf-8");
209
+ return {
210
+ title: options.title,
211
+ pages: options.pages,
212
+ chars: content.length,
213
+ outputPath
214
+ };
215
+ }
216
+ function countPageMarkers(markdown) {
217
+ return [...markdown.matchAll(PAGE_MARKER_PATTERN)].length;
218
+ }
219
+ function shouldRethrowExtractionError(err, signal) {
220
+ if (signal?.aborted)
221
+ return true;
222
+ if (err instanceof CredentialResolutionError)
223
+ return true;
224
+ const message = err instanceof Error ? err.message : String(err);
225
+ return message.startsWith("Failed to parse ");
226
+ }
227
+ function extractTitleFromURL(url) {
228
+ try {
229
+ const urlObj = new URL(url);
230
+ const pathname = urlObj.pathname;
231
+ let filename = basename(pathname, ".pdf");
232
+ if (urlObj.hostname.includes("arxiv.org")) {
233
+ const match = pathname.match(/\/(?:pdf|abs)\/(\d+\.\d+)/);
234
+ if (match) {
235
+ filename = `arxiv-${match[1]}`;
236
+ }
237
+ }
238
+ filename = filename.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
239
+ return filename || "document";
240
+ } catch {
241
+ return "document";
242
+ }
243
+ }
244
+ function sanitizeFilename(name) {
245
+ return name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 100).replace(/^-|-$/g, "") || "document";
246
+ }
247
+ export function isPDF(url, contentType) {
248
+ if (contentType?.includes("application/pdf")) {
249
+ return true;
250
+ }
251
+ try {
252
+ const urlObj = new URL(url);
253
+ return urlObj.pathname.toLowerCase().endsWith(".pdf");
254
+ } catch {
255
+ return false;
256
+ }
257
+ }
@@ -0,0 +1,151 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { activityMonitor } from "./activity.js";
3
+ import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
4
+ import { getWebSearchConfigPath } from "./utils.js";
5
+ const PERPLEXITY_API_URL = "https://api.perplexity.ai/chat/completions";
6
+ const CONFIG_PATH = getWebSearchConfigPath();
7
+ const RATE_LIMIT = {
8
+ maxRequests: 10,
9
+ windowMs: 60 * 1000
10
+ };
11
+ const requestTimestamps = [];
12
+ let cachedConfig = null;
13
+ function loadConfig() {
14
+ if (cachedConfig)
15
+ return cachedConfig;
16
+ if (!existsSync(CONFIG_PATH)) {
17
+ cachedConfig = {};
18
+ return cachedConfig;
19
+ }
20
+ const content = readFileSync(CONFIG_PATH, "utf-8");
21
+ try {
22
+ cachedConfig = JSON.parse(content);
23
+ return cachedConfig;
24
+ } catch (err) {
25
+ const message = err instanceof Error ? err.message : String(err);
26
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
27
+ }
28
+ }
29
+ async function getApiKey(signal) {
30
+ const key = await resolveCredential({
31
+ provider: "Perplexity",
32
+ configuredValue: loadConfig().perplexityApiKey,
33
+ environmentValue: process.env.PERPLEXITY_API_KEY,
34
+ signal
35
+ });
36
+ if (!key) {
37
+ throw new Error(`Perplexity API key not found. Either:
38
+ ` + ` 1. Create ${CONFIG_PATH} with { "perplexityApiKey": "your-key" }
39
+ ` + ` 2. Set PERPLEXITY_API_KEY environment variable
40
+ ` + "Get a key at https://perplexity.ai/settings/api");
41
+ }
42
+ return key;
43
+ }
44
+ function checkRateLimit() {
45
+ const now = Date.now();
46
+ const windowStart = now - RATE_LIMIT.windowMs;
47
+ while (requestTimestamps.length > 0 && requestTimestamps[0] < windowStart) {
48
+ requestTimestamps.shift();
49
+ }
50
+ if (requestTimestamps.length >= RATE_LIMIT.maxRequests) {
51
+ const waitMs = requestTimestamps[0] + RATE_LIMIT.windowMs - now;
52
+ throw new Error(`Rate limited. Try again in ${Math.ceil(waitMs / 1000)}s`);
53
+ }
54
+ requestTimestamps.push(now);
55
+ }
56
+ function validateDomainFilter(domains) {
57
+ return domains.filter((d) => {
58
+ const domain = d.startsWith("-") ? d.slice(1) : d;
59
+ return /^[a-zA-Z0-9][a-zA-Z0-9-_.]*\.[a-zA-Z]{2,}$/.test(domain);
60
+ });
61
+ }
62
+ export function isPerplexityAvailable() {
63
+ return hasCredentialSource({
64
+ provider: "Perplexity",
65
+ configuredValue: loadConfig().perplexityApiKey,
66
+ environmentValue: process.env.PERPLEXITY_API_KEY
67
+ });
68
+ }
69
+ export async function searchWithPerplexity(query, options = {}) {
70
+ checkRateLimit();
71
+ const activityId = activityMonitor.logStart({ type: "api", query });
72
+ activityMonitor.updateRateLimit({
73
+ used: requestTimestamps.length,
74
+ max: RATE_LIMIT.maxRequests,
75
+ oldestTimestamp: requestTimestamps[0] ?? null,
76
+ windowMs: RATE_LIMIT.windowMs
77
+ });
78
+ const apiKey = await getApiKey(options.signal);
79
+ const numResults = typeof options.numResults === "number" && Number.isFinite(options.numResults) ? Math.max(1, Math.min(Math.floor(options.numResults), 20)) : 5;
80
+ const requestBody = {
81
+ model: "sonar",
82
+ messages: [{ role: "user", content: query }],
83
+ max_tokens: 1024,
84
+ return_related_questions: false
85
+ };
86
+ if (options.recencyFilter) {
87
+ requestBody.search_recency_filter = options.recencyFilter;
88
+ }
89
+ if (options.domainFilter && options.domainFilter.length > 0) {
90
+ const validated = validateDomainFilter(options.domainFilter);
91
+ if (validated.length > 0) {
92
+ requestBody.search_domain_filter = validated;
93
+ }
94
+ }
95
+ let response;
96
+ try {
97
+ response = await fetch(PERPLEXITY_API_URL, {
98
+ method: "POST",
99
+ headers: {
100
+ Authorization: `Bearer ${apiKey}`,
101
+ "Content-Type": "application/json"
102
+ },
103
+ body: JSON.stringify(requestBody),
104
+ ...options.signal ? { signal: options.signal } : {}
105
+ });
106
+ } catch (err) {
107
+ const message = err instanceof Error ? err.message : String(err);
108
+ const redactedMessage = redactCredential(message, apiKey);
109
+ if (redactedMessage.toLowerCase().includes("abort")) {
110
+ activityMonitor.logComplete(activityId, 0);
111
+ } else {
112
+ activityMonitor.logError(activityId, redactedMessage);
113
+ }
114
+ if (redactedMessage === message)
115
+ throw err;
116
+ const redactedError = new Error(redactedMessage);
117
+ if (err instanceof Error)
118
+ redactedError.name = err.name;
119
+ throw redactedError;
120
+ }
121
+ if (!response.ok) {
122
+ activityMonitor.logComplete(activityId, response.status);
123
+ const errorText = redactCredential(await response.text(), apiKey);
124
+ throw new Error(`Perplexity API error ${response.status}: ${errorText}`);
125
+ }
126
+ let data;
127
+ try {
128
+ data = await response.json();
129
+ } catch (err) {
130
+ activityMonitor.logComplete(activityId, response.status);
131
+ const message = err instanceof Error ? err.message : String(err);
132
+ throw new Error(`Perplexity API returned invalid JSON: ${message}`);
133
+ }
134
+ const answer = data.choices?.[0]?.message?.content || "";
135
+ const citations = Array.isArray(data.citations) ? data.citations : [];
136
+ const results = [];
137
+ for (let i = 0;i < Math.min(citations.length, numResults); i++) {
138
+ const citation = citations[i];
139
+ if (typeof citation === "string") {
140
+ results.push({ title: `Source ${i + 1}`, url: citation, snippet: "" });
141
+ } else if (citation && typeof citation === "object" && typeof citation.url === "string") {
142
+ results.push({
143
+ title: citation.title || `Source ${i + 1}`,
144
+ url: citation.url,
145
+ snippet: ""
146
+ });
147
+ }
148
+ }
149
+ activityMonitor.logComplete(activityId, response.status);
150
+ return { answer, results };
151
+ }