@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,207 @@
1
+ //#region packages/core/box-chrome/index.ts
2
+ function boxTop(args) {
3
+ const { variant, style, innerWidth = 0, header } = args;
4
+ if (variant === "open") return header ? style.dim("╭─[") + header.text + style.dim("]") : style.dim("╭─");
5
+ if (!header) return style.dim("╭" + "─".repeat(innerWidth) + "╮");
6
+ const right = Math.max(0, innerWidth - 1 - header.width);
7
+ return style.dim("╭─") + header.text + style.dim("─".repeat(right) + "╮");
8
+ }
9
+ function boxRow(args) {
10
+ const { variant, style, inner } = args;
11
+ return variant === "closed" ? style.dim("│") + inner + style.dim("│") : style.dim("│ ") + inner;
12
+ }
13
+ /**
14
+ * Border line with left AND right labels, separated by ─ fill.
15
+ * Used by the editor for: ╭─ left ──── right ─╮ / ╰─ left ──── right ─╯
16
+ *
17
+ * Always renders a ─ after the left corner and before the right corner
18
+ * (the "edge dashes"), so labels never touch the corners directly.
19
+ * innerWidth is the space between the two corner characters.
20
+ */
21
+ function boxBorderLR(args) {
22
+ const { corner, style, innerWidth, left, right } = args;
23
+ const leftW = left?.width ?? 0;
24
+ const rightW = right?.width ?? 0;
25
+ const fill = innerWidth - 2 - leftW - rightW;
26
+ if (fill < 0) return style.dim(corner.left + "─".repeat(Math.max(0, innerWidth)) + corner.right);
27
+ return style.dim(corner.left + "─") + (left ? left.text : "") + style.dim("─".repeat(fill)) + (right ? right.text : "") + style.dim("─" + corner.right);
28
+ }
29
+ function boxBottom(args) {
30
+ const { variant, style, innerWidth = 0, footer } = args;
31
+ if (variant === "open") return style.dim("╰────");
32
+ if (!footer) return style.dim("╰" + "─".repeat(innerWidth) + "╯");
33
+ const left = Math.max(0, Math.floor((innerWidth - footer.width) / 2));
34
+ const right = Math.max(0, innerWidth - left - footer.width);
35
+ return style.dim("╰" + "─".repeat(left)) + footer.text + style.dim("─".repeat(right) + "╯");
36
+ }
37
+ if (import.meta.vitest) {
38
+ const { describe, it, expect } = import.meta.vitest;
39
+ const identity = { dim: (s) => s };
40
+ describe("boxTop", () => {
41
+ it("open variant without header", () => {
42
+ expect(boxTop({
43
+ variant: "open",
44
+ style: identity
45
+ })).toBe("╭─");
46
+ });
47
+ it("open variant with header", () => {
48
+ expect(boxTop({
49
+ variant: "open",
50
+ style: identity,
51
+ header: {
52
+ text: "title",
53
+ width: 5
54
+ }
55
+ })).toBe("╭─[title]");
56
+ });
57
+ it("closed variant without header", () => {
58
+ expect(boxTop({
59
+ variant: "closed",
60
+ style: identity,
61
+ innerWidth: 5
62
+ })).toBe("╭─────╮");
63
+ });
64
+ it("closed variant with header", () => {
65
+ expect(boxTop({
66
+ variant: "closed",
67
+ style: identity,
68
+ innerWidth: 10,
69
+ header: {
70
+ text: "hi",
71
+ width: 2
72
+ }
73
+ })).toBe("╭─hi───────╮");
74
+ });
75
+ });
76
+ describe("boxRow", () => {
77
+ it("open variant", () => {
78
+ expect(boxRow({
79
+ variant: "open",
80
+ style: identity,
81
+ inner: "text"
82
+ })).toBe("│ text");
83
+ });
84
+ it("closed variant", () => {
85
+ expect(boxRow({
86
+ variant: "closed",
87
+ style: identity,
88
+ inner: "text"
89
+ })).toBe("│text│");
90
+ });
91
+ });
92
+ describe("boxBorderLR", () => {
93
+ it("no labels", () => {
94
+ expect(boxBorderLR({
95
+ corner: {
96
+ left: "╭",
97
+ right: "╮"
98
+ },
99
+ style: identity,
100
+ innerWidth: 6
101
+ })).toBe("╭──────╮");
102
+ });
103
+ it("left label only", () => {
104
+ expect(boxBorderLR({
105
+ corner: {
106
+ left: "╭",
107
+ right: "╮"
108
+ },
109
+ style: identity,
110
+ innerWidth: 10,
111
+ left: {
112
+ text: "L",
113
+ width: 1
114
+ }
115
+ })).toBe("╭─L────────╮");
116
+ });
117
+ it("right label only", () => {
118
+ expect(boxBorderLR({
119
+ corner: {
120
+ left: "╭",
121
+ right: "╮"
122
+ },
123
+ style: identity,
124
+ innerWidth: 10,
125
+ right: {
126
+ text: "R",
127
+ width: 1
128
+ }
129
+ })).toBe("╭────────R─╮");
130
+ });
131
+ it("both labels", () => {
132
+ expect(boxBorderLR({
133
+ corner: {
134
+ left: "╭",
135
+ right: "╮"
136
+ },
137
+ style: identity,
138
+ innerWidth: 12,
139
+ left: {
140
+ text: "L",
141
+ width: 1
142
+ },
143
+ right: {
144
+ text: "R",
145
+ width: 1
146
+ }
147
+ })).toBe("╭─L────────R─╮");
148
+ });
149
+ it("overflow falls back to plain dashed line", () => {
150
+ expect(boxBorderLR({
151
+ corner: {
152
+ left: "╭",
153
+ right: "╮"
154
+ },
155
+ style: identity,
156
+ innerWidth: 4,
157
+ left: {
158
+ text: "LONG",
159
+ width: 4
160
+ },
161
+ right: {
162
+ text: "X",
163
+ width: 1
164
+ }
165
+ })).toBe("╭────╮");
166
+ });
167
+ });
168
+ describe("boxBottom", () => {
169
+ it("open variant", () => {
170
+ expect(boxBottom({
171
+ variant: "open",
172
+ style: identity
173
+ })).toBe("╰────");
174
+ });
175
+ it("closed variant without footer", () => {
176
+ expect(boxBottom({
177
+ variant: "closed",
178
+ style: identity,
179
+ innerWidth: 5
180
+ })).toBe("╰─────╯");
181
+ });
182
+ it("closed variant with centered footer", () => {
183
+ expect(boxBottom({
184
+ variant: "closed",
185
+ style: identity,
186
+ innerWidth: 10,
187
+ footer: {
188
+ text: "ok",
189
+ width: 2
190
+ }
191
+ })).toBe("╰────ok────╯");
192
+ });
193
+ it("footer centering handles odd widths", () => {
194
+ expect(boxBottom({
195
+ variant: "closed",
196
+ style: identity,
197
+ innerWidth: 9,
198
+ footer: {
199
+ text: "ok",
200
+ width: 2
201
+ }
202
+ })).toBe("╰───ok────╯");
203
+ });
204
+ });
205
+ }
206
+ //#endregion
207
+ export { boxBorderLR, boxBottom, boxRow, boxTop };
@@ -0,0 +1,82 @@
1
+ import { Excerpt } from "./show.js";
2
+ import { Component } from "@mariozechner/pi-tui";
3
+
4
+ //#region packages/core/box-format/index.d.ts
5
+ interface BoxLine {
6
+ /** optional gutter text (e.g., line number). right-aligned to gutter width. */
7
+ gutter?: string;
8
+ /** line content */
9
+ text: string;
10
+ /** when true, gutter + content render at base color instead of dim */
11
+ highlight?: boolean;
12
+ }
13
+ interface BoxBlock {
14
+ lines: BoxLine[];
15
+ }
16
+ interface BoxSection {
17
+ /** text inside ╭─[...]. omit for headless boxes (no opening line). */
18
+ header?: string;
19
+ /** contiguous blocks. gaps between blocks show · elision marker. */
20
+ blocks: BoxBlock[];
21
+ }
22
+ interface BoxWindowedOpts {
23
+ /** max sections to show (rest get "… N more" footer) */
24
+ maxSections?: number;
25
+ /**
26
+ * excerpts applied independently to each block's visual lines.
27
+ * e.g., [{ focus: "head", context: 12 }, { focus: "tail", context: 13 }]
28
+ * caps each block at 25 visual lines (head 12 + tail 13).
29
+ */
30
+ excerpts?: Excerpt[];
31
+ }
32
+ /**
33
+ * visual-line-aware box renderer.
34
+ *
35
+ * pipeline: compute chrome width → expand to visual lines at content width
36
+ * → window per-block via excerpts → render chrome around the result.
37
+ *
38
+ * wraps content to fit width
39
+ * via pi-tui Text.render(). truncateToWidth is kept as a safety net.
40
+ *
41
+ * usage:
42
+ * formatBoxesWindowed(
43
+ * sections,
44
+ * { excerpts: [{ focus: "head", context: 12 }, { focus: "tail", context: 13 }] },
45
+ * ["some notice"],
46
+ * 90,
47
+ * )
48
+ */
49
+ declare function formatBoxesWindowed(sections: BoxSection[], opts?: BoxWindowedOpts, notices?: string[], width?: number): string;
50
+ /**
51
+ * convenience: wrap a single text block in a box section with no gutter.
52
+ * all lines get highlight=true (base color) by default.
53
+ */
54
+ declare function textSection(header: string | undefined, text: string, dim?: boolean): BoxSection;
55
+ /**
56
+ * visual-line-aware boxRenderer. uses formatBoxesWindowed under the hood.
57
+ * caches by (width, expanded).
58
+ */
59
+ declare function boxRendererWindowed(buildSections: () => BoxSection[], opts: {
60
+ collapsed: BoxWindowedOpts;
61
+ expanded: BoxWindowedOpts;
62
+ }, notices?: string[], expanded?: boolean): Component & {
63
+ invalidate(): void;
64
+ };
65
+ /**
66
+ * wrap visible text in an OSC 8 terminal hyperlink.
67
+ * terminals that support OSC 8 render this as a clickable link;
68
+ * others silently ignore the sequences and show plain text.
69
+ */
70
+ declare function osc8Link(url: string, text: string): string;
71
+ /**
72
+ * standardized call-line component for renderCall.
73
+ * renders: bold(label) dim(context)
74
+ *
75
+ * usage: renderCallLine("Edit", "~/path/to/file.ts", theme)
76
+ */
77
+ declare function renderCallLine(label: string, context: string, theme: any): {
78
+ render(width: number): string[];
79
+ invalidate(): void;
80
+ };
81
+ //#endregion
82
+ export { BoxBlock, BoxLine, BoxSection, BoxWindowedOpts, type Excerpt, boxRendererWindowed, formatBoxesWindowed, osc8Link, renderCallLine, textSection };
@@ -0,0 +1,381 @@
1
+ import { boxBottom, boxTop } from "./box-chrome.js";
2
+ import { windowItems } from "./show.js";
3
+ import { Text } from "@mariozechner/pi-tui";
4
+ //#region packages/core/box-format/index.ts
5
+ /**
6
+ * box-format — shared diagnostic-style box renderer for tool output.
7
+ *
8
+ * produces miette/ariadne-style box-drawing output:
9
+ * ╭─[header text]
10
+ * 42 │ highlighted line (base-color gutter)
11
+ * 43 │ dim context line
12
+ * ·
13
+ * 100 │ another block
14
+ * ╰────
15
+ *
16
+ * chrome (╭│╰─·) renders DIM. highlighted lines get base-color
17
+ * gutter + content; non-highlighted lines are fully dim.
18
+ * tools without line numbers omit the gutter column.
19
+ *
20
+ * pipeline: callers produce BoxSection[], optionally pass Excerpt[] for
21
+ * per-block visual-line windowing. box-format computes chrome width,
22
+ * expands content to visual lines at (width - chrome), applies
23
+ * windowItems() from show.ts, then wraps the result in box chrome.
24
+ *
25
+ * IMPORTANT: all output lines are truncated to the provided `width`
26
+ * via truncateToWidth() as a safety net. the TUI will crash if any
27
+ * rendered line exceeds terminal width.
28
+ */
29
+ const DIM = "\x1B[2m";
30
+ const RST = "\x1B[0m";
31
+ /**
32
+ * ANSI-aware visible width + truncation.
33
+ * pi-tui exports these too (with better wide-char support), but we
34
+ * keep local versions so box-format works in test environments where
35
+ * pi-tui isn't available.
36
+ */
37
+ const ANSI_RE = /\x1b\[[0-9;]*m|\x1b\]8;;[^\x07]*\x07/g;
38
+ /** tab stop width — terminals default to 8 but most code uses 4 */
39
+ const TAB_WIDTH = 4;
40
+ function visibleWidth$1(text) {
41
+ const stripped = text.replace(ANSI_RE, "");
42
+ let w = 0;
43
+ for (const ch of stripped) w += ch === " " ? TAB_WIDTH : 1;
44
+ return w;
45
+ }
46
+ function truncateToWidth$1(text, maxWidth, ellipsis = "…") {
47
+ if (visibleWidth$1(text) <= maxWidth) return text;
48
+ const target = maxWidth - ellipsis.length;
49
+ if (target <= 0) return ellipsis.slice(0, maxWidth);
50
+ let visible = 0;
51
+ let i = 0;
52
+ while (i < text.length && visible < target) {
53
+ if (text[i] === "\x1B" && text[i + 1] === "[") {
54
+ const end = text.indexOf("m", i);
55
+ if (end !== -1) {
56
+ i = end + 1;
57
+ continue;
58
+ }
59
+ }
60
+ if (text[i] === "\x1B" && text[i + 1] === "]") {
61
+ const end = text.indexOf("\x07", i);
62
+ if (end !== -1) {
63
+ i = end + 1;
64
+ continue;
65
+ }
66
+ }
67
+ visible += text[i] === " " ? TAB_WIDTH : 1;
68
+ i++;
69
+ }
70
+ return text.slice(0, i) + RST + ellipsis;
71
+ }
72
+ /**
73
+ * defensive padding subtracted from width before truncating.
74
+ * the pi TUI passes the content-area width to render(), but
75
+ * border/padding chars can still cause off-by-one wrapping
76
+ * that eats subsequent lines. 2 chars is conservative enough
77
+ * to prevent wrapping without wasting visible space.
78
+ */
79
+ const WIDTH_SAFETY_MARGIN = 2;
80
+ /**
81
+ * expand a block's logical BoxLine[] to visual lines at contentWidth.
82
+ * wrapping is done by pi-tui's Text.render(). first visual line of
83
+ * a wrapped logical line gets the gutter; continuation lines get "".
84
+ */
85
+ function expandBlock(block, contentWidth) {
86
+ const result = [];
87
+ for (const line of block.lines) {
88
+ const visualLines = contentWidth > 0 ? new Text(line.text, 0, 0).render(contentWidth) : [line.text];
89
+ for (let i = 0; i < visualLines.length; i++) {
90
+ const visualLine = visualLines[i];
91
+ if (visualLine === void 0) continue;
92
+ result.push({
93
+ text: visualLine,
94
+ gutter: i === 0 ? line.gutter ?? "" : "",
95
+ highlight: line.highlight ?? false,
96
+ isElision: false,
97
+ isGap: false
98
+ });
99
+ }
100
+ }
101
+ return result;
102
+ }
103
+ /**
104
+ * compute the chrome prefix width for a given gutter width.
105
+ * with gutter: " 42 │ " = gutterWidth + 3
106
+ * without: "│ " = 2
107
+ */
108
+ function chromeWidth(gutterWidth) {
109
+ return gutterWidth > 0 ? gutterWidth + 3 : 2;
110
+ }
111
+ /**
112
+ * visual-line-aware box renderer.
113
+ *
114
+ * pipeline: compute chrome width → expand to visual lines at content width
115
+ * → window per-block via excerpts → render chrome around the result.
116
+ *
117
+ * wraps content to fit width
118
+ * via pi-tui Text.render(). truncateToWidth is kept as a safety net.
119
+ *
120
+ * usage:
121
+ * formatBoxesWindowed(
122
+ * sections,
123
+ * { excerpts: [{ focus: "head", context: 12 }, { focus: "tail", context: 13 }] },
124
+ * ["some notice"],
125
+ * 90,
126
+ * )
127
+ */
128
+ function formatBoxesWindowed(sections, opts = {}, notices, width) {
129
+ const maxSections = opts.maxSections ?? sections.length;
130
+ const excerpts = opts.excerpts ?? [];
131
+ const shown = sections.slice(0, maxSections);
132
+ const out = [];
133
+ const safeWidth = width != null ? Math.max(1, width - WIDTH_SAFETY_MARGIN) : void 0;
134
+ const clamp = (line) => safeWidth != null ? truncateToWidth$1(line, safeWidth, "…") : line;
135
+ const chrome = { dim: (s) => `${DIM}${s}${RST}` };
136
+ for (let si = 0; si < shown.length; si++) {
137
+ const section = shown[si];
138
+ if (!section) continue;
139
+ const allGutters = section.blocks.flatMap((b) => b.lines.map((l) => l.gutter ?? ""));
140
+ const gw = Math.max(0, ...allGutters.map((g) => g.length));
141
+ const pad = " ".repeat(gw);
142
+ const cw = chromeWidth(gw);
143
+ const contentWidth = safeWidth != null ? Math.max(1, safeWidth - cw) : 80;
144
+ if (si > 0) out.push("");
145
+ if (section.header != null) out.push(clamp(boxTop({
146
+ variant: "open",
147
+ style: chrome,
148
+ header: {
149
+ text: section.header,
150
+ width: section.header.replace(/\x1b\[[0-9;]*m/g, "").length
151
+ }
152
+ })));
153
+ for (let bi = 0; bi < section.blocks.length; bi++) {
154
+ const block = section.blocks[bi];
155
+ if (!block) continue;
156
+ if (bi > 0) out.push(gw > 0 ? `${DIM}${pad} ·${RST}` : `${DIM}·${RST}`);
157
+ const expanded = expandBlock(block, contentWidth);
158
+ const windowed = excerpts.length > 0 ? windowItems(expanded, excerpts, (count) => ({
159
+ text: `· ··· ${count} more lines`,
160
+ gutter: "",
161
+ highlight: false,
162
+ isElision: true,
163
+ isGap: false
164
+ })) : {
165
+ items: expanded,
166
+ skippedRanges: []
167
+ };
168
+ if (windowed.skippedRanges.length > 0);
169
+ for (const vl of windowed.items) if (vl.isElision) {
170
+ const prefix = gw > 0 ? `${pad} ` : "";
171
+ out.push(`${DIM}${prefix}${vl.text}${RST}`);
172
+ } else if (gw > 0) {
173
+ const gutter = vl.gutter.padStart(gw);
174
+ if (vl.highlight) out.push(clamp(`${gutter} ${DIM}│${RST} ${vl.text}`));
175
+ else out.push(clamp(`${DIM}${gutter} │ ${vl.text}${RST}`));
176
+ } else if (vl.highlight) out.push(clamp(`${DIM}│${RST} ${vl.text}`));
177
+ else out.push(clamp(`${DIM}│ ${vl.text}${RST}`));
178
+ }
179
+ out.push(boxBottom({
180
+ variant: "open",
181
+ style: chrome
182
+ }));
183
+ }
184
+ if (sections.length > maxSections) {
185
+ const rem = sections.length - maxSections;
186
+ out.push(`${DIM}… ${rem} more${RST}`);
187
+ }
188
+ if (notices?.length) {
189
+ out.push("");
190
+ out.push(clamp(`${DIM}[${notices.join(". ")}]${RST}`));
191
+ }
192
+ return out.join("\n");
193
+ }
194
+ /**
195
+ * convenience: wrap a single text block in a box section with no gutter.
196
+ * all lines get highlight=true (base color) by default.
197
+ */
198
+ function textSection(header, text, dim = false) {
199
+ return {
200
+ ...header != null && { header },
201
+ blocks: [{ lines: text.split("\n").map((line) => ({
202
+ text: line,
203
+ highlight: !dim
204
+ })) }]
205
+ };
206
+ }
207
+ /**
208
+ * visual-line-aware boxRenderer. uses formatBoxesWindowed under the hood.
209
+ * caches by (width, expanded).
210
+ */
211
+ function boxRendererWindowed(buildSections, opts, notices, expanded = false) {
212
+ let cachedWidth;
213
+ let cachedExpanded;
214
+ let cachedLines;
215
+ return {
216
+ render(width) {
217
+ if (cachedLines !== void 0 && cachedExpanded === expanded && cachedWidth === width) return cachedLines;
218
+ cachedLines = formatBoxesWindowed(buildSections(), expanded ? opts.expanded : opts.collapsed, notices, width).split("\n");
219
+ cachedExpanded = expanded;
220
+ cachedWidth = width;
221
+ return cachedLines;
222
+ },
223
+ invalidate() {
224
+ cachedLines = void 0;
225
+ cachedExpanded = void 0;
226
+ cachedWidth = void 0;
227
+ }
228
+ };
229
+ }
230
+ /**
231
+ * wrap visible text in an OSC 8 terminal hyperlink.
232
+ * terminals that support OSC 8 render this as a clickable link;
233
+ * others silently ignore the sequences and show plain text.
234
+ */
235
+ function osc8Link(url, text) {
236
+ return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
237
+ }
238
+ /**
239
+ * standardized call-line component for renderCall.
240
+ * renders: bold(label) dim(context)
241
+ *
242
+ * usage: renderCallLine("Edit", "~/path/to/file.ts", theme)
243
+ */
244
+ function renderCallLine(label, context, theme) {
245
+ const line = theme.fg("toolTitle", theme.bold(label)) + (context ? " " + theme.fg("dim", context) : "");
246
+ return {
247
+ render(_width) {
248
+ return [line];
249
+ },
250
+ invalidate() {}
251
+ };
252
+ }
253
+ if (import.meta.vitest) {
254
+ const { describe, it, expect } = import.meta.vitest;
255
+ describe("visibleWidth", () => {
256
+ it("counts plain text characters", () => {
257
+ expect(visibleWidth$1("hello")).toBe(5);
258
+ expect(visibleWidth$1("")).toBe(0);
259
+ });
260
+ it("expands tabs to TAB_WIDTH", () => {
261
+ expect(visibleWidth$1(" ")).toBe(4);
262
+ expect(visibleWidth$1("a b")).toBe(6);
263
+ });
264
+ it("ignores ANSI SGR sequences", () => {
265
+ expect(visibleWidth$1("\x1B[31mred\x1B[0m")).toBe(3);
266
+ expect(visibleWidth$1("\x1B[1;32mgreen\x1B[0m")).toBe(5);
267
+ });
268
+ it("ignores OSC 8 hyperlink sequences", () => {
269
+ expect(visibleWidth$1("\x1B]8;;https://example.com\x07link\x1B]8;;\x07")).toBe(4);
270
+ });
271
+ });
272
+ describe("truncateToWidth", () => {
273
+ it("returns text unchanged if within width", () => {
274
+ expect(truncateToWidth$1("hello", 10)).toBe("hello");
275
+ });
276
+ it("truncates and adds ellipsis", () => {
277
+ const result = truncateToWidth$1("hello world", 8);
278
+ expect(result).toContain("…");
279
+ expect(visibleWidth$1(result.replace(/\x1b\[[0-9;]*m/g, ""))).toBe(8);
280
+ });
281
+ it("preserves ANSI codes in truncated output", () => {
282
+ const result = truncateToWidth$1("\x1B[31mhello world\x1B[0m", 8);
283
+ expect(result).toContain("\x1B[31m");
284
+ expect(result).toContain("\x1B[0m");
285
+ expect(result).toContain("…");
286
+ });
287
+ it("handles text shorter than ellipsis", () => {
288
+ expect(truncateToWidth$1("hi", 1)).toBe("…");
289
+ });
290
+ });
291
+ describe("chromeWidth", () => {
292
+ it("returns 2 for no gutter", () => {
293
+ expect(chromeWidth(0)).toBe(2);
294
+ });
295
+ it("returns gutterWidth + 3 for gutter", () => {
296
+ expect(chromeWidth(3)).toBe(6);
297
+ expect(chromeWidth(5)).toBe(8);
298
+ });
299
+ });
300
+ describe("osc8Link", () => {
301
+ it("wraps text in OSC 8 hyperlink", () => {
302
+ expect(osc8Link("https://example.com", "click me")).toBe("\x1B]8;;https://example.com\x07click me\x1B]8;;\x07");
303
+ });
304
+ it("handles empty text", () => {
305
+ expect(osc8Link("https://example.com", "")).toBe("\x1B]8;;https://example.com\x07\x1B]8;;\x07");
306
+ });
307
+ });
308
+ describe("textSection", () => {
309
+ it("creates section with header and text", () => {
310
+ const section = textSection("Title", "line1\nline2");
311
+ expect(section.header).toBe("Title");
312
+ expect(section.blocks).toHaveLength(1);
313
+ expect(section.blocks[0]?.lines).toHaveLength(2);
314
+ });
315
+ it("creates headless section when header is undefined", () => {
316
+ expect(textSection(void 0, "content").header).toBeUndefined();
317
+ });
318
+ it("sets highlight=true by default", () => {
319
+ expect(textSection("Title", "text").blocks[0]?.lines[0]?.highlight).toBe(true);
320
+ });
321
+ it("sets highlight=false when dim=true", () => {
322
+ expect(textSection("Title", "text", true).blocks[0]?.lines[0]?.highlight).toBe(false);
323
+ });
324
+ });
325
+ describe("formatBoxesWindowed", () => {
326
+ it("renders single section with header", () => {
327
+ const result = formatBoxesWindowed([{
328
+ header: "Test",
329
+ blocks: [{ lines: [{ text: "hello" }] }]
330
+ }], {}, void 0, 80);
331
+ expect(result).toContain("Test");
332
+ expect(result).toContain("hello");
333
+ });
334
+ it("renders gutter with line numbers", () => {
335
+ const result = formatBoxesWindowed([{
336
+ header: "File",
337
+ blocks: [{ lines: [{
338
+ gutter: "1",
339
+ text: "first",
340
+ highlight: true
341
+ }, {
342
+ gutter: "2",
343
+ text: "second",
344
+ highlight: false
345
+ }] }]
346
+ }], {}, void 0, 80);
347
+ expect(result).toContain("1");
348
+ expect(result).toContain("2");
349
+ expect(result).toContain("first");
350
+ expect(result).toContain("second");
351
+ });
352
+ it("respects maxSections option", () => {
353
+ const result = formatBoxesWindowed([
354
+ {
355
+ header: "A",
356
+ blocks: [{ lines: [{ text: "a" }] }]
357
+ },
358
+ {
359
+ header: "B",
360
+ blocks: [{ lines: [{ text: "b" }] }]
361
+ },
362
+ {
363
+ header: "C",
364
+ blocks: [{ lines: [{ text: "c" }] }]
365
+ }
366
+ ], { maxSections: 2 }, void 0, 80);
367
+ expect(result).toContain("A");
368
+ expect(result).toContain("B");
369
+ expect(result).not.toContain("C");
370
+ expect(result).toContain("… 1 more");
371
+ });
372
+ it("appends notices", () => {
373
+ expect(formatBoxesWindowed([{
374
+ header: "Test",
375
+ blocks: [{ lines: [{ text: "content" }] }]
376
+ }], {}, ["Notice 1", "Notice 2"], 80)).toContain("[Notice 1. Notice 2]");
377
+ });
378
+ });
379
+ }
380
+ //#endregion
381
+ export { boxRendererWindowed, formatBoxesWindowed, osc8Link, renderCallLine, textSection };
@@ -0,0 +1,14 @@
1
+ //#region packages/core/config/index.d.ts
2
+ declare function setGlobalSettingsPath(p: string): void;
3
+ declare function clearConfigCache(): void;
4
+ interface GetExtensionConfigOpts {
5
+ cwd?: string;
6
+ allowProjectConfig?: boolean;
7
+ }
8
+ declare function getExtensionConfig<T extends Record<string, unknown>>(namespace: string, defaults: T, opts?: GetExtensionConfigOpts): T;
9
+ /** read a top-level (non-namespaced) key from the global settings file. */
10
+ declare function getGlobalConfig<T>(key: string): T | undefined;
11
+ /** directory containing the global settings file (e.g. ~/.pi/agent/). */
12
+ declare function resolveConfigDir(): string;
13
+ //#endregion
14
+ export { GetExtensionConfigOpts, clearConfigCache, getExtensionConfig, getGlobalConfig, resolveConfigDir, setGlobalSettingsPath };