@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,26 @@
1
+ import { createRequire } from "node:module";
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
21
+ value: mod,
22
+ enumerable: true
23
+ }) : target, mod));
24
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
25
+ //#endregion
26
+ export { __require as n, __toESM as r, __commonJSMin as t };
@@ -0,0 +1,406 @@
1
+ import { boxBottom, boxRow, boxTop } from "./core/box-chrome.js";
2
+ import { CURSOR_MARKER, Key, fuzzyFilter, matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
3
+ //#region packages/extensions/command-palette/adapters.ts
4
+ function buildModelView(pi, ctx) {
5
+ return {
6
+ title: "Select Model",
7
+ items: ctx.modelRegistry.getAvailable().map((model) => ({
8
+ id: `model:${model.provider}:${model.id}`,
9
+ label: model.name,
10
+ category: model.provider,
11
+ onSelect: async (actx) => {
12
+ if (!await actx.pi.setModel(model)) actx.ctx.ui.notify("No API key for this model", "error");
13
+ actx.close();
14
+ }
15
+ }))
16
+ };
17
+ }
18
+ function buildThinkingView(pi) {
19
+ const levels = [
20
+ "off",
21
+ "minimal",
22
+ "low",
23
+ "medium",
24
+ "high",
25
+ "xhigh"
26
+ ];
27
+ const current = pi.getThinkingLevel();
28
+ return {
29
+ title: "Thinking Level",
30
+ items: levels.map((level) => ({
31
+ id: `thinking:${level}`,
32
+ label: `${level === current ? "✓ " : " "}${level}`,
33
+ category: "setting",
34
+ onSelect: (actx) => {
35
+ actx.pi.setThinkingLevel(level);
36
+ actx.close();
37
+ }
38
+ })),
39
+ searchable: false
40
+ };
41
+ }
42
+ function buildToolsView(pi) {
43
+ const all = pi.getAllTools();
44
+ const active = new Set(pi.getActiveTools());
45
+ return {
46
+ title: "Toggle Tools",
47
+ items: all.map((tool) => ({
48
+ id: `tool:${tool.name}`,
49
+ label: `${active.has(tool.name) ? "✓" : "○"} ${tool.name}`,
50
+ description: tool.description,
51
+ category: "tool",
52
+ onSelect: (actx) => {
53
+ const currentActive = new Set(actx.pi.getActiveTools());
54
+ if (currentActive.has(tool.name)) currentActive.delete(tool.name);
55
+ else currentActive.add(tool.name);
56
+ actx.pi.setActiveTools([...currentActive]);
57
+ actx.push(buildToolsView(actx.pi));
58
+ }
59
+ }))
60
+ };
61
+ }
62
+ function fireAndForget(id, label, description, action) {
63
+ return {
64
+ id,
65
+ label,
66
+ description,
67
+ category: "cmd",
68
+ onSelect: async (actx) => {
69
+ await action(actx);
70
+ actx.close();
71
+ }
72
+ };
73
+ }
74
+ function delegate(name, description) {
75
+ return {
76
+ id: `builtin:${name}`,
77
+ label: name,
78
+ description,
79
+ category: "cmd",
80
+ delegate: true,
81
+ onSelect: (actx) => {
82
+ actx.close();
83
+ actx.pi.sendUserMessage(`/${name}`);
84
+ }
85
+ };
86
+ }
87
+ function drillDown(id, label, description, viewFn) {
88
+ return {
89
+ id,
90
+ label,
91
+ description,
92
+ category: "cmd",
93
+ onSelect: (actx) => {
94
+ actx.push(viewFn());
95
+ }
96
+ };
97
+ }
98
+ function buildRootView(pi, ctx) {
99
+ const items = [];
100
+ items.push(fireAndForget("builtin:new", "new", "Start a new session", (actx) => {
101
+ actx.pi.sendUserMessage("/new");
102
+ }), fireAndForget("builtin:fork", "fork", "Fork current session", (actx) => {
103
+ actx.pi.sendUserMessage("/fork");
104
+ }), fireAndForget("builtin:copy", "copy", "Copy last message to clipboard", (actx) => {
105
+ actx.pi.sendUserMessage("/copy");
106
+ }), fireAndForget("builtin:reload", "reload", "Reload extensions, skills, prompts", (actx) => {
107
+ actx.pi.sendUserMessage("/reload");
108
+ }), fireAndForget("builtin:quit", "quit", "Quit pi", (actx) => {
109
+ actx.ctx.shutdown();
110
+ }), fireAndForget("builtin:exit", "exit", "Quit pi", (actx) => {
111
+ actx.ctx.shutdown();
112
+ }));
113
+ items.push(drillDown("builtin:model", "model", "Switch models", () => buildModelView(pi, ctx)), drillDown("setting:thinking", "thinking", "Set thinking level", () => buildThinkingView(pi)), drillDown("setting:tools", "tools", "Toggle active tools", () => buildToolsView(pi)));
114
+ items.push(delegate("name", "Set session display name"), delegate("compact", "Manually compact context"), delegate("export", "Export session to HTML file"));
115
+ items.push(delegate("login", "OAuth authentication"), delegate("logout", "OAuth authentication"), delegate("scoped-models", "Enable/disable models for Ctrl+P cycling"), delegate("settings", "Thinking level, theme, message delivery, transport"), delegate("resume", "Pick from previous sessions"), delegate("session", "Show session info"), delegate("tree", "Jump to any point in the session"), delegate("share", "Upload as private GitHub gist"), delegate("hotkeys", "Show all keyboard shortcuts"), delegate("changelog", "Display version history"));
116
+ const commands = pi.getCommands();
117
+ const sourceCategory = {
118
+ extension: "ext",
119
+ skill: "skill",
120
+ prompt: "tpl"
121
+ };
122
+ for (const cmd of commands) {
123
+ if (cmd.name === "palette") continue;
124
+ const cat = sourceCategory[cmd.source] ?? "ext";
125
+ items.push({
126
+ id: `${cmd.source}:${cmd.name}`,
127
+ label: cmd.name,
128
+ description: cmd.description,
129
+ category: cat,
130
+ onSelect: (actx) => {
131
+ actx.pi.sendUserMessage(`/${cmd.name}`);
132
+ actx.close();
133
+ }
134
+ });
135
+ }
136
+ const order = {
137
+ cmd: 0,
138
+ setting: 0,
139
+ ext: 1,
140
+ tpl: 2,
141
+ skill: 3
142
+ };
143
+ items.sort((a, b) => {
144
+ const so = (order[a.category ?? ""] ?? 99) - (order[b.category ?? ""] ?? 99);
145
+ if (so !== 0) return so;
146
+ return a.label.localeCompare(b.label);
147
+ });
148
+ return {
149
+ title: "Command Palette",
150
+ items
151
+ };
152
+ }
153
+ //#endregion
154
+ //#region packages/extensions/command-palette/palette.ts
155
+ const MAX_VISIBLE = 12;
156
+ /**
157
+ * Stack-based generic list overlay.
158
+ * Renders whichever PaletteView sits on top of the stack.
159
+ * Items control navigation via the PaletteActionContext passed to onSelect.
160
+ */
161
+ var StackPalette = class {
162
+ stack;
163
+ searchText = "";
164
+ filtered;
165
+ highlightedIndex = 0;
166
+ scrollOffset = 0;
167
+ cachedLines;
168
+ cachedWidth;
169
+ _focused = false;
170
+ get focused() {
171
+ return this._focused;
172
+ }
173
+ set focused(value) {
174
+ this._focused = value;
175
+ }
176
+ constructor(initialView, theme, pi, extensionCtx, done) {
177
+ this.initialView = initialView;
178
+ this.theme = theme;
179
+ this.pi = pi;
180
+ this.extensionCtx = extensionCtx;
181
+ this.done = done;
182
+ this.stack = [initialView];
183
+ this.filtered = [...initialView.items];
184
+ }
185
+ handleInput(data) {
186
+ if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
187
+ if (this.stack.length > 1) {
188
+ this.stack.pop();
189
+ this.resetView();
190
+ this.invalidate();
191
+ } else this.done();
192
+ return;
193
+ }
194
+ if (matchesKey(data, Key.enter)) {
195
+ const item = this.filtered[this.highlightedIndex];
196
+ if (item) {
197
+ const actionCtx = {
198
+ pi: this.pi,
199
+ ctx: this.extensionCtx,
200
+ push: (view) => {
201
+ this.stack.push(view);
202
+ this.resetView();
203
+ this.invalidate();
204
+ },
205
+ close: () => this.done()
206
+ };
207
+ item.onSelect(actionCtx);
208
+ }
209
+ return;
210
+ }
211
+ if (matchesKey(data, Key.up) || matchesKey(data, Key.ctrl("p"))) {
212
+ this.highlightedIndex = Math.max(0, this.highlightedIndex - 1);
213
+ this.ensureVisible();
214
+ this.invalidate();
215
+ return;
216
+ }
217
+ if (matchesKey(data, Key.down) || matchesKey(data, Key.ctrl("n"))) {
218
+ this.highlightedIndex = Math.min(this.filtered.length - 1, this.highlightedIndex + 1);
219
+ this.ensureVisible();
220
+ this.invalidate();
221
+ return;
222
+ }
223
+ if (matchesKey(data, Key.backspace)) {
224
+ if (this.searchText.length > 0) {
225
+ this.searchText = this.searchText.slice(0, -1);
226
+ this.applyFilter();
227
+ this.invalidate();
228
+ }
229
+ return;
230
+ }
231
+ if (data.length >= 1 && !data.startsWith("\x1B") && data.charCodeAt(0) >= 32) {
232
+ this.searchText += data;
233
+ this.applyFilter();
234
+ this.invalidate();
235
+ }
236
+ }
237
+ render(width) {
238
+ if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
239
+ const th = this.theme;
240
+ const view = this.currentView();
241
+ const innerW = Math.min(width, 72) - 2;
242
+ const lines = [];
243
+ const dim = (s) => th.fg("dim", s);
244
+ const pad = (s, len) => {
245
+ const vis = visibleWidth(s);
246
+ return s + " ".repeat(Math.max(0, len - vis));
247
+ };
248
+ const chrome = { dim };
249
+ const row = (content) => boxRow({
250
+ variant: "closed",
251
+ style: chrome,
252
+ inner: pad(content, innerW)
253
+ });
254
+ const showTitle = this.stack.length > 1;
255
+ const headerText = showTitle ? dim(`[${view.title}]`) : void 0;
256
+ const headerWidth = showTitle ? visibleWidth(`[${view.title}]`) : 0;
257
+ lines.push(boxTop({
258
+ variant: "closed",
259
+ style: chrome,
260
+ innerWidth: innerW,
261
+ header: showTitle ? {
262
+ text: headerText,
263
+ width: headerWidth
264
+ } : void 0
265
+ }));
266
+ if (view.searchable !== false) {
267
+ const prompt = dim(" > ");
268
+ const searchDisplay = th.fg("text", this.searchText);
269
+ const cursor = this._focused ? CURSOR_MARKER + th.fg("accent", "▏") : dim("▏");
270
+ const placeholder = this.searchText.length === 0 ? dim("type to search…") : "";
271
+ lines.push(row(prompt + searchDisplay + cursor + placeholder));
272
+ lines.push(row(""));
273
+ }
274
+ if (this.filtered.length === 0) lines.push(row(dim(" no matches")));
275
+ else {
276
+ const visibleEnd = Math.min(this.scrollOffset + MAX_VISIBLE, this.filtered.length);
277
+ const maxBadgeW = this.filtered.reduce((max, item) => {
278
+ return Math.max(max, item.category ? visibleWidth(item.category) : 0);
279
+ }, 0);
280
+ if (this.scrollOffset > 0) lines.push(row(dim(` ↑ ${this.scrollOffset} more`)));
281
+ for (let i = this.scrollOffset; i < visibleEnd; i++) {
282
+ const item = this.filtered[i];
283
+ if (!item) continue;
284
+ const isHl = i === this.highlightedIndex;
285
+ let badge = "";
286
+ if (maxBadgeW > 0) {
287
+ const cat = item.category ?? "";
288
+ const padLen = maxBadgeW - visibleWidth(cat);
289
+ badge = " ".repeat(padLen) + dim(cat) + " ";
290
+ }
291
+ const label = isHl ? th.bold(item.label) : th.fg("text", item.label);
292
+ const delegateMarker = item.delegate ? dim(" *") : "";
293
+ let desc = "";
294
+ if (item.description) desc = " " + dim(item.description);
295
+ let shortcut = "";
296
+ if (item.shortcut) shortcut = " " + th.fg("muted", item.shortcut);
297
+ let line = ` ${badge}${label}${delegateMarker}${desc}${shortcut}`;
298
+ line = truncateToWidth(line, innerW);
299
+ if (isHl) line = th.bg("selectedBg", pad(line, innerW));
300
+ lines.push(boxRow({
301
+ variant: "closed",
302
+ style: chrome,
303
+ inner: isHl ? line : pad(line, innerW)
304
+ }));
305
+ }
306
+ const remaining = this.filtered.length - visibleEnd;
307
+ if (remaining > 0) lines.push(row(dim(` ↓ ${remaining} more`)));
308
+ }
309
+ const escHint = this.stack.length > 1 ? "esc back" : "esc close";
310
+ const hasDelegates = this.filtered.some((item) => item.delegate);
311
+ const footerParts = [
312
+ `↑↓ navigate`,
313
+ `enter select`,
314
+ escHint
315
+ ];
316
+ if (hasDelegates) footerParts.push("* opens native ui");
317
+ const footerStr = dim(footerParts.join(" • "));
318
+ const footerWidth = visibleWidth(footerStr);
319
+ lines.push(boxBottom({
320
+ variant: "closed",
321
+ style: chrome,
322
+ innerWidth: innerW,
323
+ footer: {
324
+ text: footerStr,
325
+ width: footerWidth
326
+ }
327
+ }));
328
+ this.cachedLines = lines;
329
+ this.cachedWidth = width;
330
+ return lines;
331
+ }
332
+ invalidate() {
333
+ this.cachedLines = void 0;
334
+ this.cachedWidth = void 0;
335
+ }
336
+ currentView() {
337
+ const view = this.stack[this.stack.length - 1];
338
+ if (!view) throw new Error("No view in stack");
339
+ return view;
340
+ }
341
+ resetView() {
342
+ this.searchText = "";
343
+ this.highlightedIndex = 0;
344
+ this.scrollOffset = 0;
345
+ this.filtered = [...this.currentView().items];
346
+ }
347
+ applyFilter() {
348
+ const view = this.currentView();
349
+ if (this.searchText === "" || view.searchable === false) this.filtered = [...view.items];
350
+ else this.filtered = fuzzyFilter(view.items, this.searchText, (item) => `${item.label} ${item.description ?? ""} ${item.category ?? ""}`);
351
+ this.highlightedIndex = 0;
352
+ this.scrollOffset = 0;
353
+ }
354
+ ensureVisible() {
355
+ if (this.highlightedIndex < this.scrollOffset) this.scrollOffset = this.highlightedIndex;
356
+ else if (this.highlightedIndex >= this.scrollOffset + MAX_VISIBLE) this.scrollOffset = this.highlightedIndex - MAX_VISIBLE + 1;
357
+ }
358
+ };
359
+ //#endregion
360
+ //#region packages/extensions/command-palette/index.ts
361
+ function commandPaletteExtension(pi) {
362
+ async function openPalette(ctx) {
363
+ if (!ctx.hasUI) return;
364
+ const rootView = buildRootView(pi, ctx);
365
+ await ctx.ui.custom((tui, theme, _kb, done) => {
366
+ const palette = new StackPalette(rootView, theme, pi, ctx, done);
367
+ return {
368
+ render: (w) => palette.render(w),
369
+ handleInput: (data) => {
370
+ palette.handleInput(data);
371
+ tui.requestRender();
372
+ },
373
+ invalidate: () => palette.invalidate(),
374
+ get focused() {
375
+ return palette.focused;
376
+ },
377
+ set focused(v) {
378
+ palette.focused = v;
379
+ }
380
+ };
381
+ }, {
382
+ overlay: true,
383
+ overlayOptions: {
384
+ anchor: "top-center",
385
+ width: 72,
386
+ minWidth: 40,
387
+ maxHeight: "60%",
388
+ offsetY: 2
389
+ }
390
+ });
391
+ }
392
+ pi.registerShortcut("ctrl+p", {
393
+ description: "Open command palette",
394
+ handler: async (ctx) => {
395
+ await openPalette(ctx);
396
+ }
397
+ });
398
+ pi.registerCommand("palette", {
399
+ description: "Open command palette",
400
+ handler: async (_args, ctx) => {
401
+ await openPalette(ctx);
402
+ }
403
+ });
404
+ }
405
+ //#endregion
406
+ export { commandPaletteExtension as t };
@@ -0,0 +1,22 @@
1
+ //#region packages/core/agents-md/index.d.ts
2
+ interface AgentsGuidance {
3
+ /** absolute path to the AGENTS.md file */
4
+ path: string;
5
+ /** file contents */
6
+ content: string;
7
+ /** human-readable scope: "global", "project", or relative dir path */
8
+ scope: string;
9
+ }
10
+ /**
11
+ * discover AGENTS.md files relevant to `filePath`, from broadest to
12
+ * most specific scope. walks from workspace root down to the file's
13
+ * directory, prepending the global config if it exists.
14
+ */
15
+ declare function discoverAgentsMd(filePath: string, workspaceRoot: string): AgentsGuidance[];
16
+ /**
17
+ * format discovered guidance for injection into tool results.
18
+ * output format: header with scope, then content.
19
+ */
20
+ declare function formatGuidance(guidance: AgentsGuidance[]): string;
21
+ //#endregion
22
+ export { AgentsGuidance, discoverAgentsMd, formatGuidance };
@@ -0,0 +1,174 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ //#region packages/core/agents-md/index.ts
5
+ /**
6
+ * AGENTS.md discovery — walks directory hierarchy to find guidance files.
7
+ *
8
+ * injects AGENTS.md contents into tool results after file reads/writes
9
+ * so the LLM sees directory-specific instructions. guidance files closer
10
+ * to the target file take precedence (returned last in array).
11
+ *
12
+ * also checks the user's global AGENTS.md at ~/.config/pi/AGENTS.md.
13
+ */
14
+ const FILENAME = "AGENTS.md";
15
+ const GLOBAL_PATH = path.join(os.homedir(), ".config", "pi", FILENAME);
16
+ /**
17
+ * discover AGENTS.md files relevant to `filePath`, from broadest to
18
+ * most specific scope. walks from workspace root down to the file's
19
+ * directory, prepending the global config if it exists.
20
+ */
21
+ function discoverAgentsMd(filePath, workspaceRoot) {
22
+ const results = [];
23
+ const root = path.resolve(workspaceRoot);
24
+ if (fs.existsSync(GLOBAL_PATH)) try {
25
+ results.push({
26
+ path: GLOBAL_PATH,
27
+ content: fs.readFileSync(GLOBAL_PATH, "utf-8"),
28
+ scope: "global"
29
+ });
30
+ } catch {}
31
+ const dirResults = [];
32
+ let dir = path.dirname(path.resolve(filePath));
33
+ while (dir.startsWith(root) || dir === root) {
34
+ const candidate = path.join(dir, FILENAME);
35
+ if (fs.existsSync(candidate)) try {
36
+ const content = fs.readFileSync(candidate, "utf-8");
37
+ const scope = dir === root ? "project" : path.relative(root, dir);
38
+ dirResults.push({
39
+ path: candidate,
40
+ content,
41
+ scope
42
+ });
43
+ } catch {}
44
+ const parent = path.dirname(dir);
45
+ if (parent === dir) break;
46
+ dir = parent;
47
+ }
48
+ results.push(...dirResults.reverse());
49
+ return results;
50
+ }
51
+ /**
52
+ * format discovered guidance for injection into tool results.
53
+ * output format: header with scope, then content.
54
+ */
55
+ function formatGuidance(guidance) {
56
+ if (guidance.length === 0) return "";
57
+ return guidance.map((g) => {
58
+ return `${`Contents of ${g.path} (directory-specific instructions for ${g.scope}):`}\n\n<instructions>\n${g.content}\n</instructions>`;
59
+ }).join("\n\n");
60
+ }
61
+ if (import.meta.vitest) {
62
+ const { describe, it, expect, beforeEach, afterEach } = import.meta.vitest;
63
+ let tmpDir;
64
+ let projectDir;
65
+ beforeEach(() => {
66
+ tmpDir = path.join(os.tmpdir(), `pi-agents-md-test-${Date.now()}`);
67
+ projectDir = path.join(tmpDir, "project");
68
+ fs.mkdirSync(path.join(projectDir, "src", "components"), { recursive: true });
69
+ fs.writeFileSync(path.join(projectDir, "AGENTS.md"), "# Project Root\nGeneral project guidance.", "utf-8");
70
+ fs.writeFileSync(path.join(projectDir, "src", "AGENTS.md"), "# Src\nSource code guidance.", "utf-8");
71
+ fs.writeFileSync(path.join(projectDir, "src", "components", "AGENTS.md"), "# Components\nUI component guidance.", "utf-8");
72
+ });
73
+ afterEach(() => {
74
+ try {
75
+ fs.rmSync(tmpDir, {
76
+ recursive: true,
77
+ force: true
78
+ });
79
+ } catch {}
80
+ });
81
+ describe("discoverAgentsMd", () => {
82
+ it("finds all AGENTS.md from file dir up to workspace root", () => {
83
+ const guidance = discoverAgentsMd(path.join(projectDir, "src", "components", "Button.tsx"), projectDir);
84
+ expect(guidance.length).toBeGreaterThanOrEqual(3);
85
+ const scopes = guidance.map((g) => g.scope);
86
+ expect(scopes).toContain("project");
87
+ expect(scopes).toContain("src");
88
+ expect(scopes).toContain("src/components");
89
+ });
90
+ it("returns empty array when no AGENTS.md files exist", () => {
91
+ const emptyDir = path.join(tmpDir, "empty");
92
+ fs.mkdirSync(emptyDir, { recursive: true });
93
+ expect(discoverAgentsMd(path.join(emptyDir, "test.ts"), emptyDir).filter((g) => g.scope !== "global")).toHaveLength(0);
94
+ });
95
+ it("includes file content in guidance", () => {
96
+ const componentsGuidance = discoverAgentsMd(path.join(projectDir, "src", "components", "Button.tsx"), projectDir).find((g) => g.scope === "src/components");
97
+ expect(componentsGuidance).toBeDefined();
98
+ expect(componentsGuidance?.content).toContain("Components");
99
+ expect(componentsGuidance?.content).toContain("UI component guidance");
100
+ });
101
+ it("returns absolute paths for guidance files", () => {
102
+ const guidance = discoverAgentsMd(path.join(projectDir, "src", "components", "Button.tsx"), projectDir);
103
+ for (const g of guidance) expect(path.isAbsolute(g.path)).toBe(true);
104
+ });
105
+ it("handles file at workspace root", () => {
106
+ expect(discoverAgentsMd(path.join(projectDir, "README.md"), projectDir).some((g) => g.scope === "project")).toBe(true);
107
+ });
108
+ it("handles file in nested directory correctly", () => {
109
+ const deepDir = path.join(projectDir, "src", "components", "ui", "buttons");
110
+ fs.mkdirSync(deepDir, { recursive: true });
111
+ const scopes = discoverAgentsMd(path.join(deepDir, "IconButton.tsx"), projectDir).map((g) => g.scope);
112
+ expect(scopes).toContain("project");
113
+ expect(scopes).toContain("src");
114
+ expect(scopes).toContain("src/components");
115
+ });
116
+ it("stops at workspace root", () => {
117
+ const paths = discoverAgentsMd(path.join(projectDir, "src", "test.ts"), projectDir).map((g) => g.path);
118
+ for (const p of paths) expect(p.startsWith(tmpDir) || p.includes(".config")).toBe(true);
119
+ });
120
+ });
121
+ describe("formatGuidance", () => {
122
+ it("returns empty string for empty array", () => {
123
+ expect(formatGuidance([])).toBe("");
124
+ });
125
+ it("formats single guidance with scope header", () => {
126
+ const result = formatGuidance([{
127
+ path: "/project/AGENTS.md",
128
+ content: "# Rules\nBe helpful.",
129
+ scope: "project"
130
+ }]);
131
+ expect(result).toContain("Contents of /project/AGENTS.md");
132
+ expect(result).toContain("(directory-specific instructions for project)");
133
+ expect(result).toContain("<instructions>");
134
+ expect(result).toContain("Be helpful.");
135
+ expect(result).toContain("</instructions>");
136
+ });
137
+ it("formats multiple guidance entries", () => {
138
+ const result = formatGuidance([{
139
+ path: "/project/AGENTS.md",
140
+ content: "Project rules.",
141
+ scope: "project"
142
+ }, {
143
+ path: "/project/src/AGENTS.md",
144
+ content: "Src rules.",
145
+ scope: "src"
146
+ }]);
147
+ expect(result).toContain("Project rules.");
148
+ expect(result).toContain("Src rules.");
149
+ expect(result).toContain("directory-specific instructions for project");
150
+ expect(result).toContain("directory-specific instructions for src");
151
+ });
152
+ it("preserves content exactly", () => {
153
+ const content = "```typescript\nconst x = 1;\n```\n\n- rule 1\n- rule 2";
154
+ expect(formatGuidance([{
155
+ path: "/test/AGENTS.md",
156
+ content,
157
+ scope: "test"
158
+ }])).toContain(content);
159
+ });
160
+ it("separates entries with double newline", () => {
161
+ expect(formatGuidance([{
162
+ path: "/a",
163
+ content: "A",
164
+ scope: "a"
165
+ }, {
166
+ path: "/b",
167
+ content: "B",
168
+ scope: "b"
169
+ }])).toMatch(/<\/instructions>\n\nContents of/);
170
+ });
171
+ });
172
+ }
173
+ //#endregion
174
+ export { discoverAgentsMd, formatGuidance };
@@ -0,0 +1,56 @@
1
+ //#region packages/core/box-chrome/index.d.ts
2
+ /**
3
+ * box-chrome — shared box-drawing primitives for open and closed frames.
4
+ *
5
+ * "open" (tool output style): ╭─[header] / │ content / ╰────
6
+ * "closed" (overlay style): ╭─[header]──╮ / │content│ / ╰──footer──╯
7
+ *
8
+ * only concern is layout chrome. callers own content rendering,
9
+ * truncation, and interactivity. styling is injected via BoxChromeStyle
10
+ * so both raw ANSI (box-format) and theme functions (palette) work.
11
+ */
12
+ type BoxChromeVariant = "open" | "closed";
13
+ type BoxChromeStyle = {
14
+ dim: (s: string) => string;
15
+ };
16
+ type MeasuredText = {
17
+ text: string;
18
+ width: number;
19
+ };
20
+ declare function boxTop(args: {
21
+ variant: BoxChromeVariant;
22
+ style: BoxChromeStyle;
23
+ innerWidth?: number;
24
+ header?: MeasuredText;
25
+ }): string;
26
+ declare function boxRow(args: {
27
+ variant: BoxChromeVariant;
28
+ style: BoxChromeStyle;
29
+ inner: string;
30
+ }): string;
31
+ /**
32
+ * Border line with left AND right labels, separated by ─ fill.
33
+ * Used by the editor for: ╭─ left ──── right ─╮ / ╰─ left ──── right ─╯
34
+ *
35
+ * Always renders a ─ after the left corner and before the right corner
36
+ * (the "edge dashes"), so labels never touch the corners directly.
37
+ * innerWidth is the space between the two corner characters.
38
+ */
39
+ declare function boxBorderLR(args: {
40
+ corner: {
41
+ left: string;
42
+ right: string;
43
+ };
44
+ style: BoxChromeStyle;
45
+ innerWidth: number;
46
+ left?: MeasuredText;
47
+ right?: MeasuredText;
48
+ }): string;
49
+ declare function boxBottom(args: {
50
+ variant: BoxChromeVariant;
51
+ style: BoxChromeStyle;
52
+ innerWidth?: number;
53
+ footer?: MeasuredText;
54
+ }): string;
55
+ //#endregion
56
+ export { BoxChromeStyle, BoxChromeVariant, MeasuredText, boxBorderLR, boxBottom, boxRow, boxTop };