@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,57 +0,0 @@
|
|
|
1
|
-
import { formatEngineName, humanizeSourceType } from "../utils/helpers.js";
|
|
2
|
-
export function sourceUrl(source) {
|
|
3
|
-
return String(source.displayUrl || source.canonicalUrl || source.url || "");
|
|
4
|
-
}
|
|
5
|
-
export function sourceLabel(source) {
|
|
6
|
-
return String(source.title || source.domain || sourceUrl(source) || "Untitled source");
|
|
7
|
-
}
|
|
8
|
-
export function sourceConsensus(source) {
|
|
9
|
-
if (typeof source.engineCount === "number")
|
|
10
|
-
return source.engineCount;
|
|
11
|
-
const engines = Array.isArray(source.engines) ? source.engines : [];
|
|
12
|
-
return engines.length;
|
|
13
|
-
}
|
|
14
|
-
export function getSourceMap(sources) {
|
|
15
|
-
return new Map(sources.map((source) => [String(source.id || ""), source]).filter(([id]) => id));
|
|
16
|
-
}
|
|
17
|
-
export function formatSourceLine(source) {
|
|
18
|
-
const id = String(source.id || "?");
|
|
19
|
-
const url = sourceUrl(source);
|
|
20
|
-
const title = sourceLabel(source);
|
|
21
|
-
const domain = String(source.domain || "");
|
|
22
|
-
const engines = Array.isArray(source.engines) ? source.engines : [];
|
|
23
|
-
const consensus = sourceConsensus(source);
|
|
24
|
-
const typeLabel = humanizeSourceType(String(source.sourceType || ""));
|
|
25
|
-
const fetch = source.fetch;
|
|
26
|
-
const fetchStatus = fetch?.ok ? `fetched ${fetch.status || 200}` : fetch?.attempted ? "fetch failed" : "";
|
|
27
|
-
const pieces = [
|
|
28
|
-
`${id} - [${title}](${url})`,
|
|
29
|
-
domain,
|
|
30
|
-
typeLabel,
|
|
31
|
-
engines.length ? `cited by ${engines.map(formatEngineName).join(", ")} (${consensus}/3)` : `${consensus}/3`,
|
|
32
|
-
fetchStatus
|
|
33
|
-
].filter(Boolean);
|
|
34
|
-
return `- ${pieces.join(" - ")}`;
|
|
35
|
-
}
|
|
36
|
-
export function renderSourceEvidence(lines, source) {
|
|
37
|
-
const fetch = source.fetch;
|
|
38
|
-
if (!fetch?.attempted)
|
|
39
|
-
return;
|
|
40
|
-
const snippet = String(fetch.snippet || "").trim();
|
|
41
|
-
const lastModified = String(fetch.lastModified || "").trim();
|
|
42
|
-
if (snippet)
|
|
43
|
-
lines.push(` Evidence: ${snippet}`);
|
|
44
|
-
if (lastModified)
|
|
45
|
-
lines.push(` Last-Modified: ${lastModified}`);
|
|
46
|
-
if (fetch.error)
|
|
47
|
-
lines.push(` Fetch error: ${String(fetch.error)}`);
|
|
48
|
-
}
|
|
49
|
-
export function pickSources(sources, recommendedIds = [], max = 6) {
|
|
50
|
-
if (!sources.length)
|
|
51
|
-
return [];
|
|
52
|
-
const sourceMap = getSourceMap(sources);
|
|
53
|
-
const recommended = recommendedIds.map((id) => sourceMap.get(id)).filter((source) => Boolean(source));
|
|
54
|
-
if (recommended.length > 0)
|
|
55
|
-
return recommended.slice(0, max);
|
|
56
|
-
return sources.slice(0, max);
|
|
57
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { formatAgreementLevel } from "../utils/helpers.js";
|
|
2
|
-
import { formatSourceLine, pickSources } from "./sources.js";
|
|
3
|
-
export function renderSynthesis(lines, synthesis, sources, maxSources = 6) {
|
|
4
|
-
if (synthesis.answer) {
|
|
5
|
-
lines.push("## Answer");
|
|
6
|
-
lines.push(String(synthesis.answer));
|
|
7
|
-
lines.push("");
|
|
8
|
-
}
|
|
9
|
-
const agreement = synthesis.agreement;
|
|
10
|
-
const agreementSummary = String(agreement?.summary || "").trim();
|
|
11
|
-
const agreementLevel = String(agreement?.level || "").trim();
|
|
12
|
-
if (agreementSummary || agreementLevel) {
|
|
13
|
-
lines.push("## Consensus");
|
|
14
|
-
lines.push(`- ${formatAgreementLevel(agreementLevel)}${agreementSummary ? ` - ${agreementSummary}` : ""}`);
|
|
15
|
-
lines.push("");
|
|
16
|
-
}
|
|
17
|
-
const differences = Array.isArray(synthesis.differences) ? synthesis.differences : [];
|
|
18
|
-
if (differences.length > 0) {
|
|
19
|
-
lines.push("## Where Engines Differ");
|
|
20
|
-
for (const difference of differences)
|
|
21
|
-
lines.push(`- ${difference}`);
|
|
22
|
-
lines.push("");
|
|
23
|
-
}
|
|
24
|
-
const caveats = Array.isArray(synthesis.caveats) ? synthesis.caveats : [];
|
|
25
|
-
if (caveats.length > 0) {
|
|
26
|
-
lines.push("## Caveats");
|
|
27
|
-
for (const caveat of caveats)
|
|
28
|
-
lines.push(`- ${caveat}`);
|
|
29
|
-
lines.push("");
|
|
30
|
-
}
|
|
31
|
-
const claims = Array.isArray(synthesis.claims) ? synthesis.claims : [];
|
|
32
|
-
if (claims.length > 0) {
|
|
33
|
-
lines.push("## Key Claims");
|
|
34
|
-
for (const claim of claims) {
|
|
35
|
-
const sourceIds = Array.isArray(claim.sourceIds) ? claim.sourceIds : [];
|
|
36
|
-
const support = String(claim.support || "moderate");
|
|
37
|
-
lines.push(`- ${String(claim.claim || "")} [${support}${sourceIds.length ? `; ${sourceIds.join(", ")}` : ""}]`);
|
|
38
|
-
}
|
|
39
|
-
lines.push("");
|
|
40
|
-
}
|
|
41
|
-
const recommendedIds = Array.isArray(synthesis.recommendedSources) ? synthesis.recommendedSources : [];
|
|
42
|
-
const topSources = pickSources(sources, recommendedIds, maxSources);
|
|
43
|
-
if (topSources.length > 0) {
|
|
44
|
-
lines.push("## Top Sources");
|
|
45
|
-
for (const source of topSources)
|
|
46
|
-
lines.push(formatSourceLine(source));
|
|
47
|
-
lines.push("");
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
const GITHUB_API = "https://api.github.com";
|
|
2
|
-
const DEFAULT_HEADERS = {
|
|
3
|
-
"user-agent": "GreedySearch/1.0",
|
|
4
|
-
accept: "application/vnd.github+json",
|
|
5
|
-
"x-github-api-version": "2022-11-28"
|
|
6
|
-
};
|
|
7
|
-
export function parseGitHubUrl(url) {
|
|
8
|
-
try {
|
|
9
|
-
const parsed = new URL(url);
|
|
10
|
-
if (!(parsed.hostname === "github.com" || parsed.hostname.endsWith(".github.com"))) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
14
|
-
if (parts.length < 2) {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
const [owner, repo] = parts;
|
|
18
|
-
if (parts.length === 2) {
|
|
19
|
-
return { owner, repo, type: "root" };
|
|
20
|
-
}
|
|
21
|
-
if (parts.length >= 4 && (parts[2] === "blob" || parts[2] === "tree")) {
|
|
22
|
-
const type = parts[2];
|
|
23
|
-
const ref = parts[3];
|
|
24
|
-
const path = parts.slice(4).join("/");
|
|
25
|
-
return { owner, repo, type, ref, path };
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
} catch {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
async function apiGet(path, timeoutMs = 1e4) {
|
|
33
|
-
const controller = new AbortController;
|
|
34
|
-
const tid = setTimeout(() => controller.abort(), timeoutMs);
|
|
35
|
-
try {
|
|
36
|
-
const res = await fetch(`${GITHUB_API}${path}`, {
|
|
37
|
-
headers: DEFAULT_HEADERS,
|
|
38
|
-
signal: controller.signal
|
|
39
|
-
});
|
|
40
|
-
clearTimeout(tid);
|
|
41
|
-
if (!res.ok) {
|
|
42
|
-
throw new Error(`GitHub API ${res.status}: ${path}`);
|
|
43
|
-
}
|
|
44
|
-
return await res.json();
|
|
45
|
-
} catch (err) {
|
|
46
|
-
clearTimeout(tid);
|
|
47
|
-
throw err;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
async function fetchReadme(owner, repo) {
|
|
51
|
-
try {
|
|
52
|
-
const data = await apiGet(`/repos/${owner}/${repo}/readme`);
|
|
53
|
-
if (data.content && data.encoding === "base64") {
|
|
54
|
-
return Buffer.from(data.content, "base64").toString("utf8");
|
|
55
|
-
}
|
|
56
|
-
return "";
|
|
57
|
-
} catch {
|
|
58
|
-
return "";
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async function fetchTree(owner, repo, ref = "HEAD", subPath = "", defaultBranch) {
|
|
62
|
-
try {
|
|
63
|
-
let refData;
|
|
64
|
-
if (ref === "HEAD") {
|
|
65
|
-
if (defaultBranch) {
|
|
66
|
-
refData = await apiGet(`/repos/${owner}/${repo}/git/ref/heads/${defaultBranch}`).catch(() => null);
|
|
67
|
-
} else {
|
|
68
|
-
refData = await Promise.any([
|
|
69
|
-
apiGet(`/repos/${owner}/${repo}/git/ref/heads/main`),
|
|
70
|
-
apiGet(`/repos/${owner}/${repo}/git/ref/heads/master`)
|
|
71
|
-
]).catch(() => null);
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
refData = await apiGet(`/repos/${owner}/${repo}/git/ref/heads/${ref}`).catch(() => apiGet(`/repos/${owner}/${repo}/git/ref/heads/master`).catch(() => null));
|
|
75
|
-
}
|
|
76
|
-
if (!refData?.object?.sha) {
|
|
77
|
-
return [];
|
|
78
|
-
}
|
|
79
|
-
const commit = await apiGet(`/repos/${owner}/${repo}/git/commits/${refData.object.sha}`);
|
|
80
|
-
const treeSha = commit.tree.sha;
|
|
81
|
-
const treeData = await apiGet(`/repos/${owner}/${repo}/git/trees/${treeSha}`);
|
|
82
|
-
let items = treeData.tree || [];
|
|
83
|
-
if (subPath) {
|
|
84
|
-
items = items.filter((item) => item.path.startsWith(subPath));
|
|
85
|
-
}
|
|
86
|
-
return items.slice(0, 50).map((item) => ({
|
|
87
|
-
path: item.path,
|
|
88
|
-
type: item.type === "tree" ? "dir" : "file",
|
|
89
|
-
size: item.size
|
|
90
|
-
}));
|
|
91
|
-
} catch {
|
|
92
|
-
return [];
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
async function fetchRawFile(owner, repo, ref, filePath, timeoutMs = 1e4, defaultBranch) {
|
|
96
|
-
const fetchOne = async (url) => {
|
|
97
|
-
const controller = new AbortController;
|
|
98
|
-
const tid = setTimeout(() => controller.abort(), timeoutMs);
|
|
99
|
-
try {
|
|
100
|
-
const res = await fetch(url, {
|
|
101
|
-
headers: { "user-agent": DEFAULT_HEADERS["user-agent"] },
|
|
102
|
-
signal: controller.signal
|
|
103
|
-
});
|
|
104
|
-
clearTimeout(tid);
|
|
105
|
-
if (res.ok) {
|
|
106
|
-
return await res.text();
|
|
107
|
-
}
|
|
108
|
-
throw new Error("not ok");
|
|
109
|
-
} catch {
|
|
110
|
-
clearTimeout(tid);
|
|
111
|
-
throw new Error("failed");
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
if (!ref || ref === "HEAD") {
|
|
115
|
-
if (defaultBranch) {
|
|
116
|
-
try {
|
|
117
|
-
return await fetchOne(`https://raw.githubusercontent.com/${owner}/${repo}/${defaultBranch}/${filePath}`);
|
|
118
|
-
} catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
return await Promise.any([
|
|
124
|
-
fetchOne(`https://raw.githubusercontent.com/${owner}/${repo}/main/${filePath}`),
|
|
125
|
-
fetchOne(`https://raw.githubusercontent.com/${owner}/${repo}/master/${filePath}`)
|
|
126
|
-
]);
|
|
127
|
-
} catch {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
const urls = [
|
|
132
|
-
`https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${filePath}`,
|
|
133
|
-
`https://raw.githubusercontent.com/${owner}/${repo}/master/${filePath}`
|
|
134
|
-
];
|
|
135
|
-
for (const url of urls) {
|
|
136
|
-
try {
|
|
137
|
-
return await fetchOne(url);
|
|
138
|
-
} catch {}
|
|
139
|
-
}
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
export async function fetchGitHubContent(url) {
|
|
143
|
-
const parsed = parseGitHubUrl(url);
|
|
144
|
-
if (!parsed) {
|
|
145
|
-
return { ok: false, error: "Not a valid GitHub URL" };
|
|
146
|
-
}
|
|
147
|
-
const { owner, repo, type, ref, path } = parsed;
|
|
148
|
-
try {
|
|
149
|
-
if (type === "root" || type === "tree" && !path) {
|
|
150
|
-
const info = await apiGet(`/repos/${owner}/${repo}`);
|
|
151
|
-
const [readme, tree] = await Promise.allSettled([
|
|
152
|
-
fetchReadme(owner, repo),
|
|
153
|
-
fetchTree(owner, repo, ref || "HEAD", "", info.default_branch)
|
|
154
|
-
]);
|
|
155
|
-
const readmeText = readme.status === "fulfilled" ? readme.value : "";
|
|
156
|
-
const treeItems = tree.status === "fulfilled" ? tree.value : [];
|
|
157
|
-
const description = info?.description ? `
|
|
158
|
-
|
|
159
|
-
> ${info.description}` : "";
|
|
160
|
-
const stars = info?.stargazers_count == null ? "" : ` ⭐ ${info.stargazers_count}`;
|
|
161
|
-
const language = info?.language ? ` · ${info.language}` : "";
|
|
162
|
-
let content = `# ${owner}/${repo}${stars}${language}${description}
|
|
163
|
-
|
|
164
|
-
`;
|
|
165
|
-
if (readmeText) {
|
|
166
|
-
content += readmeText.slice(0, 6000);
|
|
167
|
-
} else {
|
|
168
|
-
content += `[No README found]
|
|
169
|
-
|
|
170
|
-
Files:
|
|
171
|
-
${treeItems.map((t) => ` ${t.type === "dir" ? "\uD83D\uDCC1" : "\uD83D\uDCC4"} ${t.path}`).join(`
|
|
172
|
-
`)}`;
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
ok: true,
|
|
176
|
-
title: `${owner}/${repo}`,
|
|
177
|
-
content,
|
|
178
|
-
tree: treeItems.slice(0, 30)
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
if (type === "blob" && path) {
|
|
182
|
-
let defaultBranch;
|
|
183
|
-
if (!ref || ref === "HEAD") {
|
|
184
|
-
try {
|
|
185
|
-
const repoInfo = await apiGet(`/repos/${owner}/${repo}`);
|
|
186
|
-
defaultBranch = repoInfo.default_branch;
|
|
187
|
-
} catch {
|
|
188
|
-
defaultBranch = undefined;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
const content = await fetchRawFile(owner, repo, ref, path, 1e4, defaultBranch);
|
|
192
|
-
if (content === null) {
|
|
193
|
-
return { ok: false, error: `File not found: ${path}` };
|
|
194
|
-
}
|
|
195
|
-
return {
|
|
196
|
-
ok: true,
|
|
197
|
-
title: `${owner}/${repo}: ${path}`,
|
|
198
|
-
content
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
if (type === "tree" && path) {
|
|
202
|
-
let defaultBranch;
|
|
203
|
-
if (!ref || ref === "HEAD") {
|
|
204
|
-
try {
|
|
205
|
-
const repoInfo = await apiGet(`/repos/${owner}/${repo}`);
|
|
206
|
-
defaultBranch = repoInfo.default_branch;
|
|
207
|
-
} catch {
|
|
208
|
-
defaultBranch = undefined;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const treeItems = await fetchTree(owner, repo, ref || "HEAD", path, defaultBranch);
|
|
212
|
-
const listing = treeItems.map((t) => ` ${t.type === "dir" ? "\uD83D\uDCC1" : "\uD83D\uDCC4"} ${t.path}`).join(`
|
|
213
|
-
`);
|
|
214
|
-
return {
|
|
215
|
-
ok: true,
|
|
216
|
-
title: `${owner}/${repo}/${path}`,
|
|
217
|
-
content: `[Directory: ${path}]
|
|
218
|
-
|
|
219
|
-
Files:
|
|
220
|
-
${listing}`,
|
|
221
|
-
tree: treeItems
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
return { ok: false, error: "Unsupported GitHub URL type" };
|
|
225
|
-
} catch (err) {
|
|
226
|
-
return { ok: false, error: err.message };
|
|
227
|
-
}
|
|
228
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
const REDDIT_HEADERS = {
|
|
2
|
-
"user-agent": "GreedySearch/1.0 (Research Bot)",
|
|
3
|
-
accept: "application/json"
|
|
4
|
-
};
|
|
5
|
-
export function parseRedditUrl(url) {
|
|
6
|
-
try {
|
|
7
|
-
const parsed = new URL(url);
|
|
8
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
9
|
-
if (!(hostname === "reddit.com" || hostname.endsWith(".reddit.com"))) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
const pathname = parsed.pathname;
|
|
13
|
-
if (pathname.match(/^\/(u|user)\/[^/]+\/?$/i)) {
|
|
14
|
-
return { type: "user", cleanUrl: normalizeRedditUrl(url) };
|
|
15
|
-
}
|
|
16
|
-
if (pathname.match(/^\/r\/[^/]+\/comments\/[^/]+/i)) {
|
|
17
|
-
return { type: "post", cleanUrl: normalizeRedditUrl(url) };
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
} catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function normalizeRedditUrl(url) {
|
|
25
|
-
try {
|
|
26
|
-
const parsed = new URL(url);
|
|
27
|
-
return `${parsed.protocol}//${parsed.hostname}${parsed.pathname}`;
|
|
28
|
-
} catch {
|
|
29
|
-
return url;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
export async function fetchRedditContent(url, maxChars = 8000) {
|
|
33
|
-
const start = Date.now();
|
|
34
|
-
try {
|
|
35
|
-
const jsonUrl = url.replace(/\/+$/, "") + ".json";
|
|
36
|
-
const controller = new AbortController;
|
|
37
|
-
const timeoutId = setTimeout(() => controller.abort(), 15000);
|
|
38
|
-
const response = await fetch(jsonUrl, {
|
|
39
|
-
headers: REDDIT_HEADERS,
|
|
40
|
-
signal: controller.signal
|
|
41
|
-
});
|
|
42
|
-
clearTimeout(timeoutId);
|
|
43
|
-
if (!response.ok) {
|
|
44
|
-
throw new Error(`Reddit API ${response.status}`);
|
|
45
|
-
}
|
|
46
|
-
const data = await response.json();
|
|
47
|
-
if (!Array.isArray(data) || data.length < 1) {
|
|
48
|
-
throw new Error("Invalid Reddit API response structure");
|
|
49
|
-
}
|
|
50
|
-
const postListing = data[0];
|
|
51
|
-
const commentsListing = data[1];
|
|
52
|
-
const post = postListing?.data?.children?.[0]?.data;
|
|
53
|
-
if (!post) {
|
|
54
|
-
throw new Error("No post data in Reddit response");
|
|
55
|
-
}
|
|
56
|
-
const markdown = formatRedditPost(post, commentsListing, maxChars);
|
|
57
|
-
return {
|
|
58
|
-
ok: true,
|
|
59
|
-
url,
|
|
60
|
-
finalUrl: url,
|
|
61
|
-
status: 200,
|
|
62
|
-
contentType: "text/markdown",
|
|
63
|
-
lastModified: "",
|
|
64
|
-
title: post.title || "Reddit Post",
|
|
65
|
-
byline: `u/${post.author}`,
|
|
66
|
-
siteName: `r/${post.subreddit}`,
|
|
67
|
-
lang: "en",
|
|
68
|
-
publishedTime: new Date(post.created_utc * 1000).toISOString(),
|
|
69
|
-
excerpt: post.selftext?.slice(0, 300).replace(/\n/g, " ") || "",
|
|
70
|
-
markdown,
|
|
71
|
-
contentLength: markdown.length,
|
|
72
|
-
needsBrowser: false,
|
|
73
|
-
duration: Date.now() - start
|
|
74
|
-
};
|
|
75
|
-
} catch (error) {
|
|
76
|
-
return {
|
|
77
|
-
ok: false,
|
|
78
|
-
url,
|
|
79
|
-
finalUrl: url,
|
|
80
|
-
status: 0,
|
|
81
|
-
error: `Reddit fetch failed: ${error.message}`,
|
|
82
|
-
needsBrowser: false,
|
|
83
|
-
duration: Date.now() - start
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
function formatRedditPost(post, commentsListing, maxChars) {
|
|
88
|
-
let md = "";
|
|
89
|
-
md += `# ${post.title}
|
|
90
|
-
|
|
91
|
-
`;
|
|
92
|
-
md += `**Subreddit:** r/${post.subreddit} | **Author:** u/${post.author} | **Score:** ${post.score}
|
|
93
|
-
|
|
94
|
-
`;
|
|
95
|
-
if (post.selftext) {
|
|
96
|
-
md += post.selftext;
|
|
97
|
-
md += `
|
|
98
|
-
|
|
99
|
-
`;
|
|
100
|
-
} else if (post.url) {
|
|
101
|
-
try {
|
|
102
|
-
const postUrlHost = new URL(post.url).hostname.toLowerCase();
|
|
103
|
-
if (postUrlHost !== "reddit.com" && !postUrlHost.endsWith(".reddit.com")) {
|
|
104
|
-
md += `**Link:** ${post.url}
|
|
105
|
-
|
|
106
|
-
`;
|
|
107
|
-
}
|
|
108
|
-
} catch {
|
|
109
|
-
md += `**Link:** ${post.url}
|
|
110
|
-
|
|
111
|
-
`;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (commentsListing?.data?.children?.length > 0) {
|
|
115
|
-
md += `---
|
|
116
|
-
|
|
117
|
-
## Comments
|
|
118
|
-
|
|
119
|
-
`;
|
|
120
|
-
const comments = commentsListing.data.children.filter((c) => c.kind === "t1").slice(0, 10);
|
|
121
|
-
for (const comment of comments) {
|
|
122
|
-
md += formatComment(comment.data, 0);
|
|
123
|
-
md += `
|
|
124
|
-
`;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (md.length > maxChars) {
|
|
128
|
-
md = md.slice(0, maxChars).trim() + `
|
|
129
|
-
|
|
130
|
-
... (truncated)`;
|
|
131
|
-
}
|
|
132
|
-
return md;
|
|
133
|
-
}
|
|
134
|
-
function formatComment(comment, depth) {
|
|
135
|
-
if (!comment || comment.body === "[deleted]" || comment.body === "[removed]") {
|
|
136
|
-
return "";
|
|
137
|
-
}
|
|
138
|
-
const indent = "> ".repeat(depth);
|
|
139
|
-
let md = "";
|
|
140
|
-
md += `${indent}**u/${comment.author}** (${comment.score} pts)
|
|
141
|
-
`;
|
|
142
|
-
md += `${indent}${comment.body.replaceAll(`
|
|
143
|
-
`, `
|
|
144
|
-
` + indent)}
|
|
145
|
-
`;
|
|
146
|
-
if (depth < 3 && comment.replies?.data?.children) {
|
|
147
|
-
const replies = comment.replies.data.children.filter((r) => r.kind === "t1");
|
|
148
|
-
for (const reply of replies.slice(0, 5)) {
|
|
149
|
-
md += `
|
|
150
|
-
` + formatComment(reply.data, depth + 1);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return md;
|
|
154
|
-
}
|