@gavin-hu/my-pi-agent 0.17.0 → 0.19.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 +61 -45
- package/README.zh-CN.md +54 -42
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +344 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +465 -416
- package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
- package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
- package/docs/extensions/file-browse.md +554 -0
- package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
- package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
- package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
- package/{extensions/job/README.md → docs/extensions/job.md} +66 -53
- package/{extensions/memory/README.md → docs/extensions/memory.md} +74 -63
- package/docs/extensions/plan.md +976 -0
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -28
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +285 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +166 -119
- package/docs/extensions/team.md +226 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +60 -38
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +275 -192
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
- package/extensions/board/commands.ts +87 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +72 -0
- package/extensions/board/index.ts +40 -0
- package/extensions/board/input.ts +54 -0
- package/extensions/board/join.ts +42 -0
- package/extensions/board/page.ts +312 -0
- package/extensions/board/result.ts +99 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +75 -0
- package/extensions/board/seat.ts +29 -0
- package/extensions/board/snapshot.ts +41 -0
- package/extensions/board/tools.ts +279 -0
- package/extensions/board/transport/router.ts +172 -0
- package/extensions/board/transport/server.ts +149 -0
- package/extensions/board/types.ts +54 -0
- package/extensions/board/view/actions.ts +306 -0
- package/extensions/board/view/client.ts +41 -0
- package/extensions/board/view/keys.ts +121 -0
- package/extensions/board/view/live.ts +397 -0
- package/extensions/board/view/page.ts +158 -0
- package/extensions/board/view/render.ts +496 -0
- package/extensions/board/view/styles.ts +211 -0
- package/extensions/board/view/team.ts +305 -0
- package/extensions/board/writes.ts +99 -0
- package/extensions/bytefly/notify/types.ts +1 -1
- package/extensions/bytefly/panel/commands.ts +40 -7
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +11 -21
- package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
- package/extensions/bytefly/picker/discovery.ts +0 -2
- package/extensions/bytefly/picker/types.ts +0 -2
- package/extensions/bytefly/update/check.ts +39 -0
- package/extensions/bytefly/update/format.ts +15 -0
- package/extensions/bytefly/update/register.ts +73 -34
- package/extensions/bytefly/update/state.ts +6 -3
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +13 -4
- package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
- package/extensions/file-browse/index.ts +4 -4
- package/extensions/file-browse/{live.ts → live/live.ts} +54 -82
- package/extensions/file-browse/live/rev.ts +42 -0
- package/extensions/file-browse/{router.ts → transport/router.ts} +44 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/view/chrome.ts +83 -0
- package/extensions/file-browse/{client.ts → view/client.ts} +63 -1
- package/extensions/file-browse/view/diff-html.ts +94 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +32 -158
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{live-client.ts → view/live-client.ts} +47 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
- package/extensions/file-browse/{styles.ts → view/styles.ts} +31 -5
- package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
- package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
- package/extensions/file-read/schema.ts +2 -1
- package/extensions/goal/plan-route.ts +8 -2
- package/extensions/job/index.ts +15 -7
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +77 -21
- package/extensions/plan/feedback.ts +409 -63
- package/extensions/plan/index.ts +7 -5
- package/extensions/plan/plan-index.ts +44 -10
- package/extensions/plan/plan-text.ts +51 -24
- package/extensions/plan/plans.ts +162 -57
- package/extensions/plan/policy.ts +20 -12
- package/extensions/plan/review-prompt.ts +93 -0
- package/extensions/plan/review.ts +51 -95
- package/extensions/plan/runtime.ts +22 -10
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +63 -95
- package/extensions/plan/tools/enter.ts +8 -3
- package/extensions/plan/tools/exit.ts +13 -19
- package/extensions/plan/tools/write.ts +8 -3
- package/extensions/plan/tools.ts +1 -1
- package/extensions/plan/tui.ts +29 -15
- package/extensions/plan/types.ts +29 -3
- package/extensions/plan/web-chrome.ts +2 -1
- package/extensions/plan/web-client.ts +122 -178
- package/extensions/plan/web-collab.ts +207 -0
- package/extensions/plan/web-comments.ts +269 -0
- package/extensions/plan/web-page.ts +96 -17
- package/extensions/plan/web-router.ts +109 -47
- package/extensions/plan/web-share-session.ts +191 -0
- package/extensions/plan/web-share.ts +169 -0
- package/extensions/plan/web-streams.ts +124 -0
- package/extensions/plan/web-tokens.ts +63 -0
- package/extensions/plan/web.ts +245 -151
- package/extensions/question/dialogs.ts +91 -1
- package/extensions/question/tools.ts +2 -1
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
- package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
- package/extensions/rewind/{config.ts → config/config.ts} +1 -1
- package/extensions/rewind/index.ts +3 -3
- package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
- package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
- package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
- package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
- package/extensions/rewind/{format.ts → text/format.ts} +3 -3
- package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
- package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
- package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
- package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
- package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
- package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
- package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
- package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
- package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
- package/extensions/rewind/{status.ts → view/status.ts} +4 -4
- package/extensions/session-bus/commands.ts +113 -0
- package/extensions/session-bus/format.ts +101 -0
- package/extensions/session-bus/index.ts +75 -0
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +86 -0
- package/extensions/session-bus/overlap.ts +36 -0
- package/extensions/session-bus/protocol.ts +104 -0
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +208 -0
- package/extensions/session-bus/rows.ts +171 -0
- package/extensions/session-bus/runtime.ts +466 -0
- package/extensions/session-bus/store.ts +365 -0
- package/extensions/session-bus/tools.ts +192 -0
- package/extensions/session-bus/tui.ts +394 -0
- package/extensions/session-bus/types.ts +98 -0
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +112 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +105 -0
- package/extensions/team/index.ts +33 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +246 -0
- package/extensions/team/types.ts +48 -0
- package/extensions/watch/index.ts +15 -9
- package/extensions/web-access/fetch/page.ts +4 -1
- package/extensions/web-access/fetch/structured.ts +447 -0
- package/extensions/web-access/fetch/tool.ts +2 -1
- package/extensions/web-access/search/tool.ts +2 -1
- package/extensions/wechat/bridge.ts +76 -92
- package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
- package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
- package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
- package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
- package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
- package/extensions/wechat/dialog/channels.ts +136 -0
- package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +66 -51
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +40 -25
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
- package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
- package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +119 -56
- package/extensions/wechat/{presence.ts → peer/presence.ts} +11 -11
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +13 -5
- package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
- package/extensions/wechat/{state.ts → state/state.ts} +14 -2
- package/extensions/wechat/{format.ts → text/format.ts} +3 -3
- package/extensions/wechat/{notices.ts → text/notices.ts} +15 -7
- package/extensions/wechat/tools.ts +29 -12
- package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
- package/extensions/wechat/{deliver.ts → transport/deliver.ts} +104 -40
- package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
- package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
- package/extensions/wechat/transport/outbound.ts +142 -0
- package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
- package/extensions/wechat/types.ts +10 -1
- package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
- package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
- package/extensions/worktree/git.ts +5 -0
- package/extensions/worktree/guard.ts +3 -5
- package/extensions/worktree/registry.ts +11 -5
- package/extensions/worktree/status.ts +1 -3
- package/lib/README.md +12 -6
- package/lib/board/card.ts +290 -0
- package/lib/board/links.ts +117 -0
- package/lib/board/page-pointer.ts +256 -0
- package/lib/board/round-text.ts +72 -0
- package/lib/board/round.ts +89 -0
- package/lib/board/rules.ts +304 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +310 -0
- package/lib/board/view.ts +150 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +13 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/router.ts +40 -13
- package/lib/http/serve.ts +234 -0
- package/lib/http/server.ts +29 -13
- package/lib/http/sink.ts +110 -0
- package/lib/http/token.ts +38 -0
- package/lib/interaction.ts +10 -0
- package/lib/notice.ts +29 -2
- package/lib/plan-entry.ts +7 -2
- package/lib/policy.ts +12 -2
- package/lib/qr.ts +64 -0
- package/lib/repo-key.ts +74 -0
- package/lib/report.ts +52 -33
- package/lib/requirements.ts +7 -9
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +130 -0
- package/lib/session-tool.ts +184 -0
- package/lib/tool-names.ts +25 -2
- package/lib/tui/README.md +16 -2
- package/lib/tui/dock-screen.ts +85 -17
- package/lib/tui/glyphs.ts +2 -0
- package/lib/tui/line-pane.ts +15 -4
- package/lib/tui/tool.ts +23 -4
- package/lib/web/README.md +5 -2
- package/lib/web/components.ts +6 -1
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +27 -14
- package/skills/init/SKILL.md +80 -45
- package/skills/init/references/agents-md-quality.md +26 -2
- package/skills/init/references/interview-questions.md +36 -14
- package/skills/next/SKILL.md +77 -51
- package/skills/project-docs/SKILL.md +133 -0
- package/skills/project-docs/references/audit.md +131 -0
- package/skills/project-docs/references/changelog.md +105 -0
- package/skills/project-docs/references/discovery.md +127 -0
- package/skills/project-docs/references/readme.md +80 -0
- package/skills/project-docs/references/release.md +84 -0
- package/skills/project-docs/references/roadmap.md +93 -0
- package/skills/project-docs/scripts/state.ts +316 -0
- package/skills/refactor/SKILL.md +100 -125
- package/skills/refactor/references/handoff.md +35 -0
- package/skills/refactor/references/signals.md +54 -0
- package/skills/refactor/scripts/inventory.ts +587 -0
- package/extensions/file-browse/README.md +0 -467
- package/extensions/file-browse/server.ts +0 -134
- package/extensions/plan/README.md +0 -709
- package/extensions/plan/config.ts +0 -27
- package/extensions/wechat/outbound.ts +0 -100
- package/extensions/wechat/qrcode.ts +0 -46
- package/skills/changelog/SKILL.md +0 -148
- /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
- /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
- /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
- /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
- /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
- /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
- /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
- /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
- /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `/bytefly` surface: one command, `/bytefly [doctor] [settings] [skills] [update] [extension] [refresh]`.
|
|
2
|
+
* The `/bytefly` surface: one command, `/bytefly [doctor] [settings] [skills] [update] [upgrade] [extension] [refresh]`.
|
|
3
3
|
*
|
|
4
4
|
* Without a section word it opens the extension screen: the dock in `tui`, the
|
|
5
5
|
* grouped listing elsewhere. `settings` opens the Settings section, or prints
|
|
6
6
|
* its grouped listing outside `tui` without resolving packages. `skills` opens
|
|
7
7
|
* the Skills section and lists the resolved skills in text modes. `update`
|
|
8
8
|
* re-runs the published-version check and reports what it found, which is the
|
|
9
|
-
* one place a request is made on demand
|
|
9
|
+
* one place a request is made on demand; `upgrade` asks Pi's own package manager
|
|
10
|
+
* to install what that check found, behind a confirmation, so the two verbs stay
|
|
11
|
+
* one job each — ask, and act. With `doctor` it produces the doctor
|
|
10
12
|
* report — the same text `run_doctor` returns — for the whole set or one named
|
|
11
13
|
* extension; in `tui` the report is the dock screen. Discovery, the doctor
|
|
12
14
|
* probe, and the dirty-to-reload confirm all live here. The doctor runtime and
|
|
@@ -63,14 +65,22 @@ function selectableNames(discovery: Discovery): Set<string> {
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
/** The values a bare `/bytefly <tab>` offers, in screen order. */
|
|
66
|
-
const FIRST_TOKENS = [
|
|
68
|
+
const FIRST_TOKENS = [
|
|
69
|
+
"doctor",
|
|
70
|
+
"settings",
|
|
71
|
+
"skills",
|
|
72
|
+
"update",
|
|
73
|
+
"upgrade",
|
|
74
|
+
...Object.keys(REQUIREMENTS),
|
|
75
|
+
"refresh",
|
|
76
|
+
] as const;
|
|
67
77
|
|
|
68
78
|
/**
|
|
69
79
|
* Argument completions. The host replaces the whole argument text with the
|
|
70
80
|
* returned `value`, so a second token returns the full `doctor <name>` or
|
|
71
81
|
* `<name> refresh` argument rather than just the word. `/bytefly settings`,
|
|
72
|
-
* `/bytefly skills`, and `/bytefly
|
|
73
|
-
* followed by another token.
|
|
82
|
+
* `/bytefly skills`, `/bytefly update`, and `/bytefly upgrade` take no name, and
|
|
83
|
+
* `refresh` is never followed by another token.
|
|
74
84
|
*/
|
|
75
85
|
function completions(prefix: string): Array<{ value: string; label: string }> | null {
|
|
76
86
|
const [first = "", second, ...rest] = prefix.trimStart().split(/\s+/);
|
|
@@ -79,7 +89,8 @@ function completions(prefix: string): Array<{ value: string; label: string }> |
|
|
|
79
89
|
const options = FIRST_TOKENS.filter((option) => option.startsWith(first));
|
|
80
90
|
return options.length > 0 ? options.map((option) => ({ value: `${option} `, label: option })) : null;
|
|
81
91
|
}
|
|
82
|
-
if (first === "settings" || first === "skills" || first === "refresh" || first === "update"
|
|
92
|
+
if (first === "settings" || first === "skills" || first === "refresh" || first === "update" || first === "upgrade")
|
|
93
|
+
return null;
|
|
83
94
|
const options = (first === "doctor" ? [...Object.keys(REQUIREMENTS), "refresh"] : ["refresh"]).filter((option) =>
|
|
84
95
|
option.startsWith(second),
|
|
85
96
|
);
|
|
@@ -87,7 +98,7 @@ function completions(prefix: string): Array<{ value: string; label: string }> |
|
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
/** Which bytefly surface the arguments select. */
|
|
90
|
-
type PanelMode = "screen" | "doctor" | "update";
|
|
101
|
+
type PanelMode = "screen" | "doctor" | "update" | "upgrade";
|
|
91
102
|
|
|
92
103
|
/** `/bytefly [doctor] [settings] [extension] [refresh]`, parsed. */
|
|
93
104
|
interface PanelArgs {
|
|
@@ -109,6 +120,10 @@ function parseArgs(args: string): PanelArgs {
|
|
|
109
120
|
// `update` is already a refresh of the published version, so a stray `refresh`
|
|
110
121
|
// beside it is accepted and ignored rather than refused.
|
|
111
122
|
if (rest[0] === "update") return { mode: "update", section: "extensions", refresh };
|
|
123
|
+
// `upgrade` is the same subject as `update`, one verb further: it installs what a
|
|
124
|
+
// check found. Refusing to fold them into one word is what keeps a command that
|
|
125
|
+
// writes behind a confirmation instead of behind an argument.
|
|
126
|
+
if (rest[0] === "upgrade") return { mode: "upgrade", section: "extensions", refresh };
|
|
112
127
|
if (rest[0] === "settings") return { mode: "screen", section: "settings", name: rest[1], refresh };
|
|
113
128
|
if (rest[0] === "skills") return { mode: "screen", section: "skills", name: rest[1], refresh };
|
|
114
129
|
return { mode: "screen", section: "extensions", name: rest[0], refresh };
|
|
@@ -190,6 +205,24 @@ export function registerPanel(pi: ExtensionAPI, deps: PanelCommandDeps): void {
|
|
|
190
205
|
return;
|
|
191
206
|
}
|
|
192
207
|
|
|
208
|
+
// `/bytefly upgrade` is the only path here that writes, and it writes outside
|
|
209
|
+
// the session: Pi's package manager replaces the installed files, so the
|
|
210
|
+
// confirmation and the reload line are part of the answer, not decoration.
|
|
211
|
+
if (mode === "upgrade") {
|
|
212
|
+
const confirmed = await ctx.ui.confirm(
|
|
213
|
+
"Update packages?",
|
|
214
|
+
`Run Pi's package manager for every configured package, then reload to use it.`,
|
|
215
|
+
);
|
|
216
|
+
if (!confirmed) {
|
|
217
|
+
ctx.ui.notify("bytefly: update cancelled.", "info");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const outcome = await deps.update.apply();
|
|
221
|
+
const { text, severity } = updateOutcomeText(outcome, pkg.version);
|
|
222
|
+
ctx.ui.notify(text, severity);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
193
226
|
// Settings outside `tui` prints its grouped listing without resolving
|
|
194
227
|
// packages or probing the doctor.
|
|
195
228
|
if (section === "settings" && ctx.mode !== "tui") {
|
|
@@ -88,12 +88,6 @@ function applyAppSetting(store: SettingsStore, row: SettingDescriptor, raw: stri
|
|
|
88
88
|
case "byteflyUi":
|
|
89
89
|
store.setQuietStartup(value as QuietStartup);
|
|
90
90
|
return;
|
|
91
|
-
case "outputPad":
|
|
92
|
-
store.setOutputPad(value as 0 | 1);
|
|
93
|
-
return;
|
|
94
|
-
case "showHardwareCursor":
|
|
95
|
-
store.setShowHardwareCursor(value as boolean);
|
|
96
|
-
return;
|
|
97
91
|
case "theme":
|
|
98
92
|
store.setTheme(value as string);
|
|
99
93
|
return;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* The catalogue is the shortlist a session changes, not the whole document: every
|
|
10
10
|
* row is one a workflow differs on, and each states its own rule so a value the
|
|
11
11
|
* screen accepts is a value the loader keeps. The catalogue order is the screen's
|
|
12
|
-
* order, and it is capped so the section fits a 24-row terminal (`
|
|
12
|
+
* order, and it is capped so the section fits a 24-row terminal (`panel.test.ts`
|
|
13
13
|
* holds the ceiling); a new row has to displace one. Everything else stays
|
|
14
14
|
* `bytefly.json` — a list (`doctor.skip`), a table (`price.rates`), a schedule
|
|
15
15
|
* (`price.peak`), and the tuning numbers and labels this screen deliberately does
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* pane of any row whose section is still on the list.
|
|
18
18
|
*
|
|
19
19
|
* Three sections have a normalizer here, so the screen shows what the notifier,
|
|
20
|
-
* the doctor, and the footer keep. The
|
|
21
|
-
* `job
|
|
20
|
+
* the doctor, and the footer keep. The two owned by other extensions (`rewind`
|
|
21
|
+
* and `job`) are read as the file states them, because importing their
|
|
22
22
|
* normalizer would be one extension importing another's module; each such row
|
|
23
23
|
* declares the loader's default, and `test/settings-coverage.test.ts` holds the
|
|
24
24
|
* pair together.
|
|
@@ -61,7 +61,7 @@ interface TextRule {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/** The `bytefly.json` sections a row still reaches. */
|
|
64
|
-
export type ConfigSectionName = "notify" | "doctor" | "price" | "rewind" | "job"
|
|
64
|
+
export type ConfigSectionName = "notify" | "doctor" | "price" | "rewind" | "job";
|
|
65
65
|
|
|
66
66
|
/** Where a `bytefly.json` row lives: a section, and the path to its leaf. */
|
|
67
67
|
interface SettingConfig {
|
|
@@ -124,8 +124,6 @@ const ON_OFF: readonly SettingOption[] = [
|
|
|
124
124
|
{ value: "true", label: "On" },
|
|
125
125
|
];
|
|
126
126
|
|
|
127
|
-
/** The notify section's delivery methods, labeled by the surface each reaches. */
|
|
128
|
-
|
|
129
127
|
/**
|
|
130
128
|
* A text rule for a money amount inside an inclusive range: a whole number or a
|
|
131
129
|
* decimal with up to four places, matching the loader's clamp for `price.budget`.
|
|
@@ -147,7 +145,7 @@ function amountRule(min: number, max: number): TextRule {
|
|
|
147
145
|
/**
|
|
148
146
|
* The catalogue, in screen order.
|
|
149
147
|
*
|
|
150
|
-
* Global scope lists all
|
|
148
|
+
* Global scope lists all twelve; Project lists the ten `bytefly.json` rows,
|
|
151
149
|
* because Pi's setters write the personal `settings.json` only. The list is
|
|
152
150
|
* capped so the screen keeps fitting a 24-row terminal.
|
|
153
151
|
*/
|
|
@@ -220,7 +218,7 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
220
218
|
{
|
|
221
219
|
id: "notify.matchFound",
|
|
222
220
|
label: "Watcher matched",
|
|
223
|
-
description: "Announce it when a `watch` whose `wake` is on matches
|
|
221
|
+
description: "Announce it when a `watch` whose `wake` is on matches, once the agent is idle.",
|
|
224
222
|
source: "bytefly",
|
|
225
223
|
kind: "boolean",
|
|
226
224
|
fallback: "true",
|
|
@@ -239,17 +237,6 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
239
237
|
options: ON_OFF,
|
|
240
238
|
config: { section: "job", path: ["wakeOnFinish"] },
|
|
241
239
|
},
|
|
242
|
-
{
|
|
243
|
-
id: "plan.goalPrompt",
|
|
244
|
-
label: "Offer the session goal",
|
|
245
|
-
description: "Ask before recording an approved plan as the session goal; off records it without asking.",
|
|
246
|
-
source: "bytefly",
|
|
247
|
-
kind: "boolean",
|
|
248
|
-
fallback: "true",
|
|
249
|
-
json: "boolean",
|
|
250
|
-
options: ON_OFF,
|
|
251
|
-
config: { section: "plan", path: ["goalPrompt"] },
|
|
252
|
-
},
|
|
253
240
|
{
|
|
254
241
|
id: "rewind.autoSnapshots",
|
|
255
242
|
label: "Automatic snapshots",
|
|
@@ -311,9 +298,12 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
311
298
|
/** The one refusal for a Project write while the project is untrusted. */
|
|
312
299
|
export const UNTRUSTED_SETTINGS_NOTE = "Project is not trusted; settings can only be changed in Global scope.";
|
|
313
300
|
|
|
314
|
-
/**
|
|
301
|
+
/**
|
|
302
|
+
* The rows a scope can write: app rows are personal-file only, and Project shows
|
|
303
|
+
* the rows backed by `bytefly.json` — whichever extension owns the section.
|
|
304
|
+
*/
|
|
315
305
|
export function rowsForScope(scope: WriteScope): readonly SettingDescriptor[] {
|
|
316
|
-
return scope === "global" ? SETTING_ROWS : SETTING_ROWS.filter((row) => row.
|
|
306
|
+
return scope === "global" ? SETTING_ROWS : SETTING_ROWS.filter((row) => row.config !== undefined);
|
|
317
307
|
}
|
|
318
308
|
|
|
319
309
|
/**
|
|
@@ -129,7 +129,7 @@ export function settingDetail(options: {
|
|
|
129
129
|
if (budget === 0) return saved;
|
|
130
130
|
|
|
131
131
|
const lines = [headLine];
|
|
132
|
-
const descriptionWidth = Math.max(1, width - BODY_INDENT
|
|
132
|
+
const descriptionWidth = Math.max(1, width - BODY_INDENT);
|
|
133
133
|
const description = wrapTextWithAnsi(sanitizeLine(view.description), descriptionWidth).slice(
|
|
134
134
|
0,
|
|
135
135
|
Math.max(0, Math.min(2, budget - 2 - saved.length)),
|
|
@@ -46,8 +46,6 @@ export function createSettingsStore(manager: SettingsManager): SettingsStore {
|
|
|
46
46
|
setProjectSkillPaths: (paths) => manager.setProjectSkillPaths(paths),
|
|
47
47
|
setProjectPackages: (packages) => manager.setProjectPackages(packages),
|
|
48
48
|
setQuietStartup: (quiet) => manager.setQuietStartup(quiet),
|
|
49
|
-
setOutputPad: (padding) => manager.setOutputPad(padding),
|
|
50
|
-
setShowHardwareCursor: (enabled) => manager.setShowHardwareCursor(enabled),
|
|
51
49
|
setTheme: (theme) => manager.setTheme(theme),
|
|
52
50
|
isProjectTrusted: () => manager.isProjectTrusted(),
|
|
53
51
|
flush: () => manager.flush(),
|
|
@@ -47,8 +47,6 @@ export interface SettingsStore {
|
|
|
47
47
|
setProjectPackages(packages: PackageSource[]): void;
|
|
48
48
|
/** App preferences are written to the personal settings file. */
|
|
49
49
|
setQuietStartup(quiet: QuietStartup): void;
|
|
50
|
-
setOutputPad(padding: 0 | 1): void;
|
|
51
|
-
setShowHardwareCursor(enabled: boolean): void;
|
|
52
50
|
/** Pi's theme setting is a personal-file preference. */
|
|
53
51
|
setTheme(theme: string): void;
|
|
54
52
|
isProjectTrusted(): boolean;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { DefaultPackageManager, getAgentDir, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
import type { ProgressEvent } from "@earendil-works/pi-coding-agent";
|
|
14
15
|
import type { PackageUpdate, PackageUpdateCheck } from "./types.ts";
|
|
15
16
|
import { parseVersion } from "./version.ts";
|
|
16
17
|
|
|
@@ -51,6 +52,44 @@ export interface AvailableUpdatesDeps {
|
|
|
51
52
|
checkForAvailableUpdates?: PackageUpdateCheck;
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
/** What applying an update needs: the same manager the check builds, plus a source. */
|
|
56
|
+
export interface ApplyUpdatesDeps {
|
|
57
|
+
/** Session working directory. */
|
|
58
|
+
cwd: string;
|
|
59
|
+
/** Pi agent directory; defaults to `getAgentDir()`. */
|
|
60
|
+
agentDir?: string;
|
|
61
|
+
/** Whether project-local packages may be read. */
|
|
62
|
+
projectTrusted: boolean;
|
|
63
|
+
/** One source to update; every configured package when omitted. */
|
|
64
|
+
source?: string;
|
|
65
|
+
/** Called as Pi's manager reports progress, so a surface can show one line. */
|
|
66
|
+
onProgress?: (message: string) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Apply the update through Pi's own package manager.
|
|
71
|
+
*
|
|
72
|
+
* This is deliberately not a shell: `pi update --extensions` is a command a human
|
|
73
|
+
* types, while `update()` is the manager that owns the install root, the settings
|
|
74
|
+
* that name each package, and the npm command a project configured. It resolves
|
|
75
|
+
* nothing interactively, honors `PI_OFFLINE` by returning early, and skips a
|
|
76
|
+
* pinned npm version — so the caller's job is the confirmation and the report, not
|
|
77
|
+
* the mechanics. It throws when a named source matches no configured package or
|
|
78
|
+
* when the install command fails, and returns nothing on success.
|
|
79
|
+
*/
|
|
80
|
+
export async function applyUpdates(deps: ApplyUpdatesDeps): Promise<void> {
|
|
81
|
+
const agentDir = deps.agentDir ?? getAgentDir();
|
|
82
|
+
const settingsManager = SettingsManager.create(deps.cwd, agentDir, { projectTrusted: deps.projectTrusted });
|
|
83
|
+
const manager = new DefaultPackageManager({ cwd: deps.cwd, agentDir, settingsManager });
|
|
84
|
+
const report = deps.onProgress;
|
|
85
|
+
if (report) {
|
|
86
|
+
manager.setProgressCallback((event: ProgressEvent) => {
|
|
87
|
+
if (event.message) report(event.message);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
await manager.update(deps.source);
|
|
91
|
+
}
|
|
92
|
+
|
|
54
93
|
/**
|
|
55
94
|
* Every package Pi would update, or `undefined` when the check cannot run.
|
|
56
95
|
*
|
|
@@ -174,6 +174,21 @@ export function updateOutcomeText(
|
|
|
174
174
|
};
|
|
175
175
|
case "current":
|
|
176
176
|
return { text: "bytefly: up to date — no configured package has an update.", severity: "info" };
|
|
177
|
+
case "update-failed":
|
|
178
|
+
return {
|
|
179
|
+
text: `bytefly: the update did not finish — ${sanitizeLine(outcome.reason)}`,
|
|
180
|
+
severity: "warning",
|
|
181
|
+
};
|
|
182
|
+
case "updated":
|
|
183
|
+
return outcome.pending
|
|
184
|
+
? {
|
|
185
|
+
text: `bytefly: the update ran, but a package is still behind — run ${UPDATE_COMMAND} or /bytefly update.`,
|
|
186
|
+
severity: "warning",
|
|
187
|
+
}
|
|
188
|
+
: {
|
|
189
|
+
text: "bytefly: every configured package is updated — reload to use the new version.",
|
|
190
|
+
severity: "info",
|
|
191
|
+
};
|
|
177
192
|
case "available": {
|
|
178
193
|
const own = outcome.updates.some(isOwnPackage);
|
|
179
194
|
const release = own ? ownRelease(outcome.ownLatest) : undefined;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
19
19
|
import pkg from "../../../package.json";
|
|
20
|
+
import { messageOf } from "../../../lib/errors.ts";
|
|
20
21
|
import { isOffline } from "../../../lib/env.ts";
|
|
21
22
|
import {
|
|
22
23
|
isFresh,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
writeUpdateCache,
|
|
27
28
|
type UpdateCacheEntry,
|
|
28
29
|
} from "./cache.ts";
|
|
29
|
-
import { fetchAvailableUpdates, fetchLatestVersion } from "./check.ts";
|
|
30
|
+
import { applyUpdates, fetchAvailableUpdates, fetchLatestVersion } from "./check.ts";
|
|
30
31
|
import { isOwnPackage, updateNotice } from "./format.ts";
|
|
31
32
|
import { paintLatest, type UpdateOutcome, type UpdateSource } from "./state.ts";
|
|
32
33
|
import { suppressPiPackageUpdates, type UpdateSuppression } from "./suppress.ts";
|
|
@@ -45,6 +46,8 @@ export interface UpdateCache {
|
|
|
45
46
|
export interface UpdateHandle {
|
|
46
47
|
/** Read the package list now, ignoring the cache window. Never throws. */
|
|
47
48
|
recheck(): Promise<UpdateOutcome>;
|
|
49
|
+
/** Apply the update, then re-read it. Never throws. */
|
|
50
|
+
apply(): Promise<UpdateOutcome>;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
/** Test seams; production uses Pi's package manager, its cache, and the agent dir. */
|
|
@@ -53,6 +56,8 @@ export interface UpdateDeps {
|
|
|
53
56
|
readUpdates?: (ctx: ExtensionContext, check: PackageUpdateCheck) => Promise<PackageUpdate[] | undefined>;
|
|
54
57
|
/** Replace the registry read that names this package's new version. */
|
|
55
58
|
fetchLatest?: () => Promise<string | undefined>;
|
|
59
|
+
/** Replace the package-manager update; defaults to Pi's own manager. */
|
|
60
|
+
applyUpdates?: (ctx: ExtensionContext) => Promise<void>;
|
|
56
61
|
/** Replace the `PI_OFFLINE` check. */
|
|
57
62
|
isOffline?: () => boolean;
|
|
58
63
|
/** Replace the disk cache. */
|
|
@@ -94,6 +99,16 @@ export function registerUpdate(pi: ExtensionAPI, updates: UpdateSource, deps: Up
|
|
|
94
99
|
checkForAvailableUpdates: check,
|
|
95
100
|
}));
|
|
96
101
|
const readLatest = deps.fetchLatest ?? (() => fetchLatestVersion());
|
|
102
|
+
const applyUpdate =
|
|
103
|
+
deps.applyUpdates ??
|
|
104
|
+
((ctx: ExtensionContext) =>
|
|
105
|
+
applyUpdates({
|
|
106
|
+
cwd: ctx.cwd,
|
|
107
|
+
agentDir: getAgentDir(),
|
|
108
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
109
|
+
// Pi's manager reports one line per source; a slow install is otherwise silent.
|
|
110
|
+
onProgress: (message) => ctx.ui.notify(message, "info"),
|
|
111
|
+
}));
|
|
97
112
|
const offline = deps.isOffline ?? isOffline;
|
|
98
113
|
const cache = deps.cache ?? diskCache();
|
|
99
114
|
const now = deps.now ?? Date.now;
|
|
@@ -155,6 +170,22 @@ export function registerUpdate(pi: ExtensionAPI, updates: UpdateSource, deps: Up
|
|
|
155
170
|
return latest && isNewerVersion(latest, pkg.version) ? latest : undefined;
|
|
156
171
|
};
|
|
157
172
|
|
|
173
|
+
/**
|
|
174
|
+
* One snapshot from a package list: this package's version label, the cache key,
|
|
175
|
+
* and the clock. The background check and the panel's recheck differ only in what
|
|
176
|
+
* they do with a snapshot, so the assembly lives here once instead of being
|
|
177
|
+
* copied wherever something new needs to record a reading.
|
|
178
|
+
*/
|
|
179
|
+
const buildSnapshot = async (ctx: ExtensionContext, list: PackageUpdate[]): Promise<UpdateCacheEntry> => {
|
|
180
|
+
const latest = await resolveOwnLatest(list.find(isOwnPackage));
|
|
181
|
+
return {
|
|
182
|
+
key: keyFor(ctx),
|
|
183
|
+
checkedAt: now(),
|
|
184
|
+
updates: list,
|
|
185
|
+
...(latest ? { latest } : {}),
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
|
|
158
189
|
const check = async (ctx: ExtensionContext, token: number): Promise<void> => {
|
|
159
190
|
try {
|
|
160
191
|
const list = await readUpdates(ctx, activeCheck());
|
|
@@ -163,14 +194,8 @@ export function registerUpdate(pi: ExtensionAPI, updates: UpdateSource, deps: Up
|
|
|
163
194
|
// the next session "up to date" for a day and clear a marker that may still
|
|
164
195
|
// be right, so leave both alone and try again next time.
|
|
165
196
|
if (list === undefined) return;
|
|
166
|
-
const
|
|
197
|
+
const entry = await buildSnapshot(ctx, list);
|
|
167
198
|
if (token !== generation) return;
|
|
168
|
-
const entry: UpdateCacheEntry = {
|
|
169
|
-
key: keyFor(ctx),
|
|
170
|
-
checkedAt: now(),
|
|
171
|
-
updates: list,
|
|
172
|
-
...(latest ? { latest } : {}),
|
|
173
|
-
};
|
|
174
199
|
writeCache(entry);
|
|
175
200
|
record(entry);
|
|
176
201
|
} catch {
|
|
@@ -210,31 +235,45 @@ export function registerUpdate(pi: ExtensionAPI, updates: UpdateSource, deps: Up
|
|
|
210
235
|
context = undefined;
|
|
211
236
|
});
|
|
212
237
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// The caller prints the answer, so this path never posts a notice.
|
|
234
|
-
paintLatest(updates, latest, deps.onUpdate);
|
|
235
|
-
return list.length > 0
|
|
236
|
-
? { kind: "available", updates: list, ...(latest ? { ownLatest: latest } : {}) }
|
|
237
|
-
: { kind: "current" };
|
|
238
|
-
},
|
|
238
|
+
/** Re-read the published state on demand; `/bytefly update` prints the answer. */
|
|
239
|
+
const recheck = async (): Promise<UpdateOutcome> => {
|
|
240
|
+
if (offline()) return { kind: "offline" };
|
|
241
|
+
if (!context) return { kind: "failed" };
|
|
242
|
+
let list: PackageUpdate[];
|
|
243
|
+
try {
|
|
244
|
+
const found = await readUpdates(context, activeCheck());
|
|
245
|
+
if (found === undefined) return { kind: "failed" };
|
|
246
|
+
list = found;
|
|
247
|
+
} catch {
|
|
248
|
+
return { kind: "failed" };
|
|
249
|
+
}
|
|
250
|
+
const entry = await buildSnapshot(context, list);
|
|
251
|
+
writeCache(entry);
|
|
252
|
+
// The caller prints the answer, so this path never posts a notice.
|
|
253
|
+
const ownLatest = ownLatestOf(entry);
|
|
254
|
+
paintLatest(updates, ownLatest, deps.onUpdate);
|
|
255
|
+
return list.length > 0
|
|
256
|
+
? { kind: "available", updates: list, ...(ownLatest ? { ownLatest } : {}) }
|
|
257
|
+
: { kind: "current" };
|
|
239
258
|
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Apply the update through Pi's manager, then re-read it: the snapshot on disk and
|
|
262
|
+
* on screen described the version that was just replaced, so leaving it would keep
|
|
263
|
+
* a marker for code this session no longer runs. `pending` is the manager reporting
|
|
264
|
+
* success over a package that is still behind, which is worth saying out loud.
|
|
265
|
+
*/
|
|
266
|
+
const apply = async (): Promise<UpdateOutcome> => {
|
|
267
|
+
if (offline()) return { kind: "offline" };
|
|
268
|
+
if (!context) return { kind: "failed" };
|
|
269
|
+
try {
|
|
270
|
+
await applyUpdate(context);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return { kind: "update-failed", reason: messageOf(error) };
|
|
273
|
+
}
|
|
274
|
+
const after = await recheck();
|
|
275
|
+
return { kind: "updated", pending: after.kind === "available" };
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
return { recheck, apply };
|
|
240
279
|
}
|
|
@@ -20,13 +20,16 @@ export interface UpdateSource {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* What one explicit
|
|
24
|
-
* asked — so this vocabulary exists only for `/bytefly update
|
|
25
|
-
* a skip is an answer rather
|
|
23
|
+
* What one explicit update reported. The background check reports nothing — nobody
|
|
24
|
+
* asked — so this vocabulary exists only for `/bytefly update` and
|
|
25
|
+
* `/bytefly upgrade`, where a failure, a skip, or a refusal is an answer rather
|
|
26
|
+
* than silence.
|
|
26
27
|
*/
|
|
27
28
|
export type UpdateOutcome =
|
|
28
29
|
| { kind: "offline" }
|
|
29
30
|
| { kind: "failed" }
|
|
31
|
+
| { kind: "update-failed"; reason: string }
|
|
32
|
+
| { kind: "updated"; pending: boolean }
|
|
30
33
|
| { kind: "current" }
|
|
31
34
|
| { kind: "available"; updates: readonly PackageUpdate[]; ownLatest?: string };
|
|
32
35
|
|
|
@@ -203,7 +203,7 @@ export class ContextComponent implements Component {
|
|
|
203
203
|
lines.push("", ` ${paint(this.theme, "label", "Largest messages")}`);
|
|
204
204
|
for (const block of report.largest) {
|
|
205
205
|
const tokens = formatTokens(block.tokens).padStart(6);
|
|
206
|
-
const role = padCell(block.role,
|
|
206
|
+
const role = padCell(block.role, 12);
|
|
207
207
|
const candidate = ` ${paint(this.theme, "meta", tokens)} ${paint(this.theme, "label", role)}${block.label}`;
|
|
208
208
|
lines.push(truncateToWidth(candidate, width));
|
|
209
209
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { randomInt } from "node:crypto";
|
|
10
|
-
import { asBoolean, clampInteger, loadConfigSection } from "
|
|
10
|
+
import { asBoolean, clampInteger, loadConfigSection } from "../../../lib/config.ts";
|
|
11
11
|
|
|
12
12
|
export interface ServeConfig {
|
|
13
13
|
/** TCP port on 127.0.0.1; `0` picks one random port fixed for the session, a positive value pins it. */
|
|
@@ -57,7 +57,7 @@ export const DEFAULT_CONFIG: ServeConfig = {
|
|
|
57
57
|
diffContext: 3,
|
|
58
58
|
maxDiffLines: 4000,
|
|
59
59
|
live: true,
|
|
60
|
-
liveIntervalMs:
|
|
60
|
+
liveIntervalMs: 5000,
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
/** Base of the per-session port range. */
|
|
@@ -66,8 +66,8 @@ export const PORT_BASE = 4780;
|
|
|
66
66
|
/** Number of ports in the per-session range. */
|
|
67
67
|
export const PORT_RANGE = 1000;
|
|
68
68
|
|
|
69
|
-
/** Fastest live-status cadence a page may be given
|
|
70
|
-
*
|
|
69
|
+
/** Fastest live-status cadence a page may be given: short enough to watch, and a
|
|
70
|
+
* floor so a page cannot outrun the git reads behind each frame. */
|
|
71
71
|
export const LIVE_INTERVAL_MIN = 250;
|
|
72
72
|
|
|
73
73
|
/** Slowest live-status cadence, so a page cannot look abandoned. */
|
|
@@ -81,6 +81,15 @@ export const LIVE_INTERVAL_MAX = 60_000;
|
|
|
81
81
|
*/
|
|
82
82
|
export const IDLE_STOP_MS = 10_000;
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* How many pages may hold the status stream at once. One snapshot serves them
|
|
86
|
+
* all, but every attached page keeps the ticker — and the git read behind each
|
|
87
|
+
* frame — running, so a local page cannot open a thousand streams and pin the
|
|
88
|
+
* server alive. The refusal is a `204`, which tells an event source to stop
|
|
89
|
+
* reconnecting instead of retrying for a slot that will not open.
|
|
90
|
+
*/
|
|
91
|
+
export const LIVE_MAX_CLIENTS = 8;
|
|
92
|
+
|
|
84
93
|
/** A random port in the per-session range `PORT_BASE`–`PORT_BASE + PORT_RANGE - 1`. */
|
|
85
94
|
export function randomPort(): number {
|
|
86
95
|
return PORT_BASE + randomInt(PORT_RANGE);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { Dirent, Stats } from "node:fs";
|
|
9
9
|
import { readFile, readdir, stat, lstat } from "node:fs/promises";
|
|
10
10
|
import { basename, extname, join } from "node:path";
|
|
11
|
-
import { mapConcurrent } from "
|
|
11
|
+
import { mapConcurrent } from "../../../lib/pool.ts";
|
|
12
12
|
import { HttpError } from "./paths.ts";
|
|
13
13
|
|
|
14
14
|
export type EntryKind = "directory" | "text" | "image" | "binary";
|
|
@@ -166,19 +166,6 @@ export function isProbablyText(sample: Buffer): boolean {
|
|
|
166
166
|
return suspicious / sample.length < 0.3;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
/** Compact byte size: `68 B`, `12.4 kB`, `1.5 MB`. */
|
|
170
|
-
export function formatSize(bytes: number): string {
|
|
171
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
172
|
-
const units = ["kB", "MB", "GB", "TB"];
|
|
173
|
-
let value = bytes;
|
|
174
|
-
let index = -1;
|
|
175
|
-
do {
|
|
176
|
-
value /= 1024;
|
|
177
|
-
index++;
|
|
178
|
-
} while (value >= 1024 && index < units.length - 1);
|
|
179
|
-
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[index]}`;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
169
|
/** MIME type for a name; text is always served as plain text. */
|
|
183
170
|
export function contentTypeFor(name: string): string {
|
|
184
171
|
const ext = extname(name).slice(1).toLowerCase();
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { realpathSync } from "node:fs";
|
|
11
11
|
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
12
|
-
import { isInside } from "
|
|
12
|
+
import { isInside } from "../../../lib/path.ts";
|
|
13
13
|
|
|
14
14
|
/** An HTTP-shaped failure the router turns into a status page. */
|
|
15
15
|
export class HttpError extends Error {
|
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
createExecRunner,
|
|
14
|
+
currentBranch,
|
|
15
|
+
hasCommits,
|
|
16
|
+
repoRoot,
|
|
17
|
+
revParse,
|
|
18
|
+
type RunGit,
|
|
19
|
+
} from "../../../lib/git/index.ts";
|
|
13
20
|
import {
|
|
14
21
|
diffArgs,
|
|
15
22
|
inMode,
|
|
@@ -18,8 +25,8 @@ import {
|
|
|
18
25
|
type ChangedFile,
|
|
19
26
|
type DiffLine,
|
|
20
27
|
type DiffMode,
|
|
21
|
-
} from "
|
|
22
|
-
import { readFileView } from "
|
|
28
|
+
} from "../text/diff.ts";
|
|
29
|
+
import { readFileView } from "../files/files.ts";
|
|
23
30
|
|
|
24
31
|
export interface GitStatus {
|
|
25
32
|
/** Checked-out branch, when HEAD is attached. */
|
|
@@ -16,11 +16,11 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
16
16
|
import { isExtensionEnabled, resolveEffectiveCwd } from "../../lib/env.ts";
|
|
17
17
|
import { GLYPHS, STATUS_KEYS } from "../../lib/tui/glyphs.ts";
|
|
18
18
|
import { paint } from "../../lib/tui/tokens.ts";
|
|
19
|
-
import { loadConfig, randomPort } from "./config.ts";
|
|
20
|
-
import { createGitDiff, createGitStatus } from "./git.ts";
|
|
19
|
+
import { loadConfig, randomPort } from "./config/config.ts";
|
|
20
|
+
import { createGitDiff, createGitStatus } from "./git/git.ts";
|
|
21
21
|
import { openInBrowser } from "../../lib/browser.ts";
|
|
22
|
-
import { HttpError, resolveRequestPath } from "./paths.ts";
|
|
23
|
-
import { createFileServer, type FileServer } from "./server.ts";
|
|
22
|
+
import { HttpError, resolveRequestPath } from "./files/paths.ts";
|
|
23
|
+
import { createFileServer, type FileServer } from "./transport/server.ts";
|
|
24
24
|
|
|
25
25
|
export interface FileBrowserDeps {
|
|
26
26
|
/** Override the server factory (tests). */
|