@oh-my-pi/pi-coding-agent 3.30.0 → 3.32.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/CHANGELOG.md +85 -0
- package/package.json +5 -5
- package/src/cli/args.ts +4 -0
- package/src/core/agent-session.ts +29 -2
- package/src/core/bash-executor.ts +2 -1
- package/src/core/custom-commands/bundled/review/index.ts +367 -14
- package/src/core/custom-commands/bundled/wt/index.ts +1 -1
- package/src/core/sdk.ts +10 -2
- package/src/core/session-manager.ts +158 -246
- package/src/core/session-storage.ts +379 -0
- package/src/core/settings-manager.ts +155 -4
- package/src/core/slash-commands.ts +39 -13
- package/src/core/system-prompt.ts +62 -64
- package/src/core/tools/ask.ts +5 -4
- package/src/core/tools/bash-interceptor.ts +26 -61
- package/src/core/tools/bash.ts +13 -8
- package/src/core/tools/edit-diff.ts +11 -4
- package/src/core/tools/edit.ts +7 -13
- package/src/core/tools/find.ts +111 -50
- package/src/core/tools/gemini-image.ts +128 -147
- package/src/core/tools/grep.ts +397 -415
- package/src/core/tools/index.test.ts +5 -1
- package/src/core/tools/index.ts +8 -4
- package/src/core/tools/ls.ts +12 -10
- package/src/core/tools/lsp/client.ts +84 -19
- package/src/core/tools/lsp/config.ts +205 -656
- package/src/core/tools/lsp/defaults.json +465 -0
- package/src/core/tools/lsp/index.ts +72 -35
- package/src/core/tools/lsp/rust-analyzer.ts +49 -10
- package/src/core/tools/lsp/types.ts +1 -0
- package/src/core/tools/lsp/utils.ts +1 -1
- package/src/core/tools/read.ts +150 -74
- package/src/core/tools/render-utils.ts +70 -10
- package/src/core/tools/review.ts +38 -126
- package/src/core/tools/task/artifacts.ts +5 -4
- package/src/core/tools/task/commands.ts +4 -0
- package/src/core/tools/task/executor.ts +94 -83
- package/src/core/tools/task/index.ts +130 -92
- package/src/core/tools/task/parallel.ts +30 -3
- package/src/core/tools/task/render.ts +85 -39
- package/src/core/tools/task/types.ts +15 -6
- package/src/core/tools/task/worker.ts +124 -89
- package/src/core/tools/web-fetch.ts +112 -377
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/artifacthub.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/arxiv.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/aur.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/biorxiv.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/bluesky.ts +10 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/brew.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/cheatsh.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/chocolatey.ts +6 -1
- package/src/core/tools/web-scrapers/choosealicense.ts +110 -0
- package/src/core/tools/web-scrapers/cisa-kev.ts +100 -0
- package/src/core/tools/web-scrapers/clojars.ts +180 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/coingecko.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/crates-io.ts +7 -2
- package/src/core/tools/web-scrapers/crossref.ts +149 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/devto.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/discogs.ts +6 -1
- package/src/core/tools/web-scrapers/discourse.ts +221 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/dockerhub.ts +7 -3
- package/src/core/tools/web-scrapers/fdroid.ts +158 -0
- package/src/core/tools/web-scrapers/firefox-addons.ts +214 -0
- package/src/core/tools/web-scrapers/flathub.ts +239 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/github-gist.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/github.ts +63 -32
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/gitlab.ts +31 -19
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/go-pkg.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackage.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackernews.ts +18 -18
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hex.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/huggingface.ts +10 -10
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/iacr.ts +8 -4
- package/src/core/tools/web-scrapers/index.ts +250 -0
- package/src/core/tools/web-scrapers/jetbrains-marketplace.ts +169 -0
- package/src/core/tools/web-scrapers/lemmy.ts +220 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/lobsters.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/mastodon.ts +11 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/maven.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/mdn.ts +2 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/metacpan.ts +13 -7
- package/src/core/tools/web-scrapers/musicbrainz.ts +273 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/npm.ts +12 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/nuget.ts +9 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/nvd.ts +6 -1
- package/src/core/tools/web-scrapers/ollama.ts +267 -0
- package/src/core/tools/web-scrapers/open-vsx.ts +119 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/opencorporates.ts +2 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/openlibrary.ts +18 -12
- package/src/core/tools/web-scrapers/orcid.ts +299 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/osv.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/packagist.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pub-dev.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pubmed.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pypi.ts +7 -3
- package/src/core/tools/web-scrapers/rawg.ts +124 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/readthedocs.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/reddit.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/repology.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/rfc.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/rubygems.ts +6 -1
- package/src/core/tools/web-scrapers/searchcode.ts +217 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/sec-edgar.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/semantic-scholar.ts +2 -2
- package/src/core/tools/web-scrapers/snapcraft.ts +200 -0
- package/src/core/tools/web-scrapers/sourcegraph.ts +373 -0
- package/src/core/tools/web-scrapers/spdx.ts +121 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/spotify.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackoverflow.ts +3 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/terraform.ts +11 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/tldr.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/twitter.ts +15 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/types.ts +98 -27
- package/src/core/tools/web-scrapers/utils.ts +162 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/vimeo.ts +3 -3
- package/src/core/tools/web-scrapers/vscode-marketplace.ts +195 -0
- package/src/core/tools/web-scrapers/w3c.ts +163 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikidata.ts +13 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.ts +72 -20
- package/src/core/tools/write.ts +21 -18
- package/src/core/voice.ts +3 -2
- package/src/lib/worktree/collapse.ts +2 -1
- package/src/lib/worktree/git.ts +2 -18
- package/src/main.ts +59 -3
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +33 -19
- package/src/modes/interactive/components/extensions/extension-list.ts +15 -8
- package/src/modes/interactive/components/hook-editor.ts +2 -1
- package/src/modes/interactive/components/model-selector.ts +19 -4
- package/src/modes/interactive/interactive-mode.ts +41 -63
- package/src/modes/interactive/theme/theme.ts +58 -58
- package/src/modes/rpc/rpc-mode.ts +10 -9
- package/src/prompts/review-request.md +27 -0
- package/src/prompts/reviewer.md +64 -68
- package/src/prompts/tools/output.md +22 -3
- package/src/prompts/tools/task.md +32 -33
- package/src/utils/clipboard.ts +2 -1
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/src/core/tools/web-fetch-handlers/index.ts +0 -69
- package/src/core/tools/web-fetch-handlers/utils.ts +0 -91
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/academic.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/business.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/dev-platforms.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/documentation.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/finance-media.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/git-hosting.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/media.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers-2.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-registries.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/research.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/security.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social-extended.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackexchange.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/standards.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.test.ts +0 -0
|
@@ -86,11 +86,11 @@ function parseGitLabUrl(url: string): GitLabUrl | null {
|
|
|
86
86
|
/**
|
|
87
87
|
* Get project ID from namespace/project path
|
|
88
88
|
*/
|
|
89
|
-
async function getProjectId(gl: GitLabUrl, timeout: number): Promise<number | null> {
|
|
89
|
+
async function getProjectId(gl: GitLabUrl, timeout: number, signal?: AbortSignal): Promise<number | null> {
|
|
90
90
|
const encodedPath = encodeURIComponent(`${gl.namespace}/${gl.project}`);
|
|
91
91
|
const apiUrl = `https://gitlab.com/api/v4/projects/${encodedPath}`;
|
|
92
92
|
|
|
93
|
-
const result = await loadPage(apiUrl, { timeout });
|
|
93
|
+
const result = await loadPage(apiUrl, { timeout, signal });
|
|
94
94
|
if (!result.ok) return null;
|
|
95
95
|
|
|
96
96
|
try {
|
|
@@ -104,11 +104,15 @@ async function getProjectId(gl: GitLabUrl, timeout: number): Promise<number | nu
|
|
|
104
104
|
/**
|
|
105
105
|
* Render GitLab repository
|
|
106
106
|
*/
|
|
107
|
-
async function renderGitLabRepo(
|
|
107
|
+
async function renderGitLabRepo(
|
|
108
|
+
gl: GitLabUrl,
|
|
109
|
+
timeout: number,
|
|
110
|
+
signal?: AbortSignal,
|
|
111
|
+
): Promise<{ content: string; ok: boolean }> {
|
|
108
112
|
const encodedPath = encodeURIComponent(`${gl.namespace}/${gl.project}`);
|
|
109
113
|
const apiUrl = `https://gitlab.com/api/v4/projects/${encodedPath}`;
|
|
110
114
|
|
|
111
|
-
const result = await loadPage(apiUrl, { timeout });
|
|
115
|
+
const result = await loadPage(apiUrl, { timeout, signal });
|
|
112
116
|
if (!result.ok) return { content: "", ok: false };
|
|
113
117
|
|
|
114
118
|
try {
|
|
@@ -137,7 +141,7 @@ async function renderGitLabRepo(gl: GitLabUrl, timeout: number): Promise<{ conte
|
|
|
137
141
|
|
|
138
142
|
// Try to fetch README
|
|
139
143
|
if (repo.readme_url) {
|
|
140
|
-
const readmeResult = await loadPage(repo.readme_url, { timeout });
|
|
144
|
+
const readmeResult = await loadPage(repo.readme_url, { timeout, signal });
|
|
141
145
|
if (readmeResult.ok && readmeResult.content.trim().length > 0) {
|
|
142
146
|
md += `---\n\n## README\n\n${readmeResult.content}\n`;
|
|
143
147
|
}
|
|
@@ -156,11 +160,12 @@ async function renderGitLabFile(
|
|
|
156
160
|
gl: GitLabUrl,
|
|
157
161
|
projectId: number,
|
|
158
162
|
timeout: number,
|
|
163
|
+
signal?: AbortSignal,
|
|
159
164
|
): Promise<{ content: string; ok: boolean }> {
|
|
160
165
|
const encodedPath = encodeURIComponent(gl.path!);
|
|
161
166
|
const apiUrl = `https://gitlab.com/api/v4/projects/${projectId}/repository/files/${encodedPath}/raw?ref=${gl.ref}`;
|
|
162
167
|
|
|
163
|
-
const result = await loadPage(apiUrl, { timeout });
|
|
168
|
+
const result = await loadPage(apiUrl, { timeout, signal });
|
|
164
169
|
if (!result.ok) return { content: "", ok: false };
|
|
165
170
|
|
|
166
171
|
return { content: result.content, ok: true };
|
|
@@ -173,10 +178,11 @@ async function renderGitLabTree(
|
|
|
173
178
|
gl: GitLabUrl,
|
|
174
179
|
projectId: number,
|
|
175
180
|
timeout: number,
|
|
181
|
+
signal?: AbortSignal,
|
|
176
182
|
): Promise<{ content: string; ok: boolean }> {
|
|
177
183
|
const apiUrl = `https://gitlab.com/api/v4/projects/${projectId}/repository/tree?ref=${gl.ref}&path=${gl.path || ""}&per_page=100`;
|
|
178
184
|
|
|
179
|
-
const result = await loadPage(apiUrl, { timeout });
|
|
185
|
+
const result = await loadPage(apiUrl, { timeout, signal });
|
|
180
186
|
if (!result.ok) return { content: "", ok: false };
|
|
181
187
|
|
|
182
188
|
try {
|
|
@@ -222,10 +228,11 @@ async function renderGitLabIssue(
|
|
|
222
228
|
gl: GitLabUrl,
|
|
223
229
|
projectId: number,
|
|
224
230
|
timeout: number,
|
|
231
|
+
signal?: AbortSignal,
|
|
225
232
|
): Promise<{ content: string; ok: boolean }> {
|
|
226
233
|
const apiUrl = `https://gitlab.com/api/v4/projects/${projectId}/issues/${gl.id}`;
|
|
227
234
|
|
|
228
|
-
const result = await loadPage(apiUrl, { timeout });
|
|
235
|
+
const result = await loadPage(apiUrl, { timeout, signal });
|
|
229
236
|
if (!result.ok) return { content: "", ok: false };
|
|
230
237
|
|
|
231
238
|
try {
|
|
@@ -272,10 +279,11 @@ async function renderGitLabMR(
|
|
|
272
279
|
gl: GitLabUrl,
|
|
273
280
|
projectId: number,
|
|
274
281
|
timeout: number,
|
|
282
|
+
signal?: AbortSignal,
|
|
275
283
|
): Promise<{ content: string; ok: boolean }> {
|
|
276
284
|
const apiUrl = `https://gitlab.com/api/v4/projects/${projectId}/merge_requests/${gl.id}`;
|
|
277
285
|
|
|
278
|
-
const result = await loadPage(apiUrl, { timeout });
|
|
286
|
+
const result = await loadPage(apiUrl, { timeout, signal });
|
|
279
287
|
if (!result.ok) return { content: "", ok: false };
|
|
280
288
|
|
|
281
289
|
try {
|
|
@@ -324,7 +332,11 @@ async function renderGitLabMR(
|
|
|
324
332
|
/**
|
|
325
333
|
* Handle GitLab URLs specially
|
|
326
334
|
*/
|
|
327
|
-
export const handleGitLab: SpecialHandler = async (
|
|
335
|
+
export const handleGitLab: SpecialHandler = async (
|
|
336
|
+
url: string,
|
|
337
|
+
timeout: number,
|
|
338
|
+
signal?: AbortSignal,
|
|
339
|
+
): Promise<RenderResult | null> => {
|
|
328
340
|
const gl = parseGitLabUrl(url);
|
|
329
341
|
if (!gl) return null;
|
|
330
342
|
|
|
@@ -333,11 +345,11 @@ export const handleGitLab: SpecialHandler = async (url: string, timeout: number)
|
|
|
333
345
|
|
|
334
346
|
switch (gl.type) {
|
|
335
347
|
case "blob": {
|
|
336
|
-
const projectId = await getProjectId(gl, timeout);
|
|
348
|
+
const projectId = await getProjectId(gl, timeout, signal);
|
|
337
349
|
if (!projectId) break;
|
|
338
350
|
|
|
339
351
|
notes.push(`Fetched raw file via GitLab API`);
|
|
340
|
-
const result = await renderGitLabFile(gl, projectId, timeout);
|
|
352
|
+
const result = await renderGitLabFile(gl, projectId, timeout, signal);
|
|
341
353
|
if (result.ok) {
|
|
342
354
|
const output = finalizeOutput(result.content);
|
|
343
355
|
return {
|
|
@@ -355,11 +367,11 @@ export const handleGitLab: SpecialHandler = async (url: string, timeout: number)
|
|
|
355
367
|
}
|
|
356
368
|
|
|
357
369
|
case "tree": {
|
|
358
|
-
const projectId = await getProjectId(gl, timeout);
|
|
370
|
+
const projectId = await getProjectId(gl, timeout, signal);
|
|
359
371
|
if (!projectId) break;
|
|
360
372
|
|
|
361
373
|
notes.push(`Fetched directory tree via GitLab API`);
|
|
362
|
-
const result = await renderGitLabTree(gl, projectId, timeout);
|
|
374
|
+
const result = await renderGitLabTree(gl, projectId, timeout, signal);
|
|
363
375
|
if (result.ok) {
|
|
364
376
|
const output = finalizeOutput(result.content);
|
|
365
377
|
return {
|
|
@@ -377,11 +389,11 @@ export const handleGitLab: SpecialHandler = async (url: string, timeout: number)
|
|
|
377
389
|
}
|
|
378
390
|
|
|
379
391
|
case "issue": {
|
|
380
|
-
const projectId = await getProjectId(gl, timeout);
|
|
392
|
+
const projectId = await getProjectId(gl, timeout, signal);
|
|
381
393
|
if (!projectId) break;
|
|
382
394
|
|
|
383
395
|
notes.push(`Fetched issue via GitLab API`);
|
|
384
|
-
const result = await renderGitLabIssue(gl, projectId, timeout);
|
|
396
|
+
const result = await renderGitLabIssue(gl, projectId, timeout, signal);
|
|
385
397
|
if (result.ok) {
|
|
386
398
|
const output = finalizeOutput(result.content);
|
|
387
399
|
return {
|
|
@@ -399,11 +411,11 @@ export const handleGitLab: SpecialHandler = async (url: string, timeout: number)
|
|
|
399
411
|
}
|
|
400
412
|
|
|
401
413
|
case "merge_request": {
|
|
402
|
-
const projectId = await getProjectId(gl, timeout);
|
|
414
|
+
const projectId = await getProjectId(gl, timeout, signal);
|
|
403
415
|
if (!projectId) break;
|
|
404
416
|
|
|
405
417
|
notes.push(`Fetched merge request via GitLab API`);
|
|
406
|
-
const result = await renderGitLabMR(gl, projectId, timeout);
|
|
418
|
+
const result = await renderGitLabMR(gl, projectId, timeout, signal);
|
|
407
419
|
if (result.ok) {
|
|
408
420
|
const output = finalizeOutput(result.content);
|
|
409
421
|
return {
|
|
@@ -422,7 +434,7 @@ export const handleGitLab: SpecialHandler = async (url: string, timeout: number)
|
|
|
422
434
|
|
|
423
435
|
case "repo": {
|
|
424
436
|
notes.push(`Fetched repository via GitLab API`);
|
|
425
|
-
const result = await renderGitLabRepo(gl, timeout);
|
|
437
|
+
const result = await renderGitLabRepo(gl, timeout, signal);
|
|
426
438
|
if (result.ok) {
|
|
427
439
|
const output = finalizeOutput(result.content);
|
|
428
440
|
return {
|
|
@@ -10,7 +10,11 @@ interface GoModuleInfo {
|
|
|
10
10
|
/**
|
|
11
11
|
* Handle pkg.go.dev URLs via proxy API and page parsing
|
|
12
12
|
*/
|
|
13
|
-
export const handleGoPkg: SpecialHandler = async (
|
|
13
|
+
export const handleGoPkg: SpecialHandler = async (
|
|
14
|
+
url: string,
|
|
15
|
+
timeout: number,
|
|
16
|
+
signal?: AbortSignal,
|
|
17
|
+
): Promise<RenderResult | null> => {
|
|
14
18
|
try {
|
|
15
19
|
const parsed = new URL(url);
|
|
16
20
|
if (parsed.hostname !== "pkg.go.dev") return null;
|
|
@@ -58,7 +62,7 @@ export const handleGoPkg: SpecialHandler = async (url: string, timeout: number):
|
|
|
58
62
|
if (version === "latest") {
|
|
59
63
|
try {
|
|
60
64
|
const proxyUrl = `https://proxy.golang.org/${encodeURIComponent(modulePath)}/@latest`;
|
|
61
|
-
const proxyResult = await loadPage(proxyUrl, { timeout });
|
|
65
|
+
const proxyResult = await loadPage(proxyUrl, { timeout, signal });
|
|
62
66
|
|
|
63
67
|
if (proxyResult.ok) {
|
|
64
68
|
moduleInfo = JSON.parse(proxyResult.content) as GoModuleInfo;
|
|
@@ -70,7 +74,7 @@ export const handleGoPkg: SpecialHandler = async (url: string, timeout: number):
|
|
|
70
74
|
} else {
|
|
71
75
|
try {
|
|
72
76
|
const proxyUrl = `https://proxy.golang.org/${encodeURIComponent(modulePath)}/@v/${encodeURIComponent(version)}.info`;
|
|
73
|
-
const proxyResult = await loadPage(proxyUrl, { timeout });
|
|
77
|
+
const proxyResult = await loadPage(proxyUrl, { timeout, signal });
|
|
74
78
|
|
|
75
79
|
if (proxyResult.ok) {
|
|
76
80
|
moduleInfo = JSON.parse(proxyResult.content) as GoModuleInfo;
|
|
@@ -81,7 +85,7 @@ export const handleGoPkg: SpecialHandler = async (url: string, timeout: number):
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
// Fetch the pkg.go.dev page
|
|
84
|
-
const pageResult = await loadPage(url, { timeout });
|
|
88
|
+
const pageResult = await loadPage(url, { timeout, signal });
|
|
85
89
|
if (!pageResult.ok) {
|
|
86
90
|
return {
|
|
87
91
|
url,
|
|
@@ -19,7 +19,11 @@ interface HackagePackage {
|
|
|
19
19
|
/**
|
|
20
20
|
* Handle Hackage (Haskell package registry) URLs via JSON API
|
|
21
21
|
*/
|
|
22
|
-
export const handleHackage: SpecialHandler = async (
|
|
22
|
+
export const handleHackage: SpecialHandler = async (
|
|
23
|
+
url: string,
|
|
24
|
+
timeout: number,
|
|
25
|
+
signal?: AbortSignal,
|
|
26
|
+
): Promise<RenderResult | null> => {
|
|
23
27
|
try {
|
|
24
28
|
const parsed = new URL(url);
|
|
25
29
|
if (parsed.hostname !== "hackage.haskell.org") return null;
|
|
@@ -36,6 +40,7 @@ export const handleHackage: SpecialHandler = async (url: string, timeout: number
|
|
|
36
40
|
const result = await loadPage(apiUrl, {
|
|
37
41
|
timeout,
|
|
38
42
|
headers: { Accept: "application/json" },
|
|
43
|
+
signal,
|
|
39
44
|
});
|
|
40
45
|
|
|
41
46
|
if (!result.ok) return null;
|
|
@@ -21,15 +21,15 @@ interface HNItem {
|
|
|
21
21
|
|
|
22
22
|
const API_BASE = "https://hacker-news.firebaseio.com/v0";
|
|
23
23
|
|
|
24
|
-
async function fetchItem(id: number, timeout: number): Promise<HNItem | null> {
|
|
24
|
+
async function fetchItem(id: number, timeout: number, signal?: AbortSignal): Promise<HNItem | null> {
|
|
25
25
|
const url = `${API_BASE}/item/${id}.json`;
|
|
26
|
-
const { content, ok } = await loadPage(url, { timeout });
|
|
26
|
+
const { content, ok } = await loadPage(url, { timeout, signal });
|
|
27
27
|
if (!ok) return null;
|
|
28
28
|
return JSON.parse(content) as HNItem;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async function fetchItems(ids: number[], timeout: number, limit = 20): Promise<HNItem[]> {
|
|
32
|
-
const promises = ids.slice(0, limit).map((id) => fetchItem(id, timeout));
|
|
31
|
+
async function fetchItems(ids: number[], timeout: number, limit = 20, signal?: AbortSignal): Promise<HNItem[]> {
|
|
32
|
+
const promises = ids.slice(0, limit).map((id) => fetchItem(id, timeout, signal));
|
|
33
33
|
const results = await Promise.all(promises);
|
|
34
34
|
return results.filter((item): item is HNItem => item !== null && !item.deleted && !item.dead);
|
|
35
35
|
}
|
|
@@ -69,7 +69,7 @@ function formatTimestamp(unixTime: number): string {
|
|
|
69
69
|
return `${minutes}m ago`;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
async function renderStory(item: HNItem, timeout: number, depth = 0): Promise<string> {
|
|
72
|
+
async function renderStory(item: HNItem, timeout: number, depth = 0, signal?: AbortSignal): Promise<string> {
|
|
73
73
|
let output = "";
|
|
74
74
|
|
|
75
75
|
if (depth === 0) {
|
|
@@ -90,7 +90,7 @@ async function renderStory(item: HNItem, timeout: number, depth = 0): Promise<st
|
|
|
90
90
|
|
|
91
91
|
if (item.kids && item.kids.length > 0 && depth < 2) {
|
|
92
92
|
const topComments = item.kids.slice(0, depth === 0 ? 20 : 10);
|
|
93
|
-
const comments = await fetchItems(topComments, timeout, topComments.length);
|
|
93
|
+
const comments = await fetchItems(topComments, timeout, topComments.length, signal);
|
|
94
94
|
|
|
95
95
|
if (comments.length > 0) {
|
|
96
96
|
if (depth === 0) output += "---\n\n## Comments\n\n";
|
|
@@ -107,7 +107,7 @@ async function renderStory(item: HNItem, timeout: number, depth = 0): Promise<st
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
if (comment.kids && comment.kids.length > 0 && depth < 1) {
|
|
110
|
-
const childOutput = await renderStory(comment, timeout, depth + 1);
|
|
110
|
+
const childOutput = await renderStory(comment, timeout, depth + 1, signal);
|
|
111
111
|
output += childOutput;
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -117,9 +117,9 @@ async function renderStory(item: HNItem, timeout: number, depth = 0): Promise<st
|
|
|
117
117
|
return output;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
async function renderListing(ids: number[], timeout: number, title: string): Promise<string> {
|
|
120
|
+
async function renderListing(ids: number[], timeout: number, title: string, signal?: AbortSignal): Promise<string> {
|
|
121
121
|
let output = `# ${title}\n\n`;
|
|
122
|
-
const stories = await fetchItems(ids, timeout, 20);
|
|
122
|
+
const stories = await fetchItems(ids, timeout, 20, signal);
|
|
123
123
|
|
|
124
124
|
for (let i = 0; i < stories.length; i++) {
|
|
125
125
|
const story = stories[i];
|
|
@@ -137,7 +137,7 @@ async function renderListing(ids: number[], timeout: number, title: string): Pro
|
|
|
137
137
|
return output;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
export const handleHackerNews: SpecialHandler = async (url, timeout) => {
|
|
140
|
+
export const handleHackerNews: SpecialHandler = async (url, timeout, signal) => {
|
|
141
141
|
const parsed = new URL(url);
|
|
142
142
|
if (!parsed.hostname.includes("news.ycombinator.com")) return null;
|
|
143
143
|
|
|
@@ -149,28 +149,28 @@ export const handleHackerNews: SpecialHandler = async (url, timeout) => {
|
|
|
149
149
|
const itemId = parsed.searchParams.get("id");
|
|
150
150
|
|
|
151
151
|
if (itemId) {
|
|
152
|
-
const item = await fetchItem(parseInt(itemId, 10), timeout);
|
|
152
|
+
const item = await fetchItem(parseInt(itemId, 10), timeout, signal);
|
|
153
153
|
if (!item) throw new Error(`Failed to fetch item ${itemId}`);
|
|
154
154
|
|
|
155
|
-
content = await renderStory(item, timeout);
|
|
155
|
+
content = await renderStory(item, timeout, 0, signal);
|
|
156
156
|
notes.push(`Fetched HN item ${itemId} with top-level comments (depth 2)`);
|
|
157
157
|
} else if (parsed.pathname === "/" || parsed.pathname === "/news") {
|
|
158
|
-
const { content: raw, ok } = await loadPage(`${API_BASE}/topstories.json`, { timeout });
|
|
158
|
+
const { content: raw, ok } = await loadPage(`${API_BASE}/topstories.json`, { timeout, signal });
|
|
159
159
|
if (!ok) throw new Error("Failed to fetch top stories");
|
|
160
160
|
const ids = JSON.parse(raw) as number[];
|
|
161
|
-
content = await renderListing(ids, timeout, "Hacker News - Top Stories");
|
|
161
|
+
content = await renderListing(ids, timeout, "Hacker News - Top Stories", signal);
|
|
162
162
|
notes.push("Fetched top 20 stories from HN front page");
|
|
163
163
|
} else if (parsed.pathname === "/newest") {
|
|
164
|
-
const { content: raw, ok } = await loadPage(`${API_BASE}/newstories.json`, { timeout });
|
|
164
|
+
const { content: raw, ok } = await loadPage(`${API_BASE}/newstories.json`, { timeout, signal });
|
|
165
165
|
if (!ok) throw new Error("Failed to fetch new stories");
|
|
166
166
|
const ids = JSON.parse(raw) as number[];
|
|
167
|
-
content = await renderListing(ids, timeout, "Hacker News - New Stories");
|
|
167
|
+
content = await renderListing(ids, timeout, "Hacker News - New Stories", signal);
|
|
168
168
|
notes.push("Fetched top 20 new stories");
|
|
169
169
|
} else if (parsed.pathname === "/best") {
|
|
170
|
-
const { content: raw, ok } = await loadPage(`${API_BASE}/beststories.json`, { timeout });
|
|
170
|
+
const { content: raw, ok } = await loadPage(`${API_BASE}/beststories.json`, { timeout, signal });
|
|
171
171
|
if (!ok) throw new Error("Failed to fetch best stories");
|
|
172
172
|
const ids = JSON.parse(raw) as number[];
|
|
173
|
-
content = await renderListing(ids, timeout, "Hacker News - Best Stories");
|
|
173
|
+
content = await renderListing(ids, timeout, "Hacker News - Best Stories", signal);
|
|
174
174
|
notes.push("Fetched top 20 best stories");
|
|
175
175
|
} else {
|
|
176
176
|
return null;
|
|
@@ -4,7 +4,7 @@ import { finalizeOutput, formatCount, loadPage } from "./types";
|
|
|
4
4
|
/**
|
|
5
5
|
* Handle Hex.pm (Elixir package registry) URLs via API
|
|
6
6
|
*/
|
|
7
|
-
export const handleHex: SpecialHandler = async (url, timeout) => {
|
|
7
|
+
export const handleHex: SpecialHandler = async (url, timeout, signal) => {
|
|
8
8
|
try {
|
|
9
9
|
const parsed = new URL(url);
|
|
10
10
|
if (parsed.hostname !== "hex.pm" && parsed.hostname !== "www.hex.pm") return null;
|
|
@@ -18,7 +18,7 @@ export const handleHex: SpecialHandler = async (url, timeout) => {
|
|
|
18
18
|
|
|
19
19
|
// Fetch from Hex.pm API
|
|
20
20
|
const apiUrl = `https://hex.pm/api/packages/${packageName}`;
|
|
21
|
-
const result = await loadPage(apiUrl, { timeout });
|
|
21
|
+
const result = await loadPage(apiUrl, { timeout, signal });
|
|
22
22
|
|
|
23
23
|
if (!result.ok) return null;
|
|
24
24
|
|
|
@@ -74,7 +74,7 @@ export const handleHex: SpecialHandler = async (url, timeout) => {
|
|
|
74
74
|
// Fetch releases if available
|
|
75
75
|
if (data.releases?.length) {
|
|
76
76
|
const releasesUrl = `https://hex.pm/api/packages/${packageName}/releases/${version}`;
|
|
77
|
-
const releaseResult = await loadPage(releasesUrl, { timeout: Math.min(timeout, 5) });
|
|
77
|
+
const releaseResult = await loadPage(releasesUrl, { timeout: Math.min(timeout, 5), signal });
|
|
78
78
|
|
|
79
79
|
if (releaseResult.ok) {
|
|
80
80
|
try {
|
|
@@ -106,7 +106,7 @@ function parseHuggingFaceUrl(url: string): {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
export const handleHuggingFace: SpecialHandler = async (url: string, timeout: number) => {
|
|
109
|
+
export const handleHuggingFace: SpecialHandler = async (url: string, timeout: number, signal?: AbortSignal) => {
|
|
110
110
|
const parsed = parseHuggingFaceUrl(url);
|
|
111
111
|
if (!parsed) return null;
|
|
112
112
|
|
|
@@ -120,8 +120,8 @@ export const handleHuggingFace: SpecialHandler = async (url: string, timeout: nu
|
|
|
120
120
|
const readmeUrl = `https://huggingface.co/${parsed.id}/raw/main/README.md`;
|
|
121
121
|
|
|
122
122
|
const [apiResult, readmeResult] = await Promise.all([
|
|
123
|
-
loadPage(apiUrl, { timeout }),
|
|
124
|
-
loadPage(readmeUrl, { timeout: Math.min(timeout, 5) }),
|
|
123
|
+
loadPage(apiUrl, { timeout, signal }),
|
|
124
|
+
loadPage(readmeUrl, { timeout: Math.min(timeout, 5), signal }),
|
|
125
125
|
]);
|
|
126
126
|
|
|
127
127
|
if (!apiResult.ok) return null;
|
|
@@ -186,8 +186,8 @@ export const handleHuggingFace: SpecialHandler = async (url: string, timeout: nu
|
|
|
186
186
|
const readmeUrl = `https://huggingface.co/datasets/${parsed.id}/raw/main/README.md`;
|
|
187
187
|
|
|
188
188
|
const [apiResult, readmeResult] = await Promise.all([
|
|
189
|
-
loadPage(apiUrl, { timeout }),
|
|
190
|
-
loadPage(readmeUrl, { timeout: Math.min(timeout, 5) }),
|
|
189
|
+
loadPage(apiUrl, { timeout, signal }),
|
|
190
|
+
loadPage(readmeUrl, { timeout: Math.min(timeout, 5), signal }),
|
|
191
191
|
]);
|
|
192
192
|
|
|
193
193
|
if (!apiResult.ok) return null;
|
|
@@ -251,8 +251,8 @@ export const handleHuggingFace: SpecialHandler = async (url: string, timeout: nu
|
|
|
251
251
|
const readmeUrl = `https://huggingface.co/spaces/${parsed.id}/raw/main/README.md`;
|
|
252
252
|
|
|
253
253
|
const [apiResult, readmeResult] = await Promise.all([
|
|
254
|
-
loadPage(apiUrl, { timeout }),
|
|
255
|
-
loadPage(readmeUrl, { timeout: Math.min(timeout, 5) }),
|
|
254
|
+
loadPage(apiUrl, { timeout, signal }),
|
|
255
|
+
loadPage(readmeUrl, { timeout: Math.min(timeout, 5), signal }),
|
|
256
256
|
]);
|
|
257
257
|
|
|
258
258
|
if (!apiResult.ok) return null;
|
|
@@ -303,7 +303,7 @@ export const handleHuggingFace: SpecialHandler = async (url: string, timeout: nu
|
|
|
303
303
|
case "model_or_user": {
|
|
304
304
|
// Try model API first
|
|
305
305
|
const modelApiUrl = `https://huggingface.co/api/models/${parsed.id}`;
|
|
306
|
-
const modelResult = await loadPage(modelApiUrl, { timeout });
|
|
306
|
+
const modelResult = await loadPage(modelApiUrl, { timeout, signal });
|
|
307
307
|
|
|
308
308
|
if (modelResult.ok) {
|
|
309
309
|
let model: HfModelData | null = null;
|
|
@@ -314,7 +314,7 @@ export const handleHuggingFace: SpecialHandler = async (url: string, timeout: nu
|
|
|
314
314
|
}
|
|
315
315
|
if (model) {
|
|
316
316
|
const readmeUrl = `https://huggingface.co/${parsed.id}/raw/main/README.md`;
|
|
317
|
-
const readmeResult = await loadPage(readmeUrl, { timeout: Math.min(timeout, 5) });
|
|
317
|
+
const readmeResult = await loadPage(readmeUrl, { timeout: Math.min(timeout, 5), signal });
|
|
318
318
|
|
|
319
319
|
let md = `# ${model.modelId}\n\n`;
|
|
320
320
|
if (model.pipeline_tag) md += `**Task:** ${model.pipeline_tag}\n`;
|
|
@@ -343,7 +343,7 @@ export const handleHuggingFace: SpecialHandler = async (url: string, timeout: nu
|
|
|
343
343
|
|
|
344
344
|
// Fall back to user API
|
|
345
345
|
const userApiUrl = `https://huggingface.co/api/users/${parsed.id}`;
|
|
346
|
-
const userResult = await loadPage(userApiUrl, { timeout });
|
|
346
|
+
const userResult = await loadPage(userApiUrl, { timeout, signal });
|
|
347
347
|
if (!userResult.ok) return null;
|
|
348
348
|
|
|
349
349
|
let user: HfUserData;
|
|
@@ -6,7 +6,11 @@ import { convertWithMarkitdown, fetchBinary } from "./utils";
|
|
|
6
6
|
/**
|
|
7
7
|
* Handle IACR ePrint Archive URLs
|
|
8
8
|
*/
|
|
9
|
-
export const handleIacr: SpecialHandler = async (
|
|
9
|
+
export const handleIacr: SpecialHandler = async (
|
|
10
|
+
url: string,
|
|
11
|
+
timeout: number,
|
|
12
|
+
signal?: AbortSignal,
|
|
13
|
+
): Promise<RenderResult | null> => {
|
|
10
14
|
try {
|
|
11
15
|
const parsed = new URL(url);
|
|
12
16
|
if (parsed.hostname !== "eprint.iacr.org") return null;
|
|
@@ -22,7 +26,7 @@ export const handleIacr: SpecialHandler = async (url: string, timeout: number):
|
|
|
22
26
|
|
|
23
27
|
// Fetch the HTML page for metadata
|
|
24
28
|
const pageUrl = `https://eprint.iacr.org/${paperId}`;
|
|
25
|
-
const result = await loadPage(pageUrl, { timeout });
|
|
29
|
+
const result = await loadPage(pageUrl, { timeout, signal });
|
|
26
30
|
|
|
27
31
|
if (!result.ok) return null;
|
|
28
32
|
|
|
@@ -55,9 +59,9 @@ export const handleIacr: SpecialHandler = async (url: string, timeout: number):
|
|
|
55
59
|
if (parsed.pathname.endsWith(".pdf")) {
|
|
56
60
|
const pdfUrl = `https://eprint.iacr.org/${paperId}.pdf`;
|
|
57
61
|
notes.push("Fetching PDF for full content...");
|
|
58
|
-
const pdfResult = await fetchBinary(pdfUrl, timeout);
|
|
62
|
+
const pdfResult = await fetchBinary(pdfUrl, timeout, signal);
|
|
59
63
|
if (pdfResult.ok) {
|
|
60
|
-
const converted = await convertWithMarkitdown(pdfResult.buffer, ".pdf", timeout);
|
|
64
|
+
const converted = await convertWithMarkitdown(pdfResult.buffer, ".pdf", timeout, signal);
|
|
61
65
|
if (converted.ok && converted.content.length > 500) {
|
|
62
66
|
md += `---\n\n## Full Paper\n\n${converted.content}\n`;
|
|
63
67
|
notes.push("PDF converted via markitdown");
|