@gavin-hu/my-pi-agent 0.7.0 → 0.8.0
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/README.md +10 -10
- package/README.zh-CN.md +153 -0
- package/extensions/bytefly/README.md +129 -122
- package/extensions/bytefly/doctor/format.ts +3 -22
- package/extensions/bytefly/doctor/register.ts +10 -8
- package/extensions/bytefly/footer/register.ts +17 -91
- package/extensions/bytefly/header/component.ts +8 -4
- package/extensions/bytefly/header/config.ts +11 -6
- package/extensions/bytefly/header/register.ts +7 -27
- package/extensions/bytefly/index.ts +15 -14
- package/extensions/bytefly/panel/commands.ts +147 -0
- package/extensions/bytefly/panel/format.ts +135 -0
- package/extensions/bytefly/panel/join.ts +103 -0
- package/extensions/bytefly/panel/tui.ts +416 -0
- package/extensions/bytefly/panel/types.ts +44 -0
- package/extensions/bytefly/picker/resources.ts +0 -11
- package/extensions/file-browse/README.md +12 -5
- package/extensions/file-browse/markdown.ts +227 -45
- package/extensions/plan/README.md +16 -12
- package/extensions/plan/plans.ts +16 -17
- package/extensions/plan/runtime.ts +1 -1
- package/extensions/plan/steps.ts +31 -19
- package/extensions/plan/tools.ts +62 -32
- package/extensions/plan/types.ts +6 -6
- package/lib/README.md +1 -1
- package/lib/requirements.ts +3 -3
- package/lib/tui.ts +7 -8
- package/lib/web/components.ts +11 -0
- package/package.json +1 -1
- package/extensions/bytefly/doctor/commands.ts +0 -68
- package/extensions/bytefly/doctor/tui.ts +0 -140
- package/extensions/bytefly/picker/commands.ts +0 -73
- package/extensions/bytefly/picker/tui.ts +0 -260
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `/extensions` dock screen: a scrollable, keyboard-driven list of every
|
|
3
|
-
* resolved extension with its group header.
|
|
4
|
-
*
|
|
5
|
-
* The component is derived: it reads `runtime.items()` on each render and
|
|
6
|
-
* requests a repaint after a toggle or a scope switch through the injected
|
|
7
|
-
* callbacks. It reuses the shared screen chrome (`lib/tui.ts`) and scroll math
|
|
8
|
-
* (`lib/list-cursor.ts`) rather than re-deriving either.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import {
|
|
13
|
-
Key,
|
|
14
|
-
matchesKey,
|
|
15
|
-
truncateToWidth,
|
|
16
|
-
type Component,
|
|
17
|
-
type TuiMouseEvent,
|
|
18
|
-
type TuiMouseEventResult,
|
|
19
|
-
} from "@earendil-works/pi-tui";
|
|
20
|
-
import { sanitizeLine } from "../../../lib/format.ts";
|
|
21
|
-
import {
|
|
22
|
-
fitRows,
|
|
23
|
-
formatRange,
|
|
24
|
-
isCloseKey,
|
|
25
|
-
ListCursor,
|
|
26
|
-
navIntent,
|
|
27
|
-
selectionMarker,
|
|
28
|
-
wheelDelta,
|
|
29
|
-
} from "../../../lib/list-cursor.ts";
|
|
30
|
-
import { dockListFrame, screenHint, type ViewportRowsSource } from "../../../lib/tui.ts";
|
|
31
|
-
import { buildEntries, firstSelectable, nextSelectable } from "./resources.ts";
|
|
32
|
-
import type { ExtensionsPickerRuntime as Runtime } from "./runtime.ts";
|
|
33
|
-
import type { Entry } from "./types.ts";
|
|
34
|
-
|
|
35
|
-
/** Rows the screen shows when the terminal height is unknown. */
|
|
36
|
-
const SCREEN_DEFAULT_ROWS = 16;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Fixed rows around the list: header, summary, blank, blank, hint, blank. The
|
|
40
|
-
* focused-detail and pending-changes rows are added on top when present.
|
|
41
|
-
*/
|
|
42
|
-
const CHROME = 6;
|
|
43
|
-
|
|
44
|
-
export class ExtensionsListComponent implements Component {
|
|
45
|
-
private readonly cursor: ListCursor;
|
|
46
|
-
/** Serialises toggles so a rapid double press toggles twice, not once. */
|
|
47
|
-
private chain: Promise<void> = Promise.resolve();
|
|
48
|
-
|
|
49
|
-
constructor(
|
|
50
|
-
private readonly runtime: Runtime,
|
|
51
|
-
private readonly theme: Theme,
|
|
52
|
-
private readonly agentDir: string,
|
|
53
|
-
private readonly onClose: () => void,
|
|
54
|
-
private readonly requestRender: () => void,
|
|
55
|
-
private readonly viewportRowsSource?: ViewportRowsSource,
|
|
56
|
-
private readonly onNotice?: (message: string) => void,
|
|
57
|
-
) {
|
|
58
|
-
this.cursor = new ListCursor(() => this.requestRender());
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private entries(): Entry[] {
|
|
62
|
-
return buildEntries(this.runtime.items(), this.agentDir);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private currentItem() {
|
|
66
|
-
const entry = this.entries()[this.cursor.selected];
|
|
67
|
-
return entry?.kind === "item" ? entry.item : undefined;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private select(index: number | undefined): void {
|
|
71
|
-
if (index === undefined) return;
|
|
72
|
-
this.cursor.set(index, this.entries().length);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Keep the cursor on an item row when the list shape changes. */
|
|
76
|
-
private ensureSelectable(): void {
|
|
77
|
-
const entries = this.entries();
|
|
78
|
-
if (entries[this.cursor.selected]?.kind === "item") return;
|
|
79
|
-
this.select(firstSelectable(entries));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Move the cursor onto an item row when the list shape changed. No repaint. */
|
|
83
|
-
private clampSelection(entries: Entry[]): void {
|
|
84
|
-
if (entries[this.cursor.selected]?.kind === "item") return;
|
|
85
|
-
const first = firstSelectable(entries);
|
|
86
|
-
if (first !== undefined) this.cursor.selected = first;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** Move `delta` item rows, skipping group headers. */
|
|
90
|
-
private move(delta: number): void {
|
|
91
|
-
if (delta === 0) return;
|
|
92
|
-
this.ensureSelectable();
|
|
93
|
-
const entries = this.entries();
|
|
94
|
-
const direction = delta < 0 ? -1 : 1;
|
|
95
|
-
let index = this.cursor.selected;
|
|
96
|
-
let remaining = Math.abs(delta);
|
|
97
|
-
while (remaining > 0) {
|
|
98
|
-
const next = nextSelectable(entries, index, direction);
|
|
99
|
-
if (next === undefined) break;
|
|
100
|
-
index = next;
|
|
101
|
-
remaining -= 1;
|
|
102
|
-
}
|
|
103
|
-
this.cursor.set(index, entries.length);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private switchScope(): void {
|
|
107
|
-
const next = this.runtime.scope() === "global" ? "project" : "global";
|
|
108
|
-
if (next === "project" && !this.runtime.projectAvailable()) {
|
|
109
|
-
this.onNotice?.("Project is not trusted; manage extensions in Global scope only.");
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
this.runtime.setScope(next);
|
|
113
|
-
this.clampSelection(this.entries());
|
|
114
|
-
this.requestRender();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private toggle(): void {
|
|
118
|
-
const item = this.currentItem();
|
|
119
|
-
if (!item) return;
|
|
120
|
-
this.chain = this.chain
|
|
121
|
-
.then(() => this.runtime.toggle(item))
|
|
122
|
-
.catch(() => undefined)
|
|
123
|
-
.then(() => this.requestRender());
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
handleInput(data: string): void {
|
|
127
|
-
if (isCloseKey(data)) {
|
|
128
|
-
this.onClose();
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
this.ensureSelectable();
|
|
132
|
-
if (matchesKey(data, Key.space) || matchesKey(data, Key.enter)) {
|
|
133
|
-
this.toggle();
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
if (matchesKey(data, Key.tab)) {
|
|
137
|
-
this.switchScope();
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (data === "g") {
|
|
141
|
-
this.select(firstSelectable(this.entries()));
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (data === "G") {
|
|
145
|
-
this.select(nextSelectable(this.entries(), this.entries().length, -1));
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
switch (navIntent(data)) {
|
|
149
|
-
case "up":
|
|
150
|
-
this.move(-1);
|
|
151
|
-
return;
|
|
152
|
-
case "down":
|
|
153
|
-
this.move(1);
|
|
154
|
-
return;
|
|
155
|
-
case "pageUp":
|
|
156
|
-
this.move(-Math.max(1, this.cursor.visible));
|
|
157
|
-
return;
|
|
158
|
-
case "pageDown":
|
|
159
|
-
this.move(Math.max(1, this.cursor.visible));
|
|
160
|
-
return;
|
|
161
|
-
case "home":
|
|
162
|
-
this.select(firstSelectable(this.entries()));
|
|
163
|
-
return;
|
|
164
|
-
case "end":
|
|
165
|
-
this.select(nextSelectable(this.entries(), this.entries().length, -1));
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
171
|
-
const delta = wheelDelta(event);
|
|
172
|
-
if (delta === undefined) return undefined;
|
|
173
|
-
this.move(delta);
|
|
174
|
-
return { handled: true };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
invalidate(): void {
|
|
178
|
-
// Everything is rebuilt from live state on each render.
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
private summary(width: number): string {
|
|
182
|
-
const items = this.runtime.items();
|
|
183
|
-
const scope = this.runtime.scope() === "global" ? "Global" : "Project";
|
|
184
|
-
const untrusted = this.runtime.scope() === "global" && !this.runtime.projectAvailable();
|
|
185
|
-
const counts = `${items.length} extension${items.length === 1 ? "" : "s"} · ${this.runtime.disabledCount()} disabled`;
|
|
186
|
-
return truncateToWidth(
|
|
187
|
-
` ${this.theme.fg("accent", scope)}${untrusted ? this.theme.fg("warning", " (project untrusted)") : ""}${this.theme.fg("muted", ` · ${counts}`)}`,
|
|
188
|
-
width,
|
|
189
|
-
"…",
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private itemRow(entry: Extract<Entry, { kind: "item" }>, selected: boolean, width: number): string {
|
|
194
|
-
const checkbox = entry.item.enabled ? this.theme.fg("success", "[x]") : this.theme.fg("dim", "[ ]");
|
|
195
|
-
const label = sanitizeLine(entry.label);
|
|
196
|
-
const styled = selected ? this.theme.fg("accent", label) : entry.item.enabled ? label : this.theme.fg("dim", label);
|
|
197
|
-
return truncateToWidth(`${selectionMarker(this.theme, selected)}${checkbox} ${styled}`, width, "…");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
render(width: number): string[] {
|
|
201
|
-
const w = Math.max(1, width);
|
|
202
|
-
const entries = this.entries();
|
|
203
|
-
const summary = this.summary(w);
|
|
204
|
-
|
|
205
|
-
if (entries.length === 0) {
|
|
206
|
-
return dockListFrame({
|
|
207
|
-
theme: this.theme,
|
|
208
|
-
width: w,
|
|
209
|
-
title: "Extensions",
|
|
210
|
-
summary,
|
|
211
|
-
body: [truncateToWidth(` ${this.theme.fg("dim", "No extensions found.")}`, w)],
|
|
212
|
-
hint: screenHint(this.theme, w, ["Esc close"]),
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
this.clampSelection(entries);
|
|
217
|
-
|
|
218
|
-
const focused = entries[this.cursor.selected];
|
|
219
|
-
const detail = focused?.kind === "item" ? sanitizeLine(focused.item.path) : "";
|
|
220
|
-
const chrome = CHROME + (detail ? 1 : 0) + (this.runtime.isDirty() ? 1 : 0);
|
|
221
|
-
this.cursor.visible = fitRows(this.viewportRowsSource, entries.length, chrome, SCREEN_DEFAULT_ROWS);
|
|
222
|
-
this.cursor.sync(entries.length);
|
|
223
|
-
|
|
224
|
-
const { start, end } = this.cursor.window(entries.length);
|
|
225
|
-
const body: string[] = [];
|
|
226
|
-
for (let index = start; index < end; index++) {
|
|
227
|
-
const entry = entries[index];
|
|
228
|
-
if (entry.kind === "group") {
|
|
229
|
-
body.push(
|
|
230
|
-
truncateToWidth(
|
|
231
|
-
` ${this.theme.fg("muted", sanitizeLine(entry.label))} ${this.theme.fg("dim", `(${entry.count})`)}`,
|
|
232
|
-
w,
|
|
233
|
-
"…",
|
|
234
|
-
),
|
|
235
|
-
);
|
|
236
|
-
} else {
|
|
237
|
-
body.push(this.itemRow(entry, index === this.cursor.selected, w));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const footnotes: string[] = [];
|
|
242
|
-
if (detail) footnotes.push(truncateToWidth(` ${this.theme.fg("muted", detail)}`, w, "…"));
|
|
243
|
-
if (this.runtime.isDirty()) {
|
|
244
|
-
footnotes.push(truncateToWidth(` ${this.theme.fg("warning", "Saved · reload to apply")}`, w, "…"));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return dockListFrame({
|
|
248
|
-
theme: this.theme,
|
|
249
|
-
width: w,
|
|
250
|
-
title: "Extensions",
|
|
251
|
-
summary,
|
|
252
|
-
body,
|
|
253
|
-
range: this.cursor.clipped(entries.length)
|
|
254
|
-
? formatRange(this.theme, w, { start, end, total: entries.length })
|
|
255
|
-
: undefined,
|
|
256
|
-
detail: footnotes.length > 0 ? footnotes : undefined,
|
|
257
|
-
hint: screenHint(this.theme, w, ["Esc close", "Space/Enter toggle", "Tab scope", "↑/↓ select", "g/G"]),
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|