@bdsqqq/pi 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/chunk-DsIazq42.js +26 -0
  2. package/dist/command-palette-B97yKqne.js +406 -0
  3. package/dist/core/agents-md.d.ts +22 -0
  4. package/dist/core/agents-md.js +174 -0
  5. package/dist/core/box-chrome.d.ts +56 -0
  6. package/dist/core/box-chrome.js +207 -0
  7. package/dist/core/box-format.d.ts +82 -0
  8. package/dist/core/box-format.js +381 -0
  9. package/dist/core/config.d.ts +14 -0
  10. package/dist/core/config.js +201 -0
  11. package/dist/core/file-tracker.d.ts +65 -0
  12. package/dist/core/file-tracker.js +439 -0
  13. package/dist/core/github-api.d.ts +45 -0
  14. package/dist/core/github-api.js +101 -0
  15. package/dist/core/html-to-md.d.ts +5 -0
  16. package/dist/core/html-to-md.js +289 -0
  17. package/dist/core/interpolate.d.ts +44 -0
  18. package/dist/core/interpolate.js +475 -0
  19. package/dist/core/mutex.d.ts +8 -0
  20. package/dist/core/mutex.js +142 -0
  21. package/dist/core/output-buffer.d.ts +80 -0
  22. package/dist/core/output-buffer.js +517 -0
  23. package/dist/core/permissions.d.ts +19 -0
  24. package/dist/core/permissions.js +140 -0
  25. package/dist/core/pi-spawn.d.ts +62 -0
  26. package/dist/core/pi-spawn.js +244 -0
  27. package/dist/core/prompt-patch.d.ts +11 -0
  28. package/dist/core/prompt-patch.js +16 -0
  29. package/dist/core/show-renderer.d.ts +10 -0
  30. package/dist/core/show-renderer.js +39 -0
  31. package/dist/core/show.d.ts +29 -0
  32. package/dist/core/show.js +288 -0
  33. package/dist/core/sub-agent-render.d.ts +56 -0
  34. package/dist/core/sub-agent-render.js +579 -0
  35. package/dist/core/tool-cost.d.ts +17 -0
  36. package/dist/core/tool-cost.js +58 -0
  37. package/dist/core/tui.d.ts +9 -0
  38. package/dist/core/tui.js +33 -0
  39. package/dist/editor-BljciPdx.js +537 -0
  40. package/dist/extensions/bash.d.ts +12 -0
  41. package/dist/extensions/bash.js +261 -0
  42. package/dist/extensions/code-review.d.ts +14 -0
  43. package/dist/extensions/code-review.js +175 -0
  44. package/dist/extensions/command-palette.d.ts +6 -0
  45. package/dist/extensions/command-palette.js +2 -0
  46. package/dist/extensions/create-file.d.ts +7 -0
  47. package/dist/extensions/create-file.js +88 -0
  48. package/dist/extensions/edit-file.d.ts +7 -0
  49. package/dist/extensions/edit-file.js +395 -0
  50. package/dist/extensions/editor.d.ts +6 -0
  51. package/dist/extensions/editor.js +2 -0
  52. package/dist/extensions/finder.d.ts +13 -0
  53. package/dist/extensions/finder.js +108 -0
  54. package/dist/extensions/format-file.d.ts +11 -0
  55. package/dist/extensions/format-file.js +145 -0
  56. package/dist/extensions/github.d.ts +13 -0
  57. package/dist/extensions/github.js +527 -0
  58. package/dist/extensions/glob.d.ts +10 -0
  59. package/dist/extensions/glob.js +163 -0
  60. package/dist/extensions/grep.d.ts +13 -0
  61. package/dist/extensions/grep.js +356 -0
  62. package/dist/extensions/handoff.d.ts +6 -0
  63. package/dist/extensions/handoff.js +277 -0
  64. package/dist/extensions/librarian.d.ts +13 -0
  65. package/dist/extensions/librarian.js +113 -0
  66. package/dist/extensions/look-at.d.ts +13 -0
  67. package/dist/extensions/look-at.js +121 -0
  68. package/dist/extensions/ls.d.ts +8 -0
  69. package/dist/extensions/ls.js +87 -0
  70. package/dist/extensions/mermaid.d.ts +20 -0
  71. package/dist/extensions/mermaid.js +2 -0
  72. package/dist/extensions/oracle.d.ts +13 -0
  73. package/dist/extensions/oracle.js +143 -0
  74. package/dist/extensions/read-session.d.ts +13 -0
  75. package/dist/extensions/read-session.js +265 -0
  76. package/dist/extensions/read-web-page.d.ts +11 -0
  77. package/dist/extensions/read-web-page.js +234 -0
  78. package/dist/extensions/read.d.ts +23 -0
  79. package/dist/extensions/read.js +323 -0
  80. package/dist/extensions/search-sessions.d.ts +29 -0
  81. package/dist/extensions/search-sessions.js +426 -0
  82. package/dist/extensions/session-name.d.ts +6 -0
  83. package/dist/extensions/session-name.js +54 -0
  84. package/dist/extensions/skill.d.ts +7 -0
  85. package/dist/extensions/skill.js +232 -0
  86. package/dist/extensions/system-prompt.d.ts +6 -0
  87. package/dist/extensions/system-prompt.js +31 -0
  88. package/dist/extensions/task.d.ts +11 -0
  89. package/dist/extensions/task.js +114 -0
  90. package/dist/extensions/tool-harness.d.ts +6 -0
  91. package/dist/extensions/tool-harness.js +37 -0
  92. package/dist/extensions/undo-edit.d.ts +7 -0
  93. package/dist/extensions/undo-edit.js +127 -0
  94. package/dist/extensions/web-search.d.ts +12 -0
  95. package/dist/extensions/web-search.js +254 -0
  96. package/dist/extensions.d.ts +29 -0
  97. package/dist/extensions.js +29 -0
  98. package/dist/mermaid-C7xCVBrw.js +95296 -0
  99. package/package.json +67 -0
