@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
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* flags then narrow what is announced.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { cleanString, loadConfigSection } from "../../../lib/config.ts";
|
|
17
|
+
import { asBoolean, cleanString, loadConfigSection } from "../../../lib/config.ts";
|
|
18
18
|
import { NOTIFY_METHODS, type NotifyConfig, type NotifyMethod } from "./types.ts";
|
|
19
19
|
|
|
20
20
|
/** A notification title longer than this is clamped; it is a label, not a message. */
|
|
21
|
-
const MAX_TITLE_LENGTH = 40;
|
|
21
|
+
export const MAX_TITLE_LENGTH = 40;
|
|
22
22
|
|
|
23
23
|
export const DEFAULT_NOTIFY_CONFIG: NotifyConfig = {
|
|
24
24
|
enabled: false,
|
|
@@ -30,11 +30,6 @@ export const DEFAULT_NOTIFY_CONFIG: NotifyConfig = {
|
|
|
30
30
|
title: "Pi",
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
/** A boolean, or the fallback. */
|
|
34
|
-
function bool(value: unknown, fallback: boolean): boolean {
|
|
35
|
-
return typeof value === "boolean" ? value : fallback;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
33
|
/** One of the known methods, or the fallback. */
|
|
39
34
|
function method(value: unknown, fallback: NotifyMethod): NotifyMethod {
|
|
40
35
|
return typeof value === "string" && (NOTIFY_METHODS as readonly string[]).includes(value)
|
|
@@ -46,11 +41,11 @@ function method(value: unknown, fallback: NotifyMethod): NotifyMethod {
|
|
|
46
41
|
export function normalizeNotifyConfig(raw: Record<string, unknown> | undefined, base: NotifyConfig): NotifyConfig {
|
|
47
42
|
if (!raw) return base;
|
|
48
43
|
return {
|
|
49
|
-
enabled:
|
|
50
|
-
turnFinished:
|
|
51
|
-
needsInput:
|
|
52
|
-
jobFinished:
|
|
53
|
-
matchFound:
|
|
44
|
+
enabled: asBoolean(raw.enabled, base.enabled),
|
|
45
|
+
turnFinished: asBoolean(raw.turnFinished, base.turnFinished),
|
|
46
|
+
needsInput: asBoolean(raw.needsInput, base.needsInput),
|
|
47
|
+
jobFinished: asBoolean(raw.jobFinished, base.jobFinished),
|
|
48
|
+
matchFound: asBoolean(raw.matchFound, base.matchFound),
|
|
54
49
|
method: method(raw.method, base.method),
|
|
55
50
|
title: cleanString(raw.title, base.title).slice(0, MAX_TITLE_LENGTH),
|
|
56
51
|
};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* to smuggle a control character into an escape sequence or a shell argument.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
18
18
|
import { sanitizeLine } from "../../../lib/format.ts";
|
|
19
19
|
import type { NotifyConfig, NotifyMethod } from "./types.ts";
|
|
20
20
|
|
|
@@ -30,7 +30,7 @@ export interface NotifySeams {
|
|
|
30
30
|
run(command: string, args: string[]): void;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/** The production seams: this process's platform, environment, stdout, and
|
|
33
|
+
/** The production seams: this process's platform, environment, stdout, and a detached notifier. */
|
|
34
34
|
export function defaultSeams(): NotifySeams {
|
|
35
35
|
return {
|
|
36
36
|
platform: process.platform,
|
|
@@ -38,9 +38,14 @@ export function defaultSeams(): NotifySeams {
|
|
|
38
38
|
stdout: process.stdout,
|
|
39
39
|
run: (command, args) => {
|
|
40
40
|
try {
|
|
41
|
-
|
|
41
|
+
// No pipes and unref'd: the notifier is best-effort and must not keep
|
|
42
|
+
// Pi alive, which `/quit` right after a turn would otherwise wait out
|
|
43
|
+
// (PowerShell's cold start alone is a second).
|
|
44
|
+
const child = spawn(command, args, { windowsHide: true, stdio: "ignore" });
|
|
45
|
+
child.on("error", () => {});
|
|
46
|
+
child.unref();
|
|
42
47
|
} catch {
|
|
43
|
-
//
|
|
48
|
+
// A missing command must not break a turn.
|
|
44
49
|
}
|
|
45
50
|
},
|
|
46
51
|
};
|
|
@@ -89,7 +94,10 @@ function windowsToastScript(title: string, body: string): string {
|
|
|
89
94
|
return [
|
|
90
95
|
`${mgr} > $null`,
|
|
91
96
|
`$xml = [${type}.ToastNotificationManager]::GetTemplateContent(${template})`,
|
|
92
|
-
`$xml.GetElementsByTagName('text')
|
|
97
|
+
`$nodes = $xml.GetElementsByTagName('text')`,
|
|
98
|
+
// PowerShell 5.1 cannot index the live node list with `[0]`; `.Item(0)`
|
|
99
|
+
// reads the single node without enumerating a collection AppendChild mutates.
|
|
100
|
+
`$nodes.Item(0).AppendChild($xml.CreateTextNode('${powershellLiteral(body)}')) > $null`,
|
|
93
101
|
`[${type}.ToastNotificationManager]::CreateToastNotifier('${powershellLiteral(title)}').Show(${toast})`,
|
|
94
102
|
].join("; ");
|
|
95
103
|
}
|
|
@@ -29,6 +29,7 @@ import type { Discovery } from "../picker/types.ts";
|
|
|
29
29
|
import { formatListing, formatSettingsListing } from "./format.ts";
|
|
30
30
|
import { buildPanelModel, extensionName } from "./join.ts";
|
|
31
31
|
import { createSettingsRuntime, type SettingsRuntime, type SettingsRuntimeDeps } from "./settings-runtime.ts";
|
|
32
|
+
import { shippedThemeNames } from "./themes.ts";
|
|
32
33
|
import { PanelComponent } from "./tui.ts";
|
|
33
34
|
import type { PanelSection } from "./types.ts";
|
|
34
35
|
|
|
@@ -130,6 +131,19 @@ function notifyExtension(
|
|
|
130
131
|
);
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Theme names the row offers: the host's resolved list, narrowed to this
|
|
136
|
+
* package's own palettes. The list is empty outside the TUI (RPC, print, JSON),
|
|
137
|
+
* where the row still shows the stored value.
|
|
138
|
+
*/
|
|
139
|
+
function settingsThemeNames(ctx: ExtensionCommandContext): string[] {
|
|
140
|
+
try {
|
|
141
|
+
return shippedThemeNames(ctx.ui.getAllThemes());
|
|
142
|
+
} catch {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
133
147
|
/** Register the single `/bytefly` command. */
|
|
134
148
|
export function registerPanel(pi: ExtensionAPI, deps: PanelCommandDeps): void {
|
|
135
149
|
const discover = deps.discover ?? discoverExtensions;
|
|
@@ -146,6 +160,7 @@ export function registerPanel(pi: ExtensionAPI, deps: PanelCommandDeps): void {
|
|
|
146
160
|
const cwd = ctx.cwd ?? process.cwd();
|
|
147
161
|
const agentDir = getAgentDir();
|
|
148
162
|
const trusted = ctx.isProjectTrusted();
|
|
163
|
+
const themes = settingsThemeNames(ctx);
|
|
149
164
|
// The mounted screen owns its feedback: a refusal or a failed write belongs in
|
|
150
165
|
// the frame, not appended to the transcript behind it. Before the screen
|
|
151
166
|
// exists (the non-TUI settings path, a pre-mount failure) the transcript is
|
|
@@ -171,8 +186,8 @@ export function registerPanel(pi: ExtensionAPI, deps: PanelCommandDeps): void {
|
|
|
171
186
|
// packages or probing the doctor.
|
|
172
187
|
if (section === "settings" && ctx.mode !== "tui") {
|
|
173
188
|
const stores = createSettingsStores(cwd, agentDir, trusted);
|
|
174
|
-
const settings = makeSettings({ stores, cwd, agentDir, trusted, onError: settingsNotice });
|
|
175
|
-
ctx.ui.notify(formatSettingsListing(settings.views("global")
|
|
189
|
+
const settings = makeSettings({ stores, cwd, agentDir, trusted, themes, onError: settingsNotice });
|
|
190
|
+
ctx.ui.notify(formatSettingsListing(settings.views("global")), "info");
|
|
176
191
|
return;
|
|
177
192
|
}
|
|
178
193
|
|
|
@@ -189,6 +204,7 @@ export function registerPanel(pi: ExtensionAPI, deps: PanelCommandDeps): void {
|
|
|
189
204
|
cwd,
|
|
190
205
|
agentDir,
|
|
191
206
|
trusted: discovery.trusted,
|
|
207
|
+
themes,
|
|
192
208
|
onError: settingsNotice,
|
|
193
209
|
});
|
|
194
210
|
|
|
@@ -236,6 +252,9 @@ export function registerPanel(pi: ExtensionAPI, deps: PanelCommandDeps): void {
|
|
|
236
252
|
await withRailsSuppressed(pi, () =>
|
|
237
253
|
ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
|
|
238
254
|
const unsubscribe = runtime.onChange(() => tui.requestRender());
|
|
255
|
+
// The settings runtime re-reads its snapshot after a write; without a
|
|
256
|
+
// subscriber that repaint depends on every write chain's own callback.
|
|
257
|
+
const unsubscribeSettings = settings.onChange(() => tui.requestRender());
|
|
239
258
|
const component = new PanelComponent({
|
|
240
259
|
runtime,
|
|
241
260
|
reports: () => reports,
|
|
@@ -257,6 +276,7 @@ export function registerPanel(pi: ExtensionAPI, deps: PanelCommandDeps): void {
|
|
|
257
276
|
// A late error falls back to the transcript, never a disposed screen.
|
|
258
277
|
panel = undefined;
|
|
259
278
|
unsubscribe();
|
|
279
|
+
unsubscribeSettings();
|
|
260
280
|
done();
|
|
261
281
|
},
|
|
262
282
|
});
|
|
@@ -31,7 +31,7 @@ export interface SummarySegment {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/** Row and problem counts over a model. */
|
|
34
|
-
|
|
34
|
+
interface PanelCounts {
|
|
35
35
|
extensions: number;
|
|
36
36
|
/** Rows switched off in settings (the picker's checkbox), not the env flag. */
|
|
37
37
|
off: number;
|
|
@@ -113,28 +113,21 @@ export function sectionSegments(active: PanelSection): SummarySegment[] {
|
|
|
113
113
|
export function settingsSummarySegments(scope: PanelScope, count: number): SummarySegment[] {
|
|
114
114
|
return [
|
|
115
115
|
{ text: scope === "global" ? "Global" : "Project", color: "accent" },
|
|
116
|
-
{ text: `${count}
|
|
116
|
+
{ text: `${count} settings`, color: "meta" },
|
|
117
117
|
];
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
/** The Settings headline for non-TUI modes, e.g. `bytefly: Settings · Global · 5 settings`. */
|
|
121
|
-
export function formatSettingsHeadline(scope: PanelScope, count: number): string {
|
|
122
|
-
return `bytefly: Settings · ${settingsSummarySegments(scope, count)
|
|
123
|
-
.map((segment) => segment.text)
|
|
124
|
-
.join(" · ")}`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
120
|
/**
|
|
128
|
-
* The
|
|
129
|
-
*
|
|
121
|
+
* The Settings headline for non-TUI modes, e.g. `bytefly: Settings · Global · 16 settings`.
|
|
122
|
+
* The listing only ever reads the personal document, so the scope is not a parameter.
|
|
130
123
|
*/
|
|
131
|
-
export function
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
124
|
+
export function formatSettingsHeadline(count: number): string {
|
|
125
|
+
return `bytefly: Settings · Global · ${count} settings`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The grouped Settings listing for non-TUI modes. */
|
|
129
|
+
export function formatSettingsListing(views: readonly SettingView[]): string {
|
|
130
|
+
const lines = [formatSettingsHeadline(views.length)];
|
|
138
131
|
for (const group of groupViews(views)) {
|
|
139
132
|
lines.push("", `${group.label} (${group.views.length})`);
|
|
140
133
|
for (const view of group.views) {
|
|
@@ -152,7 +145,7 @@ export function formatHeadline(model: PanelModel, scope: PanelScope, options: Pa
|
|
|
152
145
|
}
|
|
153
146
|
|
|
154
147
|
/** A row's glyph and verdict, drawn from its enablement and health axes. */
|
|
155
|
-
|
|
148
|
+
interface RowBadge {
|
|
156
149
|
/** The status glyph, or `undefined` when the row has nothing to say. */
|
|
157
150
|
glyph?: string;
|
|
158
151
|
/** The role the glyph is painted in. */
|
|
@@ -165,8 +158,10 @@ export interface RowBadge {
|
|
|
165
158
|
* The row's badge. Settings is the enablement axis the checkbox already shows,
|
|
166
159
|
* so an off extension gets one verdict (`off`) instead of a health word that
|
|
167
160
|
* contradicts it. `PI_DISABLED_EXTENSIONS` is a different axis, so it is named
|
|
168
|
-
* rather than sharing the word: `env disabled`.
|
|
169
|
-
*
|
|
161
|
+
* rather than sharing the word: `env disabled`. The glyph marks what needs
|
|
162
|
+
* attention — a warning, a miss, a disable — so a row with nothing to report
|
|
163
|
+
* carries no glyph at all, and the column reads as one shape instead of a
|
|
164
|
+
* checkmark that comes and goes.
|
|
170
165
|
*/
|
|
171
166
|
export function rowBadge(row: PanelRow): RowBadge {
|
|
172
167
|
if (row.item && !row.item.enabled) return { glyph: GLYPHS.disabled, role: "faint", text: "off" };
|
|
@@ -174,6 +169,7 @@ export function rowBadge(row: PanelRow): RowBadge {
|
|
|
174
169
|
if (!report) return { role: "faint" };
|
|
175
170
|
if (!report.enabled) return { glyph: GLYPHS.disabled, role: "faint", text: "env disabled" };
|
|
176
171
|
const severity = reportSeverity(report);
|
|
172
|
+
if (severity === "ok") return { role: "faint", text: reportSummary(report) };
|
|
177
173
|
return { glyph: severityGlyphChar(severity), role: severityRole(severity), text: reportSummary(report) };
|
|
178
174
|
}
|
|
179
175
|
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The stateful Settings section: one snapshot of the two config documents and
|
|
3
|
-
* the personal settings, the dirty flag, and the
|
|
3
|
+
* the personal settings, the dirty flag, and the writes it offers.
|
|
4
4
|
*
|
|
5
5
|
* App rows are written through Pi's `SettingsStore` (personal file only), and
|
|
6
|
-
* `bytefly.json` rows through the per-key
|
|
7
|
-
* sibling key or another extension's section. A
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* `bytefly.json` rows through the per-key writers, so an edit cannot disturb a
|
|
7
|
+
* sibling key or another extension's section. A reset deletes one leaf through the
|
|
8
|
+
* same path, and the JSON pane reads the section the row belongs to. A failed
|
|
9
|
+
* write leaves the snapshot untouched and reports through `onError`; the screen
|
|
10
|
+
* never shows a value the file did not accept.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { join } from "node:path";
|
|
13
14
|
import { CONFIG_DIR_NAME, type QuietStartup } from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
configSection,
|
|
17
|
+
configValueAt,
|
|
18
|
+
readJson,
|
|
19
|
+
SHARED_CONFIG_FILE,
|
|
20
|
+
unsetConfigValueAt,
|
|
21
|
+
writeConfigValuesAt,
|
|
22
|
+
} from "../../../lib/config.ts";
|
|
15
23
|
import type { SettingsStore, WriteScope } from "../picker/types.ts";
|
|
16
24
|
import {
|
|
17
|
-
|
|
25
|
+
type ConfigSectionName,
|
|
18
26
|
nextValue,
|
|
19
27
|
type SettingDescriptor,
|
|
20
28
|
type SettingsSnapshot,
|
|
@@ -23,7 +31,7 @@ import {
|
|
|
23
31
|
settingViews,
|
|
24
32
|
storedValue,
|
|
25
33
|
textError,
|
|
26
|
-
|
|
34
|
+
UNTRUSTED_SETTINGS_NOTE,
|
|
27
35
|
} from "./settings.ts";
|
|
28
36
|
|
|
29
37
|
/** Host seams for the Settings runtime. */
|
|
@@ -35,16 +43,21 @@ export interface SettingsRuntimeDeps {
|
|
|
35
43
|
trusted: boolean;
|
|
36
44
|
/** Report a refused or failed write to the screen. */
|
|
37
45
|
onError?: (message: string) => void;
|
|
46
|
+
/** Theme names for the Theme row, already narrowed to this package's palettes; empty outside the TUI. */
|
|
47
|
+
themes?: readonly string[];
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
/** What the panel needs from the runtime. */
|
|
41
51
|
export interface SettingsSurface {
|
|
42
52
|
views(scope: WriteScope): SettingView[];
|
|
43
|
-
count(scope: WriteScope): number;
|
|
44
53
|
/** Cycle an enum or toggle a boolean. */
|
|
45
54
|
change(scope: WriteScope, id: string): Promise<void>;
|
|
46
55
|
/** Store a value the user typed; `false` when the row refused it. */
|
|
47
56
|
setValue(scope: WriteScope, id: string, raw: string): Promise<boolean>;
|
|
57
|
+
/** Delete the focused row's leaf so its default applies; `false` when nothing changed. */
|
|
58
|
+
reset(scope: WriteScope, id: string): Promise<boolean>;
|
|
59
|
+
/** The raw `bytefly.json` section for `scope`, for the JSON pane. */
|
|
60
|
+
sectionDocument(scope: WriteScope, section: ConfigSectionName): unknown;
|
|
48
61
|
isDirty(): boolean;
|
|
49
62
|
}
|
|
50
63
|
|
|
@@ -62,8 +75,9 @@ export function configPath(scope: WriteScope, cwd: string, agentDir: string): st
|
|
|
62
75
|
function readSnapshot(deps: SettingsRuntimeDeps): SettingsSnapshot {
|
|
63
76
|
return {
|
|
64
77
|
app: deps.stores.global.globalSettings(),
|
|
65
|
-
global: readJson(
|
|
66
|
-
project: deps.trusted ? (readJson(
|
|
78
|
+
global: readJson(configPath("global", deps.cwd, deps.agentDir)) ?? {},
|
|
79
|
+
project: deps.trusted ? (readJson(configPath("project", deps.cwd, deps.agentDir)) ?? {}) : {},
|
|
80
|
+
themes: deps.themes ?? [],
|
|
67
81
|
};
|
|
68
82
|
}
|
|
69
83
|
|
|
@@ -80,6 +94,9 @@ function applyAppSetting(store: SettingsStore, row: SettingDescriptor, raw: stri
|
|
|
80
94
|
case "showHardwareCursor":
|
|
81
95
|
store.setShowHardwareCursor(value as boolean);
|
|
82
96
|
return;
|
|
97
|
+
case "theme":
|
|
98
|
+
store.setTheme(value as string);
|
|
99
|
+
return;
|
|
83
100
|
default:
|
|
84
101
|
throw new Error(`Unknown app setting ${row.id}`);
|
|
85
102
|
}
|
|
@@ -88,7 +105,7 @@ function applyAppSetting(store: SettingsStore, row: SettingDescriptor, raw: stri
|
|
|
88
105
|
/** Why `row` cannot be written in `scope`, or `undefined` when it can. */
|
|
89
106
|
function refusal(row: SettingDescriptor, scope: WriteScope, trusted: boolean): string | undefined {
|
|
90
107
|
if (scope === "project" && !trusted) {
|
|
91
|
-
return
|
|
108
|
+
return UNTRUSTED_SETTINGS_NOTE;
|
|
92
109
|
}
|
|
93
110
|
if (row.source === "app" && scope !== "global") return `${row.label} is a Global setting.`;
|
|
94
111
|
return undefined;
|
|
@@ -120,8 +137,12 @@ export function createSettingsRuntime(deps: SettingsRuntimeDeps): SettingsRuntim
|
|
|
120
137
|
const config = row.config;
|
|
121
138
|
if (!config) throw new Error(`Setting ${row.id} has no config section`);
|
|
122
139
|
const path = configPath(scope, deps.cwd, deps.agentDir);
|
|
123
|
-
|
|
124
|
-
|
|
140
|
+
// One document pass for the row and every companion leaf it declares, so a
|
|
141
|
+
// failure cannot leave the row written and its companion not.
|
|
142
|
+
writeConfigValuesAt(path, [
|
|
143
|
+
{ section: config.section, path: config.path, value: storedValue(row, raw) },
|
|
144
|
+
...(row.also?.(raw) ?? []),
|
|
145
|
+
]);
|
|
125
146
|
};
|
|
126
147
|
|
|
127
148
|
/** Take a completed write: re-read the snapshot, mark it dirty, repaint. */
|
|
@@ -133,10 +154,15 @@ export function createSettingsRuntime(deps: SettingsRuntimeDeps): SettingsRuntim
|
|
|
133
154
|
|
|
134
155
|
return {
|
|
135
156
|
views: (scope) => settingViews(snapshot, scope),
|
|
136
|
-
count: (scope) => countSettings(scope),
|
|
137
157
|
change: async (scope, id) => {
|
|
138
158
|
const row = SETTING_ROWS.find((entry) => entry.id === id);
|
|
139
159
|
if (!row) return;
|
|
160
|
+
// Only a boolean or an enum cycles; a text row is stored from its editor,
|
|
161
|
+
// so refusing here keeps `nextValue`'s cycle contract honest.
|
|
162
|
+
if (row.kind === "text") {
|
|
163
|
+
deps.onError?.(`${row.label} is edited as text, not cycled.`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
140
166
|
const refused = refusal(row, scope, deps.trusted);
|
|
141
167
|
if (refused !== undefined) {
|
|
142
168
|
deps.onError?.(refused);
|
|
@@ -144,11 +170,9 @@ export function createSettingsRuntime(deps: SettingsRuntimeDeps): SettingsRuntim
|
|
|
144
170
|
}
|
|
145
171
|
const current = settingViews(snapshot, scope).find((entry) => entry.id === id);
|
|
146
172
|
if (!current) return;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
173
|
+
// Use the view's options: the Theme row's list comes from the host, not
|
|
174
|
+
// from the static descriptor.
|
|
175
|
+
const next = nextValue(current, current.value);
|
|
152
176
|
try {
|
|
153
177
|
await write(scope, row, next.value);
|
|
154
178
|
} catch (error) {
|
|
@@ -159,8 +183,8 @@ export function createSettingsRuntime(deps: SettingsRuntimeDeps): SettingsRuntim
|
|
|
159
183
|
},
|
|
160
184
|
async setValue(scope, id, raw) {
|
|
161
185
|
const row = SETTING_ROWS.find((entry) => entry.id === id);
|
|
162
|
-
if (!row
|
|
163
|
-
deps.onError?.(`${
|
|
186
|
+
if (!row) {
|
|
187
|
+
deps.onError?.(`${id} is not a setting.`);
|
|
164
188
|
return false;
|
|
165
189
|
}
|
|
166
190
|
const refused = refusal(row, scope, deps.trusted);
|
|
@@ -182,6 +206,46 @@ export function createSettingsRuntime(deps: SettingsRuntimeDeps): SettingsRuntim
|
|
|
182
206
|
committed();
|
|
183
207
|
return true;
|
|
184
208
|
},
|
|
209
|
+
async reset(scope, id) {
|
|
210
|
+
const row = SETTING_ROWS.find((entry) => entry.id === id);
|
|
211
|
+
if (!row) {
|
|
212
|
+
deps.onError?.(`${id} is not a setting.`);
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (row.source === "app") {
|
|
216
|
+
deps.onError?.(`${row.label} is a Pi setting; Pi has no unset, so set the value instead.`);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
const refused = refusal(row, scope, deps.trusted);
|
|
220
|
+
if (refused !== undefined) {
|
|
221
|
+
deps.onError?.(refused);
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
const config = row.config;
|
|
225
|
+
if (!config) return false;
|
|
226
|
+
const document = scope === "global" ? snapshot.global : snapshot.project;
|
|
227
|
+
if (configValueAt(configSection(document, config.section), config.path) === undefined) {
|
|
228
|
+
const inherited =
|
|
229
|
+
scope === "project" &&
|
|
230
|
+
configValueAt(configSection(snapshot.global, config.section), config.path) !== undefined;
|
|
231
|
+
deps.onError?.(
|
|
232
|
+
inherited
|
|
233
|
+
? `${row.label} is inherited from Global; there is nothing to reset in Project.`
|
|
234
|
+
: `${row.label} is already at its default.`,
|
|
235
|
+
);
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
unsetConfigValueAt(configPath(scope, deps.cwd, deps.agentDir), config.section, config.path);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
deps.onError?.(`Could not reset ${row.label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
committed();
|
|
245
|
+
return true;
|
|
246
|
+
},
|
|
247
|
+
sectionDocument: (scope, section) =>
|
|
248
|
+
configSection(scope === "global" ? snapshot.global : snapshot.project, section),
|
|
185
249
|
isDirty: () => dirty,
|
|
186
250
|
onChange: (listener) => {
|
|
187
251
|
listeners.add(listener);
|