@gavin-hu/my-pi-agent 0.13.1 → 0.15.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 +66 -41
- package/README.zh-CN.md +53 -36
- package/extensions/bytefly/README.md +321 -186
- package/extensions/bytefly/block/component.ts +7 -15
- package/extensions/bytefly/block/register.ts +6 -1
- package/extensions/bytefly/doctor/config.ts +4 -1
- package/extensions/bytefly/doctor/register.ts +24 -5
- package/extensions/bytefly/doctor/tools.ts +4 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +6 -1
- package/extensions/bytefly/footer/config.ts +10 -6
- package/extensions/bytefly/footer/price/config.ts +3 -2
- package/extensions/bytefly/footer/price/price.ts +5 -3
- package/extensions/bytefly/footer/price/rates.ts +63 -26
- package/extensions/bytefly/footer/render/status.ts +3 -3
- package/extensions/bytefly/header/component.ts +33 -10
- package/extensions/bytefly/header/config.ts +22 -0
- package/extensions/bytefly/header/register.ts +2 -1
- package/extensions/bytefly/index.ts +6 -6
- package/extensions/bytefly/notice/component.ts +8 -3
- package/extensions/bytefly/notify/config.ts +7 -12
- package/extensions/bytefly/notify/dispatch.ts +13 -5
- package/extensions/bytefly/panel/commands.ts +22 -2
- package/extensions/bytefly/panel/format.ts +17 -21
- package/extensions/bytefly/panel/settings-runtime.ts +86 -22
- package/extensions/bytefly/panel/settings.ts +279 -46
- package/extensions/bytefly/panel/themes.ts +32 -0
- package/extensions/bytefly/panel/tui/editing.ts +77 -0
- package/extensions/bytefly/panel/tui/entries.ts +24 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +13 -8
- package/extensions/bytefly/panel/tui/frame.ts +121 -4
- package/extensions/bytefly/panel/tui/settings-view.ts +98 -13
- package/extensions/bytefly/panel/tui.ts +176 -166
- package/extensions/bytefly/picker/discovery.ts +1 -0
- package/extensions/bytefly/picker/types.ts +2 -0
- package/extensions/bytefly/separator/component.ts +17 -6
- package/extensions/bytefly/separator/config.ts +15 -0
- package/extensions/bytefly/separator/format.ts +60 -6
- package/extensions/bytefly/separator/register.ts +68 -9
- package/extensions/bytefly/separator/types.ts +8 -0
- package/extensions/bytefly/update/cache.ts +58 -19
- package/extensions/bytefly/update/check.ts +35 -0
- package/extensions/bytefly/update/format.ts +76 -35
- package/extensions/bytefly/update/register.ts +144 -55
- package/extensions/bytefly/update/state.ts +4 -2
- package/extensions/bytefly/update/suppress.ts +66 -0
- package/extensions/bytefly/update/types.ts +15 -0
- package/extensions/bytefly/working/config.ts +4 -5
- package/extensions/bytefly/working/format.ts +24 -7
- package/extensions/bytefly/working/register.ts +34 -3
- package/extensions/check/README.md +253 -0
- package/extensions/check/commands.ts +128 -0
- package/extensions/check/config.ts +39 -0
- package/extensions/check/detect.ts +269 -0
- package/extensions/check/failures.ts +230 -0
- package/extensions/check/format.ts +135 -0
- package/extensions/check/index.ts +26 -0
- package/extensions/check/render.ts +185 -0
- package/extensions/check/runner.ts +176 -0
- package/extensions/check/runtime.ts +244 -0
- package/extensions/check/source.ts +80 -0
- package/extensions/check/tools.ts +173 -0
- package/extensions/check/tui.ts +195 -0
- package/extensions/check/types.ts +99 -0
- package/extensions/document/README.md +1 -1
- package/extensions/document/extract/pdf.ts +8 -16
- package/extensions/document/formats.ts +2 -2
- package/extensions/file-browse/README.md +66 -32
- package/extensions/file-browse/client.ts +95 -32
- package/extensions/file-browse/config.ts +9 -9
- package/extensions/file-browse/git.ts +37 -47
- package/extensions/file-browse/html.ts +39 -58
- package/extensions/file-browse/markdown.ts +110 -92
- package/extensions/file-browse/raw-html.ts +478 -0
- package/extensions/file-browse/router.ts +32 -70
- package/extensions/file-browse/server.ts +18 -56
- package/extensions/file-browse/urls.ts +80 -0
- package/extensions/goal/README.md +103 -47
- package/extensions/goal/clarify-parse.ts +4 -9
- package/extensions/goal/clarify.ts +27 -4
- package/extensions/goal/commands.ts +24 -11
- package/extensions/goal/format.ts +41 -10
- package/extensions/goal/index.ts +39 -16
- package/extensions/goal/plan-route.ts +51 -0
- package/extensions/goal/schema.ts +6 -23
- package/extensions/goal/state.ts +39 -57
- package/extensions/goal/tools.ts +26 -20
- package/extensions/goal/types.ts +11 -1
- package/extensions/job/README.md +20 -6
- package/extensions/job/format.ts +18 -68
- package/extensions/job/index.ts +26 -41
- package/extensions/job/log-writer.ts +208 -0
- package/extensions/job/logs.ts +3 -2
- package/extensions/job/process.ts +6 -99
- package/extensions/job/runtime.ts +42 -181
- package/extensions/job/schema.ts +1 -4
- package/extensions/job/tools.ts +1 -15
- package/extensions/job/tui.ts +117 -307
- package/extensions/job/ui.ts +7 -33
- package/extensions/memory/README.md +3 -1
- package/extensions/memory/errors.ts +5 -6
- package/extensions/memory/tui.ts +14 -24
- package/extensions/plan/README.md +106 -18
- package/extensions/plan/commands.ts +9 -5
- package/extensions/plan/format.ts +87 -0
- package/extensions/plan/index.ts +31 -15
- package/extensions/plan/plans.ts +2 -3
- package/extensions/plan/policy.ts +0 -6
- package/extensions/plan/runtime.ts +4 -2
- package/extensions/plan/steps.ts +37 -8
- package/extensions/plan/todos.ts +44 -3
- package/extensions/plan/tools/enter.ts +94 -0
- package/extensions/plan/tools/exit.ts +296 -0
- package/extensions/plan/tools/nested.ts +21 -0
- package/extensions/plan/tools/steps.ts +136 -0
- package/extensions/plan/tools/todo-seed.ts +76 -0
- package/extensions/plan/tools/write.ts +127 -0
- package/extensions/plan/tools.ts +18 -703
- package/extensions/plan/web-client.ts +6 -22
- package/extensions/plan/web-page.ts +19 -30
- package/extensions/plan/web-router.ts +34 -71
- package/extensions/plan/web.ts +13 -40
- package/extensions/question/answers.ts +3 -8
- package/extensions/question/tui.ts +6 -6
- package/extensions/rewind/README.md +95 -22
- package/extensions/rewind/checklist.ts +185 -0
- package/extensions/rewind/commands.ts +8 -0
- package/extensions/rewind/config.ts +3 -11
- package/extensions/rewind/format.ts +220 -2
- package/extensions/rewind/git.ts +74 -7
- package/extensions/rewind/hunk-picker.ts +114 -0
- package/extensions/rewind/patch.ts +88 -0
- package/extensions/rewind/path-picker.ts +67 -100
- package/extensions/rewind/paths.ts +93 -19
- package/extensions/rewind/restore.ts +53 -10
- package/extensions/rewind/review-tui.ts +320 -0
- package/extensions/rewind/review.ts +397 -0
- package/extensions/rewind/rewind-tui.ts +4 -3
- package/extensions/rewind/runtime.ts +94 -17
- package/extensions/subagent/README.md +118 -8
- package/extensions/subagent/background-tui.ts +208 -0
- package/extensions/subagent/background.ts +470 -0
- package/extensions/subagent/commands.ts +80 -0
- package/extensions/subagent/config.ts +62 -0
- package/extensions/subagent/format.ts +24 -1
- package/extensions/subagent/index.ts +116 -1
- package/extensions/subagent/orchestrate.ts +16 -3
- package/extensions/subagent/render.ts +25 -0
- package/extensions/subagent/run.ts +113 -49
- package/extensions/subagent/schema.ts +12 -0
- package/extensions/subagent/transcripts.ts +202 -0
- package/extensions/subagent/types.ts +23 -0
- package/extensions/subagent/ui.ts +50 -0
- package/extensions/todo/README.md +18 -13
- package/extensions/todo/format.ts +3 -2
- package/extensions/todo/runtime.ts +3 -2
- package/extensions/todo/schema.ts +8 -84
- package/extensions/todo/state.ts +5 -46
- package/extensions/todo/tools.ts +2 -1
- package/extensions/todo/types.ts +6 -15
- package/extensions/watch/README.md +16 -6
- package/extensions/watch/child.ts +150 -0
- package/extensions/watch/format.ts +8 -48
- package/extensions/watch/index.ts +42 -58
- package/extensions/watch/runtime.ts +49 -128
- package/extensions/watch/schema.ts +1 -4
- package/extensions/watch/tools.ts +1 -11
- package/extensions/watch/tui.ts +103 -273
- package/extensions/watch/ui.ts +5 -32
- package/extensions/web-access/README.md +1 -1
- package/extensions/web-access/fetch/page.ts +1 -1
- package/extensions/web-access/fetch/tool.ts +6 -2
- package/extensions/web-access/search/search.ts +2 -5
- package/extensions/wechat/README.md +3 -1
- package/extensions/wechat/client.ts +23 -0
- package/extensions/wechat/commands.ts +57 -42
- package/extensions/wechat/deliver.ts +5 -9
- package/extensions/wechat/errors.ts +3 -6
- package/extensions/wechat/inbound.ts +2 -5
- package/extensions/wechat/index.ts +2 -8
- package/extensions/wechat/outbound.ts +2 -4
- package/extensions/wechat/peer-worker.ts +3 -8
- package/extensions/wechat/poll.ts +10 -9
- package/extensions/wechat/presence.ts +35 -62
- package/extensions/wechat/reply-pipeline.ts +3 -1
- package/extensions/wechat/status-screen.ts +1 -1
- package/extensions/wechat/timer.ts +57 -0
- package/extensions/worktree/config.ts +8 -20
- package/extensions/worktree/enter.ts +7 -12
- package/extensions/worktree/git.ts +38 -69
- package/extensions/worktree/index.ts +5 -4
- package/extensions/worktree/registry.ts +7 -7
- package/extensions/worktree/root-tools.ts +2 -2
- package/extensions/worktree/status.ts +3 -2
- package/lib/README.md +22 -8
- package/lib/config.ts +83 -12
- package/lib/errors.ts +12 -0
- package/lib/format.ts +56 -0
- package/lib/git/read.ts +8 -0
- package/lib/goal-state.ts +23 -8
- package/lib/http/headers.ts +54 -0
- package/lib/http/index.ts +13 -0
- package/lib/http/response.ts +46 -0
- package/lib/http/router.ts +52 -0
- package/lib/http/server.ts +91 -0
- package/lib/lines.ts +77 -0
- package/lib/pdf.ts +82 -0
- package/lib/plan-entry.ts +71 -0
- package/lib/plan-state.ts +34 -0
- package/lib/report.ts +99 -0
- package/lib/requirements.ts +25 -19
- package/lib/spawn.ts +170 -0
- package/lib/todo-state.ts +110 -23
- package/lib/tool-names.ts +7 -0
- package/lib/tool-result.ts +33 -0
- package/lib/tui/README.md +68 -3
- package/lib/tui/chrome.ts +28 -4
- package/lib/tui/columns.ts +6 -1
- package/lib/tui/confirm.ts +65 -0
- package/lib/tui/dock-screen.ts +410 -0
- package/lib/tui/glyphs.ts +2 -1
- package/lib/tui/indent.ts +1 -1
- package/lib/tui/index.ts +2 -0
- package/lib/tui/rows.ts +21 -0
- package/lib/tui/status-controller.ts +35 -1
- package/lib/tui/tool.ts +59 -1
- package/lib/web/components.ts +20 -1
- package/lib/web/markup.ts +32 -0
- package/lib/web/runtime.ts +50 -0
- package/package.json +23 -5
- package/skills/init/SKILL.md +165 -0
- package/skills/init/references/agents-md-quality.md +103 -0
- package/skills/init/references/interview-questions.md +81 -0
- package/skills/refactor/SKILL.md +141 -0
- package/themes/bytefly-dark.json +2 -3
- package/themes/bytefly-light.json +2 -3
- package/extensions/watch/process.ts +0 -63
- package/extensions/web-access/fetch/pdf.ts +0 -45
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import type { ScreenHint } from "../../../../lib/tui/chrome.ts";
|
|
10
11
|
import { sanitizeLine } from "../../../../lib/format.ts";
|
|
11
12
|
import { clip } from "../../../../lib/tui/columns.ts";
|
|
12
13
|
import { checkbox, listRow } from "../../../../lib/tui/rows.ts";
|
|
@@ -46,7 +47,7 @@ export function extensionRow(theme: Theme, width: number, row: PanelRow, selecte
|
|
|
46
47
|
});
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
/** The focused extension's
|
|
50
|
+
/** The focused extension's findings and the pending-changes note. */
|
|
50
51
|
export function extensionDetail(
|
|
51
52
|
theme: Theme,
|
|
52
53
|
width: number,
|
|
@@ -57,10 +58,10 @@ export function extensionDetail(
|
|
|
57
58
|
): string[] {
|
|
58
59
|
const lines: string[] = [];
|
|
59
60
|
const badge = row ? rowBadge(row) : undefined;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
// The focused row directly above already carries the entry's name and verdict,
|
|
62
|
+
// so the pane starts with what the row cannot say: the findings, or the
|
|
63
|
+
// resource path when nothing was reported.
|
|
64
|
+
if (badge?.text === undefined && row?.item) {
|
|
64
65
|
lines.push(clip(` ${paint(theme, "meta", sanitizeLine(row.item.path))}`, width));
|
|
65
66
|
}
|
|
66
67
|
if (row?.report && budget > 0) {
|
|
@@ -71,12 +72,16 @@ export function extensionDetail(
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
/** The section's key hints, in the order they drop as the width runs out. */
|
|
74
|
-
export function extensionHints(options: {
|
|
75
|
+
export function extensionHints(options: {
|
|
76
|
+
expanded: boolean;
|
|
77
|
+
refreshing: boolean;
|
|
78
|
+
projectScope: boolean;
|
|
79
|
+
}): ScreenHint[] {
|
|
75
80
|
return [
|
|
76
81
|
"Esc close",
|
|
77
82
|
"←/→",
|
|
78
|
-
"Space toggle",
|
|
79
|
-
options.expanded ? "Enter collapse" : "Enter detail",
|
|
83
|
+
{ text: "Space toggle", color: "accent" },
|
|
84
|
+
{ text: options.expanded ? "Enter collapse" : "Enter detail", color: "accent" },
|
|
80
85
|
"Tab scope",
|
|
81
86
|
// Only Project scope can hold an override, so the key is not offered elsewhere.
|
|
82
87
|
...(options.projectScope ? ["x inherit"] : []),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The chrome around the panel's body: the layout constants, the section bar with
|
|
3
|
-
* its counts, a group heading,
|
|
3
|
+
* its counts, a group heading, the hint row that a transient notice replaces, and
|
|
4
|
+
* the frame the two sections share.
|
|
4
5
|
*
|
|
5
6
|
* Every section draws its body through these, so the two views cannot drift on
|
|
6
7
|
* spacing, roles, or the width arithmetic.
|
|
@@ -8,12 +9,20 @@
|
|
|
8
9
|
|
|
9
10
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
10
11
|
import { sanitizeLine } from "../../../../lib/format.ts";
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
detailBlockRows,
|
|
14
|
+
dockListFrame,
|
|
15
|
+
screenHint,
|
|
16
|
+
type ScreenHint,
|
|
17
|
+
type ViewportRowsSource,
|
|
18
|
+
} from "../../../../lib/tui/chrome.ts";
|
|
12
19
|
import { clip, fitCells, splitRow } from "../../../../lib/tui/columns.ts";
|
|
20
|
+
import { fitRows, formatRange, type ListCursor } from "../../../../lib/tui/cursor.ts";
|
|
13
21
|
import { BODY_INDENT } from "../../../../lib/tui/indent.ts";
|
|
14
22
|
import { paint } from "../../../../lib/tui/tokens.ts";
|
|
15
23
|
import { sectionSegments, type SummarySegment } from "../format.ts";
|
|
16
24
|
import type { PanelSection } from "../types.ts";
|
|
25
|
+
import { selectableRange, type PanelEntry } from "./entries.ts";
|
|
17
26
|
|
|
18
27
|
/** Body rows when the terminal height is unknown. */
|
|
19
28
|
export const SCREEN_DEFAULT_ROWS = 16;
|
|
@@ -54,8 +63,8 @@ export function groupLine(theme: Theme, width: number, entry: { label: string; c
|
|
|
54
63
|
return clip(` ${label} ${count}`, width);
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
/** The key-hint row. */
|
|
58
|
-
export function hintLine(theme: Theme, width: number, hints:
|
|
66
|
+
/** The key-hint row, or a section's notice that replaces it. */
|
|
67
|
+
export function hintLine(theme: Theme, width: number, hints: ScreenHint[]): string {
|
|
59
68
|
return screenHint(theme, width, hints);
|
|
60
69
|
}
|
|
61
70
|
|
|
@@ -69,3 +78,111 @@ export function savedLine(theme: Theme, width: number, dirty: boolean): string[]
|
|
|
69
78
|
if (!dirty) return [];
|
|
70
79
|
return [clip(`${INDENT}${paint(theme, "warn", "Saved · reload to apply")}`, width)];
|
|
71
80
|
}
|
|
81
|
+
|
|
82
|
+
/** Terminal rows, or `undefined` when the height is unknown. */
|
|
83
|
+
export function totalRows(source: ViewportRowsSource | undefined): number | undefined {
|
|
84
|
+
const rows = typeof source === "function" ? source() : source;
|
|
85
|
+
return rows !== undefined && Number.isFinite(rows) && rows > 0 ? rows : undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Detail rows the focused row gets: the fixed pane, or the free height when
|
|
90
|
+
* expanded. Zero when there is no free row for a pane at all, so a short
|
|
91
|
+
* terminal keeps its body; the caller then drops optional rows if needed.
|
|
92
|
+
*/
|
|
93
|
+
export function detailRows(expanded: boolean, viewportRows: ViewportRowsSource | undefined): number {
|
|
94
|
+
const total = totalRows(viewportRows);
|
|
95
|
+
if (total === undefined) return expanded ? EXPANDED_DETAIL_FALLBACK : DETAIL_ROWS;
|
|
96
|
+
// One row pays for the blank line the frame puts above the pane.
|
|
97
|
+
const available = total - FRAME_ROWS - MIN_BODY_ROWS - 1;
|
|
98
|
+
if (available < 1) return 0;
|
|
99
|
+
return expanded ? available : Math.min(DETAIL_ROWS, available);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Drop optional rows until the frame fits the viewport: the range row first,
|
|
104
|
+
* then the project note, then the blank above the detail pane, then the pane.
|
|
105
|
+
* The header, summary, one body row, and the hint always survive; a terminal
|
|
106
|
+
* shorter than those is left to the host.
|
|
107
|
+
*/
|
|
108
|
+
export function fitOptionalLines(options: {
|
|
109
|
+
viewportRows: ViewportRowsSource | undefined;
|
|
110
|
+
body: string[];
|
|
111
|
+
note: string | undefined;
|
|
112
|
+
range: string | undefined;
|
|
113
|
+
detail: string[];
|
|
114
|
+
}): { body: string[]; range: string | undefined; detail: string[]; noteShown: boolean; detailBlank: boolean } {
|
|
115
|
+
const total = totalRows(options.viewportRows);
|
|
116
|
+
let { body, range, detail } = options;
|
|
117
|
+
let noteShown = options.note !== undefined && body[0] === options.note;
|
|
118
|
+
if (total === undefined) return { body, range, detail, noteShown, detailBlank: true };
|
|
119
|
+
const fits = (blank: boolean): boolean =>
|
|
120
|
+
FRAME_ROWS + body.length + (range === undefined ? 0 : 1) + detailBlockRows(detail, blank) <= total;
|
|
121
|
+
if (!fits(true)) range = undefined;
|
|
122
|
+
if (!fits(true) && noteShown) {
|
|
123
|
+
body = body.slice(1);
|
|
124
|
+
noteShown = false;
|
|
125
|
+
}
|
|
126
|
+
// The separator goes before the pane does: the focused row's findings are
|
|
127
|
+
// worth more than the blank above them.
|
|
128
|
+
const detailBlank = fits(true) || !fits(false);
|
|
129
|
+
if (!fits(false)) detail = [];
|
|
130
|
+
return { body, range, detail, noteShown, detailBlank };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** What {@link panelFrame} draws: the window, the body, and the chrome around it. */
|
|
134
|
+
export interface PanelFrameInput {
|
|
135
|
+
theme: Theme;
|
|
136
|
+
viewportRows: ViewportRowsSource | undefined;
|
|
137
|
+
cursor: ListCursor;
|
|
138
|
+
width: number;
|
|
139
|
+
header: string;
|
|
140
|
+
summary: string;
|
|
141
|
+
entries: readonly PanelEntry[];
|
|
142
|
+
note?: string;
|
|
143
|
+
detail: string[];
|
|
144
|
+
hint: string;
|
|
145
|
+
draw: (entry: PanelEntry, index: number) => string | undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The frame both sections share: the cursor window, the visible body rows, the
|
|
150
|
+
* range row, the optional rows that give way on a short terminal, and the
|
|
151
|
+
* chrome around them. Each section supplies its summary, its detail pane, its
|
|
152
|
+
* hint row, and how one entry is drawn.
|
|
153
|
+
*/
|
|
154
|
+
export function panelFrame(input: PanelFrameInput): { lines: string[]; noteShown: boolean } {
|
|
155
|
+
const { theme, viewportRows, cursor, width, entries, note, detail, hint, draw } = input;
|
|
156
|
+
cursor.visible = fitRows(
|
|
157
|
+
viewportRows,
|
|
158
|
+
entries.length,
|
|
159
|
+
FRAME_ROWS + detailBlockRows(detail) + (note ? 1 : 0),
|
|
160
|
+
SCREEN_DEFAULT_ROWS,
|
|
161
|
+
);
|
|
162
|
+
cursor.sync(entries.length);
|
|
163
|
+
const { start, end } = cursor.window(entries.length);
|
|
164
|
+
|
|
165
|
+
const body: string[] = [];
|
|
166
|
+
if (note) body.push(note);
|
|
167
|
+
for (let index = start; index < end; index += 1) {
|
|
168
|
+
const line = draw(entries[index], index);
|
|
169
|
+
if (line !== undefined) body.push(line);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const range = cursor.clipped(entries.length)
|
|
173
|
+
? formatRange(theme, width, selectableRange(entries, { start, end }))
|
|
174
|
+
: undefined;
|
|
175
|
+
const fitted = fitOptionalLines({ viewportRows, body, note, range, detail });
|
|
176
|
+
return {
|
|
177
|
+
lines: dockListFrame({
|
|
178
|
+
header: input.header,
|
|
179
|
+
summary: input.summary,
|
|
180
|
+
body: fitted.body,
|
|
181
|
+
range: fitted.range,
|
|
182
|
+
detail: fitted.detail.length > 0 ? fitted.detail : undefined,
|
|
183
|
+
detailBlank: fitted.detailBlank,
|
|
184
|
+
hint,
|
|
185
|
+
}),
|
|
186
|
+
noteShown: fitted.noteShown,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
11
|
+
import { sliceByColumn, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
12
12
|
import { sanitizeLine } from "../../../../lib/format.ts";
|
|
13
13
|
import { clip } from "../../../../lib/tui/columns.ts";
|
|
14
14
|
import { BODY_INDENT } from "../../../../lib/tui/indent.ts";
|
|
@@ -23,6 +23,34 @@ import { countsLine, INDENT, savedLine } from "./frame.ts";
|
|
|
23
23
|
/** Pi's personal settings file, named in the detail pane. */
|
|
24
24
|
const PI_SETTINGS_FILE = "settings.json";
|
|
25
25
|
|
|
26
|
+
/** Columns the marker and the leading cell take before the label. */
|
|
27
|
+
const ROW_PREFIX_WIDTH = 4;
|
|
28
|
+
/** Columns a value keeps when the row has to choose between the label and the value. */
|
|
29
|
+
const MIN_VALUE_WIDTH = 8;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The editing cell: the buffer around the caret, scrolled so the caret is always
|
|
33
|
+
* visible, with ellipses where text is off-screen. The cell fits `budget`
|
|
34
|
+
* columns, so a long buffer cannot push the label out of the row.
|
|
35
|
+
*/
|
|
36
|
+
function editingCell(buffer: string, caret: number, budget: number): string {
|
|
37
|
+
const width = Math.max(1, budget);
|
|
38
|
+
const before = buffer.slice(0, caret);
|
|
39
|
+
const beforeWidth = visibleWidth(before);
|
|
40
|
+
const caretRoom = width - 1;
|
|
41
|
+
if (beforeWidth > caretRoom) {
|
|
42
|
+
// Reserve one column for the leading ellipsis; the caret sits at the right edge.
|
|
43
|
+
const shownWidth = Math.max(0, caretRoom - 1);
|
|
44
|
+
const prefix = shownWidth > 0 ? "…" : "";
|
|
45
|
+
const shown = shownWidth > 0 ? sliceByColumn(before, beforeWidth - shownWidth, shownWidth, true) : "";
|
|
46
|
+
return `${prefix}${shown}▏`;
|
|
47
|
+
}
|
|
48
|
+
const used = beforeWidth + 1;
|
|
49
|
+
const afterRoom = Math.max(0, width - used);
|
|
50
|
+
const after = afterRoom > 0 ? truncateToWidth(buffer.slice(caret), afterRoom, "…") : "";
|
|
51
|
+
return `${before}▏${after}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
26
54
|
/** The rows this section shows, and the scope they were read in. */
|
|
27
55
|
export function settingsSummary(theme: Theme, width: number, scope: PanelScope, count: number): string {
|
|
28
56
|
return countsLine(theme, width, settingsSummarySegments(scope, count));
|
|
@@ -36,17 +64,19 @@ export function settingRow(
|
|
|
36
64
|
selected: boolean,
|
|
37
65
|
editing?: { buffer: string; caret: number },
|
|
38
66
|
): string {
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
const label = sanitizeLine(view.label);
|
|
68
|
+
// Keep the whole label when it fits, and never let a long value or an editing
|
|
69
|
+
// buffer erase it: the value gets what is left, with a floor of eight columns.
|
|
70
|
+
const available = Math.max(1, width - ROW_PREFIX_WIDTH - 1);
|
|
71
|
+
const labelReserve = Math.min(visibleWidth(label), Math.max(0, available - MIN_VALUE_WIDTH));
|
|
72
|
+
const valueBudget = Math.max(1, available - labelReserve);
|
|
73
|
+
const rawValue = editing ? editingCell(editing.buffer, editing.caret, valueBudget) : view.valueLabel;
|
|
44
74
|
return listRow(theme, width, {
|
|
45
75
|
selected,
|
|
46
76
|
leading: view.kind === "boolean" ? checkbox(theme, view.value === "true") : " ",
|
|
47
|
-
label
|
|
77
|
+
label,
|
|
48
78
|
labelRole: selected ? "accent" : "label",
|
|
49
|
-
trailing: sanitizeLine(
|
|
79
|
+
trailing: clip(sanitizeLine(rawValue), valueBudget),
|
|
50
80
|
trailingRole: editing ? "accent" : "meta",
|
|
51
81
|
});
|
|
52
82
|
}
|
|
@@ -57,9 +87,10 @@ export function projectNote(theme: Theme, width: number): string {
|
|
|
57
87
|
}
|
|
58
88
|
|
|
59
89
|
/** Where the focused row's value is written, and which scope supplied it. */
|
|
60
|
-
|
|
90
|
+
function originLine(view: SettingView, paths: { scope: WriteScope; cwd: string; agentDir: string }): string {
|
|
91
|
+
const scope = view.origin === "inherited" ? "global" : paths.scope;
|
|
61
92
|
const path =
|
|
62
|
-
view.source === "app" ? join(paths.agentDir, PI_SETTINGS_FILE) : configPath(
|
|
93
|
+
view.source === "app" ? join(paths.agentDir, PI_SETTINGS_FILE) : configPath(scope, paths.cwd, paths.agentDir);
|
|
63
94
|
const file = sanitizeLine(path);
|
|
64
95
|
switch (view.origin) {
|
|
65
96
|
case "inherited":
|
|
@@ -90,7 +121,7 @@ export function settingDetail(options: {
|
|
|
90
121
|
// The pending-changes marker survives the collapsed body: on a short terminal
|
|
91
122
|
// the value still changed, and the screen has to say so.
|
|
92
123
|
const saved = savedLine(theme, width, dirty);
|
|
93
|
-
if (budget === 0) return
|
|
124
|
+
if (budget === 0) return saved;
|
|
94
125
|
|
|
95
126
|
const lines = [headLine];
|
|
96
127
|
const descriptionWidth = Math.max(1, width - BODY_INDENT * 2);
|
|
@@ -105,8 +136,62 @@ export function settingDetail(options: {
|
|
|
105
136
|
}
|
|
106
137
|
|
|
107
138
|
/** The section's key hints, in the order they drop as the width runs out. */
|
|
108
|
-
export function settingsHints(): string[] {
|
|
109
|
-
return [
|
|
139
|
+
export function settingsHints(json = false): string[] {
|
|
140
|
+
return [
|
|
141
|
+
"Esc close",
|
|
142
|
+
"←/→",
|
|
143
|
+
"Space change",
|
|
144
|
+
"Tab scope",
|
|
145
|
+
"u reset",
|
|
146
|
+
json ? "J hide" : "J JSON",
|
|
147
|
+
"[ ] group",
|
|
148
|
+
"↑/↓ select",
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The focused row's section as the active scope stores it.
|
|
154
|
+
*
|
|
155
|
+
* This is the only view of the keys the catalogue leaves to JSON, so it names the
|
|
156
|
+
* section and its file rather than pretending to be complete: the pane is clipped
|
|
157
|
+
* to the detail budget and says how many lines it left out. `section` is the row's
|
|
158
|
+
* `bytefly.json` section, which the caller has already established.
|
|
159
|
+
*/
|
|
160
|
+
export function sectionJsonPane(options: {
|
|
161
|
+
theme: Theme;
|
|
162
|
+
width: number;
|
|
163
|
+
section: string;
|
|
164
|
+
document: unknown;
|
|
165
|
+
budget: number;
|
|
166
|
+
dirty: boolean;
|
|
167
|
+
paths: { scope: WriteScope; cwd: string; agentDir: string };
|
|
168
|
+
}): string[] {
|
|
169
|
+
const { theme, width, section, document, budget, dirty, paths } = options;
|
|
170
|
+
const saved = savedLine(theme, width, dirty);
|
|
171
|
+
if (budget === 0) return saved;
|
|
172
|
+
const scope = paths.scope === "global" ? "Global" : "Project";
|
|
173
|
+
const file = sanitizeLine(configPath(paths.scope, paths.cwd, paths.agentDir));
|
|
174
|
+
const source = paint(theme, "faint", `${scope} · ${file}`);
|
|
175
|
+
const head = `${paint(theme, "meta", sanitizeLine(section))} ${paint(theme, "faint", "—")} ${source}`;
|
|
176
|
+
const lines = [clip(`${INDENT}${head}`, width)];
|
|
177
|
+
const room = Math.max(0, budget - lines.length - saved.length);
|
|
178
|
+
const body = document === undefined ? [] : JSON.stringify(document, null, 2).split("\n");
|
|
179
|
+
|
|
180
|
+
if (body.length === 0) {
|
|
181
|
+
if (room > 0) lines.push(clip(`${INDENT}${paint(theme, "faint", "Not set in this scope.")}`, width));
|
|
182
|
+
lines.push(...saved);
|
|
183
|
+
return lines;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const shown = body.slice(0, room);
|
|
187
|
+
if (body.length > room && shown.length > 0) {
|
|
188
|
+
// The marker replaces one already-shown line, so the hidden count is one
|
|
189
|
+
// larger than the number of lines the slice left out.
|
|
190
|
+
shown[shown.length - 1] = `… ${body.length - room + 1} more lines`;
|
|
191
|
+
}
|
|
192
|
+
for (const line of shown) lines.push(clip(`${INDENT}${paint(theme, "faint", sanitizeLine(line))}`, width));
|
|
193
|
+
lines.push(...saved);
|
|
194
|
+
return lines;
|
|
110
195
|
}
|
|
111
196
|
|
|
112
197
|
/** The editor's key hints: `Esc` cancels the edit, it does not close the screen. */
|