@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,9 @@
1
+ //#region packages/core/tui/index.d.ts
2
+ /** lazy Text constructor — call getText() to get the Text class, then instantiate */
3
+ declare function getText(): new (text: string, paddingX: number, paddingY: number) => any;
4
+ /** lazy Container constructor */
5
+ declare function getContainer(): new (...args: any[]) => any;
6
+ /** lazy Markdown constructor */
7
+ declare function getMarkdown(): new (...args: any[]) => any;
8
+ //#endregion
9
+ export { getContainer, getMarkdown, getText };
@@ -0,0 +1,33 @@
1
+ import { n as __require } from "../chunk-DsIazq42.js";
2
+ //#region packages/core/tui/index.ts
3
+ /**
4
+ * lazy re-exports of @mariozechner/pi-tui components.
5
+ *
6
+ * pi-tui is provided by pi's runtime and isn't resolvable in
7
+ * standalone test environments (bun test). lazy-loading via
8
+ * require() defers resolution to first use — which only happens
9
+ * inside renderCall/renderResult at runtime, never during
10
+ * execute()-only tests.
11
+ *
12
+ * usage: import { getText, getContainer } from "@bds_pi/tui";
13
+ * return getText()("hello", 0, 0);
14
+ */
15
+ let _piTui;
16
+ function tui() {
17
+ if (!_piTui) _piTui = __require("@mariozechner/pi-tui");
18
+ return _piTui;
19
+ }
20
+ /** lazy Text constructor — call getText() to get the Text class, then instantiate */
21
+ function getText() {
22
+ return tui().Text;
23
+ }
24
+ /** lazy Container constructor */
25
+ function getContainer() {
26
+ return tui().Container;
27
+ }
28
+ /** lazy Markdown constructor */
29
+ function getMarkdown() {
30
+ return tui().Markdown;
31
+ }
32
+ //#endregion
33
+ export { getContainer, getMarkdown, getText };
@@ -0,0 +1,537 @@
1
+ import { boxBorderLR, boxRow } from "./core/box-chrome.js";
2
+ import { hasToolCost } from "./core/tool-cost.js";
3
+ import { execFile } from "node:child_process";
4
+ import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
5
+ import { CustomEditor, estimateTokens } from "@mariozechner/pi-coding-agent";
6
+ import { promisify } from "node:util";
7
+ //#region packages/extensions/editor/widget-row.ts
8
+ var WidgetRowRegistry = class {
9
+ segments = /* @__PURE__ */ new Map();
10
+ _version = 0;
11
+ constructor(tui) {
12
+ this.tui = tui;
13
+ }
14
+ get version() {
15
+ return this._version;
16
+ }
17
+ set(id, segment) {
18
+ this.segments.set(id, segment);
19
+ this._version++;
20
+ this.tui.requestRender();
21
+ }
22
+ remove(id) {
23
+ if (this.segments.delete(id)) {
24
+ this._version++;
25
+ this.tui.requestRender();
26
+ }
27
+ }
28
+ clear() {
29
+ if (this.segments.size === 0) return;
30
+ this.segments.clear();
31
+ this._version++;
32
+ this.tui.requestRender();
33
+ }
34
+ snapshot() {
35
+ return [...this.segments.values()];
36
+ }
37
+ };
38
+ function sortByPriority(children) {
39
+ return [...children].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
40
+ }
41
+ function joinGroup(children, width, gap) {
42
+ if (children.length === 0) return "";
43
+ return sortByPriority(children).map((child) => child.renderInline(width)).filter((part) => part.length > 0).join(gap);
44
+ }
45
+ function clampToWidth(text, maxWidth) {
46
+ if (maxWidth <= 0) return "";
47
+ return truncateToWidth(text, maxWidth);
48
+ }
49
+ function layoutLine(children, width, gap) {
50
+ if (width <= 0) return "";
51
+ const grouped = {
52
+ left: [],
53
+ center: [],
54
+ right: []
55
+ };
56
+ for (const child of children) grouped[child.align].push(child);
57
+ let left = joinGroup(grouped.left, width, gap);
58
+ let center = joinGroup(grouped.center, width, gap);
59
+ let right = joinGroup(grouped.right, width, gap);
60
+ let leftWidth = visibleWidth(left);
61
+ let centerWidth = visibleWidth(center);
62
+ let rightWidth = visibleWidth(right);
63
+ const shrinkCenter = () => {
64
+ const budget = Math.max(0, width - leftWidth - rightWidth);
65
+ if (centerWidth > budget) {
66
+ center = clampToWidth(center, budget);
67
+ centerWidth = visibleWidth(center);
68
+ }
69
+ };
70
+ shrinkCenter();
71
+ if (leftWidth + rightWidth > width) {
72
+ const leftBudget = Math.max(0, width - rightWidth);
73
+ if (leftWidth > leftBudget) {
74
+ left = clampToWidth(left, leftBudget);
75
+ leftWidth = visibleWidth(left);
76
+ }
77
+ }
78
+ if (leftWidth + rightWidth > width) {
79
+ const rightBudget = Math.max(0, width - leftWidth);
80
+ if (rightWidth > rightBudget) {
81
+ right = clampToWidth(right, rightBudget);
82
+ rightWidth = visibleWidth(right);
83
+ }
84
+ }
85
+ const availableCenter = Math.max(0, width - leftWidth - rightWidth);
86
+ if (centerWidth > availableCenter) {
87
+ center = clampToWidth(center, availableCenter);
88
+ centerWidth = visibleWidth(center);
89
+ }
90
+ const paddingForCenter = Math.max(0, availableCenter - centerWidth);
91
+ const padLeft = Math.floor(paddingForCenter / 2);
92
+ const padRight = paddingForCenter - padLeft;
93
+ return truncateToWidth(left + " ".repeat(padLeft) + center + " ".repeat(padRight) + right, width);
94
+ }
95
+ var HorizontalLineWidget = class {
96
+ cachedWidth;
97
+ cachedLines;
98
+ cachedVersion;
99
+ constructor(getChildren, options = {}, getVersion) {
100
+ this.getChildren = getChildren;
101
+ this.options = options;
102
+ this.getVersion = getVersion;
103
+ }
104
+ invalidate() {
105
+ this.cachedWidth = void 0;
106
+ this.cachedLines = void 0;
107
+ this.cachedVersion = void 0;
108
+ }
109
+ render(width) {
110
+ const version = this.getVersion?.();
111
+ if (this.cachedLines && this.cachedWidth === width && (!this.getVersion || version != null && this.cachedVersion === version)) return this.cachedLines;
112
+ const lines = [layoutLine(this.getChildren(), width, this.options.gap ?? " ")];
113
+ this.cachedWidth = width;
114
+ this.cachedLines = lines;
115
+ this.cachedVersion = version;
116
+ return lines;
117
+ }
118
+ };
119
+ //#endregion
120
+ //#region packages/extensions/editor/index.ts
121
+ const execFileAsync = promisify(execFile);
122
+ const SEPARATOR = " · ";
123
+ const HORIZONTAL = "─";
124
+ var LabeledEditor = class extends CustomEditor {
125
+ labels = /* @__PURE__ */ new Map();
126
+ appTheme;
127
+ borderCache = {
128
+ top: null,
129
+ bottom: null
130
+ };
131
+ constructor(tui, editorTheme, keybindings, appTheme) {
132
+ super(tui, editorTheme, keybindings);
133
+ this.appTheme = appTheme;
134
+ }
135
+ /** always-dim color for box chrome (corners, lines, rails) */
136
+ dim(str) {
137
+ return this.appTheme.fg("dim", str);
138
+ }
139
+ setLabel(key, text, position = "top", align = "left") {
140
+ this.labels.set(key, {
141
+ key,
142
+ text,
143
+ position,
144
+ align
145
+ });
146
+ }
147
+ removeLabel(key) {
148
+ this.labels.delete(key);
149
+ }
150
+ getLabelsFor(position, align) {
151
+ const matching = [...this.labels.values()].filter((l) => l.position === position && l.align === align);
152
+ if (matching.length === 0) return "";
153
+ return matching.map((l) => l.text).join(SEPARATOR);
154
+ }
155
+ extractScrollIndicator(originalLine) {
156
+ if (!originalLine.includes("↑") && !originalLine.includes("↓")) return "";
157
+ const match = originalLine.match(/[↑↓]\s+\d+\s+more/);
158
+ return match ? match[0] : "";
159
+ }
160
+ /**
161
+ * build a border line like: ╭─ left label ─────── right label ─╮
162
+ *
163
+ * inherits scroll indicator text from the original border line if present.
164
+ * delegates chrome layout to boxBorderLR; caching stays here.
165
+ */
166
+ buildBorderLine(outerWidth, corner, position, originalLine) {
167
+ const leftText = this.getLabelsFor(position, "left");
168
+ const rightCombined = [this.getLabelsFor(position, "right"), this.extractScrollIndicator(originalLine)].filter(Boolean).join(SEPARATOR);
169
+ const cacheKey = `${outerWidth}|${position}|${leftText}|${rightCombined}`;
170
+ const cached = this.borderCache[position];
171
+ if (cached?.key === cacheKey) return cached.line;
172
+ const line = boxBorderLR({
173
+ corner,
174
+ style: { dim: (s) => this.dim(s) },
175
+ innerWidth: outerWidth - 2,
176
+ left: leftText ? {
177
+ text: leftText,
178
+ width: visibleWidth(leftText)
179
+ } : void 0,
180
+ right: rightCombined ? {
181
+ text: rightCombined,
182
+ width: visibleWidth(rightCombined)
183
+ } : void 0
184
+ });
185
+ this.borderCache[position] = {
186
+ key: cacheKey,
187
+ line
188
+ };
189
+ return line;
190
+ }
191
+ /**
192
+ * find the bottom border index in the lines array from super.render().
193
+ * the bottom border is a full-width line of ─ characters (possibly with a scroll indicator).
194
+ * autocomplete lines appear after it and contain mixed content (not all ─).
195
+ *
196
+ * strategy: walk backward from the end, looking for a line whose stripped content
197
+ * is predominantly ─ characters. the first such line (from the end) is the bottom border.
198
+ */
199
+ findBottomBorderIndex(lines) {
200
+ for (let i = lines.length - 1; i >= 1; i--) {
201
+ const line = lines[i];
202
+ if (!line) continue;
203
+ const stripped = line.replace(/\x1b\[[0-9;]*[mGKHJ]/g, "").replace(/\x1b_[^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\]8;;[^\x07]*\x07/g, "");
204
+ if (stripped.length > 0 && stripped[0] === HORIZONTAL) return i;
205
+ }
206
+ return lines.length - 1;
207
+ }
208
+ render(width) {
209
+ const innerWidth = width - 2;
210
+ if (innerWidth < 4) return super.render(width);
211
+ const lines = super.render(innerWidth);
212
+ if (lines.length < 2) return lines;
213
+ const bottomIdx = this.findBottomBorderIndex(lines);
214
+ const result = [];
215
+ const chrome = { dim: (s) => this.dim(s) };
216
+ result.push(this.buildBorderLine(width, {
217
+ left: "╭",
218
+ right: "╮"
219
+ }, "top", lines[0]));
220
+ for (let i = 1; i < bottomIdx; i++) result.push(boxRow({
221
+ variant: "closed",
222
+ style: chrome,
223
+ inner: lines[i]
224
+ }));
225
+ result.push(this.buildBorderLine(width, {
226
+ left: "╰",
227
+ right: "╯"
228
+ }, "bottom", lines[bottomIdx]));
229
+ for (let i = bottomIdx + 1; i < lines.length; i++) result.push(" " + lines[i] + " ");
230
+ return result;
231
+ }
232
+ };
233
+ function formatTokens(n) {
234
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
235
+ return String(n);
236
+ }
237
+ function shortenPath(cwd) {
238
+ const home = process.env.HOME || process.env.USERPROFILE || "";
239
+ if (home && cwd.startsWith(home)) return "~" + cwd.slice(home.length);
240
+ return cwd;
241
+ }
242
+ /**
243
+ * estimate context tokens from session entries using chars/4 heuristic.
244
+ * fallback when provider hasn't reported usage yet (e.g., after compaction).
245
+ */
246
+ function estimateContextFromEntries(entries) {
247
+ let total = 0;
248
+ for (const entry of entries) switch (entry.type) {
249
+ case "message":
250
+ total += estimateTokens(entry.message);
251
+ break;
252
+ case "custom_message": {
253
+ const content = entry.content;
254
+ const text = typeof content === "string" ? content : content.filter((c) => c.type === "text").map((c) => c.text).join("");
255
+ total += Math.ceil(text.length / 4);
256
+ break;
257
+ }
258
+ case "branch_summary":
259
+ if (entry.summary) total += Math.ceil(entry.summary.length / 4);
260
+ break;
261
+ case "compaction":
262
+ if (entry.summary) total += Math.ceil(entry.summary.length / 4);
263
+ break;
264
+ }
265
+ return total;
266
+ }
267
+ function updateStatsLabels(editor, pi, ctx, cachedLen) {
268
+ if (!editor) return;
269
+ const branch = ctx.sessionManager.getBranch();
270
+ if (cachedLen && branch.length === cachedLen.value) return;
271
+ if (cachedLen) cachedLen.value = branch.length;
272
+ const usage = ctx.getContextUsage();
273
+ const model = ctx.model;
274
+ let cost = 0;
275
+ for (const entry of branch) {
276
+ if (entry.type !== "message") continue;
277
+ const msg = entry.message;
278
+ if (msg.role === "assistant") cost += msg.usage?.cost?.total ?? 0;
279
+ else if (msg.role === "toolResult") {
280
+ const details = msg.details;
281
+ if (hasToolCost(details)) cost += details.cost;
282
+ }
283
+ }
284
+ const topLeftParts = [];
285
+ if (usage?.percent != null && usage.tokens != null && usage.tokens > 0) topLeftParts.push(`${Math.round(usage.percent)}% of ${formatTokens(usage.contextWindow)}`);
286
+ else if (model?.contextWindow) {
287
+ const percent = estimateContextFromEntries(branch) / model.contextWindow * 100;
288
+ topLeftParts.push(`~${Math.round(percent)}% of ${formatTokens(model.contextWindow)}`);
289
+ }
290
+ if (cost > 0) topLeftParts.push(`$${cost.toFixed(2)}`);
291
+ if (topLeftParts.length > 0) editor.setLabel("stats", topLeftParts.join(" · "), "top", "left");
292
+ const topRightParts = [];
293
+ if (model) {
294
+ const provider = model.provider ? `(${model.provider})` : "";
295
+ topRightParts.push(`${provider} ${model.id}`.trim());
296
+ }
297
+ const thinkingLevel = pi.getThinkingLevel();
298
+ if (thinkingLevel && thinkingLevel !== "off") topRightParts.push(thinkingLevel);
299
+ if (topRightParts.length > 0) editor.setLabel("model", topRightParts.join(" · "), "top", "right");
300
+ }
301
+ async function getGitDiffStats(cwd) {
302
+ try {
303
+ const { stdout } = await execFileAsync("git", ["diff", "--stat"], {
304
+ cwd,
305
+ timeout: 3e3
306
+ });
307
+ const out = stdout.trim();
308
+ if (!out) return "";
309
+ const lines = out.split("\n");
310
+ const summary = lines[lines.length - 1];
311
+ if (!summary) return "";
312
+ const filesMatch = summary.match(/(\d+)\s+files?\s+changed/);
313
+ const insMatch = summary.match(/(\d+)\s+insertions?\(\+\)/);
314
+ const delMatch = summary.match(/(\d+)\s+deletions?\(-\)/);
315
+ if (!filesMatch) return "";
316
+ const parts = [`${filesMatch[1]} files changed`];
317
+ if (insMatch) parts.push(`+${insMatch[1]}`);
318
+ if (delMatch) parts.push(`-${delMatch[1]}`);
319
+ return parts.join(" ");
320
+ } catch {
321
+ return "";
322
+ }
323
+ }
324
+ const SPINNER_FRAMES = [
325
+ "·",
326
+ "•",
327
+ "*",
328
+ "⁑",
329
+ "⁂",
330
+ "⁑",
331
+ "*",
332
+ "•",
333
+ "·"
334
+ ];
335
+ function createActivityState() {
336
+ return {
337
+ phase: "idle",
338
+ turnIndex: 0,
339
+ activeTools: /* @__PURE__ */ new Map(),
340
+ startedAt: 0,
341
+ intervalId: null,
342
+ frame: 0
343
+ };
344
+ }
345
+ function formatElapsed(ms) {
346
+ const s = Math.floor(ms / 1e3);
347
+ if (s < 60) return `${s}s`;
348
+ const m = Math.floor(s / 60);
349
+ const rem = s % 60;
350
+ return `${m}m${rem > 0 ? `${rem}s` : ""}`;
351
+ }
352
+ /**
353
+ * shorten a tool name for display: "tool_execution" → "tool_execution",
354
+ * but we also extract a meaningful arg when possible (e.g. file path).
355
+ */
356
+ function describeToolCall(toolName, args) {
357
+ const hint = args?.path ?? args?.pattern ?? args?.query ?? args?.filePattern ?? args?.cmd;
358
+ if (typeof hint === "string") return `${toolName}(${hint.includes("/") ? hint.split("/").pop() : hint.length > 24 ? hint.slice(0, 24) + "…" : hint})`;
359
+ return toolName;
360
+ }
361
+ function renderActivity(state) {
362
+ if (state.phase === "idle") return "";
363
+ const parts = [];
364
+ parts.push(SPINNER_FRAMES[state.frame % SPINNER_FRAMES.length]);
365
+ if (state.turnIndex > 0) parts.push(`turn ${state.turnIndex + 1}`);
366
+ if (state.activeTools.size > 0) {
367
+ const toolDescs = [...state.activeTools.values()];
368
+ const shown = toolDescs.slice(0, 2).join(", ");
369
+ const overflow = toolDescs.length > 2 ? ` +${toolDescs.length - 2}` : "";
370
+ parts.push(shown + overflow);
371
+ } else if (state.phase === "thinking") parts.push("thinking");
372
+ else if (state.phase === "streaming") parts.push("writing");
373
+ if (state.startedAt > 0) {
374
+ const elapsed = Date.now() - state.startedAt;
375
+ if (elapsed >= 1e3) parts.push(formatElapsed(elapsed));
376
+ }
377
+ return parts.join(" · ");
378
+ }
379
+ function editor_default(pi) {
380
+ let editor = null;
381
+ const statsCacheBranchLen = { value: -1 };
382
+ let gitBranch = null;
383
+ let branchUnsub = null;
384
+ let statusRow = null;
385
+ const activity = createActivityState();
386
+ pi.on("session_start", async (_event, ctx) => {
387
+ if (!ctx.hasUI) return;
388
+ const themeAny = ctx.ui.theme;
389
+ if (themeAny.bgColors instanceof Map) {
390
+ const transparent = "\x1B[49m";
391
+ themeAny.bgColors.set("toolPendingBg", transparent);
392
+ themeAny.bgColors.set("toolSuccessBg", transparent);
393
+ themeAny.bgColors.set("toolErrorBg", transparent);
394
+ }
395
+ ctx.ui.setEditorComponent((tui, editorTheme, keybindings) => {
396
+ editor = new LabeledEditor(tui, editorTheme, keybindings, ctx.ui.theme);
397
+ return editor;
398
+ });
399
+ ctx.ui.setFooter((tui, _theme, footerData) => {
400
+ gitBranch = footerData.getGitBranch();
401
+ branchUnsub = footerData.onBranchChange(() => {
402
+ gitBranch = footerData.getGitBranch();
403
+ updateBottomLabel();
404
+ tui.requestRender();
405
+ });
406
+ updateBottomLabel();
407
+ return {
408
+ dispose: () => {
409
+ branchUnsub?.();
410
+ branchUnsub = null;
411
+ },
412
+ invalidate() {},
413
+ render(_width) {
414
+ return [];
415
+ }
416
+ };
417
+ });
418
+ ctx.ui.setWidget("status-line", (tui) => {
419
+ statusRow = new WidgetRowRegistry(tui);
420
+ return new HorizontalLineWidget(() => statusRow.snapshot(), { gap: " " }, () => statusRow.version);
421
+ }, { placement: "belowEditor" });
422
+ function updateBottomLabel() {
423
+ if (!editor) return;
424
+ const cwd = shortenPath(ctx.cwd);
425
+ const branchText = gitBranch ? `(${gitBranch})` : "";
426
+ editor.setLabel("cwd", `${cwd} ${branchText}`.trim(), "bottom", "right");
427
+ }
428
+ updateBottomLabel();
429
+ updateStatsLabels(editor, pi, ctx, statsCacheBranchLen);
430
+ });
431
+ const ACTIVITY_SEGMENT = "activity";
432
+ const GIT_SEGMENT = "git-changes";
433
+ /** push current activity state into the widget row */
434
+ const syncActivitySegment = () => {
435
+ if (activity.phase === "idle") {
436
+ statusRow?.remove(ACTIVITY_SEGMENT);
437
+ return;
438
+ }
439
+ if (!renderActivity(activity)) return;
440
+ statusRow?.set(ACTIVITY_SEGMENT, {
441
+ align: "left",
442
+ priority: 10,
443
+ renderInline: () => renderActivity(activity)
444
+ });
445
+ };
446
+ const startSpinner = () => {
447
+ if (activity.intervalId) return;
448
+ activity.intervalId = setInterval(() => {
449
+ activity.frame = (activity.frame + 1) % SPINNER_FRAMES.length;
450
+ syncActivitySegment();
451
+ }, 150);
452
+ };
453
+ const stopSpinner = () => {
454
+ if (activity.intervalId) {
455
+ clearInterval(activity.intervalId);
456
+ activity.intervalId = null;
457
+ }
458
+ };
459
+ const updateGitSegment = (text) => {
460
+ if (!text) {
461
+ statusRow?.remove(GIT_SEGMENT);
462
+ return;
463
+ }
464
+ statusRow?.set(GIT_SEGMENT, {
465
+ align: "right",
466
+ priority: 0,
467
+ renderInline: () => text
468
+ });
469
+ };
470
+ pi.on("agent_start", async (_event, ctx) => {
471
+ ctx.ui.setWorkingMessage(" ");
472
+ activity.phase = "thinking";
473
+ activity.turnIndex = 0;
474
+ activity.activeTools.clear();
475
+ activity.startedAt = Date.now();
476
+ activity.frame = 0;
477
+ startSpinner();
478
+ syncActivitySegment();
479
+ });
480
+ pi.on("turn_start", async (event, _ctx) => {
481
+ activity.turnIndex = event.turnIndex;
482
+ activity.phase = activity.activeTools.size > 0 ? "tool" : "thinking";
483
+ syncActivitySegment();
484
+ });
485
+ pi.on("tool_execution_start", async (event, _ctx) => {
486
+ activity.phase = "tool";
487
+ activity.activeTools.set(event.toolCallId, describeToolCall(event.toolName, event.args));
488
+ syncActivitySegment();
489
+ });
490
+ pi.on("tool_execution_end", async (event, ctx) => {
491
+ activity.activeTools.delete(event.toolCallId);
492
+ activity.phase = activity.activeTools.size > 0 ? "tool" : "thinking";
493
+ syncActivitySegment();
494
+ if (editor) updateStatsLabels(editor, pi, ctx, statsCacheBranchLen);
495
+ });
496
+ pi.on("message_start", async (event, _ctx) => {
497
+ if (event.message.role === "assistant") {
498
+ activity.phase = activity.activeTools.size > 0 ? "tool" : "streaming";
499
+ syncActivitySegment();
500
+ }
501
+ });
502
+ pi.on("agent_end", async (_event, ctx) => {
503
+ stopSpinner();
504
+ activity.phase = "idle";
505
+ activity.activeTools.clear();
506
+ statusRow?.remove(ACTIVITY_SEGMENT);
507
+ if (editor) updateStatsLabels(editor, pi, ctx, statsCacheBranchLen);
508
+ updateGitSegment(await getGitDiffStats(ctx.cwd));
509
+ });
510
+ pi.events.on("editor:set-label", (data) => {
511
+ const payload = data;
512
+ if (!payload.key || !payload.text) return;
513
+ editor?.setLabel(payload.key, payload.text, payload.position ?? "top", payload.align ?? "left");
514
+ });
515
+ pi.events.on("editor:remove-label", (data) => {
516
+ const payload = data;
517
+ if (!payload.key) return;
518
+ editor?.removeLabel(payload.key);
519
+ });
520
+ pi.on("model_select", async (_event, ctx) => {
521
+ statsCacheBranchLen.value = -1;
522
+ if (editor) updateStatsLabels(editor, pi, ctx, statsCacheBranchLen);
523
+ });
524
+ pi.on("session_switch", async (_event, ctx) => {
525
+ branchUnsub?.();
526
+ branchUnsub = null;
527
+ gitBranch = null;
528
+ stopSpinner();
529
+ activity.phase = "idle";
530
+ activity.activeTools.clear();
531
+ statusRow?.clear();
532
+ statsCacheBranchLen.value = -1;
533
+ if (editor) updateStatsLabels(editor, pi, ctx, statsCacheBranchLen);
534
+ });
535
+ }
536
+ //#endregion
537
+ export { editor_default as t };
@@ -0,0 +1,12 @@
1
+ import { ExtensionAPI, ToolDefinition } from "@mariozechner/pi-coding-agent";
2
+
3
+ //#region packages/extensions/bash/index.d.ts
4
+ type BashExtConfig = {
5
+ headLines: number;
6
+ tailLines: number;
7
+ sigkillDelayMs: number;
8
+ };
9
+ declare function createBashTool(config?: BashExtConfig): ToolDefinition;
10
+ declare function export_default(pi: ExtensionAPI): void;
11
+ //#endregion
12
+ export { createBashTool, export_default as default };