@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,261 @@
1
+ import { withPromptPatch } from "../core/prompt-patch.js";
2
+ import { boxRendererWindowed } from "../core/box-format.js";
3
+ import { getText } from "../core/tui.js";
4
+ import { withFileLock } from "../core/mutex.js";
5
+ import { evaluatePermission, loadPermissions } from "../core/permissions.js";
6
+ import { OutputBuffer } from "../core/output-buffer.js";
7
+ import { getExtensionConfig } from "../core/config.js";
8
+ import { resolveToAbsolute } from "./read.js";
9
+ import { existsSync } from "node:fs";
10
+ import * as path from "node:path";
11
+ import { spawn } from "node:child_process";
12
+ import { Type } from "@sinclair/typebox";
13
+ //#region packages/extensions/bash/index.ts
14
+ /**
15
+ * bash tool — replaces pi's built-in with enhanced command execution.
16
+ *
17
+ * differences from pi's built-in:
18
+ * - `cmd` + `cwd` params (model-compatible interface, not pi's `command`)
19
+ * - auto-splits `cd dir && cmd` into cwd + command (fallback for models)
20
+ * - strips trailing `&` (prevents background processes)
21
+ * - git commit trailer injection (session ID)
22
+ * - git lock serialization via withFileLock (prevents concurrent git ops)
23
+ * - SIGTERM → SIGKILL fallback on cancel/timeout (pi goes straight to SIGKILL)
24
+ * - output truncation with head + tail (first/last N lines, not just tail)
25
+ * - constant memory via OutputBuffer (no unbounded string growth)
26
+ * - permission rules from ~/.pi/agent/permissions.json (allow/reject)
27
+ *
28
+ * shadows pi's built-in `bash` tool via same-name registration.
29
+ */
30
+ const CONFIG_DEFAULTS = {
31
+ headLines: 50,
32
+ tailLines: 50,
33
+ sigkillDelayMs: 3e3
34
+ };
35
+ /**
36
+ * pi's getShellConfig() lives in utils/shell.js, not re-exported
37
+ * from the main package. reimplemented here — on macOS (our target)
38
+ * this is always /bin/bash.
39
+ */
40
+ function getShell() {
41
+ if (existsSync("/bin/bash")) return {
42
+ shell: "/bin/bash",
43
+ args: ["-c"]
44
+ };
45
+ return {
46
+ shell: "sh",
47
+ args: ["-c"]
48
+ };
49
+ }
50
+ /**
51
+ * models sometimes emit `cd dir && cmd` despite the system prompt
52
+ * discouraging it. split into cwd + command so the cd takes effect
53
+ * in the spawn call rather than being lost between invocations.
54
+ */
55
+ function splitCdCommand(cmd) {
56
+ const match = cmd.match(/^\s*cd\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*(?:&&|;)\s*(.+)$/s);
57
+ if (!match) return null;
58
+ const dir = match[1] ?? match[2] ?? match[3] ?? "";
59
+ const command = match[4];
60
+ if (!command) return null;
61
+ return {
62
+ cwd: dir,
63
+ command
64
+ };
65
+ }
66
+ function stripBackground(cmd) {
67
+ return cmd.replace(/\s*&\s*$/, "");
68
+ }
69
+ function isGitCommand(cmd) {
70
+ return /\bgit\s+/.test(cmd);
71
+ }
72
+ /**
73
+ * inject session ID trailer into git commit commands so commits
74
+ * are traceable back to the pi session that authored them.
75
+ * skips if trailers are already present (model added them manually).
76
+ */
77
+ function injectGitTrailers(cmd, sessionId) {
78
+ if (!/\bgit\s+commit\b/.test(cmd)) return cmd;
79
+ if (/--trailer/.test(cmd)) return cmd;
80
+ return cmd.replace(/\bgit\s+commit\b/, `git commit --trailer "Session-Id: ${sessionId}"`);
81
+ }
82
+ /**
83
+ * SIGTERM the process group first, escalate to SIGKILL after delay.
84
+ * pi's built-in goes straight to SIGKILL via killProcessTree().
85
+ * graceful fallback so processes can clean up.
86
+ */
87
+ function killGracefully(pid, delayMs) {
88
+ try {
89
+ process.kill(-pid, "SIGTERM");
90
+ } catch {
91
+ return;
92
+ }
93
+ setTimeout(() => {
94
+ try {
95
+ process.kill(-pid, 0);
96
+ process.kill(-pid, "SIGKILL");
97
+ } catch {}
98
+ }, delayMs);
99
+ }
100
+ /** per-block excerpts for collapsed display — head 3 + tail 5 = 8 visual lines */
101
+ const COLLAPSED_EXCERPTS = [{
102
+ focus: "head",
103
+ context: 3
104
+ }, {
105
+ focus: "tail",
106
+ context: 5
107
+ }];
108
+ function createBashTool(config = CONFIG_DEFAULTS) {
109
+ return {
110
+ name: "bash",
111
+ label: "Bash",
112
+ description: `Executes the given shell command using bash.
113
+
114
+ - Do NOT chain commands with \`;\` or \`&&\` or use \`&\` for background processes; make separate tool calls instead
115
+ - Do NOT use interactive commands (REPLs, editors, password prompts)
116
+ - Output shows first ${config.headLines} and last ${config.tailLines} lines; middle is truncated for large outputs\n- Environment variables and \`cd\` do not persist between commands; use the \`cwd\` parameter instead
117
+ - Commands run in the workspace root by default; only use \`cwd\` when you need a different directory
118
+ - ALWAYS quote file paths: \`cat "path with spaces/file.txt"\`
119
+ - Use the Grep tool instead of grep, the Read tool instead of cat
120
+ - Only run \`git commit\` and \`git push\` if explicitly instructed by the user.`,
121
+ parameters: Type.Object({
122
+ cmd: Type.String({ description: "The shell command to execute." }),
123
+ cwd: Type.Optional(Type.String({ description: "Working directory for the command (absolute path). Defaults to workspace root." })),
124
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds." }))
125
+ }),
126
+ renderCall(args, theme) {
127
+ const Text = getText();
128
+ const cmd = args.cmd || args.command || "...";
129
+ const timeout = args.timeout;
130
+ const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
131
+ const lines = cmd.split("\n");
132
+ const firstLine = lines[0];
133
+ const multiSuffix = lines.length > 1 ? theme.fg("muted", " …") : "";
134
+ return new Text(theme.fg("toolTitle", theme.bold(`$ ${firstLine}`)) + multiSuffix + timeoutSuffix, 0, 0);
135
+ },
136
+ renderResult(result, { expanded }, theme) {
137
+ const Text = getText();
138
+ const content = result.content?.[0];
139
+ if (!content || content.type !== "text") return new Text(theme.fg("dim", "(no output)"), 0, 0);
140
+ let text = content.text;
141
+ let command = result.details?.command ?? "";
142
+ if (!command && text.startsWith("$ ")) {
143
+ const firstNewline = text.indexOf("\n");
144
+ if (firstNewline !== -1) command = text.slice(2, firstNewline);
145
+ }
146
+ if (text.startsWith("$ ")) {
147
+ const sep = text.indexOf("\n\n");
148
+ if (sep !== -1) text = text.slice(sep + 2);
149
+ }
150
+ if (!text || text === "(no output)") return new Text(theme.fg("dim", "(no output)"), 0, 0);
151
+ const lines = text.split("\n");
152
+ const buildSections = () => [{ blocks: [{ lines: lines.map((l) => ({
153
+ text: theme.fg("toolOutput", l),
154
+ highlight: true
155
+ })) }] }];
156
+ return boxRendererWindowed(buildSections, {
157
+ collapsed: { excerpts: COLLAPSED_EXCERPTS },
158
+ expanded: {}
159
+ }, void 0, expanded);
160
+ },
161
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
162
+ const p = params;
163
+ let command = stripBackground(p.cmd);
164
+ let effectiveCwd = p.cwd ? resolveToAbsolute(p.cwd, ctx.cwd) : ctx.cwd;
165
+ const cdSplit = splitCdCommand(command);
166
+ if (cdSplit) {
167
+ effectiveCwd = resolveToAbsolute(cdSplit.cwd, effectiveCwd);
168
+ command = cdSplit.command;
169
+ }
170
+ if (!existsSync(effectiveCwd)) throw new Error(`working directory does not exist: ${effectiveCwd}`);
171
+ const verdict = evaluatePermission("Bash", { cmd: command }, loadPermissions());
172
+ if (verdict.action === "reject") {
173
+ const msg = verdict.message ? `command rejected: ${verdict.message}` : `command rejected by permission rule. command: ${command}`;
174
+ throw new Error(msg);
175
+ }
176
+ const sessionId = ctx.sessionManager.getSessionId();
177
+ command = injectGitTrailers(command, sessionId);
178
+ const run = () => runCommand(command, effectiveCwd, p.timeout, signal, onUpdate, config);
179
+ if (isGitCommand(command)) return withFileLock(path.join(effectiveCwd, ".git", "__pi_git_lock__"), run);
180
+ return run();
181
+ }
182
+ };
183
+ }
184
+ async function runCommand(command, cwd, timeout, signal, onUpdate, config) {
185
+ const { shell, args } = getShell();
186
+ return new Promise((resolve, reject) => {
187
+ const child = spawn(shell, [...args, command], {
188
+ cwd,
189
+ detached: true,
190
+ env: process.env,
191
+ stdio: [
192
+ "ignore",
193
+ "pipe",
194
+ "pipe"
195
+ ]
196
+ });
197
+ const output = new OutputBuffer(config.headLines, config.tailLines);
198
+ let timedOut = false;
199
+ let aborted = false;
200
+ let timeoutHandle;
201
+ if (timeout && timeout > 0) timeoutHandle = setTimeout(() => {
202
+ timedOut = true;
203
+ if (child.pid) killGracefully(child.pid, config.sigkillDelayMs);
204
+ }, timeout * 1e3);
205
+ const onAbort = () => {
206
+ aborted = true;
207
+ if (child.pid) killGracefully(child.pid, config.sigkillDelayMs);
208
+ };
209
+ if (signal) if (signal.aborted) onAbort();
210
+ else signal.addEventListener("abort", onAbort, { once: true });
211
+ const handleData = (data) => {
212
+ output.add(data.toString("utf-8"));
213
+ if (onUpdate) {
214
+ const { text } = output.format();
215
+ onUpdate({ content: [{
216
+ type: "text",
217
+ text
218
+ }] });
219
+ }
220
+ };
221
+ child.stdout?.on("data", handleData);
222
+ child.stderr?.on("data", handleData);
223
+ child.on("error", (err) => {
224
+ if (timeoutHandle) clearTimeout(timeoutHandle);
225
+ signal?.removeEventListener("abort", onAbort);
226
+ reject(/* @__PURE__ */ new Error(`command error: ${err.message}`));
227
+ });
228
+ child.on("close", (code) => {
229
+ if (timeoutHandle) clearTimeout(timeoutHandle);
230
+ signal?.removeEventListener("abort", onAbort);
231
+ const { text: outputText } = output.format();
232
+ if (aborted) {
233
+ const text = outputText ? `${outputText}\n\ncommand aborted` : "command aborted";
234
+ reject(new Error(text));
235
+ return;
236
+ }
237
+ if (timedOut) {
238
+ const text = outputText ? `${outputText}\n\ncommand timed out after ${timeout} seconds` : `command timed out after ${timeout} seconds`;
239
+ reject(new Error(text));
240
+ return;
241
+ }
242
+ let result = `$ ${command}\n\n${outputText || "(no output)"}`;
243
+ if (code !== 0 && code !== null) {
244
+ result += `\n\nexit code ${code}`;
245
+ reject(new Error(result));
246
+ } else resolve({
247
+ content: [{
248
+ type: "text",
249
+ text: result
250
+ }],
251
+ details: { command }
252
+ });
253
+ });
254
+ });
255
+ }
256
+ function bash_default(pi) {
257
+ const cfg = getExtensionConfig("@bds_pi/bash", CONFIG_DEFAULTS);
258
+ pi.registerTool(withPromptPatch(createBashTool(cfg)));
259
+ }
260
+ //#endregion
261
+ export { createBashTool, bash_default as default };
@@ -0,0 +1,14 @@
1
+ import { ExtensionAPI, ToolDefinition } from "@mariozechner/pi-coding-agent";
2
+
3
+ //#region packages/extensions/code-review/index.d.ts
4
+ interface CodeReviewConfig {
5
+ systemPrompt?: string;
6
+ reportFormat?: string;
7
+ model?: string;
8
+ builtinTools?: string[];
9
+ extensionTools?: string[];
10
+ }
11
+ declare function createCodeReviewTool(config?: CodeReviewConfig): ToolDefinition;
12
+ declare function export_default(pi: ExtensionAPI): void;
13
+ //#endregion
14
+ export { CodeReviewConfig, createCodeReviewTool, export_default as default };
@@ -0,0 +1,175 @@
1
+ import { withPromptPatch } from "../core/prompt-patch.js";
2
+ import { getExtensionConfig } from "../core/config.js";
3
+ import { piSpawn, resolvePrompt, zeroUsage } from "../core/pi-spawn.js";
4
+ import { getFinalOutput, renderAgentTree, subAgentResult } from "../core/sub-agent-render.js";
5
+ import { Container, Text } from "@mariozechner/pi-tui";
6
+ import { Type } from "@sinclair/typebox";
7
+ //#region packages/extensions/code-review/index.ts
8
+ const CONFIG_DEFAULTS = {
9
+ model: "openrouter/google/gemini-3.1-pro-preview",
10
+ builtinTools: [
11
+ "read",
12
+ "grep",
13
+ "find",
14
+ "ls",
15
+ "bash"
16
+ ],
17
+ extensionTools: [
18
+ "read",
19
+ "grep",
20
+ "find",
21
+ "ls",
22
+ "bash",
23
+ "web_search",
24
+ "read_web_page"
25
+ ],
26
+ promptFile: "",
27
+ promptString: "",
28
+ reportPromptFile: "",
29
+ reportPromptString: ""
30
+ };
31
+ const DEFAULT_SYSTEM_PROMPT = `You are an expert code reviewer. Review the provided diff for bugs, security issues, and code quality. Report findings with file locations and severity.
32
+
33
+ Today's date: {date}
34
+ Current working directory (cwd): {cwd}`;
35
+ const DEFAULT_REPORT_FORMAT = `Emit findings as XML: <codeReview><comment> elements with filename, startLine, endLine, severity (critical/high/medium/low), commentType (bug/suggested_edit/compliment/non_actionable), text, why, and fix fields.`;
36
+ function parseReviewXml(output) {
37
+ const comments = [];
38
+ const commentRegex = /<comment>([\s\S]*?)<\/comment>/g;
39
+ let match;
40
+ while ((match = commentRegex.exec(output)) !== null) {
41
+ const block = match[1];
42
+ const get = (tag) => {
43
+ const m = block.match(new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`));
44
+ return m && m[1] ? m[1].trim() : "";
45
+ };
46
+ comments.push({
47
+ filename: get("filename"),
48
+ startLine: parseInt(get("startLine"), 10) || 0,
49
+ endLine: parseInt(get("endLine"), 10) || 0,
50
+ severity: get("severity"),
51
+ commentType: get("commentType"),
52
+ text: get("text"),
53
+ why: get("why"),
54
+ fix: get("fix")
55
+ });
56
+ }
57
+ return comments;
58
+ }
59
+ function formatReviewSummary(comments) {
60
+ if (comments.length === 0) return "";
61
+ const bySeverity = {};
62
+ for (const c of comments) bySeverity[c.severity] = (bySeverity[c.severity] || 0) + 1;
63
+ const parts = [
64
+ "critical",
65
+ "high",
66
+ "medium",
67
+ "low"
68
+ ].filter((s) => bySeverity[s]).map((s) => `${bySeverity[s]} ${s}`);
69
+ return `${comments.length} comment${comments.length !== 1 ? "s" : ""}: ${parts.join(", ")}`;
70
+ }
71
+ function createCodeReviewTool(config = {}) {
72
+ return {
73
+ name: "code_review",
74
+ label: "Code Review",
75
+ description: "Review code changes, diffs, outstanding changes, or modified files. Use when asked to review changes, check code quality, analyze uncommitted work, or perform a code review.\n\nIt takes in a description of the diff or code change that can be used to generate the full diff, which is then reviewed. When using this tool, do not invoke `git diff` or any other tool to generate the diff but just pass a natural language description of how to compute the diff in the diff_description argument.",
76
+ parameters: Type.Object({
77
+ diff_description: Type.String({ description: "A description of the diff or code change that can be used to generate the full diff. This can include a git or bash command to generate the diff or a description of the diff which can then be used to generate the git or bash command to generate the full diff." }),
78
+ files: Type.Optional(Type.Array(Type.String(), { description: "Specific files to focus the review on. If empty, all changed files covered by the diff description are reviewed." })),
79
+ instructions: Type.Optional(Type.String({ description: "Additional instructions to guide the review agent." }))
80
+ }),
81
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
82
+ const p = params;
83
+ let sessionId = "";
84
+ try {
85
+ sessionId = ctx.sessionManager?.getSessionId?.() ?? "";
86
+ } catch {}
87
+ const parts = [];
88
+ parts.push(`Review the following diff:\n${p.diff_description}`);
89
+ if (p.files && p.files.length > 0) parts.push(`\nFocus the review on these files:\n${p.files.join("\n")}`);
90
+ if (p.instructions) parts.push(`\nAdditional review instructions:\n${p.instructions}`);
91
+ const fullTask = parts.join("\n");
92
+ const singleResult = {
93
+ agent: "code_review",
94
+ task: p.diff_description,
95
+ exitCode: -1,
96
+ messages: [],
97
+ usage: zeroUsage()
98
+ };
99
+ const systemPrompt = config.systemPrompt || DEFAULT_SYSTEM_PROMPT;
100
+ const reportFormat = config.reportFormat || DEFAULT_REPORT_FORMAT;
101
+ const result = await piSpawn({
102
+ cwd: ctx.cwd,
103
+ task: fullTask,
104
+ model: config.model ?? CONFIG_DEFAULTS.model,
105
+ builtinTools: config.builtinTools ?? CONFIG_DEFAULTS.builtinTools,
106
+ extensionTools: config.extensionTools ?? CONFIG_DEFAULTS.extensionTools,
107
+ systemPromptBody: systemPrompt,
108
+ followUp: reportFormat,
109
+ signal,
110
+ sessionId,
111
+ onUpdate: (partial) => {
112
+ singleResult.messages = partial.messages;
113
+ singleResult.usage = partial.usage;
114
+ singleResult.model = partial.model;
115
+ singleResult.stopReason = partial.stopReason;
116
+ singleResult.errorMessage = partial.errorMessage;
117
+ if (onUpdate) onUpdate({
118
+ content: [{
119
+ type: "text",
120
+ text: getFinalOutput(partial.messages) || "(reviewing...)"
121
+ }],
122
+ details: singleResult
123
+ });
124
+ }
125
+ });
126
+ singleResult.exitCode = result.exitCode;
127
+ singleResult.messages = result.messages;
128
+ singleResult.usage = result.usage;
129
+ singleResult.model = result.model;
130
+ singleResult.stopReason = result.stopReason;
131
+ singleResult.errorMessage = result.errorMessage;
132
+ const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
133
+ const output = getFinalOutput(result.messages) || "(no output)";
134
+ if (isError) return subAgentResult(result.errorMessage || result.stderr || output, singleResult, true);
135
+ return subAgentResult(output, singleResult);
136
+ },
137
+ renderCall(args, theme) {
138
+ const desc = args.diff_description || "...";
139
+ const preview = desc.length > 70 ? `${desc.slice(0, 70)}...` : desc;
140
+ let text = theme.fg("toolTitle", theme.bold("code_review ")) + theme.fg("dim", preview);
141
+ if (args.files?.length) text += theme.fg("muted", ` (${args.files.length} file${args.files.length > 1 ? "s" : ""})`);
142
+ return new Text(text, 0, 0);
143
+ },
144
+ renderResult(result, { expanded }, theme) {
145
+ const details = result.details;
146
+ if (!details) {
147
+ const text = result.content[0];
148
+ return new Text(text?.type === "text" ? text.text : "(no output)", 0, 0);
149
+ }
150
+ const container = new Container();
151
+ const comments = parseReviewXml(getFinalOutput(details.messages));
152
+ if (comments.length > 0) {
153
+ const summary = formatReviewSummary(comments);
154
+ container.addChild(new Text(theme.fg("accent", summary), 0, 0));
155
+ }
156
+ renderAgentTree(details, container, expanded, theme, {
157
+ label: "code_review",
158
+ header: "statusOnly"
159
+ });
160
+ return container;
161
+ }
162
+ };
163
+ }
164
+ function code_review_default(pi) {
165
+ const cfg = getExtensionConfig("@bds_pi/code-review", CONFIG_DEFAULTS);
166
+ pi.registerTool(withPromptPatch(createCodeReviewTool({
167
+ systemPrompt: resolvePrompt(cfg.promptString, cfg.promptFile),
168
+ reportFormat: resolvePrompt(cfg.reportPromptString, cfg.reportPromptFile),
169
+ model: cfg.model,
170
+ builtinTools: cfg.builtinTools,
171
+ extensionTools: cfg.extensionTools
172
+ })));
173
+ }
174
+ //#endregion
175
+ export { createCodeReviewTool, code_review_default as default };
@@ -0,0 +1,6 @@
1
+ import { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+
3
+ //#region packages/extensions/command-palette/index.d.ts
4
+ declare function commandPaletteExtension(pi: ExtensionAPI): void;
5
+ //#endregion
6
+ export { commandPaletteExtension as default };
@@ -0,0 +1,2 @@
1
+ import { t as commandPaletteExtension } from "../command-palette-B97yKqne.js";
2
+ export { commandPaletteExtension as default };
@@ -0,0 +1,7 @@
1
+ import { ExtensionAPI, ToolDefinition } from "@mariozechner/pi-coding-agent";
2
+
3
+ //#region packages/extensions/create-file/index.d.ts
4
+ declare function createCreateFileTool(): ToolDefinition;
5
+ declare function export_default(pi: ExtensionAPI): void;
6
+ //#endregion
7
+ export { createCreateFileTool, export_default as default };
@@ -0,0 +1,88 @@
1
+ import { withPromptPatch } from "../core/prompt-patch.js";
2
+ import { boxRendererWindowed, osc8Link, textSection } from "../core/box-format.js";
3
+ import { withFileLock } from "../core/mutex.js";
4
+ import { resolveToAbsolute } from "./read.js";
5
+ import { saveChange, simpleDiff } from "../core/file-tracker.js";
6
+ import * as fs from "node:fs";
7
+ import * as path from "node:path";
8
+ import { Text } from "@mariozechner/pi-tui";
9
+ import { Type } from "@sinclair/typebox";
10
+ import * as os from "node:os";
11
+ //#region packages/extensions/create-file/index.ts
12
+ /**
13
+ * create_file tool — replaces pi's built-in write with enhanced file creation.
14
+ *
15
+ * differences from pi's built-in:
16
+ * - mutex-locked per file path (prevents concurrent writes)
17
+ * - file change tracking for undo_edit via lib/file-tracker
18
+ * - captures before-content when overwriting existing files
19
+ *
20
+ * shadows pi's built-in `write` tool via same-name registration.
21
+ */
22
+ const COLLAPSED_EXCERPTS = [{
23
+ focus: "head",
24
+ context: 3
25
+ }, {
26
+ focus: "tail",
27
+ context: 5
28
+ }];
29
+ function createCreateFileTool() {
30
+ return {
31
+ name: "write",
32
+ label: "Create File",
33
+ description: "Create or overwrite a file in the workspace.\n\nUse this tool to create a **new file** that does not yet exist.\n\nFor **existing files**, prefer the edit tool instead — even for extensive changes. Only use this tool to overwrite an existing file when you are replacing nearly all of its content AND the file is small (under ~250 lines).\n\nAutomatically creates parent directories if they don't exist.",
34
+ parameters: Type.Object({
35
+ path: Type.String({ description: "The absolute path of the file to be created (must be absolute, not relative)." }),
36
+ content: Type.String({ description: "The content for the file." })
37
+ }),
38
+ renderCall(args, theme) {
39
+ const filePath = args.path || "...";
40
+ const home = os.homedir();
41
+ const shortened = filePath.startsWith(home) ? `~${filePath.slice(home.length)}` : filePath;
42
+ const linked = filePath.startsWith("/") ? osc8Link(`file://${filePath}`, shortened) : shortened;
43
+ return new Text(theme.fg("toolTitle", theme.bold("Write ")) + theme.fg("dim", linked), 0, 0);
44
+ },
45
+ renderResult(result, { expanded }, _theme) {
46
+ const content = result.content?.[0];
47
+ if (!content || content.type !== "text") return new Text("(no output)", 0, 0);
48
+ return boxRendererWindowed(() => [textSection(void 0, content.text)], {
49
+ collapsed: { excerpts: COLLAPSED_EXCERPTS },
50
+ expanded: {}
51
+ }, void 0, expanded);
52
+ },
53
+ async execute(toolCallId, params, _signal, _onUpdate, ctx) {
54
+ const p = params;
55
+ const resolved = resolveToAbsolute(p.path, ctx.cwd);
56
+ return withFileLock(resolved, async () => {
57
+ const isNewFile = !fs.existsSync(resolved);
58
+ const beforeContent = isNewFile ? "" : fs.readFileSync(resolved, "utf-8");
59
+ const dir = path.dirname(resolved);
60
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
61
+ fs.writeFileSync(resolved, p.content, "utf-8");
62
+ const sessionId = ctx.sessionManager.getSessionId();
63
+ const diff = simpleDiff(resolved, beforeContent, p.content);
64
+ saveChange(sessionId, toolCallId, {
65
+ uri: `file://${resolved}`,
66
+ before: beforeContent,
67
+ after: p.content,
68
+ diff,
69
+ isNewFile,
70
+ timestamp: Date.now()
71
+ });
72
+ const lines = p.content.split("\n").length;
73
+ return {
74
+ content: [{
75
+ type: "text",
76
+ text: isNewFile ? `created ${path.basename(resolved)} (${lines} lines)` : `overwrote ${path.basename(resolved)} (${lines} lines)`
77
+ }],
78
+ details: { header: resolved }
79
+ };
80
+ });
81
+ }
82
+ };
83
+ }
84
+ function create_file_default(pi) {
85
+ pi.registerTool(withPromptPatch(createCreateFileTool()));
86
+ }
87
+ //#endregion
88
+ export { createCreateFileTool, create_file_default as default };
@@ -0,0 +1,7 @@
1
+ import { ExtensionAPI, ToolDefinition } from "@mariozechner/pi-coding-agent";
2
+
3
+ //#region packages/extensions/edit-file/index.d.ts
4
+ declare function createEditFileTool(): ToolDefinition;
5
+ declare function export_default(pi: ExtensionAPI): void;
6
+ //#endregion
7
+ export { createEditFileTool, export_default as default };