@@ -0,0 +1,13 @@
1
+ import { ExtensionAPI, ToolDefinition } from "@mariozechner/pi-coding-agent";
2
+
3
+ //#region packages/extensions/github/index.d.ts
4
+ declare function createReadGithubTool(): ToolDefinition;
5
+ declare function createSearchGithubTool(): ToolDefinition;
6
+ declare function createListDirectoryGithubTool(): ToolDefinition;
7
+ declare function createListRepositoriesTool(): ToolDefinition;
8
+ declare function createGlobGithubTool(): ToolDefinition;
9
+ declare function createCommitSearchTool(): ToolDefinition;
10
+ declare function createDiffTool(): ToolDefinition;
11
+ declare function export_default(pi: ExtensionAPI): void;
12
+ //#endregion
13
+ export { createCommitSearchTool, createDiffTool, createGlobGithubTool, createListDirectoryGithubTool, createListRepositoriesTool, createReadGithubTool, createSearchGithubTool, export_default as default };
@@ -0,0 +1,527 @@
1
+ import { withPromptPatch } from "../core/prompt-patch.js";
2
+ import { boxRendererWindowed, osc8Link, textSection } from "../core/box-format.js";
3
+ import { addLineNumbers, decodeBase64Content, ghApi, parseRepoUrl, repoSlug, truncate } from "../core/github-api.js";
4
+ import { Text } from "@mariozechner/pi-tui";
5
+ import { Type } from "@sinclair/typebox";
6
+ //#region packages/extensions/github/index.ts
7
+ /** collapsed: head 3 + tail 5 = 8 visual lines */
8
+ const COLLAPSED_EXCERPTS = [{
9
+ focus: "head",
10
+ context: 3
11
+ }, {
12
+ focus: "tail",
13
+ context: 5
14
+ }];
15
+ function createReadGithubTool() {
16
+ return {
17
+ name: "read_github",
18
+ label: "Read GitHub",
19
+ description: "Read the contents of a file from a GitHub repository.\n\nWHEN TO USE THIS TOOL:\n- When you need to examine the contents of a specific file in a remote repo\n- When you want to understand implementation details across repositories\n\nPARAMETERS:\n- path: The file path to read\n- repository: Repository URL (e.g., https://github.com/owner/repo)\n- read_range: Optional [start_line, end_line] to read only specific lines",
20
+ parameters: Type.Object({
21
+ path: Type.String({ description: "The path to the file to read" }),
22
+ repository: Type.String({ description: "Repository URL (e.g., https://github.com/owner/repo)" }),
23
+ read_range: Type.Optional(Type.Array(Type.Number(), {
24
+ minItems: 2,
25
+ maxItems: 2,
26
+ description: "Optional [start_line, end_line] to read only specific lines"
27
+ }))
28
+ }),
29
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
30
+ const p = params;
31
+ try {
32
+ const ref = parseRepoUrl(p.repository);
33
+ const data = ghApi(`repos/${repoSlug(ref)}/contents/${p.path}`);
34
+ if (Array.isArray(data)) throw new Error("Path is a directory, not a file. Use list_directory_github instead.");
35
+ if (data.type !== "file" || !data.content) throw new Error(`Not a file: ${data.type}`);
36
+ let content = decodeBase64Content(data.content);
37
+ if (p.read_range) {
38
+ const [start, end] = p.read_range;
39
+ const lines = content.split("\n");
40
+ const startIdx = Math.max(0, start - 1);
41
+ const endIdx = Math.min(lines.length, end);
42
+ content = lines.slice(startIdx, endIdx).join("\n");
43
+ return {
44
+ content: [{
45
+ type: "text",
46
+ text: addLineNumbers(content, start)
47
+ }],
48
+ details: { header: `${repoSlug(ref)}/${p.path}` }
49
+ };
50
+ }
51
+ return {
52
+ content: [{
53
+ type: "text",
54
+ text: truncate(addLineNumbers(content), 64e3)
55
+ }],
56
+ details: { header: `${repoSlug(ref)}/${p.path}` }
57
+ };
58
+ } catch (e) {
59
+ throw new Error(e.message);
60
+ }
61
+ },
62
+ renderCall(args, theme) {
63
+ const path = args.path || "...";
64
+ const display = `${args.repository ? args.repository.replace(/^https?:\/\/github\.com\//, "") : ""}/${path}`;
65
+ const url = args.repository ? `${args.repository.replace(/\/$/, "")}/blob/HEAD/${path}` : "";
66
+ const linked = url ? osc8Link(url, display) : display;
67
+ return new Text(theme.fg("toolTitle", theme.bold("read_github ")) + theme.fg("dim", linked), 0, 0);
68
+ },
69
+ renderResult(result, { expanded }, _theme) {
70
+ const content = result.content?.[0];
71
+ if (!content || content.type !== "text") return new Text("(no output)", 0, 0);
72
+ const section = { blocks: [{ lines: content.text.split("\n").map((line) => {
73
+ const m = line.match(/^(\s*\d+): (.*)$/);
74
+ if (m && m[1] && m[2]) return {
75
+ gutter: m[1].trim(),
76
+ text: m[2],
77
+ highlight: true
78
+ };
79
+ return {
80
+ text: line,
81
+ highlight: true
82
+ };
83
+ }) }] };
84
+ return boxRendererWindowed(() => [section], {
85
+ collapsed: { excerpts: COLLAPSED_EXCERPTS },
86
+ expanded: {}
87
+ }, void 0, expanded);
88
+ }
89
+ };
90
+ }
91
+ function createSearchGithubTool() {
92
+ return {
93
+ name: "search_github",
94
+ label: "Search GitHub",
95
+ description: "Search for code patterns and content in a GitHub repository.\n\nWHEN TO USE THIS TOOL:\n- When you need to find code patterns across a repository\n- When you want to understand how functionality is implemented\n\nSupports GitHub search qualifiers (language:, path:, extension:, etc.).",
96
+ parameters: Type.Object({
97
+ pattern: Type.String({ description: "The search pattern. Supports GitHub search operators (AND, OR, NOT) and qualifiers." }),
98
+ repository: Type.String({ description: "Repository URL (e.g., https://github.com/owner/repo)" }),
99
+ path: Type.Optional(Type.String({ description: "Optional path to limit search to" })),
100
+ limit: Type.Optional(Type.Number({
101
+ description: "Max results (default: 30, max: 100)",
102
+ minimum: 1,
103
+ maximum: 100
104
+ })),
105
+ offset: Type.Optional(Type.Number({
106
+ description: "Results to skip for pagination (default: 0)",
107
+ minimum: 0
108
+ }))
109
+ }),
110
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
111
+ const p = params;
112
+ try {
113
+ const ref = parseRepoUrl(p.repository);
114
+ const limit = p.limit ?? 30;
115
+ const page = p.offset ? Math.floor(p.offset / limit) + 1 : 1;
116
+ let query = `${p.pattern} repo:${repoSlug(ref)}`;
117
+ if (p.path) query += ` path:${p.path}`;
118
+ const data = ghApi(`search/code`, {
119
+ params: {
120
+ q: query,
121
+ per_page: Math.min(limit, 100),
122
+ page
123
+ },
124
+ accept: "application/vnd.github.text-match+json"
125
+ });
126
+ if (!data.items || data.items.length === 0) return {
127
+ content: [{
128
+ type: "text",
129
+ text: `No results for "${p.pattern}" in ${repoSlug(ref)}`
130
+ }],
131
+ details: { header: `/${p.pattern}/ in ${repoSlug(ref)}` }
132
+ };
133
+ const results = [`Found ${data.total_count} results (showing ${data.items.length}):\n`];
134
+ for (const item of data.items) {
135
+ results.push(`## ${item.path}`);
136
+ if (item.text_matches) {
137
+ for (const match of item.text_matches) if (match.fragment) {
138
+ results.push("```");
139
+ results.push(match.fragment);
140
+ results.push("```");
141
+ }
142
+ }
143
+ results.push("");
144
+ }
145
+ return {
146
+ content: [{
147
+ type: "text",
148
+ text: truncate(results.join("\n"), 64e3)
149
+ }],
150
+ details: { header: `/${p.pattern}/ in ${repoSlug(ref)}` }
151
+ };
152
+ } catch (e) {
153
+ throw new Error(e.message);
154
+ }
155
+ },
156
+ renderCall(args, theme) {
157
+ const pattern = args.pattern || "...";
158
+ const repo = args.repository ? args.repository.replace(/^https?:\/\/github\.com\//, "") : "";
159
+ const linkedRepo = args.repository ? osc8Link(args.repository, repo) : repo;
160
+ return new Text(theme.fg("toolTitle", theme.bold("search_github ")) + theme.fg("dim", `/${pattern}/ in ${linkedRepo}`), 0, 0);
161
+ },
162
+ renderResult(result, { expanded }, _theme) {
163
+ const content = result.content?.[0];
164
+ if (!content || content.type !== "text") return new Text("(no output)", 0, 0);
165
+ return boxRendererWindowed(() => [textSection(void 0, content.text)], {
166
+ collapsed: { excerpts: COLLAPSED_EXCERPTS },
167
+ expanded: {}
168
+ }, void 0, expanded);
169
+ }
170
+ };
171
+ }
172
+ function createListDirectoryGithubTool() {
173
+ return {
174
+ name: "list_directory_github",
175
+ label: "List Directory GitHub",
176
+ description: "List the contents of a directory in a GitHub repository.\n\nWHEN TO USE THIS TOOL:\n- When you need to understand the structure of a directory\n- When exploring a codebase to find relevant files\n\nReturns files and directories with trailing / for directories.",
177
+ parameters: Type.Object({
178
+ path: Type.String({ description: "The directory path to list (defaults to root)" }),
179
+ repository: Type.String({ description: "Repository URL (e.g., https://github.com/owner/repo)" }),
180
+ limit: Type.Optional(Type.Number({
181
+ description: "Max entries (default: 100, max: 1000)",
182
+ minimum: 1,
183
+ maximum: 1e3
184
+ }))
185
+ }),
186
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
187
+ const p = params;
188
+ try {
189
+ const ref = parseRepoUrl(p.repository);
190
+ const limit = p.limit ?? 100;
191
+ const apiPath = p.path === "" || p.path === "." || p.path === "/" ? "" : p.path;
192
+ const data = ghApi(`repos/${repoSlug(ref)}/contents/${apiPath}`);
193
+ if (!Array.isArray(data)) throw new Error("Path is a file, not a directory. Use read_github instead.");
194
+ return {
195
+ content: [{
196
+ type: "text",
197
+ text: data.slice(0, limit).map((item) => {
198
+ const suffix = item.type === "dir" ? "/" : "";
199
+ const size = item.type === "file" && item.size ? ` (${item.size} bytes)` : "";
200
+ return `${item.name}${suffix}${size}`;
201
+ }).join("\n")
202
+ }],
203
+ details: { header: `${repoSlug(ref)}/${p.path}` }
204
+ };
205
+ } catch (e) {
206
+ throw new Error(e.message);
207
+ }
208
+ },
209
+ renderCall(args, theme) {
210
+ const path = args.path || "/";
211
+ const display = `${args.repository ? args.repository.replace(/^https?:\/\/github\.com\//, "") : ""}/${path}`;
212
+ const url = args.repository ? `${args.repository.replace(/\/$/, "")}/tree/HEAD/${path}` : "";
213
+ const linked = url ? osc8Link(url, display) : display;
214
+ return new Text(theme.fg("toolTitle", theme.bold("list_directory_github ")) + theme.fg("dim", linked), 0, 0);
215
+ },
216
+ renderResult(result, { expanded }, _theme) {
217
+ const content = result.content?.[0];
218
+ if (!content || content.type !== "text") return new Text("(no output)", 0, 0);
219
+ return boxRendererWindowed(() => [textSection(void 0, content.text)], {
220
+ collapsed: { excerpts: COLLAPSED_EXCERPTS },
221
+ expanded: {}
222
+ }, void 0, expanded);
223
+ }
224
+ };
225
+ }
226
+ function createListRepositoriesTool() {
227
+ return {
228
+ name: "list_repositories",
229
+ label: "List Repositories",
230
+ description: "List and search for repositories on GitHub.\n\nWHEN TO USE THIS TOOL:\n- When you need to find repositories by name\n- When exploring repositories in an organization\n- When you need repository metadata (stars, forks, descriptions)\n\nPrioritizes your own repositories, then supplements with public results.",
231
+ parameters: Type.Object({
232
+ pattern: Type.Optional(Type.String({ description: "Pattern to match in repository names" })),
233
+ organization: Type.Optional(Type.String({ description: "Organization name to filter" })),
234
+ language: Type.Optional(Type.String({ description: "Programming language to filter" })),
235
+ limit: Type.Optional(Type.Number({
236
+ description: "Max results (default: 30, max: 100)",
237
+ minimum: 1,
238
+ maximum: 100
239
+ })),
240
+ offset: Type.Optional(Type.Number({
241
+ description: "Results to skip (default: 0)",
242
+ minimum: 0
243
+ }))
244
+ }),
245
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
246
+ const p = params;
247
+ try {
248
+ const limit = p.limit ?? 30;
249
+ const page = p.offset ? Math.floor(p.offset / limit) + 1 : 1;
250
+ const queryParts = [];
251
+ if (p.pattern) queryParts.push(p.pattern);
252
+ if (p.organization) queryParts.push(`org:${p.organization}`);
253
+ if (p.language) queryParts.push(`language:${p.language}`);
254
+ if (queryParts.length === 0) queryParts.push("stars:>0");
255
+ const data = ghApi("search/repositories", { params: {
256
+ q: queryParts.join(" "),
257
+ sort: "stars",
258
+ per_page: Math.min(limit, 100),
259
+ page
260
+ } });
261
+ if (!data.items || data.items.length === 0) return {
262
+ content: [{
263
+ type: "text",
264
+ text: "No repositories found."
265
+ }],
266
+ details: { header: queryParts.join(" ") || "repositories" }
267
+ };
268
+ const lines = [`Found ${data.total_count} repositories (showing ${data.items.length}):\n`];
269
+ for (const repo of data.items) {
270
+ lines.push(`## ${repo.full_name}`);
271
+ if (repo.description) lines.push(repo.description);
272
+ const meta = [
273
+ repo.language,
274
+ `★ ${repo.stargazers_count}`,
275
+ `forks: ${repo.forks_count}`
276
+ ].filter(Boolean).join(" · ");
277
+ lines.push(meta);
278
+ lines.push(`${repo.html_url}\n`);
279
+ }
280
+ return {
281
+ content: [{
282
+ type: "text",
283
+ text: lines.join("\n")
284
+ }],
285
+ details: { header: queryParts.join(" ") }
286
+ };
287
+ } catch (e) {
288
+ throw new Error(e.message);
289
+ }
290
+ },
291
+ renderCall(args, theme) {
292
+ const query = args.pattern || args.organization || "...";
293
+ return new Text(theme.fg("toolTitle", theme.bold("list_repositories ")) + theme.fg("dim", query), 0, 0);
294
+ },
295
+ renderResult(result, { expanded }, _theme) {
296
+ const content = result.content?.[0];
297
+ if (!content || content.type !== "text") return new Text("(no output)", 0, 0);
298
+ return boxRendererWindowed(() => [textSection(void 0, content.text)], {
299
+ collapsed: { excerpts: COLLAPSED_EXCERPTS },
300
+ expanded: {}
301
+ }, void 0, expanded);
302
+ }
303
+ };
304
+ }
305
+ function createGlobGithubTool() {
306
+ return {
307
+ name: "glob_github",
308
+ label: "Glob GitHub",
309
+ description: "Find files matching a glob pattern in a GitHub repository.\n\nWHEN TO USE THIS TOOL:\n- When you need to find specific file types (e.g., all TypeScript files)\n- When exploring codebase structure quickly\n\nUses the git tree API to list all files, then filters by pattern.",
310
+ parameters: Type.Object({
311
+ filePattern: Type.String({ description: "Glob pattern (e.g., \"**/*.ts\", \"src/**/*.test.js\")" }),
312
+ repository: Type.String({ description: "Repository URL (e.g., https://github.com/owner/repo)" }),
313
+ limit: Type.Optional(Type.Number({ description: "Max results (default: 100)" })),
314
+ offset: Type.Optional(Type.Number({ description: "Results to skip (default: 0)" }))
315
+ }),
316
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
317
+ const p = params;
318
+ try {
319
+ const ref = parseRepoUrl(p.repository);
320
+ const limit = p.limit ?? 100;
321
+ const offset = p.offset ?? 0;
322
+ const branch = ghApi(`repos/${repoSlug(ref)}`).default_branch || "main";
323
+ const tree = ghApi(`repos/${repoSlug(ref)}/git/trees/${branch}?recursive=1`);
324
+ if (!tree.tree) throw new Error("Could not read repository tree.");
325
+ const pattern = p.filePattern;
326
+ const files = tree.tree.filter((item) => item.type === "blob").map((item) => item.path).filter((path) => matchGlob(path, pattern));
327
+ const total = files.length;
328
+ const sliced = files.slice(offset, offset + limit);
329
+ return {
330
+ content: [{
331
+ type: "text",
332
+ text: [
333
+ `Found ${total} files matching "${pattern}" (showing ${sliced.length}):`,
334
+ "",
335
+ ...sliced
336
+ ].join("\n")
337
+ }],
338
+ details: { header: `${p.filePattern} in ${repoSlug(ref)}` }
339
+ };
340
+ } catch (e) {
341
+ throw new Error(e.message);
342
+ }
343
+ },
344
+ renderCall(args, theme) {
345
+ const pattern = args.filePattern || "...";
346
+ const repo = args.repository ? args.repository.replace(/^https?:\/\/github\.com\//, "") : "";
347
+ const linkedRepo = args.repository ? osc8Link(args.repository, repo) : repo;
348
+ return new Text(theme.fg("toolTitle", theme.bold("glob_github ")) + theme.fg("dim", `${pattern} in ${linkedRepo}`), 0, 0);
349
+ },
350
+ renderResult(result, { expanded }, _theme) {
351
+ const content = result.content?.[0];
352
+ if (!content || content.type !== "text") return new Text("(no output)", 0, 0);
353
+ return boxRendererWindowed(() => [textSection(void 0, content.text)], {
354
+ collapsed: { excerpts: COLLAPSED_EXCERPTS },
355
+ expanded: {}
356
+ }, void 0, expanded);
357
+ }
358
+ };
359
+ }
360
+ function createCommitSearchTool() {
361
+ return {
362
+ name: "commit_search",
363
+ label: "Commit Search",
364
+ description: "Search commit history in a GitHub repository.\n\nWHEN TO USE THIS TOOL:\n- When you need to understand how code evolved over time\n- When looking for commits by a specific author or date range\n- When finding commits that changed specific files",
365
+ parameters: Type.Object({
366
+ repository: Type.String({ description: "Repository URL (e.g., https://github.com/owner/repo)" }),
367
+ query: Type.Optional(Type.String({ description: "Search query for commit messages" })),
368
+ author: Type.Optional(Type.String({ description: "Filter by author username or email" })),
369
+ since: Type.Optional(Type.String({ description: "ISO 8601 date for earliest commit (e.g., \"2024-01-01T00:00:00Z\")" })),
370
+ until: Type.Optional(Type.String({ description: "ISO 8601 date for latest commit (e.g., \"2024-02-01T00:00:00Z\")" })),
371
+ path: Type.Optional(Type.String({ description: "Filter commits that changed specific files/directories" })),
372
+ limit: Type.Optional(Type.Number({
373
+ description: "Max commits (default: 50, max: 100)",
374
+ minimum: 1,
375
+ maximum: 100
376
+ })),
377
+ offset: Type.Optional(Type.Number({
378
+ description: "Commits to skip (default: 0)",
379
+ minimum: 0
380
+ }))
381
+ }),
382
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
383
+ const p = params;
384
+ try {
385
+ const ref = parseRepoUrl(p.repository);
386
+ const limit = p.limit ?? 50;
387
+ const page = p.offset ? Math.floor(p.offset / limit) + 1 : 1;
388
+ const apiParams = {
389
+ per_page: Math.min(limit, 100),
390
+ page
391
+ };
392
+ if (p.author) apiParams.author = p.author;
393
+ if (p.since) apiParams.since = p.since;
394
+ if (p.until) apiParams.until = p.until;
395
+ let endpoint = `repos/${repoSlug(ref)}/commits`;
396
+ if (p.path) apiParams.path = p.path;
397
+ const commits = ghApi(endpoint, { params: apiParams });
398
+ if (!Array.isArray(commits) || commits.length === 0) return {
399
+ content: [{
400
+ type: "text",
401
+ text: "No commits found."
402
+ }],
403
+ details: { header: repoSlug(ref) }
404
+ };
405
+ let filtered = commits;
406
+ if (p.query) {
407
+ const q = p.query.toLowerCase();
408
+ filtered = commits.filter((c) => c.commit?.message?.toLowerCase().includes(q));
409
+ }
410
+ const lines = [`Found ${filtered.length} commits:\n`];
411
+ for (const c of filtered) {
412
+ const sha = c.sha?.slice(0, 7) ?? "???????";
413
+ const author = c.commit?.author?.name ?? c.author?.login ?? "unknown";
414
+ const date = c.commit?.author?.date ?? "";
415
+ const msg = c.commit?.message?.split("\n")[0] ?? "";
416
+ lines.push(`${sha} ${date} (${author}) ${msg}`);
417
+ }
418
+ return {
419
+ content: [{
420
+ type: "text",
421
+ text: lines.join("\n")
422
+ }],
423
+ details: { header: repoSlug(ref) }
424
+ };
425
+ } catch (e) {
426
+ throw new Error(e.message);
427
+ }
428
+ },
429
+ renderCall(args, theme) {
430
+ const repo = args.repository ? args.repository.replace(/^https?:\/\/github\.com\//, "") : "...";
431
+ const linkedRepo = args.repository ? osc8Link(args.repository, repo) : repo;
432
+ const query = args.query || args.author || "";
433
+ return new Text(theme.fg("toolTitle", theme.bold("commit_search ")) + theme.fg("dim", `${linkedRepo} ${query}`.trim()), 0, 0);
434
+ },
435
+ renderResult(result, { expanded }, _theme) {
436
+ const content = result.content?.[0];
437
+ if (!content || content.type !== "text") return new Text("(no output)", 0, 0);
438
+ return boxRendererWindowed(() => [textSection(void 0, content.text)], {
439
+ collapsed: { excerpts: COLLAPSED_EXCERPTS },
440
+ expanded: {}
441
+ }, void 0, expanded);
442
+ }
443
+ };
444
+ }
445
+ function createDiffTool() {
446
+ return {
447
+ name: "diff",
448
+ label: "Diff",
449
+ description: "Get a diff between two commits, branches, or tags in a GitHub repository.\n\nWHEN TO USE THIS TOOL:\n- When you need to see what changed between two points in history\n- When reviewing changes across branches\n- When understanding the scope of a release",
450
+ parameters: Type.Object({
451
+ base: Type.String({ description: "The base ref to compare from (e.g., \"main\", \"v1.0.0\", or commit SHA)" }),
452
+ head: Type.String({ description: "The head ref to compare to (e.g., \"feature-branch\", \"v2.0.0\", or commit SHA)" }),
453
+ repository: Type.String({ description: "Repository URL (e.g., https://github.com/owner/repo)" }),
454
+ includePatches: Type.Optional(Type.Boolean({ description: "Include unified diff patches per file (token heavy). Default false." }))
455
+ }),
456
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
457
+ const p = params;
458
+ try {
459
+ const data = ghApi(`repos/${repoSlug(parseRepoUrl(p.repository))}/compare/${encodeURIComponent(p.base)}...${encodeURIComponent(p.head)}`);
460
+ const lines = [
461
+ `Comparing ${p.base}...${p.head}`,
462
+ `Status: ${data.status}`,
463
+ `Ahead by: ${data.ahead_by} commits`,
464
+ `Behind by: ${data.behind_by} commits`,
465
+ `Changed files: ${data.files?.length ?? 0}`,
466
+ ""
467
+ ];
468
+ if (data.files) for (const file of data.files) {
469
+ const stat = `+${file.additions} -${file.deletions}`;
470
+ lines.push(`${file.status} ${file.filename} (${stat})`);
471
+ if (p.includePatches && file.patch) {
472
+ lines.push("```diff");
473
+ lines.push(truncate(file.patch, 4e3));
474
+ lines.push("```");
475
+ lines.push("");
476
+ }
477
+ }
478
+ return {
479
+ content: [{
480
+ type: "text",
481
+ text: truncate(lines.join("\n"), 64e3)
482
+ }],
483
+ details: { header: `${p.base}...${p.head}` }
484
+ };
485
+ } catch (e) {
486
+ throw new Error(e.message);
487
+ }
488
+ },
489
+ renderCall(args, theme) {
490
+ const repo = args.repository ? args.repository.replace(/^https?:\/\/github\.com\//, "") : "...";
491
+ const linkedRepo = args.repository ? osc8Link(args.repository, repo) : repo;
492
+ const range = `${args.base || "?"}...${args.head || "?"}`;
493
+ return new Text(theme.fg("toolTitle", theme.bold("diff ")) + theme.fg("dim", `${linkedRepo} ${range}`), 0, 0);
494
+ },
495
+ renderResult(result, { expanded }, _theme) {
496
+ const content = result.content?.[0];
497
+ if (!content || content.type !== "text") return new Text("(no output)", 0, 0);
498
+ return boxRendererWindowed(() => [textSection(void 0, content.text)], {
499
+ collapsed: { excerpts: COLLAPSED_EXCERPTS },
500
+ expanded: {}
501
+ }, void 0, expanded);
502
+ }
503
+ };
504
+ }
505
+ /**
506
+ * simple glob matcher. supports:
507
+ * ** (any path segments), * (any within segment), ? (single char)
508
+ */
509
+ function matchGlob(path, pattern) {
510
+ const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*\//g, "(.+/)?").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
511
+ try {
512
+ return new RegExp(`^${regexStr}$`).test(path);
513
+ } catch {
514
+ return path.includes(pattern.replace(/[*?]/g, ""));
515
+ }
516
+ }
517
+ function github_default(pi) {
518
+ pi.registerTool(withPromptPatch(createReadGithubTool()));
519
+ pi.registerTool(withPromptPatch(createSearchGithubTool()));
520
+ pi.registerTool(withPromptPatch(createListDirectoryGithubTool()));
521
+ pi.registerTool(withPromptPatch(createListRepositoriesTool()));
522
+ pi.registerTool(withPromptPatch(createGlobGithubTool()));
523
+ pi.registerTool(withPromptPatch(createCommitSearchTool()));
524
+ pi.registerTool(withPromptPatch(createDiffTool()));
525
+ }
526
+ //#endregion
527
+ export { createCommitSearchTool, createDiffTool, createGlobGithubTool, createListDirectoryGithubTool, createListRepositoriesTool, createReadGithubTool, createSearchGithubTool, github_default as default };
@@ -0,0 +1,10 @@
1
+ import { ExtensionAPI, ToolDefinition } from "@mariozechner/pi-coding-agent";
2
+
3
+ //#region packages/extensions/glob/index.d.ts
4
+ type GlobExtConfig = {
5
+ defaultLimit: number;
6
+ };
7
+ declare function createGlobTool(config?: GlobExtConfig): ToolDefinition;
8
+ declare function export_default(pi: ExtensionAPI): void;
9
+ //#endregion
10
+ export { createGlobTool, export_default as default };