@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.
- package/dist/chunk-DsIazq42.js +26 -0
- package/dist/command-palette-B97yKqne.js +406 -0
- package/dist/core/agents-md.d.ts +22 -0
- package/dist/core/agents-md.js +174 -0
- package/dist/core/box-chrome.d.ts +56 -0
- package/dist/core/box-chrome.js +207 -0
- package/dist/core/box-format.d.ts +82 -0
- package/dist/core/box-format.js +381 -0
- package/dist/core/config.d.ts +14 -0
- package/dist/core/config.js +201 -0
- package/dist/core/file-tracker.d.ts +65 -0
- package/dist/core/file-tracker.js +439 -0
- package/dist/core/github-api.d.ts +45 -0
- package/dist/core/github-api.js +101 -0
- package/dist/core/html-to-md.d.ts +5 -0
- package/dist/core/html-to-md.js +289 -0
- package/dist/core/interpolate.d.ts +44 -0
- package/dist/core/interpolate.js +475 -0
- package/dist/core/mutex.d.ts +8 -0
- package/dist/core/mutex.js +142 -0
- package/dist/core/output-buffer.d.ts +80 -0
- package/dist/core/output-buffer.js +517 -0
- package/dist/core/permissions.d.ts +19 -0
- package/dist/core/permissions.js +140 -0
- package/dist/core/pi-spawn.d.ts +62 -0
- package/dist/core/pi-spawn.js +244 -0
- package/dist/core/prompt-patch.d.ts +11 -0
- package/dist/core/prompt-patch.js +16 -0
- package/dist/core/show-renderer.d.ts +10 -0
- package/dist/core/show-renderer.js +39 -0
- package/dist/core/show.d.ts +29 -0
- package/dist/core/show.js +288 -0
- package/dist/core/sub-agent-render.d.ts +56 -0
- package/dist/core/sub-agent-render.js +579 -0
- package/dist/core/tool-cost.d.ts +17 -0
- package/dist/core/tool-cost.js +58 -0
- package/dist/core/tui.d.ts +9 -0
- package/dist/core/tui.js +33 -0
- package/dist/editor-BljciPdx.js +537 -0
- package/dist/extensions/bash.d.ts +12 -0
- package/dist/extensions/bash.js +261 -0
- package/dist/extensions/code-review.d.ts +14 -0
- package/dist/extensions/code-review.js +175 -0
- package/dist/extensions/command-palette.d.ts +6 -0
- package/dist/extensions/command-palette.js +2 -0
- package/dist/extensions/create-file.d.ts +7 -0
- package/dist/extensions/create-file.js +88 -0
- package/dist/extensions/edit-file.d.ts +7 -0
- package/dist/extensions/edit-file.js +395 -0
- package/dist/extensions/editor.d.ts +6 -0
- package/dist/extensions/editor.js +2 -0
- package/dist/extensions/finder.d.ts +13 -0
- package/dist/extensions/finder.js +108 -0
- package/dist/extensions/format-file.d.ts +11 -0
- package/dist/extensions/format-file.js +145 -0
- package/dist/extensions/github.d.ts +13 -0
- package/dist/extensions/github.js +527 -0
- package/dist/extensions/glob.d.ts +10 -0
- package/dist/extensions/glob.js +163 -0
- package/dist/extensions/grep.d.ts +13 -0
- package/dist/extensions/grep.js +356 -0
- package/dist/extensions/handoff.d.ts +6 -0
- package/dist/extensions/handoff.js +277 -0
- package/dist/extensions/librarian.d.ts +13 -0
- package/dist/extensions/librarian.js +113 -0
- package/dist/extensions/look-at.d.ts +13 -0
- package/dist/extensions/look-at.js +121 -0
- package/dist/extensions/ls.d.ts +8 -0
- package/dist/extensions/ls.js +87 -0
- package/dist/extensions/mermaid.d.ts +20 -0
- package/dist/extensions/mermaid.js +2 -0
- package/dist/extensions/oracle.d.ts +13 -0
- package/dist/extensions/oracle.js +143 -0
- package/dist/extensions/read-session.d.ts +13 -0
- package/dist/extensions/read-session.js +265 -0
- package/dist/extensions/read-web-page.d.ts +11 -0
- package/dist/extensions/read-web-page.js +234 -0
- package/dist/extensions/read.d.ts +23 -0
- package/dist/extensions/read.js +323 -0
- package/dist/extensions/search-sessions.d.ts +29 -0
- package/dist/extensions/search-sessions.js +426 -0
- package/dist/extensions/session-name.d.ts +6 -0
- package/dist/extensions/session-name.js +54 -0
- package/dist/extensions/skill.d.ts +7 -0
- package/dist/extensions/skill.js +232 -0
- package/dist/extensions/system-prompt.d.ts +6 -0
- package/dist/extensions/system-prompt.js +31 -0
- package/dist/extensions/task.d.ts +11 -0
- package/dist/extensions/task.js +114 -0
- package/dist/extensions/tool-harness.d.ts +6 -0
- package/dist/extensions/tool-harness.js +37 -0
- package/dist/extensions/undo-edit.d.ts +7 -0
- package/dist/extensions/undo-edit.js +127 -0
- package/dist/extensions/web-search.d.ts +12 -0
- package/dist/extensions/web-search.js +254 -0
- package/dist/extensions.d.ts +29 -0
- package/dist/extensions.js +29 -0
- package/dist/mermaid-C7xCVBrw.js +95296 -0
- 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 };
|