@gavin-hu/my-pi-agent 0.18.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.
Files changed (249) hide show
  1. package/README.md +52 -44
  2. package/README.zh-CN.md +46 -41
  3. package/docs/extensions/README.md +34 -0
  4. package/docs/extensions/board.md +344 -0
  5. package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +446 -404
  6. package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
  7. package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
  8. package/docs/extensions/file-browse.md +554 -0
  9. package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
  10. package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
  11. package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
  12. package/{extensions/job/README.md → docs/extensions/job.md} +61 -50
  13. package/{extensions/memory/README.md → docs/extensions/memory.md} +71 -60
  14. package/{extensions/plan/README.md → docs/extensions/plan.md} +390 -317
  15. package/{extensions/question/README.md → docs/extensions/question.md} +54 -31
  16. package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
  17. package/docs/extensions/session-bus.md +285 -0
  18. package/{extensions/subagent/README.md → docs/extensions/subagent.md} +165 -118
  19. package/docs/extensions/team.md +226 -0
  20. package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
  21. package/{extensions/watch/README.md → docs/extensions/watch.md} +54 -36
  22. package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
  23. package/{extensions/wechat/README.md → docs/extensions/wechat.md} +258 -162
  24. package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
  25. package/extensions/board/commands.ts +87 -0
  26. package/extensions/board/config.ts +36 -0
  27. package/extensions/board/format.ts +72 -0
  28. package/extensions/board/index.ts +40 -0
  29. package/extensions/board/input.ts +54 -0
  30. package/extensions/board/join.ts +42 -0
  31. package/extensions/board/page.ts +312 -0
  32. package/extensions/board/result.ts +99 -0
  33. package/extensions/board/rows.ts +48 -0
  34. package/extensions/board/schema.ts +75 -0
  35. package/extensions/board/seat.ts +29 -0
  36. package/extensions/board/snapshot.ts +41 -0
  37. package/extensions/board/tools.ts +279 -0
  38. package/extensions/board/transport/router.ts +172 -0
  39. package/extensions/board/transport/server.ts +149 -0
  40. package/extensions/board/types.ts +54 -0
  41. package/extensions/board/view/actions.ts +306 -0
  42. package/extensions/board/view/client.ts +41 -0
  43. package/extensions/board/view/keys.ts +121 -0
  44. package/extensions/board/view/live.ts +397 -0
  45. package/extensions/board/view/page.ts +158 -0
  46. package/extensions/board/view/render.ts +496 -0
  47. package/extensions/board/view/styles.ts +211 -0
  48. package/extensions/board/view/team.ts +305 -0
  49. package/extensions/board/writes.ts +99 -0
  50. package/extensions/bytefly/panel/commands.ts +40 -7
  51. package/extensions/bytefly/panel/settings-runtime.ts +0 -6
  52. package/extensions/bytefly/panel/settings.ts +3 -20
  53. package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
  54. package/extensions/bytefly/picker/discovery.ts +0 -2
  55. package/extensions/bytefly/picker/types.ts +0 -2
  56. package/extensions/bytefly/update/check.ts +39 -0
  57. package/extensions/bytefly/update/format.ts +15 -0
  58. package/extensions/bytefly/update/register.ts +73 -34
  59. package/extensions/bytefly/update/state.ts +6 -3
  60. package/extensions/context/tui.ts +1 -1
  61. package/extensions/file-browse/{config.ts → config/config.ts} +1 -1
  62. package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
  63. package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
  64. package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
  65. package/extensions/file-browse/index.ts +4 -4
  66. package/extensions/file-browse/{live.ts → live/live.ts} +20 -69
  67. package/extensions/file-browse/{router.ts → transport/router.ts} +21 -15
  68. package/extensions/file-browse/transport/server.ts +110 -0
  69. package/extensions/file-browse/{chrome.ts → view/chrome.ts} +4 -4
  70. package/extensions/file-browse/{client.ts → view/client.ts} +1 -1
  71. package/extensions/file-browse/{diff-html.ts → view/diff-html.ts} +3 -3
  72. package/extensions/file-browse/{html.ts → view/html.ts} +14 -12
  73. package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
  74. package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
  75. package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
  76. package/extensions/file-browse/{styles.ts → view/styles.ts} +29 -5
  77. package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
  78. package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
  79. package/extensions/file-read/schema.ts +2 -1
  80. package/extensions/memory/store.ts +12 -5
  81. package/extensions/plan/blocks.ts +23 -2
  82. package/extensions/plan/commands.ts +21 -16
  83. package/extensions/plan/feedback.ts +147 -38
  84. package/extensions/plan/index.ts +6 -4
  85. package/extensions/plan/plan-index.ts +43 -9
  86. package/extensions/plan/plan-text.ts +33 -23
  87. package/extensions/plan/plans.ts +8 -11
  88. package/extensions/plan/policy.ts +20 -12
  89. package/extensions/plan/review.ts +3 -27
  90. package/extensions/plan/runtime.ts +21 -9
  91. package/extensions/plan/sections.ts +112 -0
  92. package/extensions/plan/steps.ts +3 -22
  93. package/extensions/plan/tools/exit.ts +11 -95
  94. package/extensions/plan/tui.ts +5 -34
  95. package/extensions/plan/types.ts +0 -8
  96. package/extensions/plan/web-client.ts +1 -1
  97. package/extensions/plan/web-page.ts +0 -7
  98. package/extensions/plan/web-router.ts +3 -57
  99. package/extensions/plan/web-share-session.ts +24 -30
  100. package/extensions/plan/web-streams.ts +14 -14
  101. package/extensions/plan/web-tokens.ts +2 -2
  102. package/extensions/plan/web.ts +66 -120
  103. package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
  104. package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
  105. package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
  106. package/extensions/rewind/{config.ts → config/config.ts} +1 -1
  107. package/extensions/rewind/index.ts +3 -3
  108. package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
  109. package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
  110. package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
  111. package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
  112. package/extensions/rewind/{format.ts → text/format.ts} +3 -3
  113. package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
  114. package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
  115. package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
  116. package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
  117. package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
  118. package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
  119. package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
  120. package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
  121. package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
  122. package/extensions/rewind/{status.ts → view/status.ts} +4 -4
  123. package/extensions/session-bus/commands.ts +17 -69
  124. package/extensions/session-bus/format.ts +22 -45
  125. package/extensions/session-bus/index.ts +13 -7
  126. package/extensions/session-bus/join-request.ts +40 -0
  127. package/extensions/session-bus/mail-watch.ts +134 -0
  128. package/extensions/session-bus/notice.ts +32 -4
  129. package/{lib/claims → extensions/session-bus}/overlap.ts +11 -18
  130. package/extensions/session-bus/protocol.ts +19 -49
  131. package/extensions/session-bus/resolve.ts +67 -0
  132. package/extensions/session-bus/result.ts +16 -51
  133. package/extensions/session-bus/rows.ts +4 -2
  134. package/extensions/session-bus/runtime.ts +129 -238
  135. package/extensions/session-bus/store.ts +42 -6
  136. package/extensions/session-bus/tools.ts +31 -66
  137. package/extensions/session-bus/tui.ts +4 -4
  138. package/extensions/session-bus/types.ts +13 -13
  139. package/extensions/subagent/agents.ts +9 -8
  140. package/extensions/subagent/invocation.ts +4 -2
  141. package/extensions/team/commands.ts +112 -0
  142. package/extensions/team/dispatch.ts +133 -0
  143. package/extensions/team/format.ts +105 -0
  144. package/extensions/team/index.ts +33 -0
  145. package/extensions/team/schema.ts +29 -0
  146. package/extensions/team/tools.ts +246 -0
  147. package/extensions/team/types.ts +48 -0
  148. package/extensions/web-access/fetch/page.ts +4 -1
  149. package/extensions/web-access/fetch/structured.ts +447 -0
  150. package/extensions/web-access/fetch/tool.ts +2 -1
  151. package/extensions/web-access/search/tool.ts +2 -1
  152. package/extensions/wechat/bridge.ts +76 -92
  153. package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
  154. package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
  155. package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
  156. package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
  157. package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
  158. package/extensions/wechat/dialog/channels.ts +136 -0
  159. package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +37 -37
  160. package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +2 -2
  161. package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
  162. package/extensions/wechat/index.ts +10 -10
  163. package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
  164. package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
  165. package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +100 -22
  166. package/extensions/wechat/{presence.ts → peer/presence.ts} +8 -7
  167. package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +1 -1
  168. package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
  169. package/extensions/wechat/{state.ts → state/state.ts} +14 -2
  170. package/extensions/wechat/{format.ts → text/format.ts} +3 -3
  171. package/extensions/wechat/{notices.ts → text/notices.ts} +8 -0
  172. package/extensions/wechat/tools.ts +29 -12
  173. package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
  174. package/extensions/wechat/{deliver.ts → transport/deliver.ts} +72 -28
  175. package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
  176. package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
  177. package/extensions/wechat/transport/outbound.ts +142 -0
  178. package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
  179. package/extensions/wechat/types.ts +10 -1
  180. package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
  181. package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
  182. package/extensions/worktree/guard.ts +3 -5
  183. package/lib/README.md +8 -6
  184. package/lib/board/card.ts +290 -0
  185. package/lib/board/links.ts +117 -0
  186. package/lib/board/page-pointer.ts +256 -0
  187. package/lib/board/round-text.ts +72 -0
  188. package/lib/board/round.ts +89 -0
  189. package/lib/board/rules.ts +304 -0
  190. package/lib/board/session.ts +105 -0
  191. package/lib/board/store.ts +310 -0
  192. package/lib/board/view.ts +150 -0
  193. package/lib/errors.ts +12 -4
  194. package/lib/format.ts +13 -0
  195. package/lib/http/guard.ts +112 -0
  196. package/lib/http/index.ts +16 -5
  197. package/lib/http/serve.ts +234 -0
  198. package/lib/http/server.ts +12 -10
  199. package/lib/http/sink.ts +110 -0
  200. package/lib/http/token.ts +38 -0
  201. package/lib/policy.ts +12 -2
  202. package/lib/requirements.ts +6 -7
  203. package/lib/session-label.ts +64 -0
  204. package/lib/session-roster.ts +130 -0
  205. package/lib/session-tool.ts +184 -0
  206. package/lib/tool-names.ts +25 -2
  207. package/lib/tui/glyphs.ts +2 -0
  208. package/lib/web/README.md +5 -2
  209. package/lib/web/components.ts +5 -0
  210. package/lib/web/runtime.ts +7 -1
  211. package/lib/web/tokens.ts +25 -2
  212. package/package.json +26 -14
  213. package/skills/next/SKILL.md +4 -3
  214. package/skills/project-docs/SKILL.md +133 -0
  215. package/skills/project-docs/references/audit.md +131 -0
  216. package/skills/project-docs/references/changelog.md +105 -0
  217. package/skills/project-docs/references/discovery.md +127 -0
  218. package/skills/project-docs/references/readme.md +80 -0
  219. package/skills/project-docs/references/release.md +84 -0
  220. package/skills/project-docs/references/roadmap.md +93 -0
  221. package/skills/project-docs/scripts/state.ts +316 -0
  222. package/skills/refactor/SKILL.md +100 -125
  223. package/skills/refactor/references/handoff.md +35 -0
  224. package/skills/refactor/references/signals.md +54 -0
  225. package/skills/refactor/scripts/inventory.ts +587 -0
  226. package/extensions/file-browse/README.md +0 -506
  227. package/extensions/file-browse/server.ts +0 -141
  228. package/extensions/plan/split.ts +0 -87
  229. package/extensions/session-bus/README.md +0 -247
  230. package/extensions/session-bus/team.ts +0 -102
  231. package/extensions/wechat/outbound.ts +0 -100
  232. package/lib/plans/sections.ts +0 -172
  233. package/lib/plans/setting.ts +0 -52
  234. package/lib/plans/split.ts +0 -215
  235. package/lib/plans/store.ts +0 -19
  236. package/lib/plans/team.ts +0 -151
  237. package/skills/changelog/SKILL.md +0 -148
  238. package/skills/parallel-dev/SKILL.md +0 -150
  239. /package/extensions/file-browse/{rev.ts → live/rev.ts} +0 -0
  240. /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
  241. /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
  242. /package/extensions/file-browse/{live-client.ts → view/live-client.ts} +0 -0
  243. /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
  244. /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
  245. /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
  246. /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
  247. /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
  248. /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
  249. /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. With `doctor` it produces the doctor
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 = ["doctor", "settings", "skills", "update", ...Object.keys(REQUIREMENTS), "refresh"] as const;
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 update` take no name, and `refresh` is never
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") return null;
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;
@@ -25,7 +25,6 @@
25
25
  */
26
26
 
27
27
  import { configSection, configValueAt } from "../../../lib/config.ts";
28
- import { SPLIT_KEY, SPLIT_SECTION, SPLIT_SETTING_ID, SPLIT_SETTING_LABEL } from "../../../lib/plans/setting.ts";
29
28
  import { DEFAULT_CONFIG as DEFAULT_DOCTOR_CONFIG, normalizeConfig as normalizeDoctorConfig } from "../doctor/config.ts";
30
29
  import {
31
30
  CURRENCY_DEFAULTS,
@@ -39,7 +38,7 @@ import { DEFAULT_NOTIFY_CONFIG, normalizeNotifyConfig } from "../notify/config.t
39
38
  import type { Settings, WriteScope } from "../picker/types.ts";
40
39
 
41
40
  /** Where a preference is stored. */
42
- type SettingSource = "app" | "bytefly" | "plan";
41
+ type SettingSource = "app" | "bytefly";
43
42
 
44
43
  /** The row control a preference uses. */
45
44
  type SettingKind = "boolean" | "enum" | "text";
@@ -62,7 +61,7 @@ interface TextRule {
62
61
  }
63
62
 
64
63
  /** The `bytefly.json` sections a row still reaches. */
65
- export type ConfigSectionName = "notify" | "doctor" | "price" | "rewind" | "job" | "plan";
64
+ export type ConfigSectionName = "notify" | "doctor" | "price" | "rewind" | "job";
66
65
 
67
66
  /** Where a `bytefly.json` row lives: a section, and the path to its leaf. */
68
67
  interface SettingConfig {
@@ -125,8 +124,6 @@ const ON_OFF: readonly SettingOption[] = [
125
124
  { value: "true", label: "On" },
126
125
  ];
127
126
 
128
- /** The notify section's delivery methods, labeled by the surface each reaches. */
129
-
130
127
  /**
131
128
  * A text rule for a money amount inside an inclusive range: a whole number or a
132
129
  * decimal with up to four places, matching the loader's clamp for `price.budget`.
@@ -148,7 +145,7 @@ function amountRule(min: number, max: number): TextRule {
148
145
  /**
149
146
  * The catalogue, in screen order.
150
147
  *
151
- * Global scope lists all thirteen; Project lists the eleven `bytefly.json` rows,
148
+ * Global scope lists all twelve; Project lists the ten `bytefly.json` rows,
152
149
  * because Pi's setters write the personal `settings.json` only. The list is
153
150
  * capped so the screen keeps fitting a 24-row terminal.
154
151
  */
@@ -296,20 +293,6 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
296
293
  text: amountRule(0, MAX_BUDGET),
297
294
  config: { section: "price", path: ["budget"] },
298
295
  },
299
- {
300
- // The split's switch, with the label, id, and default taken from the module
301
- // that reads it, so this row cannot describe a key nothing honours.
302
- id: SPLIT_SETTING_ID,
303
- label: SPLIT_SETTING_LABEL,
304
- description:
305
- "Let an approved plan carry a team of slices, which separate sessions join by name. Off keeps every plan one flow.",
306
- source: "plan",
307
- kind: "boolean",
308
- fallback: "false",
309
- json: "boolean",
310
- options: ON_OFF,
311
- config: { section: SPLIT_SECTION, path: [SPLIT_KEY] },
312
- },
313
296
  ];
314
297
 
315
298
  /** The one refusal for a Project write while the project is untrusted. */
@@ -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 * 2);
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 latest = await resolveOwnLatest(list.find(isOwnPackage));
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
- return {
214
- async recheck() {
215
- if (offline()) return { kind: "offline" };
216
- if (!context) return { kind: "failed" };
217
- let list: PackageUpdate[];
218
- try {
219
- const found = await readUpdates(context, activeCheck());
220
- if (found === undefined) return { kind: "failed" };
221
- list = found;
222
- } catch {
223
- return { kind: "failed" };
224
- }
225
- const latest = await resolveOwnLatest(list.find(isOwnPackage));
226
- const entry: UpdateCacheEntry = {
227
- key: keyFor(context),
228
- checkedAt: now(),
229
- updates: list,
230
- ...(latest ? { latest } : {}),
231
- };
232
- writeCache(entry);
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 re-check found. The background check reports nothing — nobody
24
- * asked — so this vocabulary exists only for `/bytefly update`, where a failure or
25
- * a skip is an answer rather than silence.
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, 11);
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 "../../lib/config.ts";
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. */
@@ -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 "../../lib/pool.ts";
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 "../../lib/path.ts";
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 { createExecRunner, currentBranch, hasCommits, repoRoot, revParse, type RunGit } from "../../lib/git/index.ts";
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 "./diff.ts";
22
- import { readFileView } from "./files.ts";
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). */