@gavin-hu/my-pi-agent 0.11.4 → 0.12.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 +13 -8
- package/README.zh-CN.md +5 -3
- package/assets/chrome.png +0 -0
- package/assets/plan-review.png +0 -0
- package/extensions/bytefly/README.md +291 -207
- package/extensions/bytefly/doctor/runtime.ts +16 -0
- package/extensions/bytefly/doctor/tools.ts +1 -3
- package/extensions/bytefly/footer/price/config.ts +54 -15
- package/extensions/bytefly/footer/price/price.ts +20 -7
- package/extensions/bytefly/footer/price/types.ts +13 -7
- package/extensions/bytefly/footer/render/format.ts +3 -2
- package/extensions/bytefly/footer/render/segments.ts +3 -2
- package/extensions/bytefly/footer/types.ts +1 -0
- package/extensions/bytefly/header/component.ts +21 -6
- package/extensions/bytefly/header/config.ts +3 -0
- package/extensions/bytefly/header/register.ts +34 -3
- package/extensions/bytefly/index.ts +13 -3
- package/extensions/bytefly/panel/settings-runtime.ts +71 -28
- package/extensions/bytefly/panel/settings.ts +201 -38
- package/extensions/bytefly/panel/tui/entries.ts +58 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +86 -0
- package/extensions/bytefly/panel/tui/frame.ts +71 -0
- package/extensions/bytefly/panel/tui/nav.ts +75 -0
- package/extensions/bytefly/panel/tui/settings-view.ts +115 -0
- package/extensions/bytefly/panel/tui.ts +205 -319
- package/extensions/bytefly/picker/runtime.ts +61 -2
- package/extensions/bytefly/picker/toggle.ts +56 -0
- package/extensions/bytefly/update/check.ts +59 -0
- package/extensions/bytefly/update/format.ts +32 -0
- package/extensions/bytefly/update/register.ts +72 -0
- package/extensions/bytefly/update/state.ts +28 -0
- package/extensions/bytefly/update/version.ts +83 -0
- package/extensions/document/README.md +2 -2
- package/extensions/document/tool.ts +1 -3
- package/extensions/file-open/README.md +30 -15
- package/extensions/file-open/index.ts +4 -3
- package/extensions/file-open/open.ts +52 -21
- package/extensions/goal/README.md +39 -13
- package/extensions/goal/clarify-prompt.ts +5 -36
- package/extensions/goal/clarify.ts +55 -86
- package/extensions/goal/commands.ts +41 -35
- package/extensions/goal/format.ts +12 -4
- package/extensions/goal/tools.ts +28 -8
- package/extensions/goal/tui.ts +1 -1
- package/extensions/memory/README.md +1 -1
- package/extensions/plan/README.md +24 -8
- package/extensions/plan/blocks.ts +1 -35
- package/extensions/plan/commands.ts +18 -12
- package/extensions/plan/plans.ts +1 -1
- package/extensions/plan/policy.ts +4 -3
- package/extensions/plan/review.ts +243 -0
- package/extensions/plan/runtime.ts +4 -0
- package/extensions/plan/steps.ts +34 -23
- package/extensions/plan/todos.ts +5 -58
- package/extensions/plan/tools.ts +66 -222
- package/extensions/plan/tui.ts +17 -9
- package/extensions/plan/types.ts +3 -0
- package/extensions/plan/web-client.ts +51 -18
- package/extensions/plan/web-page.ts +5 -0
- package/extensions/plan/web-router.ts +11 -3
- package/extensions/plan/web.ts +100 -36
- package/extensions/question/README.md +1 -1
- package/extensions/question/tools.ts +1 -1
- package/extensions/subagent/index.ts +0 -2
- package/extensions/todo/README.md +19 -11
- package/extensions/todo/index.ts +5 -3
- package/extensions/todo/nudge.ts +58 -9
- package/extensions/todo/runtime.ts +28 -17
- package/extensions/web-access/README.md +3 -3
- package/extensions/web-access/fetch/tool.ts +1 -3
- package/extensions/web-access/search/tool.ts +1 -3
- package/extensions/wechat/README.md +5 -4
- package/extensions/wechat/tools.ts +0 -2
- package/lib/README.md +2 -1
- package/lib/config.ts +43 -1
- package/lib/env.ts +12 -0
- package/lib/requirements.ts +76 -2
- package/lib/todo-state.ts +62 -0
- package/lib/tui/glyphs.ts +1 -0
- package/package.json +1 -1
- package/assets/bytefly.schema.json +0 -785
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chrome around the panel's body: the layout constants, the section bar with
|
|
3
|
+
* its counts, a group heading, and the hint row that a transient notice replaces.
|
|
4
|
+
*
|
|
5
|
+
* Every section draws its body through these, so the two views cannot drift on
|
|
6
|
+
* spacing, roles, or the width arithmetic.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { sanitizeLine } from "../../../../lib/format.ts";
|
|
11
|
+
import { screenHint } from "../../../../lib/tui/chrome.ts";
|
|
12
|
+
import { clip, fitCells, splitRow } from "../../../../lib/tui/columns.ts";
|
|
13
|
+
import { BODY_INDENT } from "../../../../lib/tui/indent.ts";
|
|
14
|
+
import { paint } from "../../../../lib/tui/tokens.ts";
|
|
15
|
+
import { sectionSegments, type SummarySegment } from "../format.ts";
|
|
16
|
+
import type { PanelSection } from "../types.ts";
|
|
17
|
+
|
|
18
|
+
/** Body rows when the terminal height is unknown. */
|
|
19
|
+
export const SCREEN_DEFAULT_ROWS = 16;
|
|
20
|
+
/** Fixed rows around the body: header, summary, blank, blank, hint, blank. */
|
|
21
|
+
export const FRAME_ROWS = 6;
|
|
22
|
+
/** Detail rows reserved for the focused row. */
|
|
23
|
+
export const DETAIL_ROWS = 4;
|
|
24
|
+
/** Body rows that must survive before the detail pane collapses to its title. */
|
|
25
|
+
export const MIN_BODY_ROWS = 3;
|
|
26
|
+
/** Detail rows when the terminal height is unknown and the focused row is expanded. */
|
|
27
|
+
export const EXPANDED_DETAIL_FALLBACK = 8;
|
|
28
|
+
/** Fixed body offset in the frame: header, summary, blank line. */
|
|
29
|
+
export const BODY_START = 3;
|
|
30
|
+
|
|
31
|
+
/** The body indent, shared by the note and the detail pane. */
|
|
32
|
+
export const INDENT = " ".repeat(BODY_INDENT);
|
|
33
|
+
|
|
34
|
+
/** The themed section bar: both section names, the active one accented. */
|
|
35
|
+
export function sectionBar(theme: Theme, width: number, active: PanelSection, counts: string): string {
|
|
36
|
+
const separator = paint(theme, "faint", " · ");
|
|
37
|
+
const left = ` ${sectionSegments(active)
|
|
38
|
+
.map((segment) => paint(theme, segment.color, segment.text))
|
|
39
|
+
.join(separator)}`;
|
|
40
|
+
return clip(splitRow(left, counts, width), width);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One section's counts as whole segments, so a narrow screen drops a count. */
|
|
44
|
+
export function countsLine(theme: Theme, width: number, segments: readonly SummarySegment[]): string {
|
|
45
|
+
const separator = paint(theme, "faint", " · ");
|
|
46
|
+
const styled = segments.map((segment) => paint(theme, segment.color, segment.text));
|
|
47
|
+
return fitCells(styled, Math.max(1, width - 2), separator);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** One group heading: its label and how many rows sit under it. */
|
|
51
|
+
export function groupLine(theme: Theme, width: number, entry: { label: string; count: number }): string {
|
|
52
|
+
const label = paint(theme, "meta", sanitizeLine(entry.label));
|
|
53
|
+
const count = paint(theme, "faint", `(${entry.count})`);
|
|
54
|
+
return clip(` ${label} ${count}`, width);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The key-hint row. */
|
|
58
|
+
export function hintLine(theme: Theme, width: number, hints: string[]): string {
|
|
59
|
+
return screenHint(theme, width, hints);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The transient notice row, clipped and painted like the hint it replaces. */
|
|
63
|
+
export function noticeLine(theme: Theme, width: number, notice: string): string {
|
|
64
|
+
return clip(`${INDENT}${paint(theme, "meta", sanitizeLine(notice))}`, width);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The saved-then-reload marker, shared by both detail panes. */
|
|
68
|
+
export function savedLine(theme: Theme, width: number, dirty: boolean): string[] {
|
|
69
|
+
if (!dirty) return [];
|
|
70
|
+
return [clip(`${INDENT}${paint(theme, "warn", "Saved · reload to apply")}`, width)];
|
|
71
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor arithmetic for the panel's two lists: keep the focus on a row across a
|
|
3
|
+
* rebuild, land on the nearest row when the shape changes, and move by rows
|
|
4
|
+
* while skipping group headers.
|
|
5
|
+
*
|
|
6
|
+
* Pure: each function takes the entries and the cursor and mutates only the
|
|
7
|
+
* cursor it was handed, so the component keeps one cursor per section and no
|
|
8
|
+
* navigation state of its own.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ListCursor } from "../../../../lib/tui/cursor.ts";
|
|
12
|
+
import { entryKey, isSelectable, nextRow, type PanelEntry } from "./entries.ts";
|
|
13
|
+
|
|
14
|
+
/** The first selectable entry, or `undefined` when the list has none. */
|
|
15
|
+
export function firstRow(entries: PanelEntry[]): number | undefined {
|
|
16
|
+
const index = entries.findIndex(isSelectable);
|
|
17
|
+
return index >= 0 ? index : undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The last selectable entry, or `undefined` when the list has none. */
|
|
21
|
+
export function lastRow(entries: PanelEntry[]): number | undefined {
|
|
22
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
23
|
+
if (isSelectable(entries[index])) return index;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Keep the focus on the same row key when the list shape changes. */
|
|
29
|
+
export function retainFocus(entries: PanelEntry[], cursor: ListCursor, remembered: string | undefined): void {
|
|
30
|
+
const key = entryKey(entries[cursor.selected]);
|
|
31
|
+
if (key !== undefined) return;
|
|
32
|
+
if (remembered === undefined) return;
|
|
33
|
+
const index = entries.findIndex((candidate) => entryKey(candidate) === remembered);
|
|
34
|
+
if (index >= 0) cursor.selected = index;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Move onto the nearest selectable row when the focus is not already on one. */
|
|
38
|
+
export function clampSelection(entries: PanelEntry[], cursor: ListCursor): void {
|
|
39
|
+
if (entries.length === 0) return;
|
|
40
|
+
const index = Math.min(Math.max(0, cursor.selected), entries.length - 1);
|
|
41
|
+
if (isSelectable(entries[index])) {
|
|
42
|
+
cursor.selected = index;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Prefer the closest row above, then the closest below, so a scope switch that
|
|
46
|
+
// drops the focused row leaves the cursor nearby instead of at the top.
|
|
47
|
+
for (let back = index - 1; back >= 0; back -= 1) {
|
|
48
|
+
if (isSelectable(entries[back])) {
|
|
49
|
+
cursor.selected = back;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (let forward = index + 1; forward < entries.length; forward += 1) {
|
|
54
|
+
if (isSelectable(entries[forward])) {
|
|
55
|
+
cursor.selected = forward;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Move `delta` rows from the cursor, skipping group headers. */
|
|
62
|
+
export function moveRows(entries: PanelEntry[], cursor: ListCursor, delta: number): void {
|
|
63
|
+
if (delta === 0) return;
|
|
64
|
+
clampSelection(entries, cursor);
|
|
65
|
+
const direction = delta < 0 ? -1 : 1;
|
|
66
|
+
let index = cursor.selected;
|
|
67
|
+
let remaining = Math.abs(delta);
|
|
68
|
+
while (remaining > 0) {
|
|
69
|
+
const next = nextRow(entries, index, direction);
|
|
70
|
+
if (next === undefined) break;
|
|
71
|
+
index = next;
|
|
72
|
+
remaining -= 1;
|
|
73
|
+
}
|
|
74
|
+
cursor.set(index, entries.length);
|
|
75
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Settings section: its summary line, one row per preference, and the
|
|
3
|
+
* detail pane that names the value's source.
|
|
4
|
+
*
|
|
5
|
+
* Pure functions over the catalogue's views; the component owns the cursor, the
|
|
6
|
+
* scope, and the write.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
12
|
+
import { sanitizeLine } from "../../../../lib/format.ts";
|
|
13
|
+
import { clip } from "../../../../lib/tui/columns.ts";
|
|
14
|
+
import { BODY_INDENT } from "../../../../lib/tui/indent.ts";
|
|
15
|
+
import { checkbox, listRow } from "../../../../lib/tui/rows.ts";
|
|
16
|
+
import { paint } from "../../../../lib/tui/tokens.ts";
|
|
17
|
+
import { settingsSummarySegments, type PanelScope } from "../format.ts";
|
|
18
|
+
import type { SettingView } from "../settings.ts";
|
|
19
|
+
import { configPath } from "../settings-runtime.ts";
|
|
20
|
+
import type { WriteScope } from "../../picker/types.ts";
|
|
21
|
+
import { countsLine, INDENT, savedLine } from "./frame.ts";
|
|
22
|
+
|
|
23
|
+
/** Pi's personal settings file, named in the detail pane. */
|
|
24
|
+
const PI_SETTINGS_FILE = "settings.json";
|
|
25
|
+
|
|
26
|
+
/** The rows this section shows, and the scope they were read in. */
|
|
27
|
+
export function settingsSummary(theme: Theme, width: number, scope: PanelScope, count: number): string {
|
|
28
|
+
return countsLine(theme, width, settingsSummarySegments(scope, count));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** One setting row: a checkbox for booleans, the value right-aligned. */
|
|
32
|
+
export function settingRow(
|
|
33
|
+
theme: Theme,
|
|
34
|
+
width: number,
|
|
35
|
+
view: SettingView,
|
|
36
|
+
selected: boolean,
|
|
37
|
+
editing?: { buffer: string; caret: number },
|
|
38
|
+
): string {
|
|
39
|
+
// The editor shows the buffer with a caret block, in the accent so the row
|
|
40
|
+
// reads as active rather than as a value that happens to contain a bar.
|
|
41
|
+
const value = editing
|
|
42
|
+
? `${editing.buffer.slice(0, editing.caret)}▏${editing.buffer.slice(editing.caret)}`
|
|
43
|
+
: view.valueLabel;
|
|
44
|
+
return listRow(theme, width, {
|
|
45
|
+
selected,
|
|
46
|
+
leading: view.kind === "boolean" ? checkbox(theme, view.value === "true") : " ",
|
|
47
|
+
label: sanitizeLine(view.label),
|
|
48
|
+
labelRole: selected ? "accent" : "label",
|
|
49
|
+
trailing: sanitizeLine(value),
|
|
50
|
+
trailingRole: editing ? "accent" : "meta",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The project note: app rows are absent because Pi has no project setter. */
|
|
55
|
+
export function projectNote(theme: Theme, width: number): string {
|
|
56
|
+
return clip(`${INDENT}${paint(theme, "faint", "App settings are Global only.")}`, width);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Where the focused row's value is written, and which scope supplied it. */
|
|
60
|
+
export function originLine(view: SettingView, paths: { scope: WriteScope; cwd: string; agentDir: string }): string {
|
|
61
|
+
const path =
|
|
62
|
+
view.source === "app" ? join(paths.agentDir, PI_SETTINGS_FILE) : configPath(paths.scope, paths.cwd, paths.agentDir);
|
|
63
|
+
const file = sanitizeLine(path);
|
|
64
|
+
switch (view.origin) {
|
|
65
|
+
case "inherited":
|
|
66
|
+
return `Inherited from Global · ${file}`;
|
|
67
|
+
case "project":
|
|
68
|
+
return `Project · ${file}`;
|
|
69
|
+
case "global":
|
|
70
|
+
return `Global · ${file}`;
|
|
71
|
+
default:
|
|
72
|
+
return `Default · ${file}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The focused setting's source, description, origin, and pending changes. */
|
|
77
|
+
export function settingDetail(options: {
|
|
78
|
+
theme: Theme;
|
|
79
|
+
width: number;
|
|
80
|
+
view: SettingView | undefined;
|
|
81
|
+
budget: number;
|
|
82
|
+
dirty: boolean;
|
|
83
|
+
paths: { scope: WriteScope; cwd: string; agentDir: string };
|
|
84
|
+
}): string[] {
|
|
85
|
+
const { theme, width, view, budget, dirty, paths } = options;
|
|
86
|
+
if (!view) return savedLine(theme, width, dirty);
|
|
87
|
+
const source = view.source === "app" ? "Pi setting" : "bytefly.json";
|
|
88
|
+
const head = `${paint(theme, "meta", sanitizeLine(view.label))} ${paint(theme, "faint", "—")} ${paint(theme, "faint", source)}`;
|
|
89
|
+
const headLine = clip(`${INDENT}${head}`, width);
|
|
90
|
+
// The pending-changes marker survives the collapsed body: on a short terminal
|
|
91
|
+
// the value still changed, and the screen has to say so.
|
|
92
|
+
const saved = savedLine(theme, width, dirty);
|
|
93
|
+
if (budget === 0) return [headLine, ...saved];
|
|
94
|
+
|
|
95
|
+
const lines = [headLine];
|
|
96
|
+
const descriptionWidth = Math.max(1, width - BODY_INDENT * 2);
|
|
97
|
+
const description = wrapTextWithAnsi(sanitizeLine(view.description), descriptionWidth).slice(
|
|
98
|
+
0,
|
|
99
|
+
Math.max(0, Math.min(2, budget - 2 - saved.length)),
|
|
100
|
+
);
|
|
101
|
+
for (const line of description) lines.push(clip(`${INDENT}${paint(theme, "meta", line)}`, width));
|
|
102
|
+
lines.push(clip(`${INDENT}${paint(theme, "faint", originLine(view, paths))}`, width));
|
|
103
|
+
lines.push(...saved);
|
|
104
|
+
return lines;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The section's key hints, in the order they drop as the width runs out. */
|
|
108
|
+
export function settingsHints(): string[] {
|
|
109
|
+
return ["Esc close", "←/→", "Space change", "Tab scope", "↑/↓ select"];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The editor's key hints: `Esc` cancels the edit, it does not close the screen. */
|
|
113
|
+
export function editHints(): string[] {
|
|
114
|
+
return ["Esc cancel", "←/→ caret", "Enter save", "Backspace delete", "Ctrl+U clear"];
|
|
115
|
+
}
|