@duckmind/dm-windows-x64 0.60.6 → 0.60.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.
- package/dm.exe +0 -0
- package/extensions/.dm-extensions.json +70 -123
- package/extensions/dm-9router-ext/src/index.js +410 -5
- package/extensions/dm-caveman/extensions/caveman.js +283 -12
- package/extensions/dm-cliproxy/index.js +182 -2
- package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
- package/extensions/dm-cliproxy/src/apply.js +228 -1
- package/extensions/dm-cliproxy/src/cache.js +42 -1
- package/extensions/dm-cliproxy/src/commands.js +50 -2
- package/extensions/dm-cliproxy/src/compat.js +81 -1
- package/extensions/dm-cliproxy/src/config.js +192 -2
- package/extensions/dm-cliproxy/src/conflicts.js +46 -1
- package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
- package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
- package/extensions/dm-cliproxy/src/log.js +23 -1
- package/extensions/dm-cliproxy/src/status-quota.js +77 -1
- package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
- package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
- package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
- package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
- package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
- package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
- package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
- package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
- package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
- package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
- package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
- package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
- package/extensions/dm-context/src/context.js +144 -1
- package/extensions/dm-context/src/index.js +339 -7
- package/extensions/dm-context/src/utils.js +9 -1
- package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
- package/extensions/dm-cua/index.js +75 -6
- package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
- package/extensions/dm-cua/src/browser-install.mjs +331 -2
- package/extensions/dm-fff/src/index.js +688 -12
- package/extensions/dm-fff/src/query.js +60 -1
- package/extensions/dm-goal/src/goal.js +894 -23
- package/extensions/dm-image2/index.js +103 -9
- package/extensions/dm-image2/src/image-lib.mjs +1275 -8
- package/extensions/dm-subagents/install.mjs +62 -8
- package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
- package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
- package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
- package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
- package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
- package/extensions/dm-subagents/src/agents/agents.js +1139 -11
- package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
- package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
- package/extensions/dm-subagents/src/agents/identity.js +29 -1
- package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
- package/extensions/dm-subagents/src/agents/skills.js +614 -6
- package/extensions/dm-subagents/src/extension/config.js +35 -2
- package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
- package/extensions/dm-subagents/src/extension/doctor.js +172 -15
- package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
- package/extensions/dm-subagents/src/extension/index.js +521 -359
- package/extensions/dm-subagents/src/extension/rpc.js +266 -7
- package/extensions/dm-subagents/src/extension/schemas.js +275 -1
- package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
- package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
- package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
- package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
- package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
- package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
- package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
- package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
- package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
- package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
- package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
- package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
- package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
- package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
- package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
- package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
- package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
- package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
- package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
- package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
- package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
- package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
- package/extensions/dm-subagents/src/shared/formatters.js +98 -7
- package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
- package/extensions/dm-subagents/src/shared/model-info.js +62 -1
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
- package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
- package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
- package/extensions/dm-subagents/src/shared/settings.js +198 -11
- package/extensions/dm-subagents/src/shared/status-format.js +53 -1
- package/extensions/dm-subagents/src/shared/types.js +184 -6
- package/extensions/dm-subagents/src/shared/utils.js +462 -2
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
- package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
- package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
- package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
- package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
- package/extensions/dm-subagents/src/tui/render.js +1542 -4
- package/extensions/dm-usage/index.js +1294 -9
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
- package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
- package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
- package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
- package/extensions/greedysearch-dm/bin/search.mjs +620 -540
- package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
- package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
- package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
- package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
- package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
- package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
- package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
- package/extensions/greedysearch-dm/index.js +123 -23
- package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
- package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
- package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
- package/extensions/greedysearch-dm/src/github.mjs +222 -7
- package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
- package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
- package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
- package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
- package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
- package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
- package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
- package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
- package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
- package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
- package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
- package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
- package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
- package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
- package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
- package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
- package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
- package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
- package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
- package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
- package/package.json +1 -1
|
@@ -1,3 +1,577 @@
|
|
|
1
|
-
|
|
1
|
+
let _deps = null;
|
|
2
|
+
async function loadDeps() {
|
|
3
|
+
if (_deps)
|
|
4
|
+
return _deps;
|
|
5
|
+
const [{ Readability }, { JSDOM }, { default: TurndownService }] = await Promise.all([
|
|
6
|
+
import("@mozilla/readability"),
|
|
7
|
+
import("jsdom"),
|
|
8
|
+
import("turndown")
|
|
9
|
+
]);
|
|
10
|
+
const turndown = new TurndownService({
|
|
11
|
+
headingStyle: "atx",
|
|
12
|
+
bulletListMarker: "-",
|
|
13
|
+
codeBlockStyle: "fenced"
|
|
14
|
+
});
|
|
15
|
+
turndown.addRule("removeDataUrls", {
|
|
16
|
+
filter: (node) => node.tagName === "IMG" && node.getAttribute("src")?.startsWith("data:"),
|
|
17
|
+
replacement: () => ""
|
|
18
|
+
});
|
|
19
|
+
_deps = { Readability, JSDOM, turndown };
|
|
20
|
+
return _deps;
|
|
21
|
+
}
|
|
22
|
+
const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
|
|
23
|
+
const DEFAULT_HEADERS = {
|
|
24
|
+
"user-agent": DEFAULT_USER_AGENT,
|
|
25
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
26
|
+
"accept-language": "en-US,en;q=0.9",
|
|
27
|
+
"accept-encoding": "gzip, deflate, br",
|
|
28
|
+
"cache-control": "no-cache",
|
|
29
|
+
pragma: "no-cache",
|
|
30
|
+
"sec-ch-ua": '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
|
|
31
|
+
"sec-ch-ua-mobile": "?0",
|
|
32
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
33
|
+
"sec-fetch-dest": "document",
|
|
34
|
+
"sec-fetch-mode": "navigate",
|
|
35
|
+
"sec-fetch-site": "none",
|
|
36
|
+
"sec-fetch-user": "?1",
|
|
37
|
+
"upgrade-insecure-requests": "1"
|
|
38
|
+
};
|
|
39
|
+
const PRIVATE_URL_PATTERNS = [
|
|
40
|
+
/^localhost$/i,
|
|
41
|
+
/^127\.\d+\.\d+\.\d+$/,
|
|
42
|
+
/^0\.0\.0\.0$/,
|
|
43
|
+
/^\[::1\]$/,
|
|
44
|
+
/^10\./,
|
|
45
|
+
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
46
|
+
/^192\.168\./,
|
|
47
|
+
/^169\.254\./,
|
|
48
|
+
/^fc00:/i,
|
|
49
|
+
/^fe80:/i,
|
|
50
|
+
/\.local$/i,
|
|
51
|
+
/\.internal$/i,
|
|
52
|
+
/\.localhost$/i
|
|
53
|
+
];
|
|
54
|
+
const IPV4_MAPPED_DOTTED_RE = /^::ffff:(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/i;
|
|
55
|
+
const IPV4_MAPPED_HEX_RE = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
|
|
56
|
+
const INET_ATON_SEGMENT_RE = /^(?:0x[0-9a-f]+|0[0-7]*|[1-9][0-9]*)$/i;
|
|
57
|
+
function normalizeNumericIPv4(hostname) {
|
|
58
|
+
if (!hostname || hostname.includes(":"))
|
|
59
|
+
return null;
|
|
60
|
+
const parts = hostname.split(".");
|
|
61
|
+
if (parts.length < 1 || parts.length > 4)
|
|
62
|
+
return null;
|
|
63
|
+
if (!parts.every((p) => INET_ATON_SEGMENT_RE.test(p)))
|
|
64
|
+
return null;
|
|
65
|
+
const nums = parts.map((p) => {
|
|
66
|
+
if (/^0x/i.test(p))
|
|
67
|
+
return parseInt(p, 16);
|
|
68
|
+
if (/^0[0-7]+$/.test(p))
|
|
69
|
+
return parseInt(p, 8);
|
|
70
|
+
return parseInt(p, 10);
|
|
71
|
+
});
|
|
72
|
+
if (nums.some((n) => !Number.isFinite(n) || n < 0))
|
|
73
|
+
return null;
|
|
74
|
+
let bytes;
|
|
75
|
+
if (nums.length === 1) {
|
|
76
|
+
if (nums[0] > 4294967295)
|
|
77
|
+
return null;
|
|
78
|
+
bytes = [
|
|
79
|
+
nums[0] >>> 24 & 255,
|
|
80
|
+
nums[0] >>> 16 & 255,
|
|
81
|
+
nums[0] >>> 8 & 255,
|
|
82
|
+
nums[0] & 255
|
|
83
|
+
];
|
|
84
|
+
} else if (nums.length === 2) {
|
|
85
|
+
if (nums[0] > 255 || nums[1] > 16777215)
|
|
86
|
+
return null;
|
|
87
|
+
bytes = [nums[0], nums[1] >>> 16 & 255, nums[1] >>> 8 & 255, nums[1] & 255];
|
|
88
|
+
} else if (nums.length === 3) {
|
|
89
|
+
if (nums[0] > 255 || nums[1] > 255 || nums[2] > 65535)
|
|
90
|
+
return null;
|
|
91
|
+
bytes = [nums[0], nums[1], nums[2] >>> 8 & 255, nums[2] & 255];
|
|
92
|
+
} else {
|
|
93
|
+
if (nums.some((n) => n > 255))
|
|
94
|
+
return null;
|
|
95
|
+
bytes = nums;
|
|
96
|
+
}
|
|
97
|
+
return bytes.join(".");
|
|
98
|
+
}
|
|
99
|
+
function extractMappedIPv4(core) {
|
|
100
|
+
const dottedMatch = core.match(IPV4_MAPPED_DOTTED_RE);
|
|
101
|
+
if (dottedMatch) {
|
|
102
|
+
return `${dottedMatch[1]}.${dottedMatch[2]}.${dottedMatch[3]}.${dottedMatch[4]}`;
|
|
103
|
+
}
|
|
104
|
+
const hexMatch = core.match(IPV4_MAPPED_HEX_RE);
|
|
105
|
+
if (hexMatch) {
|
|
106
|
+
const hi = parseInt(hexMatch[1], 16);
|
|
107
|
+
const lo = parseInt(hexMatch[2], 16);
|
|
108
|
+
if (hi > 65535 || lo > 65535)
|
|
109
|
+
return null;
|
|
110
|
+
return [
|
|
111
|
+
hi >>> 8 & 255,
|
|
112
|
+
hi & 255,
|
|
113
|
+
lo >>> 8 & 255,
|
|
114
|
+
lo & 255
|
|
115
|
+
].join(".");
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function isPrivateIPv4(ip) {
|
|
120
|
+
return PRIVATE_URL_PATTERNS.some((pattern) => pattern.test(ip));
|
|
121
|
+
}
|
|
122
|
+
export function defaultFetchHeaders(overrides = {}) {
|
|
123
|
+
return { ...DEFAULT_HEADERS, ...overrides };
|
|
124
|
+
}
|
|
125
|
+
export function isPrivateUrl(url) {
|
|
126
|
+
try {
|
|
127
|
+
if (typeof url !== "string" || !url.trim()) {
|
|
128
|
+
return { blocked: true, reason: "URL must be a non-empty string" };
|
|
129
|
+
}
|
|
130
|
+
const parsed = new URL(url);
|
|
131
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
132
|
+
return {
|
|
133
|
+
blocked: true,
|
|
134
|
+
reason: `Protocol not allowed: ${parsed.protocol}`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
138
|
+
for (const pattern of PRIVATE_URL_PATTERNS) {
|
|
139
|
+
if (pattern.test(hostname)) {
|
|
140
|
+
return {
|
|
141
|
+
blocked: true,
|
|
142
|
+
reason: `Private/internal address: ${hostname}`
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (hostname.startsWith("[") && hostname.endsWith("]")) {
|
|
147
|
+
const core = hostname.slice(1, -1);
|
|
148
|
+
const mapped = extractMappedIPv4(core);
|
|
149
|
+
if (mapped && isPrivateIPv4(mapped)) {
|
|
150
|
+
return {
|
|
151
|
+
blocked: true,
|
|
152
|
+
reason: `Private/internal address: ${hostname} (maps to ${mapped})`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const normalized = normalizeNumericIPv4(hostname);
|
|
157
|
+
if (normalized && isPrivateIPv4(normalized)) {
|
|
158
|
+
return {
|
|
159
|
+
blocked: true,
|
|
160
|
+
reason: `Private/internal address: ${hostname} (normalizes to ${normalized})`
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return { blocked: false };
|
|
164
|
+
} catch (error) {
|
|
165
|
+
return { blocked: true, reason: `Invalid URL: ${error.message}` };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export function rewriteGitHubUrl(url) {
|
|
169
|
+
try {
|
|
170
|
+
const parsed = new URL(url);
|
|
171
|
+
if (!(parsed.hostname === "github.com" || parsed.hostname.endsWith(".github.com"))) {
|
|
172
|
+
return url;
|
|
173
|
+
}
|
|
174
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
175
|
+
if (parts.length < 5) {
|
|
176
|
+
return url;
|
|
177
|
+
}
|
|
178
|
+
const [owner, repo, type, ref, ...fileParts] = parts;
|
|
179
|
+
if (type !== "blob") {
|
|
180
|
+
return url;
|
|
181
|
+
}
|
|
182
|
+
const rawPath = fileParts.join("/");
|
|
183
|
+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${rawPath}`;
|
|
184
|
+
return rawUrl;
|
|
185
|
+
} catch {
|
|
186
|
+
return url;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
export async function fetchSourceHttp(url, options = {}) {
|
|
190
|
+
const privateCheck = isPrivateUrl(url);
|
|
191
|
+
if (privateCheck.blocked) {
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
url,
|
|
195
|
+
finalUrl: url,
|
|
196
|
+
status: 403,
|
|
197
|
+
error: `Blocked: ${privateCheck.reason}`,
|
|
198
|
+
needsBrowser: false
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const originalUrl = url;
|
|
202
|
+
url = rewriteGitHubUrl(url);
|
|
203
|
+
if (url !== originalUrl) {
|
|
204
|
+
console.error(`[fetcher] Rewrote GitHub URL: ${originalUrl.slice(0, 60)}... → raw.githubusercontent.com`);
|
|
205
|
+
}
|
|
206
|
+
const { timeoutMs = 15000, userAgent, signal } = options;
|
|
207
|
+
const controller = new AbortController;
|
|
208
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
209
|
+
if (signal) {
|
|
210
|
+
signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const response = await fetch(url, {
|
|
214
|
+
method: "GET",
|
|
215
|
+
headers: {
|
|
216
|
+
...DEFAULT_HEADERS,
|
|
217
|
+
"user-agent": userAgent || DEFAULT_USER_AGENT
|
|
218
|
+
},
|
|
219
|
+
redirect: "follow",
|
|
220
|
+
signal: controller.signal
|
|
221
|
+
});
|
|
222
|
+
clearTimeout(timeoutId);
|
|
223
|
+
const contentType = response.headers.get("content-type") || "";
|
|
224
|
+
const finalUrl = response.url;
|
|
225
|
+
const lastModified = response.headers.get("last-modified") || "";
|
|
226
|
+
const finalPrivateCheck = isPrivateUrl(finalUrl);
|
|
227
|
+
if (finalPrivateCheck.blocked) {
|
|
228
|
+
return {
|
|
229
|
+
ok: false,
|
|
230
|
+
url,
|
|
231
|
+
finalUrl,
|
|
232
|
+
status: response.status,
|
|
233
|
+
error: `Blocked: ${finalPrivateCheck.reason}`,
|
|
234
|
+
needsBrowser: false
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
let isRawGitHub = false;
|
|
238
|
+
try {
|
|
239
|
+
const finalHost = new URL(finalUrl).hostname.toLowerCase();
|
|
240
|
+
isRawGitHub = finalHost === "raw.githubusercontent.com";
|
|
241
|
+
} catch {}
|
|
242
|
+
if (contentType.includes("text/plain") && isRawGitHub) {
|
|
243
|
+
const text = await response.text();
|
|
244
|
+
return {
|
|
245
|
+
ok: true,
|
|
246
|
+
url: originalUrl,
|
|
247
|
+
finalUrl,
|
|
248
|
+
status: response.status,
|
|
249
|
+
title: finalUrl.split("/").pop() || "GitHub File",
|
|
250
|
+
byline: "",
|
|
251
|
+
siteName: "GitHub",
|
|
252
|
+
lang: "",
|
|
253
|
+
publishedTime: lastModified,
|
|
254
|
+
lastModified,
|
|
255
|
+
markdown: text,
|
|
256
|
+
contentLength: text.length,
|
|
257
|
+
excerpt: text.slice(0, 300).replaceAll(/\n/g, " "),
|
|
258
|
+
needsBrowser: false
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (!contentType.includes("text/html") && !contentType.includes("application/xhtml")) {
|
|
262
|
+
return {
|
|
263
|
+
ok: false,
|
|
264
|
+
url,
|
|
265
|
+
finalUrl,
|
|
266
|
+
status: response.status,
|
|
267
|
+
error: `Unsupported content type: ${contentType}`,
|
|
268
|
+
needsBrowser: false
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const html = await response.text();
|
|
272
|
+
const quickCheck = detectBotBlock(response.status, html, finalUrl, url);
|
|
273
|
+
if (quickCheck.blocked) {
|
|
274
|
+
return {
|
|
275
|
+
ok: false,
|
|
276
|
+
url,
|
|
277
|
+
finalUrl,
|
|
278
|
+
status: response.status,
|
|
279
|
+
error: `Blocked: ${quickCheck.reason}`,
|
|
280
|
+
needsBrowser: true
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
const extracted = await extractContent(html, finalUrl);
|
|
284
|
+
const quality = checkContentQuality(extracted);
|
|
285
|
+
if (!quality.ok) {
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
url,
|
|
289
|
+
finalUrl,
|
|
290
|
+
status: response.status,
|
|
291
|
+
error: `Low quality content: ${quality.reason}`,
|
|
292
|
+
needsBrowser: true
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
ok: true,
|
|
297
|
+
url,
|
|
298
|
+
finalUrl,
|
|
299
|
+
status: response.status,
|
|
300
|
+
title: extracted.title,
|
|
301
|
+
byline: extracted.byline,
|
|
302
|
+
siteName: extracted.siteName,
|
|
303
|
+
lang: extracted.lang,
|
|
304
|
+
publishedTime: extracted.publishedTime || lastModified,
|
|
305
|
+
lastModified,
|
|
306
|
+
markdown: extracted.markdown,
|
|
307
|
+
excerpt: extracted.excerpt,
|
|
308
|
+
contentLength: extracted.markdown.length,
|
|
309
|
+
needsBrowser: false
|
|
310
|
+
};
|
|
311
|
+
} catch (error) {
|
|
312
|
+
clearTimeout(timeoutId);
|
|
313
|
+
const needsBrowser = isNetworkErrorRetryableWithBrowser(error);
|
|
314
|
+
return {
|
|
315
|
+
ok: false,
|
|
316
|
+
url,
|
|
317
|
+
finalUrl: url,
|
|
318
|
+
status: 0,
|
|
319
|
+
error: error.message,
|
|
320
|
+
needsBrowser
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
export function detectBotBlock(status, html, finalUrl, originalUrl) {
|
|
325
|
+
const title = html.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1]?.toLowerCase() || "";
|
|
326
|
+
const sample = html.slice(0, 30000).toLowerCase();
|
|
327
|
+
const combined = `${title} ${sample}`;
|
|
328
|
+
if (status === 403 || status === 429 || status === 503) {
|
|
329
|
+
return { blocked: true, reason: `HTTP ${status}` };
|
|
330
|
+
}
|
|
331
|
+
const blockSignals = [
|
|
332
|
+
{
|
|
333
|
+
pattern: /class=["'][^"']*captcha["']|<div[^>]*id=["']captcha/i,
|
|
334
|
+
reason: "captcha"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
pattern: /g-recaptcha|data-sitekey|i['"]m not a robot/i,
|
|
338
|
+
reason: "captcha"
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
pattern: /checking your browser.{0,100}please wait|cf-browser-verification/i,
|
|
342
|
+
reason: "cloudflare challenge"
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
pattern: /just a moment.{0,50}security check|ddos protection by cloudflare/i,
|
|
346
|
+
reason: "cloudflare challenge"
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
pattern: /unusual traffic.{0,50}from your computer network/i,
|
|
350
|
+
reason: "unusual traffic"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
pattern: /bot detected|automated.{0,20}request/i,
|
|
354
|
+
reason: "bot detection"
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
pattern: /enable\s+javascript\s+to\s+view|javascript\s+is\s+required.{0,50}enabled/i,
|
|
358
|
+
reason: "requires javascript"
|
|
359
|
+
},
|
|
360
|
+
{ pattern: /access denied|accessdenied/i, reason: "access denied" },
|
|
361
|
+
{
|
|
362
|
+
pattern: /protected by anubis|anubis uses a proof-of-work/i,
|
|
363
|
+
reason: "anubis challenge"
|
|
364
|
+
}
|
|
365
|
+
];
|
|
366
|
+
for (const signal of blockSignals) {
|
|
367
|
+
if (signal.pattern.test(combined)) {
|
|
368
|
+
return { blocked: true, reason: signal.reason };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const loginRedirect = detectLoginRedirect(originalUrl, finalUrl, html);
|
|
372
|
+
if (loginRedirect) {
|
|
373
|
+
return { blocked: true, reason: loginRedirect };
|
|
374
|
+
}
|
|
375
|
+
return { blocked: false };
|
|
376
|
+
}
|
|
377
|
+
const AUTH_DOMAINS = [
|
|
378
|
+
"accounts.google.com",
|
|
379
|
+
"login.microsoftonline.com",
|
|
380
|
+
"login.live.com",
|
|
381
|
+
"auth0.com",
|
|
382
|
+
"okta.com",
|
|
383
|
+
"auth.mozilla.auth0.com",
|
|
384
|
+
"id.atlassian.com"
|
|
385
|
+
];
|
|
386
|
+
const AUTH_HOSTNAME_PREFIXES = [
|
|
387
|
+
"login.",
|
|
388
|
+
"signin.",
|
|
389
|
+
"auth.",
|
|
390
|
+
"sso.",
|
|
391
|
+
"accounts.",
|
|
392
|
+
"idp."
|
|
393
|
+
];
|
|
394
|
+
const LOGIN_CONTENT_PATTERNS = [
|
|
395
|
+
"sign in to continue",
|
|
396
|
+
"log in to continue",
|
|
397
|
+
"authentication required",
|
|
398
|
+
"create an account to continue",
|
|
399
|
+
"subscribe to continue reading",
|
|
400
|
+
"members only"
|
|
401
|
+
];
|
|
402
|
+
function detectLoginRedirect(requestedUrl, finalUrl, html) {
|
|
403
|
+
try {
|
|
404
|
+
const requested = new URL(requestedUrl);
|
|
405
|
+
const final = new URL(finalUrl);
|
|
406
|
+
if (requested.hostname.toLowerCase() === final.hostname.toLowerCase()) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const finalHost = final.hostname.toLowerCase();
|
|
410
|
+
if (AUTH_DOMAINS.some((d) => finalHost === d || finalHost.endsWith(`.${d}`))) {
|
|
411
|
+
return `redirected to login (${final.hostname})`;
|
|
412
|
+
}
|
|
413
|
+
if (AUTH_HOSTNAME_PREFIXES.some((p) => finalHost.startsWith(p))) {
|
|
414
|
+
return `redirected to login (${final.hostname})`;
|
|
415
|
+
}
|
|
416
|
+
const sample = html.slice(0, 20000).toLowerCase();
|
|
417
|
+
if (LOGIN_CONTENT_PATTERNS.some((p) => sample.includes(p))) {
|
|
418
|
+
return `redirected to login page (${final.hostname})`;
|
|
419
|
+
}
|
|
420
|
+
} catch {}
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
function isNetworkErrorRetryableWithBrowser(error) {
|
|
424
|
+
const message = error.message.toLowerCase();
|
|
425
|
+
return message.includes("fetch failed") || message.includes("unable to verify") || message.includes("certificate") || message.includes("timeout");
|
|
426
|
+
}
|
|
427
|
+
function extractMetaDate(document) {
|
|
428
|
+
const selectors = [
|
|
429
|
+
'meta[property="article:published_time"]',
|
|
430
|
+
'meta[name="article:published_time"]',
|
|
431
|
+
'meta[property="og:published_time"]',
|
|
432
|
+
'meta[name="publication_date"]',
|
|
433
|
+
'meta[name="date"]',
|
|
434
|
+
'meta[itemprop="datePublished"]',
|
|
435
|
+
'time[itemprop="datePublished"]',
|
|
436
|
+
'meta[name="DC.date"]'
|
|
437
|
+
];
|
|
438
|
+
for (const sel of selectors) {
|
|
439
|
+
const el = document.querySelector(sel);
|
|
440
|
+
const val = el?.getAttribute("content") || el?.getAttribute("datetime") || "";
|
|
441
|
+
if (val)
|
|
442
|
+
return val;
|
|
443
|
+
}
|
|
444
|
+
return "";
|
|
445
|
+
}
|
|
446
|
+
export async function extractContent(html, url) {
|
|
447
|
+
const { Readability, JSDOM, turndown } = await loadDeps();
|
|
448
|
+
const dom = new JSDOM(html, { url });
|
|
449
|
+
try {
|
|
450
|
+
const document = dom.window.document;
|
|
451
|
+
const reader = new Readability(document);
|
|
452
|
+
const article = reader.parse();
|
|
453
|
+
if (article && article.content) {
|
|
454
|
+
const markdown = turndown.turndown(article.content);
|
|
455
|
+
const cleanMarkdown = markdown.replaceAll(/\n{3,}/g, `
|
|
2
456
|
|
|
3
|
-
`).trim()
|
|
457
|
+
`).trim();
|
|
458
|
+
const publishedTime = article.publishedTime || extractMetaDate(document) || "";
|
|
459
|
+
return {
|
|
460
|
+
title: article.title || document.title || url,
|
|
461
|
+
byline: article.byline || "",
|
|
462
|
+
siteName: article.siteName || "",
|
|
463
|
+
lang: article.lang || "",
|
|
464
|
+
publishedTime,
|
|
465
|
+
markdown: cleanMarkdown,
|
|
466
|
+
excerpt: cleanMarkdown.slice(0, 300).replaceAll(/\n/g, " ")
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
const body = document.body;
|
|
470
|
+
if (body) {
|
|
471
|
+
const clone = body.cloneNode(true);
|
|
472
|
+
clone.querySelectorAll("script, style, nav, footer, header, aside").forEach((el) => el.remove());
|
|
473
|
+
const text = clone.textContent || "";
|
|
474
|
+
const cleanText = text.replaceAll(/\s+/g, " ").trim();
|
|
475
|
+
return {
|
|
476
|
+
title: document.title || url,
|
|
477
|
+
byline: "",
|
|
478
|
+
siteName: "",
|
|
479
|
+
lang: "",
|
|
480
|
+
publishedTime: extractMetaDate(document),
|
|
481
|
+
markdown: cleanText,
|
|
482
|
+
excerpt: cleanText.slice(0, 300)
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
title: url,
|
|
487
|
+
byline: "",
|
|
488
|
+
siteName: "",
|
|
489
|
+
lang: "",
|
|
490
|
+
publishedTime: "",
|
|
491
|
+
markdown: "",
|
|
492
|
+
excerpt: ""
|
|
493
|
+
};
|
|
494
|
+
} finally {
|
|
495
|
+
dom.window.close();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
export function checkContentQuality(extracted) {
|
|
499
|
+
const markdown = extracted.markdown.trim().toLowerCase();
|
|
500
|
+
const title = (extracted.title || "").toLowerCase();
|
|
501
|
+
if (extracted.markdown.trim().length < 100) {
|
|
502
|
+
return { ok: false, reason: "content too short (< 100 chars)" };
|
|
503
|
+
}
|
|
504
|
+
const lc = markdown.toLowerCase();
|
|
505
|
+
const suspiciousChecks = [
|
|
506
|
+
{
|
|
507
|
+
check: () => lc.includes("loading") && lc.includes("please wait"),
|
|
508
|
+
desc: "loading page"
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
check: () => lc.includes("please ensure javascript is enabled"),
|
|
512
|
+
desc: "requires javascript"
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
check: () => lc.includes("enable javascript to view"),
|
|
516
|
+
desc: "requires javascript"
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
check: () => lc.includes("just a moment"),
|
|
520
|
+
desc: "cloudflare challenge detected in content"
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
check: () => lc.includes("verify you are human"),
|
|
524
|
+
desc: "human verification"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
check: () => lc.includes("captcha required"),
|
|
528
|
+
desc: "captcha in extracted content"
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
check: () => lc.includes("access denied"),
|
|
532
|
+
desc: "access denied in content"
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
check: () => /^\s{0,10}sign\s{1,5}in\s{0,10}$|^\s{0,10}log\s{1,5}in\s{0,10}$/im.test(markdown),
|
|
536
|
+
desc: "login form only"
|
|
537
|
+
}
|
|
538
|
+
];
|
|
539
|
+
for (const { check, desc } of suspiciousChecks) {
|
|
540
|
+
if (check()) {
|
|
541
|
+
return { ok: false, reason: desc };
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (title.includes("just a moment") || title.includes("checking your browser")) {
|
|
545
|
+
return { ok: false, reason: "cloudflare challenge page detected in title" };
|
|
546
|
+
}
|
|
547
|
+
return { ok: true };
|
|
548
|
+
}
|
|
549
|
+
export function shouldUseBrowser(url) {
|
|
550
|
+
try {
|
|
551
|
+
const parsed = new URL(url);
|
|
552
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
553
|
+
const pathname = parsed.pathname.toLowerCase();
|
|
554
|
+
const jsHeavyDomains = [
|
|
555
|
+
"react.dev",
|
|
556
|
+
"nextjs.org",
|
|
557
|
+
"vuejs.org",
|
|
558
|
+
"angular.io",
|
|
559
|
+
"svelte.dev",
|
|
560
|
+
"docs.expo.dev",
|
|
561
|
+
"tailwindcss.com",
|
|
562
|
+
"storybook.js.org"
|
|
563
|
+
];
|
|
564
|
+
if (jsHeavyDomains.some((d) => hostname === d || hostname.endsWith(`.${d}`))) {
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
if (pathname.includes("/playground") || pathname.includes("/demo") || pathname.includes("/app")) {
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
if (parsed.hash && parsed.hash.length > 1) {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
return false;
|
|
574
|
+
} catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
}
|