@oh-my-pi/pi-coding-agent 12.18.3 → 12.19.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 +47 -0
- package/package.json +7 -7
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +341 -0
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +3 -17
- package/src/cli/stats-cli.ts +3 -22
- package/src/cli/web-search-cli.ts +8 -16
- package/src/commit/agentic/agent.ts +6 -9
- package/src/commit/agentic/index.ts +44 -50
- package/src/commit/agentic/state.ts +0 -9
- package/src/commit/agentic/tools/propose-commit.ts +1 -30
- package/src/commit/agentic/tools/schemas.ts +31 -0
- package/src/commit/agentic/tools/split-commit.ts +1 -30
- package/src/commit/agentic/validation.ts +1 -18
- package/src/commit/analysis/conventional.ts +3 -50
- package/src/commit/analysis/summary.ts +2 -13
- package/src/commit/changelog/detect.ts +4 -1
- package/src/commit/changelog/generate.ts +2 -25
- package/src/commit/changelog/index.ts +1 -2
- package/src/commit/cli.ts +4 -12
- package/src/commit/map-reduce/reduce-phase.ts +2 -43
- package/src/commit/pipeline.ts +7 -15
- package/src/commit/utils.ts +44 -0
- package/src/config/prompt-templates.ts +1 -81
- package/src/config/settings-schema.ts +20 -1
- package/src/config.ts +2 -3
- package/src/debug/index.ts +1 -6
- package/src/debug/system-info.ts +2 -6
- package/src/discovery/builtin.ts +5 -9
- package/src/discovery/helpers.ts +0 -26
- package/src/discovery/ssh.ts +1 -8
- package/src/exa/company.ts +8 -39
- package/src/exa/factory.ts +64 -0
- package/src/exa/index.ts +0 -16
- package/src/exa/linkedin.ts +8 -39
- package/src/exa/mcp-client.ts +0 -64
- package/src/exa/researcher.ts +17 -59
- package/src/exa/search.ts +30 -154
- package/src/extensibility/custom-tools/loader.ts +3 -41
- package/src/extensibility/extensions/loader.ts +2 -9
- package/src/extensibility/hooks/loader.ts +3 -20
- package/src/extensibility/hooks/runner.ts +3 -19
- package/src/extensibility/plugins/installer.ts +2 -1
- package/src/extensibility/plugins/loader.ts +29 -117
- package/src/extensibility/skills.ts +2 -89
- package/src/extensibility/slash-commands.ts +1 -63
- package/src/extensibility/utils.ts +38 -0
- package/src/index.ts +9 -25
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/jobs-protocol.ts +118 -0
- package/src/ipy/kernel.ts +2 -0
- package/src/lsp/config.ts +1 -5
- package/src/lsp/lspmux.ts +0 -17
- package/src/lsp/utils.ts +2 -24
- package/src/main.ts +16 -24
- package/src/mcp/client.ts +1 -46
- package/src/mcp/render.ts +8 -1
- package/src/mcp/tool-cache.ts +1 -5
- package/src/mcp/transports/http.ts +2 -7
- package/src/mcp/transports/stdio.ts +2 -7
- package/src/modes/components/bash-execution.ts +2 -16
- package/src/modes/components/extensions/inspector-panel.ts +8 -18
- package/src/modes/components/footer.ts +10 -50
- package/src/modes/components/model-selector.ts +2 -21
- package/src/modes/components/python-execution.ts +2 -16
- package/src/modes/components/settings-selector.ts +1 -10
- package/src/modes/components/status-line/segments.ts +8 -25
- package/src/modes/components/status-line.ts +14 -31
- package/src/modes/components/tool-execution.ts +8 -2
- package/src/modes/controllers/command-controller.ts +71 -30
- package/src/modes/controllers/event-controller.ts +34 -4
- package/src/modes/controllers/mcp-command-controller.ts +3 -34
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/controllers/ssh-command-controller.ts +3 -34
- package/src/modes/interactive-mode.ts +6 -2
- package/src/modes/rpc/rpc-client.ts +1 -5
- package/src/modes/shared.ts +73 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +26 -2
- package/src/patch/index.ts +4 -4
- package/src/patch/normalize.ts +22 -65
- package/src/patch/shared.ts +16 -16
- package/src/prompts/system/custom-system-prompt.md +0 -10
- package/src/prompts/system/system-prompt.md +69 -89
- package/src/prompts/tools/async-result.md +5 -0
- package/src/prompts/tools/bash.md +5 -0
- package/src/prompts/tools/cancel-job.md +7 -0
- package/src/prompts/tools/poll-jobs.md +7 -0
- package/src/prompts/tools/task.md +4 -0
- package/src/sdk.ts +70 -6
- package/src/session/agent-session.ts +40 -6
- package/src/session/agent-storage.ts +69 -278
- package/src/session/auth-storage.ts +14 -1430
- package/src/session/session-manager.ts +69 -5
- package/src/session/session-storage.ts +1 -5
- package/src/session/streaming-output.ts +637 -76
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/ssh/connection-manager.ts +4 -12
- package/src/ssh/sshfs-mount.ts +3 -7
- package/src/ssh/utils.ts +8 -0
- package/src/system-prompt.ts +24 -90
- package/src/task/executor.ts +11 -1
- package/src/task/index.ts +258 -13
- package/src/task/parallel.ts +32 -0
- package/src/task/render.ts +15 -7
- package/src/task/types.ts +5 -0
- package/src/tools/ask.ts +4 -7
- package/src/tools/bash-interactive.ts +4 -5
- package/src/tools/bash.ts +125 -41
- package/src/tools/cancel-job.ts +93 -0
- package/src/tools/fetch.ts +7 -27
- package/src/tools/find.ts +3 -3
- package/src/tools/gemini-image.ts +15 -14
- package/src/tools/grep.ts +3 -3
- package/src/tools/index.ts +13 -29
- package/src/tools/json-tree.ts +12 -1
- package/src/tools/jtd-to-json-schema.ts +10 -74
- package/src/tools/jtd-to-typescript.ts +10 -72
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/notebook.ts +4 -9
- package/src/tools/output-meta.ts +52 -26
- package/src/tools/path-utils.ts +13 -7
- package/src/tools/poll-jobs.ts +178 -0
- package/src/tools/python.ts +32 -35
- package/src/tools/read.ts +61 -82
- package/src/tools/render-utils.ts +8 -159
- package/src/tools/ssh.ts +7 -20
- package/src/tools/submit-result.ts +1 -1
- package/src/tools/tool-errors.ts +0 -30
- package/src/tools/tool-result.ts +1 -2
- package/src/tools/write.ts +8 -10
- package/src/tui/code-cell.ts +8 -3
- package/src/tui/status-line.ts +4 -4
- package/src/tui/types.ts +0 -1
- package/src/tui/utils.ts +1 -14
- package/src/utils/command-args.ts +76 -0
- package/src/utils/file-mentions.ts +15 -19
- package/src/utils/frontmatter.ts +5 -10
- package/src/utils/shell-snapshot.ts +0 -11
- package/src/utils/title-generator.ts +0 -12
- package/src/web/scrapers/artifacthub.ts +7 -16
- package/src/web/scrapers/arxiv.ts +3 -8
- package/src/web/scrapers/aur.ts +8 -22
- package/src/web/scrapers/biorxiv.ts +5 -14
- package/src/web/scrapers/bluesky.ts +13 -36
- package/src/web/scrapers/brew.ts +5 -10
- package/src/web/scrapers/cheatsh.ts +2 -12
- package/src/web/scrapers/chocolatey.ts +63 -26
- package/src/web/scrapers/choosealicense.ts +3 -18
- package/src/web/scrapers/cisa-kev.ts +4 -18
- package/src/web/scrapers/clojars.ts +6 -33
- package/src/web/scrapers/coingecko.ts +25 -33
- package/src/web/scrapers/crates-io.ts +7 -26
- package/src/web/scrapers/crossref.ts +4 -18
- package/src/web/scrapers/devto.ts +11 -41
- package/src/web/scrapers/discogs.ts +7 -10
- package/src/web/scrapers/discourse.ts +6 -31
- package/src/web/scrapers/dockerhub.ts +12 -35
- package/src/web/scrapers/fdroid.ts +8 -33
- package/src/web/scrapers/firefox-addons.ts +10 -34
- package/src/web/scrapers/flathub.ts +7 -24
- package/src/web/scrapers/github-gist.ts +2 -12
- package/src/web/scrapers/github.ts +9 -47
- package/src/web/scrapers/gitlab.ts +130 -185
- package/src/web/scrapers/go-pkg.ts +12 -22
- package/src/web/scrapers/hackage.ts +88 -43
- package/src/web/scrapers/hackernews.ts +25 -45
- package/src/web/scrapers/hex.ts +19 -36
- package/src/web/scrapers/huggingface.ts +26 -91
- package/src/web/scrapers/iacr.ts +3 -8
- package/src/web/scrapers/jetbrains-marketplace.ts +9 -20
- package/src/web/scrapers/lemmy.ts +5 -23
- package/src/web/scrapers/lobsters.ts +16 -28
- package/src/web/scrapers/mastodon.ts +24 -43
- package/src/web/scrapers/maven.ts +6 -21
- package/src/web/scrapers/mdn.ts +7 -11
- package/src/web/scrapers/metacpan.ts +9 -41
- package/src/web/scrapers/musicbrainz.ts +4 -28
- package/src/web/scrapers/npm.ts +8 -25
- package/src/web/scrapers/nuget.ts +14 -37
- package/src/web/scrapers/nvd.ts +6 -28
- package/src/web/scrapers/ollama.ts +7 -34
- package/src/web/scrapers/open-vsx.ts +5 -19
- package/src/web/scrapers/opencorporates.ts +30 -14
- package/src/web/scrapers/openlibrary.ts +49 -33
- package/src/web/scrapers/orcid.ts +4 -18
- package/src/web/scrapers/osv.ts +7 -24
- package/src/web/scrapers/packagist.ts +9 -24
- package/src/web/scrapers/pub-dev.ts +7 -50
- package/src/web/scrapers/pubmed.ts +54 -21
- package/src/web/scrapers/pypi.ts +8 -26
- package/src/web/scrapers/rawg.ts +11 -19
- package/src/web/scrapers/readthedocs.ts +4 -9
- package/src/web/scrapers/reddit.ts +5 -15
- package/src/web/scrapers/repology.ts +8 -20
- package/src/web/scrapers/rfc.ts +5 -14
- package/src/web/scrapers/rubygems.ts +6 -21
- package/src/web/scrapers/searchcode.ts +8 -36
- package/src/web/scrapers/sec-edgar.ts +4 -18
- package/src/web/scrapers/semantic-scholar.ts +15 -35
- package/src/web/scrapers/snapcraft.ts +5 -19
- package/src/web/scrapers/sourcegraph.ts +5 -43
- package/src/web/scrapers/spdx.ts +4 -18
- package/src/web/scrapers/spotify.ts +4 -23
- package/src/web/scrapers/stackoverflow.ts +8 -13
- package/src/web/scrapers/terraform.ts +9 -37
- package/src/web/scrapers/tldr.ts +3 -7
- package/src/web/scrapers/twitter.ts +3 -7
- package/src/web/scrapers/types.ts +105 -27
- package/src/web/scrapers/utils.ts +97 -103
- package/src/web/scrapers/vimeo.ts +7 -27
- package/src/web/scrapers/vscode-marketplace.ts +8 -17
- package/src/web/scrapers/w3c.ts +6 -14
- package/src/web/scrapers/wikidata.ts +5 -19
- package/src/web/scrapers/wikipedia.ts +2 -12
- package/src/web/scrapers/youtube.ts +5 -34
- package/src/web/search/index.ts +0 -9
- package/src/web/search/providers/anthropic.ts +3 -2
- package/src/web/search/providers/brave.ts +3 -18
- package/src/web/search/providers/exa.ts +1 -12
- package/src/web/search/providers/kimi.ts +5 -44
- package/src/web/search/providers/perplexity.ts +1 -12
- package/src/web/search/providers/synthetic.ts +3 -26
- package/src/web/search/providers/utils.ts +36 -0
- package/src/web/search/providers/zai.ts +9 -50
- package/src/web/search/types.ts +0 -28
- package/src/web/search/utils.ts +17 -0
- package/src/tools/output-utils.ts +0 -63
- package/src/tools/truncate.ts +0 -385
- package/src/web/search/auth.ts +0 -178
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
buildResult,
|
|
3
|
+
formatIsoDate,
|
|
4
|
+
formatNumber,
|
|
4
5
|
htmlToBasicMarkdown,
|
|
5
6
|
loadPage,
|
|
6
7
|
type RenderResult,
|
|
7
8
|
type SpecialHandler,
|
|
9
|
+
tryParseJson,
|
|
8
10
|
} from "./types";
|
|
9
11
|
|
|
10
12
|
interface GitLabUrl {
|
|
@@ -93,12 +95,9 @@ async function getProjectId(gl: GitLabUrl, timeout: number, signal?: AbortSignal
|
|
|
93
95
|
const result = await loadPage(apiUrl, { timeout, signal });
|
|
94
96
|
if (!result.ok) return null;
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
} catch {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
98
|
+
const data = tryParseJson<{ id: number }>(result.content);
|
|
99
|
+
if (!data) return null;
|
|
100
|
+
return data.id;
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
/**
|
|
@@ -115,42 +114,39 @@ async function renderGitLabRepo(
|
|
|
115
114
|
const result = await loadPage(apiUrl, { timeout, signal });
|
|
116
115
|
if (!result.ok) return { content: "", ok: false };
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
117
|
+
const repo = tryParseJson<{
|
|
118
|
+
name: string;
|
|
119
|
+
description?: string;
|
|
120
|
+
star_count: number;
|
|
121
|
+
forks_count: number;
|
|
122
|
+
open_issues_count: number;
|
|
123
|
+
default_branch: string;
|
|
124
|
+
visibility: string;
|
|
125
|
+
created_at: string;
|
|
126
|
+
last_activity_at: string;
|
|
127
|
+
topics?: string[];
|
|
128
|
+
readme_url?: string;
|
|
129
|
+
}>(result.content);
|
|
130
|
+
if (!repo) return { content: "", ok: false };
|
|
131
|
+
|
|
132
|
+
let md = `# ${repo.name}\n\n`;
|
|
133
|
+
if (repo.description) md += `${repo.description}\n\n`;
|
|
134
|
+
md += `**Stars:** ${formatNumber(repo.star_count)} · **Forks:** ${formatNumber(repo.forks_count)} · **Issues:** ${formatNumber(repo.open_issues_count)}\n`;
|
|
135
|
+
md += `**Visibility:** ${repo.visibility} · **Default Branch:** ${repo.default_branch}\n`;
|
|
136
|
+
if (repo.topics && repo.topics.length > 0) {
|
|
137
|
+
md += `**Topics:** ${repo.topics.join(", ")}\n`;
|
|
138
|
+
}
|
|
139
|
+
md += `**Created:** ${formatIsoDate(repo.created_at)} · **Last Activity:** ${formatIsoDate(repo.last_activity_at)}\n\n`;
|
|
141
140
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
141
|
+
// Try to fetch README
|
|
142
|
+
if (repo.readme_url) {
|
|
143
|
+
const readmeResult = await loadPage(repo.readme_url, { timeout, signal });
|
|
144
|
+
if (readmeResult.ok && readmeResult.content.trim().length > 0) {
|
|
145
|
+
md += `---\n\n## README\n\n${readmeResult.content}\n`;
|
|
148
146
|
}
|
|
149
|
-
|
|
150
|
-
return { content: md, ok: true };
|
|
151
|
-
} catch {
|
|
152
|
-
return { content: "", ok: false };
|
|
153
147
|
}
|
|
148
|
+
|
|
149
|
+
return { content: md, ok: true };
|
|
154
150
|
}
|
|
155
151
|
|
|
156
152
|
/**
|
|
@@ -185,40 +181,39 @@ async function renderGitLabTree(
|
|
|
185
181
|
const result = await loadPage(apiUrl, { timeout, signal });
|
|
186
182
|
if (!result.ok) return { content: "", ok: false };
|
|
187
183
|
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
const tree = tryParseJson<
|
|
185
|
+
Array<{
|
|
190
186
|
name: string;
|
|
191
187
|
type: "tree" | "blob";
|
|
192
188
|
path: string;
|
|
193
189
|
mode: string;
|
|
194
|
-
}
|
|
190
|
+
}>
|
|
191
|
+
>(result.content);
|
|
192
|
+
if (!tree) return { content: "", ok: false };
|
|
195
193
|
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
let md = `# Directory: ${gl.path || "/"}\n\n`;
|
|
195
|
+
md += `**Ref:** ${gl.ref}\n\n`;
|
|
198
196
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
// Separate directories and files
|
|
198
|
+
const dirs = tree.filter(item => item.type === "tree");
|
|
199
|
+
const files = tree.filter(item => item.type === "blob");
|
|
202
200
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
md += `\n`;
|
|
201
|
+
if (dirs.length > 0) {
|
|
202
|
+
md += `## Directories (${dirs.length})\n\n`;
|
|
203
|
+
for (const dir of dirs) {
|
|
204
|
+
md += `- 📁 ${dir.name}/\n`;
|
|
209
205
|
}
|
|
206
|
+
md += `\n`;
|
|
207
|
+
}
|
|
210
208
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
209
|
+
if (files.length > 0) {
|
|
210
|
+
md += `## Files (${files.length})\n\n`;
|
|
211
|
+
for (const file of files) {
|
|
212
|
+
md += `- 📄 ${file.name}\n`;
|
|
216
213
|
}
|
|
217
|
-
|
|
218
|
-
return { content: md, ok: true };
|
|
219
|
-
} catch {
|
|
220
|
-
return { content: "", ok: false };
|
|
221
214
|
}
|
|
215
|
+
|
|
216
|
+
return { content: md, ok: true };
|
|
222
217
|
}
|
|
223
218
|
|
|
224
219
|
/**
|
|
@@ -235,41 +230,38 @@ async function renderGitLabIssue(
|
|
|
235
230
|
const result = await loadPage(apiUrl, { timeout, signal });
|
|
236
231
|
if (!result.ok) return { content: "", ok: false };
|
|
237
232
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
233
|
+
const issue = tryParseJson<{
|
|
234
|
+
title: string;
|
|
235
|
+
description?: string;
|
|
236
|
+
state: string;
|
|
237
|
+
author: { name: string; username: string };
|
|
238
|
+
created_at: string;
|
|
239
|
+
updated_at: string;
|
|
240
|
+
labels: string[];
|
|
241
|
+
upvotes: number;
|
|
242
|
+
downvotes: number;
|
|
243
|
+
user_notes_count: number;
|
|
244
|
+
assignees?: Array<{ name: string }>;
|
|
245
|
+
}>(result.content);
|
|
246
|
+
if (!issue) return { content: "", ok: false };
|
|
247
|
+
|
|
248
|
+
let md = `# Issue #${gl.id}: ${issue.title}\n\n`;
|
|
249
|
+
md += `**State:** ${issue.state.toUpperCase()} · **Author:** ${issue.author.name} (@${issue.author.username})\n`;
|
|
250
|
+
md += `**Created:** ${formatIsoDate(issue.created_at)} · **Updated:** ${formatIsoDate(issue.updated_at)}\n`;
|
|
251
|
+
md += `**Upvotes:** ${issue.upvotes} · **Downvotes:** ${issue.downvotes} · **Comments:** ${issue.user_notes_count}\n`;
|
|
252
|
+
|
|
253
|
+
if (issue.labels.length > 0) {
|
|
254
|
+
md += `**Labels:** ${issue.labels.join(", ")}\n`;
|
|
255
|
+
}
|
|
261
256
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
257
|
+
if (issue.assignees && issue.assignees.length > 0) {
|
|
258
|
+
md += `**Assignees:** ${issue.assignees.map(a => a.name).join(", ")}\n`;
|
|
259
|
+
}
|
|
265
260
|
|
|
266
|
-
|
|
267
|
-
|
|
261
|
+
md += `\n---\n\n## Description\n\n`;
|
|
262
|
+
md += issue.description ? htmlToBasicMarkdown(issue.description) : "*No description*";
|
|
268
263
|
|
|
269
|
-
|
|
270
|
-
} catch {
|
|
271
|
-
return { content: "", ok: false };
|
|
272
|
-
}
|
|
264
|
+
return { content: md, ok: true };
|
|
273
265
|
}
|
|
274
266
|
|
|
275
267
|
/**
|
|
@@ -286,47 +278,44 @@ async function renderGitLabMR(
|
|
|
286
278
|
const result = await loadPage(apiUrl, { timeout, signal });
|
|
287
279
|
if (!result.ok) return { content: "", ok: false };
|
|
288
280
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
281
|
+
const mr = tryParseJson<{
|
|
282
|
+
title: string;
|
|
283
|
+
description?: string;
|
|
284
|
+
state: string;
|
|
285
|
+
author: { name: string; username: string };
|
|
286
|
+
created_at: string;
|
|
287
|
+
updated_at: string;
|
|
288
|
+
source_branch: string;
|
|
289
|
+
target_branch: string;
|
|
290
|
+
labels: string[];
|
|
291
|
+
upvotes: number;
|
|
292
|
+
downvotes: number;
|
|
293
|
+
user_notes_count: number;
|
|
294
|
+
assignees?: Array<{ name: string }>;
|
|
295
|
+
draft: boolean;
|
|
296
|
+
merge_status: string;
|
|
297
|
+
}>(result.content);
|
|
298
|
+
if (!mr) return { content: "", ok: false };
|
|
299
|
+
|
|
300
|
+
let md = `# MR !${gl.id}: ${mr.title}\n\n`;
|
|
301
|
+
if (mr.draft) md += `**[DRAFT]** `;
|
|
302
|
+
md += `**State:** ${mr.state.toUpperCase()} · **Author:** ${mr.author.name} (@${mr.author.username})\n`;
|
|
303
|
+
md += `**Branch:** ${mr.source_branch} → ${mr.target_branch}\n`;
|
|
304
|
+
md += `**Created:** ${formatIsoDate(mr.created_at)} · **Updated:** ${formatIsoDate(mr.updated_at)}\n`;
|
|
305
|
+
md += `**Merge Status:** ${mr.merge_status} · **Upvotes:** ${mr.upvotes} · **Downvotes:** ${mr.downvotes} · **Comments:** ${mr.user_notes_count}\n`;
|
|
306
|
+
|
|
307
|
+
if (mr.labels.length > 0) {
|
|
308
|
+
md += `**Labels:** ${mr.labels.join(", ")}\n`;
|
|
309
|
+
}
|
|
318
310
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
311
|
+
if (mr.assignees && mr.assignees.length > 0) {
|
|
312
|
+
md += `**Assignees:** ${mr.assignees.map(a => a.name).join(", ")}\n`;
|
|
313
|
+
}
|
|
322
314
|
|
|
323
|
-
|
|
324
|
-
|
|
315
|
+
md += `\n---\n\n## Description\n\n`;
|
|
316
|
+
md += mr.description ? htmlToBasicMarkdown(mr.description) : "*No description*";
|
|
325
317
|
|
|
326
|
-
|
|
327
|
-
} catch {
|
|
328
|
-
return { content: "", ok: false };
|
|
329
|
-
}
|
|
318
|
+
return { content: md, ok: true };
|
|
330
319
|
}
|
|
331
320
|
|
|
332
321
|
/**
|
|
@@ -351,17 +340,13 @@ export const handleGitLab: SpecialHandler = async (
|
|
|
351
340
|
notes.push(`Fetched raw file via GitLab API`);
|
|
352
341
|
const result = await renderGitLabFile(gl, projectId, timeout, signal);
|
|
353
342
|
if (result.ok) {
|
|
354
|
-
|
|
355
|
-
return {
|
|
343
|
+
return buildResult(result.content, {
|
|
356
344
|
url,
|
|
357
|
-
finalUrl: url,
|
|
358
|
-
contentType: "text/plain",
|
|
359
345
|
method: "gitlab-raw",
|
|
360
|
-
content: output.content,
|
|
361
346
|
fetchedAt,
|
|
362
|
-
truncated: output.truncated,
|
|
363
347
|
notes,
|
|
364
|
-
|
|
348
|
+
contentType: "text/plain",
|
|
349
|
+
});
|
|
365
350
|
}
|
|
366
351
|
break;
|
|
367
352
|
}
|
|
@@ -373,17 +358,7 @@ export const handleGitLab: SpecialHandler = async (
|
|
|
373
358
|
notes.push(`Fetched directory tree via GitLab API`);
|
|
374
359
|
const result = await renderGitLabTree(gl, projectId, timeout, signal);
|
|
375
360
|
if (result.ok) {
|
|
376
|
-
|
|
377
|
-
return {
|
|
378
|
-
url,
|
|
379
|
-
finalUrl: url,
|
|
380
|
-
contentType: "text/markdown",
|
|
381
|
-
method: "gitlab-tree",
|
|
382
|
-
content: output.content,
|
|
383
|
-
fetchedAt,
|
|
384
|
-
truncated: output.truncated,
|
|
385
|
-
notes,
|
|
386
|
-
};
|
|
361
|
+
return buildResult(result.content, { url, method: "gitlab-tree", fetchedAt, notes });
|
|
387
362
|
}
|
|
388
363
|
break;
|
|
389
364
|
}
|
|
@@ -395,17 +370,7 @@ export const handleGitLab: SpecialHandler = async (
|
|
|
395
370
|
notes.push(`Fetched issue via GitLab API`);
|
|
396
371
|
const result = await renderGitLabIssue(gl, projectId, timeout, signal);
|
|
397
372
|
if (result.ok) {
|
|
398
|
-
|
|
399
|
-
return {
|
|
400
|
-
url,
|
|
401
|
-
finalUrl: url,
|
|
402
|
-
contentType: "text/markdown",
|
|
403
|
-
method: "gitlab-issue",
|
|
404
|
-
content: output.content,
|
|
405
|
-
fetchedAt,
|
|
406
|
-
truncated: output.truncated,
|
|
407
|
-
notes,
|
|
408
|
-
};
|
|
373
|
+
return buildResult(result.content, { url, method: "gitlab-issue", fetchedAt, notes });
|
|
409
374
|
}
|
|
410
375
|
break;
|
|
411
376
|
}
|
|
@@ -417,17 +382,7 @@ export const handleGitLab: SpecialHandler = async (
|
|
|
417
382
|
notes.push(`Fetched merge request via GitLab API`);
|
|
418
383
|
const result = await renderGitLabMR(gl, projectId, timeout, signal);
|
|
419
384
|
if (result.ok) {
|
|
420
|
-
|
|
421
|
-
return {
|
|
422
|
-
url,
|
|
423
|
-
finalUrl: url,
|
|
424
|
-
contentType: "text/markdown",
|
|
425
|
-
method: "gitlab-mr",
|
|
426
|
-
content: output.content,
|
|
427
|
-
fetchedAt,
|
|
428
|
-
truncated: output.truncated,
|
|
429
|
-
notes,
|
|
430
|
-
};
|
|
385
|
+
return buildResult(result.content, { url, method: "gitlab-mr", fetchedAt, notes });
|
|
431
386
|
}
|
|
432
387
|
break;
|
|
433
388
|
}
|
|
@@ -436,17 +391,7 @@ export const handleGitLab: SpecialHandler = async (
|
|
|
436
391
|
notes.push(`Fetched repository via GitLab API`);
|
|
437
392
|
const result = await renderGitLabRepo(gl, timeout, signal);
|
|
438
393
|
if (result.ok) {
|
|
439
|
-
|
|
440
|
-
return {
|
|
441
|
-
url,
|
|
442
|
-
finalUrl: url,
|
|
443
|
-
contentType: "text/markdown",
|
|
444
|
-
method: "gitlab-repo",
|
|
445
|
-
content: output.content,
|
|
446
|
-
fetchedAt,
|
|
447
|
-
truncated: output.truncated,
|
|
448
|
-
notes,
|
|
449
|
-
};
|
|
394
|
+
return buildResult(result.content, { url, method: "gitlab-repo", fetchedAt, notes });
|
|
450
395
|
}
|
|
451
396
|
break;
|
|
452
397
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parse as parseHtml } from "node-html-parser";
|
|
2
2
|
import type { RenderResult, SpecialHandler } from "./types";
|
|
3
|
-
import {
|
|
3
|
+
import { buildResult, htmlToBasicMarkdown, loadPage, tryParseJson } from "./types";
|
|
4
4
|
|
|
5
5
|
interface GoModuleInfo {
|
|
6
6
|
Version: string;
|
|
@@ -26,7 +26,6 @@ export const handleGoPkg: SpecialHandler = async (
|
|
|
26
26
|
|
|
27
27
|
let modulePath: string;
|
|
28
28
|
let version = "latest";
|
|
29
|
-
let _subpackage = "";
|
|
30
29
|
|
|
31
30
|
// Parse @version if present
|
|
32
31
|
const atIndex = pathname.indexOf("@");
|
|
@@ -38,9 +37,7 @@ export const handleGoPkg: SpecialHandler = async (
|
|
|
38
37
|
const slashIndex = afterAt.indexOf("/");
|
|
39
38
|
if (slashIndex !== -1) {
|
|
40
39
|
version = afterAt.slice(0, slashIndex);
|
|
41
|
-
const remainder = afterAt.slice(slashIndex + 1);
|
|
42
40
|
modulePath = beforeAt;
|
|
43
|
-
_subpackage = remainder;
|
|
44
41
|
} else {
|
|
45
42
|
version = afterAt;
|
|
46
43
|
modulePath = beforeAt;
|
|
@@ -65,8 +62,10 @@ export const handleGoPkg: SpecialHandler = async (
|
|
|
65
62
|
const proxyResult = await loadPage(proxyUrl, { timeout, signal });
|
|
66
63
|
|
|
67
64
|
if (proxyResult.ok) {
|
|
68
|
-
moduleInfo =
|
|
69
|
-
|
|
65
|
+
moduleInfo = tryParseJson<GoModuleInfo>(proxyResult.content);
|
|
66
|
+
if (moduleInfo) {
|
|
67
|
+
version = moduleInfo.Version;
|
|
68
|
+
}
|
|
70
69
|
}
|
|
71
70
|
} catch {
|
|
72
71
|
// If @latest fails, might be a subpackage - will extract from page
|
|
@@ -77,7 +76,7 @@ export const handleGoPkg: SpecialHandler = async (
|
|
|
77
76
|
const proxyResult = await loadPage(proxyUrl, { timeout, signal });
|
|
78
77
|
|
|
79
78
|
if (proxyResult.ok) {
|
|
80
|
-
moduleInfo =
|
|
79
|
+
moduleInfo = tryParseJson<GoModuleInfo>(proxyResult.content);
|
|
81
80
|
}
|
|
82
81
|
} catch {
|
|
83
82
|
// Proxy lookup failed, will rely on page data
|
|
@@ -87,25 +86,20 @@ export const handleGoPkg: SpecialHandler = async (
|
|
|
87
86
|
// Fetch the pkg.go.dev page
|
|
88
87
|
const pageResult = await loadPage(url, { timeout, signal });
|
|
89
88
|
if (!pageResult.ok) {
|
|
90
|
-
return {
|
|
89
|
+
return buildResult(`Failed to fetch pkg.go.dev page (status: ${pageResult.status ?? "unknown"})`, {
|
|
91
90
|
url,
|
|
92
91
|
finalUrl: pageResult.finalUrl,
|
|
93
|
-
contentType: "text/plain",
|
|
94
92
|
method: "go-pkg",
|
|
95
|
-
content: `Failed to fetch pkg.go.dev page (status: ${pageResult.status ?? "unknown"})`,
|
|
96
93
|
fetchedAt: new Date().toISOString(),
|
|
97
|
-
truncated: false,
|
|
98
94
|
notes: ["error"],
|
|
99
|
-
|
|
95
|
+
contentType: "text/plain",
|
|
96
|
+
});
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
const doc = parseHtml(pageResult.content);
|
|
103
100
|
|
|
104
|
-
// Extract module/package information
|
|
105
|
-
const breadcrumb = doc.querySelector(".go-Breadcrumb");
|
|
106
|
-
const _headerDiv = doc.querySelector(".go-Main-header");
|
|
107
|
-
|
|
108
101
|
// Extract actual module path from breadcrumb or header
|
|
102
|
+
const breadcrumb = doc.querySelector(".go-Breadcrumb");
|
|
109
103
|
if (breadcrumb) {
|
|
110
104
|
const moduleLink = breadcrumb.querySelector("a[href^='/']");
|
|
111
105
|
if (moduleLink) {
|
|
@@ -257,18 +251,14 @@ export const handleGoPkg: SpecialHandler = async (
|
|
|
257
251
|
}
|
|
258
252
|
|
|
259
253
|
const content = sections.join("\n");
|
|
260
|
-
const { content: finalContent, truncated } = finalizeOutput(content);
|
|
261
254
|
|
|
262
|
-
return {
|
|
255
|
+
return buildResult(content, {
|
|
263
256
|
url,
|
|
264
257
|
finalUrl: pageResult.finalUrl,
|
|
265
|
-
contentType: "text/markdown",
|
|
266
258
|
method: "go-pkg",
|
|
267
|
-
content: finalContent,
|
|
268
259
|
fetchedAt: new Date().toISOString(),
|
|
269
|
-
truncated,
|
|
270
260
|
notes,
|
|
271
|
-
};
|
|
261
|
+
});
|
|
272
262
|
} catch {
|
|
273
263
|
return null;
|
|
274
264
|
}
|
|
@@ -1,19 +1,71 @@
|
|
|
1
1
|
import type { RenderResult, SpecialHandler } from "./types";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
interface
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
import { buildResult, loadPage, tryParseJson } from "./types";
|
|
3
|
+
|
|
4
|
+
interface HackageVersionMap {
|
|
5
|
+
[version: string]: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ParsedCabal {
|
|
9
|
+
name?: string;
|
|
10
|
+
version?: string;
|
|
11
|
+
synopsis?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
license?: string;
|
|
14
|
+
author?: string;
|
|
15
|
+
maintainer?: string;
|
|
12
16
|
homepage?: string;
|
|
13
|
-
|
|
17
|
+
bugReports?: string;
|
|
14
18
|
category?: string;
|
|
15
19
|
stability?: string;
|
|
16
|
-
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function compareVersions(a: string, b: string): number {
|
|
23
|
+
const aParts = a.split(".").map(part => Number.parseInt(part, 10) || 0);
|
|
24
|
+
const bParts = b.split(".").map(part => Number.parseInt(part, 10) || 0);
|
|
25
|
+
const max = Math.max(aParts.length, bParts.length);
|
|
26
|
+
for (let i = 0; i < max; i++) {
|
|
27
|
+
const delta = (aParts[i] || 0) - (bParts[i] || 0);
|
|
28
|
+
if (delta !== 0) return delta;
|
|
29
|
+
}
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractCabalField(content: string, fieldName: string): string | undefined {
|
|
34
|
+
const pattern = new RegExp(`^${fieldName}:\\s*(.*)$`, "im");
|
|
35
|
+
const match = content.match(pattern);
|
|
36
|
+
if (!match) return undefined;
|
|
37
|
+
return match[1].trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function extractCabalDescription(content: string): string | undefined {
|
|
41
|
+
const lines = content.split("\n");
|
|
42
|
+
const start = lines.findIndex(line => line.toLowerCase().startsWith("description:"));
|
|
43
|
+
if (start < 0) return undefined;
|
|
44
|
+
const value = lines[start].replace(/^description:\s*/i, "").trim();
|
|
45
|
+
const chunks: string[] = [value];
|
|
46
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
47
|
+
const line = lines[i];
|
|
48
|
+
if (!line.startsWith(" ")) break;
|
|
49
|
+
chunks.push(line.trim());
|
|
50
|
+
}
|
|
51
|
+
const description = chunks.join("\n").trim();
|
|
52
|
+
return description || undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseCabal(content: string): ParsedCabal {
|
|
56
|
+
return {
|
|
57
|
+
name: extractCabalField(content, "name"),
|
|
58
|
+
version: extractCabalField(content, "version"),
|
|
59
|
+
synopsis: extractCabalField(content, "synopsis"),
|
|
60
|
+
description: extractCabalDescription(content),
|
|
61
|
+
license: extractCabalField(content, "license"),
|
|
62
|
+
author: extractCabalField(content, "author"),
|
|
63
|
+
maintainer: extractCabalField(content, "maintainer"),
|
|
64
|
+
homepage: extractCabalField(content, "homepage"),
|
|
65
|
+
bugReports: extractCabalField(content, "bug-reports"),
|
|
66
|
+
category: extractCabalField(content, "category"),
|
|
67
|
+
stability: extractCabalField(content, "stability"),
|
|
68
|
+
};
|
|
17
69
|
}
|
|
18
70
|
|
|
19
71
|
/**
|
|
@@ -35,27 +87,37 @@ export const handleHackage: SpecialHandler = async (
|
|
|
35
87
|
const packageId = decodeURIComponent(match[1]);
|
|
36
88
|
const fetchedAt = new Date().toISOString();
|
|
37
89
|
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
const
|
|
90
|
+
// Version endpoint returns a map of version -> status.
|
|
91
|
+
const versionUrl = `https://hackage.haskell.org/package/${encodeURIComponent(packageId)}.json`;
|
|
92
|
+
const versionResult = await loadPage(versionUrl, {
|
|
41
93
|
timeout,
|
|
42
94
|
headers: { Accept: "application/json" },
|
|
43
95
|
signal,
|
|
44
96
|
});
|
|
45
97
|
|
|
46
|
-
if (!
|
|
98
|
+
if (!versionResult.ok) return null;
|
|
47
99
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
100
|
+
const versionMap = tryParseJson<HackageVersionMap>(versionResult.content);
|
|
101
|
+
if (!versionMap) return null;
|
|
102
|
+
const latestVersion = Object.keys(versionMap).sort(compareVersions).at(-1);
|
|
103
|
+
if (!latestVersion) return null;
|
|
104
|
+
|
|
105
|
+
// Fetch the latest cabal file for package metadata.
|
|
106
|
+
const cabalUrl = `https://hackage.haskell.org/package/${encodeURIComponent(packageId)}-${latestVersion}/${encodeURIComponent(packageId)}.cabal`;
|
|
107
|
+
const cabalResult = await loadPage(cabalUrl, {
|
|
108
|
+
timeout,
|
|
109
|
+
headers: { Accept: "text/plain" },
|
|
110
|
+
signal,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!cabalResult.ok) return null;
|
|
114
|
+
|
|
115
|
+
const pkg = parseCabal(cabalResult.content);
|
|
54
116
|
|
|
55
|
-
let md = `# ${pkg.name}\n\n`;
|
|
117
|
+
let md = `# ${pkg.name || packageId}\n\n`;
|
|
56
118
|
if (pkg.synopsis) md += `${pkg.synopsis}\n\n`;
|
|
57
119
|
|
|
58
|
-
md += `**Version:** ${pkg.version}`;
|
|
120
|
+
md += `**Version:** ${pkg.version || latestVersion}`;
|
|
59
121
|
if (pkg.license) md += ` · **License:** ${pkg.license}`;
|
|
60
122
|
md += "\n";
|
|
61
123
|
|
|
@@ -64,30 +126,13 @@ export const handleHackage: SpecialHandler = async (
|
|
|
64
126
|
if (pkg.category) md += `**Category:** ${pkg.category}\n`;
|
|
65
127
|
if (pkg.stability) md += `**Stability:** ${pkg.stability}\n`;
|
|
66
128
|
if (pkg.homepage) md += `**Homepage:** ${pkg.homepage}\n`;
|
|
67
|
-
if (pkg
|
|
129
|
+
if (pkg.bugReports) md += `**Bug Reports:** ${pkg.bugReports}\n`;
|
|
68
130
|
|
|
69
131
|
if (pkg.description) {
|
|
70
132
|
md += `\n## Description\n\n${pkg.description}\n`;
|
|
71
133
|
}
|
|
72
134
|
|
|
73
|
-
|
|
74
|
-
md += `\n## Dependencies\n\n`;
|
|
75
|
-
for (const [dep, version] of Object.entries(pkg.dependencies)) {
|
|
76
|
-
md += `- ${dep}: ${version}\n`;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const output = finalizeOutput(md);
|
|
81
|
-
return {
|
|
82
|
-
url,
|
|
83
|
-
finalUrl: url,
|
|
84
|
-
contentType: "text/markdown",
|
|
85
|
-
method: "hackage",
|
|
86
|
-
content: output.content,
|
|
87
|
-
fetchedAt,
|
|
88
|
-
truncated: output.truncated,
|
|
89
|
-
notes: ["Fetched via Hackage API"],
|
|
90
|
-
};
|
|
135
|
+
return buildResult(md, { url, method: "hackage", fetchedAt, notes: ["Fetched via Hackage API"] });
|
|
91
136
|
} catch {}
|
|
92
137
|
|
|
93
138
|
return null;
|