@gavin-hu/my-pi-agent 0.14.0 → 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.
Files changed (222) hide show
  1. package/README.md +55 -33
  2. package/README.zh-CN.md +44 -27
  3. package/extensions/bytefly/README.md +193 -140
  4. package/extensions/bytefly/block/component.ts +7 -15
  5. package/extensions/bytefly/block/register.ts +6 -1
  6. package/extensions/bytefly/doctor/register.ts +24 -5
  7. package/extensions/bytefly/doctor/tools.ts +4 -0
  8. package/extensions/bytefly/footer/collect/snapshot.ts +6 -1
  9. package/extensions/bytefly/footer/config.ts +10 -6
  10. package/extensions/bytefly/footer/price/price.ts +5 -3
  11. package/extensions/bytefly/footer/price/rates.ts +63 -26
  12. package/extensions/bytefly/footer/render/status.ts +3 -3
  13. package/extensions/bytefly/header/component.ts +3 -2
  14. package/extensions/bytefly/index.ts +3 -3
  15. package/extensions/bytefly/notice/component.ts +8 -3
  16. package/extensions/bytefly/notify/config.ts +6 -11
  17. package/extensions/bytefly/notify/dispatch.ts +13 -5
  18. package/extensions/bytefly/panel/commands.ts +21 -1
  19. package/extensions/bytefly/panel/format.ts +5 -2
  20. package/extensions/bytefly/panel/settings-runtime.ts +26 -6
  21. package/extensions/bytefly/panel/settings.ts +41 -4
  22. package/extensions/bytefly/panel/themes.ts +32 -0
  23. package/extensions/bytefly/panel/tui/editing.ts +77 -0
  24. package/extensions/bytefly/panel/tui/entries.ts +24 -0
  25. package/extensions/bytefly/panel/tui/extensions-view.ts +13 -8
  26. package/extensions/bytefly/panel/tui/frame.ts +121 -4
  27. package/extensions/bytefly/panel/tui/settings-view.ts +54 -15
  28. package/extensions/bytefly/panel/tui.ts +94 -152
  29. package/extensions/bytefly/picker/discovery.ts +1 -0
  30. package/extensions/bytefly/picker/types.ts +2 -0
  31. package/extensions/bytefly/separator/component.ts +14 -4
  32. package/extensions/bytefly/update/cache.ts +58 -19
  33. package/extensions/bytefly/update/check.ts +35 -0
  34. package/extensions/bytefly/update/format.ts +76 -35
  35. package/extensions/bytefly/update/register.ts +144 -55
  36. package/extensions/bytefly/update/state.ts +4 -2
  37. package/extensions/bytefly/update/suppress.ts +66 -0
  38. package/extensions/bytefly/update/types.ts +15 -0
  39. package/extensions/check/README.md +253 -0
  40. package/extensions/check/commands.ts +128 -0
  41. package/extensions/check/config.ts +39 -0
  42. package/extensions/check/detect.ts +269 -0
  43. package/extensions/check/failures.ts +230 -0
  44. package/extensions/check/format.ts +135 -0
  45. package/extensions/check/index.ts +26 -0
  46. package/extensions/check/render.ts +185 -0
  47. package/extensions/check/runner.ts +176 -0
  48. package/extensions/check/runtime.ts +244 -0
  49. package/extensions/check/source.ts +80 -0
  50. package/extensions/check/tools.ts +173 -0
  51. package/extensions/check/tui.ts +195 -0
  52. package/extensions/check/types.ts +99 -0
  53. package/extensions/document/README.md +1 -1
  54. package/extensions/document/extract/pdf.ts +8 -16
  55. package/extensions/document/formats.ts +2 -2
  56. package/extensions/file-browse/README.md +66 -32
  57. package/extensions/file-browse/client.ts +95 -32
  58. package/extensions/file-browse/config.ts +9 -9
  59. package/extensions/file-browse/git.ts +37 -47
  60. package/extensions/file-browse/html.ts +39 -58
  61. package/extensions/file-browse/markdown.ts +110 -92
  62. package/extensions/file-browse/raw-html.ts +478 -0
  63. package/extensions/file-browse/router.ts +32 -70
  64. package/extensions/file-browse/server.ts +18 -56
  65. package/extensions/file-browse/urls.ts +80 -0
  66. package/extensions/goal/README.md +103 -47
  67. package/extensions/goal/clarify-parse.ts +4 -9
  68. package/extensions/goal/clarify.ts +27 -4
  69. package/extensions/goal/commands.ts +24 -11
  70. package/extensions/goal/format.ts +41 -10
  71. package/extensions/goal/index.ts +39 -16
  72. package/extensions/goal/plan-route.ts +51 -0
  73. package/extensions/goal/schema.ts +6 -23
  74. package/extensions/goal/state.ts +39 -57
  75. package/extensions/goal/tools.ts +26 -20
  76. package/extensions/goal/types.ts +11 -1
  77. package/extensions/job/README.md +20 -6
  78. package/extensions/job/format.ts +18 -68
  79. package/extensions/job/index.ts +26 -41
  80. package/extensions/job/log-writer.ts +208 -0
  81. package/extensions/job/logs.ts +3 -2
  82. package/extensions/job/process.ts +6 -99
  83. package/extensions/job/runtime.ts +42 -181
  84. package/extensions/job/schema.ts +1 -4
  85. package/extensions/job/tools.ts +1 -15
  86. package/extensions/job/tui.ts +117 -307
  87. package/extensions/job/ui.ts +7 -33
  88. package/extensions/memory/README.md +1 -1
  89. package/extensions/memory/errors.ts +5 -6
  90. package/extensions/memory/tui.ts +14 -24
  91. package/extensions/plan/README.md +94 -18
  92. package/extensions/plan/commands.ts +9 -5
  93. package/extensions/plan/format.ts +87 -0
  94. package/extensions/plan/index.ts +31 -15
  95. package/extensions/plan/plans.ts +2 -3
  96. package/extensions/plan/policy.ts +0 -6
  97. package/extensions/plan/runtime.ts +4 -2
  98. package/extensions/plan/steps.ts +37 -8
  99. package/extensions/plan/todos.ts +44 -3
  100. package/extensions/plan/tools/enter.ts +94 -0
  101. package/extensions/plan/tools/exit.ts +296 -0
  102. package/extensions/plan/tools/nested.ts +21 -0
  103. package/extensions/plan/tools/steps.ts +136 -0
  104. package/extensions/plan/tools/todo-seed.ts +76 -0
  105. package/extensions/plan/tools/write.ts +127 -0
  106. package/extensions/plan/tools.ts +18 -703
  107. package/extensions/plan/web-client.ts +6 -22
  108. package/extensions/plan/web-page.ts +19 -30
  109. package/extensions/plan/web-router.ts +34 -71
  110. package/extensions/plan/web.ts +13 -40
  111. package/extensions/question/answers.ts +3 -8
  112. package/extensions/question/tui.ts +6 -6
  113. package/extensions/rewind/README.md +95 -22
  114. package/extensions/rewind/checklist.ts +185 -0
  115. package/extensions/rewind/commands.ts +8 -0
  116. package/extensions/rewind/config.ts +3 -11
  117. package/extensions/rewind/format.ts +220 -2
  118. package/extensions/rewind/git.ts +74 -7
  119. package/extensions/rewind/hunk-picker.ts +114 -0
  120. package/extensions/rewind/patch.ts +88 -0
  121. package/extensions/rewind/path-picker.ts +67 -100
  122. package/extensions/rewind/paths.ts +93 -19
  123. package/extensions/rewind/restore.ts +53 -10
  124. package/extensions/rewind/review-tui.ts +320 -0
  125. package/extensions/rewind/review.ts +397 -0
  126. package/extensions/rewind/rewind-tui.ts +4 -3
  127. package/extensions/rewind/runtime.ts +94 -17
  128. package/extensions/subagent/README.md +61 -6
  129. package/extensions/subagent/background-tui.ts +208 -0
  130. package/extensions/subagent/background.ts +470 -0
  131. package/extensions/subagent/commands.ts +80 -0
  132. package/extensions/subagent/config.ts +4 -0
  133. package/extensions/subagent/format.ts +24 -1
  134. package/extensions/subagent/index.ts +91 -2
  135. package/extensions/subagent/render.ts +13 -0
  136. package/extensions/subagent/run.ts +17 -16
  137. package/extensions/subagent/schema.ts +12 -0
  138. package/extensions/subagent/types.ts +11 -0
  139. package/extensions/subagent/ui.ts +50 -0
  140. package/extensions/todo/README.md +18 -13
  141. package/extensions/todo/format.ts +3 -2
  142. package/extensions/todo/runtime.ts +3 -2
  143. package/extensions/todo/schema.ts +8 -84
  144. package/extensions/todo/state.ts +5 -46
  145. package/extensions/todo/tools.ts +2 -1
  146. package/extensions/todo/types.ts +6 -15
  147. package/extensions/watch/README.md +14 -6
  148. package/extensions/watch/child.ts +150 -0
  149. package/extensions/watch/format.ts +8 -48
  150. package/extensions/watch/index.ts +42 -58
  151. package/extensions/watch/runtime.ts +49 -128
  152. package/extensions/watch/schema.ts +1 -4
  153. package/extensions/watch/tools.ts +1 -11
  154. package/extensions/watch/tui.ts +103 -273
  155. package/extensions/watch/ui.ts +5 -32
  156. package/extensions/web-access/README.md +1 -1
  157. package/extensions/web-access/fetch/page.ts +1 -1
  158. package/extensions/web-access/fetch/tool.ts +6 -2
  159. package/extensions/web-access/search/search.ts +2 -5
  160. package/extensions/wechat/README.md +1 -1
  161. package/extensions/wechat/client.ts +23 -0
  162. package/extensions/wechat/commands.ts +57 -42
  163. package/extensions/wechat/deliver.ts +5 -9
  164. package/extensions/wechat/errors.ts +3 -6
  165. package/extensions/wechat/inbound.ts +2 -5
  166. package/extensions/wechat/index.ts +2 -8
  167. package/extensions/wechat/outbound.ts +2 -4
  168. package/extensions/wechat/peer-worker.ts +3 -8
  169. package/extensions/wechat/poll.ts +10 -9
  170. package/extensions/wechat/presence.ts +35 -62
  171. package/extensions/wechat/reply-pipeline.ts +3 -1
  172. package/extensions/wechat/status-screen.ts +1 -1
  173. package/extensions/wechat/timer.ts +57 -0
  174. package/extensions/worktree/config.ts +8 -20
  175. package/extensions/worktree/enter.ts +7 -12
  176. package/extensions/worktree/git.ts +38 -69
  177. package/extensions/worktree/index.ts +5 -4
  178. package/extensions/worktree/registry.ts +7 -7
  179. package/extensions/worktree/root-tools.ts +2 -2
  180. package/extensions/worktree/status.ts +3 -2
  181. package/lib/README.md +22 -8
  182. package/lib/config.ts +50 -3
  183. package/lib/errors.ts +12 -0
  184. package/lib/format.ts +50 -0
  185. package/lib/git/read.ts +8 -0
  186. package/lib/goal-state.ts +23 -8
  187. package/lib/http/headers.ts +54 -0
  188. package/lib/http/index.ts +13 -0
  189. package/lib/http/response.ts +46 -0
  190. package/lib/http/router.ts +52 -0
  191. package/lib/http/server.ts +91 -0
  192. package/lib/lines.ts +77 -0
  193. package/lib/pdf.ts +82 -0
  194. package/lib/plan-entry.ts +71 -0
  195. package/lib/plan-state.ts +34 -0
  196. package/lib/report.ts +99 -0
  197. package/lib/requirements.ts +25 -19
  198. package/lib/spawn.ts +170 -0
  199. package/lib/todo-state.ts +110 -23
  200. package/lib/tool-names.ts +7 -0
  201. package/lib/tool-result.ts +33 -0
  202. package/lib/tui/README.md +68 -3
  203. package/lib/tui/chrome.ts +28 -4
  204. package/lib/tui/columns.ts +6 -1
  205. package/lib/tui/confirm.ts +65 -0
  206. package/lib/tui/dock-screen.ts +410 -0
  207. package/lib/tui/glyphs.ts +2 -1
  208. package/lib/tui/indent.ts +1 -1
  209. package/lib/tui/index.ts +2 -0
  210. package/lib/tui/rows.ts +21 -0
  211. package/lib/tui/status-controller.ts +35 -1
  212. package/lib/tui/tool.ts +59 -1
  213. package/lib/web/components.ts +20 -1
  214. package/lib/web/markup.ts +32 -0
  215. package/lib/web/runtime.ts +50 -0
  216. package/package.json +22 -5
  217. package/skills/init/SKILL.md +165 -0
  218. package/skills/init/references/agents-md-quality.md +103 -0
  219. package/skills/init/references/interview-questions.md +81 -0
  220. package/skills/refactor/SKILL.md +141 -0
  221. package/extensions/watch/process.ts +0 -63
  222. package/extensions/web-access/fetch/pdf.ts +0 -45
@@ -8,8 +8,9 @@ records each completed turn in the transcript, one keyboard-driven screen with t
8
8
  sections — Extensions, every resolved Pi extension with its enable checkbox and
9
9
  its health, and Settings, the common app and `bytefly.json` preferences — and
10
10
  an opt-in notify capability that raises an OS notification when a run finishes,
11
- a job finishes, or the agent asks for input, and a published-version check that
12
- marks the header and posts one notice when a newer bytefly is on npm.
11
+ a job finishes, or the agent asks for input, and a package-update check that
12
+ replaces Pi's startup block with one notice and marks bytefly's own new version
13
+ on the header.
13
14
  DeepSeek is priced from the provider's published yuan rates and peak tariff by
14
15
  default; set `price.currency.code` to `USD` to price it from the provider's
15
16
  published dollar table instead, under the same peak windows, with Pi's recorded
@@ -34,7 +35,7 @@ How the `run_doctor` call and its result look in the transcript — the call lin
34
35
  The extension owns ten surfaces: the startup header, the footer, the
35
36
  user-message marker, the rule between completed turns, the notice block for
36
37
  shared transcript notices, the tool-block frame, the live working line, the
37
- two-section control center, notifications, and the published-version check.
38
+ two-section control center, notifications, and the package-update check.
38
39
 
39
40
  **Header.** A nameplate: a two-tone half-block mark from the active theme's
40
41
  `accent` / `warn` roles, the agent identity (`Pi`, bold, and its version), the
@@ -75,7 +76,7 @@ identity, and grouped help are tuned in
75
76
  (`⎇ branch · ⑂ worktree`) and the `◉` browse chip on the right.
76
77
  - **Line 2 — resources:** the context gauge and usage meters on the left, the
77
78
  child-run roll-up (`↳ 3 · ¥0.12`, present only once a subagent has finished),
78
- the mode/alert slot (`⋮ plan`) trailing after a `╎` when present, and the model
79
+ the mode/alert slot (`≡ plan`) trailing after a `│` when present, and the model
79
80
  and thinking level on the right. The gauge is anchored at column 0, so `%` is
80
81
  always in the same place.
81
82
  The roll-up is its own number, never folded into the session's cost: a subagent
@@ -86,7 +87,7 @@ identity, and grouped help are tuned in
86
87
 
87
88
  ```
88
89
  ~/repo/my-pi-agent ⎇ main · ⑂ smoke
89
- ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↳ 2 · ¥0.06 · ↑42k ↓8.0k · R96k CH 87% ╎ ⋮ plan opus-4.5 · high
90
+ ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↳ 2 · ¥0.06 · ↑42k ↓8.0k · R96k CH 87% │ ≡ plan opus-4.5 · high
90
91
  ```
91
92
 
92
93
  (The examples show the default `¥` symbol. A model that neither `rates` nor the
@@ -94,7 +95,7 @@ built-in table covers contributes nothing in `CNY` mode, so the bar shows no
94
95
  figure rather than a guessed one; `USD` mode adds Pi's recorded dollars for that
95
96
  record instead — see the currency note under [Configuration](#configuration).)
96
97
 
97
- With no plan mode there is no mode slot, so the `╎` is omitted:
98
+ With no plan mode there is no mode slot, so the `│` is omitted:
98
99
 
99
100
  ```
100
101
  ~/repo/my-pi-agent ⎇ main · ⑂ smoke
@@ -107,19 +108,19 @@ fits, rather than collapsing to a bare glyph:
107
108
 
108
109
  ```
109
110
  ~/repo/project ⎇ main · ⑂ smoke
110
- ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 ╎ ⋮ plan opus-4.5 · high
111
+ ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 │ ≡ plan opus-4.5 · high
111
112
  ```
112
113
 
113
114
  ```
114
115
  project ⎇ main
115
- 62% ⋮ opus-4.5
116
+ 62% ≡ opus-4.5
116
117
  ```
117
118
 
118
119
  With a named session, the name follows the path and truncates before it drops:
119
120
 
120
121
  ```
121
122
  ~/repo/project · feature-refac… ⎇ main
122
- ▰▰▰▰▰▰▱▱▱▱ 62% ╎ ⋮ plan opus-4.5
123
+ ▰▰▰▰▰▰▱▱▱▱ 62% │ ≡ plan opus-4.5
123
124
  ```
124
125
 
125
126
  Zero-value meters are omitted, so a fresh session shows only `?/window` instead
@@ -318,15 +319,17 @@ method follows the terminal (OSC 777, OSC 99, a Windows toast) or falls back to
318
319
  the platform notifier (`osascript`, `notify-send`); see
319
320
  [Notifications](#notifications).
320
321
 
321
- **Update check.** In the background at an interactive session's start, the loaded
322
- version is compared with the published `@gavin-hu/my-pi-agent` on npm. A newer
323
- one puts `↑0.12.0` after the header's chrome version and posts one transcript
324
- notice naming `pi update --extensions` and its release URL. The answer is cached
325
- in `<agentDir>/bytefly/update.json` with the time it was read, so a session
326
- starting inside the 24-hour window reuses it instead of repeating the request,
327
- and `/bytefly update` re-runs the check on demand. The check is skipped when
328
- `PI_OFFLINE` is set, makes no request outside `tui`, and a failed background
329
- request is silent; see [Update check](#update-check).
322
+ **Update check.** In the background at an interactive session's start, Pi's
323
+ package manager reports which configured packages are behind. bytefly suppresses
324
+ Pi's own startup block, caches the snapshot in `<agentDir>/bytefly/update.json`
325
+ with the time it was read, and posts one transcript notice listing every package
326
+ plus `pi update --extensions`. This package's new version alone puts `↑0.12.0`
327
+ after the header's chrome version and adds its release URL to the notice. A
328
+ session inside the 24-hour window reuses the cache without a package-manager
329
+ call, and `/bytefly update` re-runs the check on demand. `PI_OFFLINE` uses the
330
+ cached marker with no notice, only `tui` starts the background check, and a
331
+ failed read is silent and not cached, so the next session tries again; see
332
+ [Update check](#update-check).
330
333
 
331
334
  ## Commands
332
335
 
@@ -335,7 +338,7 @@ request is silent; see [Update check](#update-check).
335
338
  | `/bytefly [extension] [refresh]` | Open the control center on Extensions. In `tui` it mounts a docked list; every other mode prints the grouped listing, or one extension's full findings when named. |
336
339
  | `/bytefly settings` | Open the control center on Settings. Outside `tui` it prints the grouped settings listing with each row's value. |
337
340
  | `/bytefly doctor [extension] [refresh]` | Print the doctor report. Outside `tui` it prints `run_doctor`'s text for every extension, or one named extension's findings; in `tui` it mounts the same docked screen. |
338
- | `/bytefly update` | Re-run the published-version check now, ignoring the cache window, and report what it found — the newest version, an available release with its URL, `PI_OFFLINE`, or an unreadable registry. It resolves no packages and probes no extension. |
341
+ | `/bytefly update` | Re-run the package update check now, ignoring the cache window, and report what it found — the updateable packages, this package's version and release URL when known, `PI_OFFLINE`, or an unreadable list. It resolves no packages and probes no extension. |
339
342
 
340
343
  Tab completes the subcommands, extension names after `doctor`, and `refresh`;
341
344
  `/bytefly settings` and `/bytefly update` take no name.
@@ -374,8 +377,8 @@ separators, icons, status keys), and the turn separator in
374
377
  [`separator/config.ts`](./separator/config.ts) (glyph, label prefix, padding,
375
378
  minimum duration, outcome wording, colors).
376
379
 
377
- Every key of every section is listed with its type, default, and bounds in the
378
- [configuration reference](../../docs/references/bytefly-config.md); this section
380
+ Every key of every section is listed with its type, default, and bounds in
381
+ [`docs/configuration.md`](../../docs/configuration.md); this section
379
382
  covers what that table cannot — when each section is read, and what the cost
380
383
  does when no rate matches.
381
384
 
@@ -472,7 +475,10 @@ it with its own rates for the models they match:
472
475
  - **Which rate.** The most specific key wins, with the model segment outranking
473
476
  the provider one: `provider/model`, then `*/model`, then `provider/*`, then
474
477
  `*`. A segment ending in a star is a prefix match (`*/gpt-*`); equally
475
- specific keys are resolved in file order.
478
+ specific keys are resolved in file order. A key splits at its first slash, so
479
+ the model segment may contain one: an OpenRouter id is
480
+ `anthropic/claude-fable-5` and its key is
481
+ `openrouter/anthropic/claude-fable-5`.
476
482
  - **Which formula.** `tokens × rate ÷ 1,000,000`, summed over the four token
477
483
  classes. Cache tokens are never billed again as fresh input.
478
484
  - **Which tariff.** Each record is dated by its session entry, so a window is
@@ -519,9 +525,9 @@ The worktree status is read from the `worktree` status key, so the
519
525
  from the `browse` status key published by [file-browse](../file-browse/) while
520
526
  its server runs, and shows the two-tone `◉ <port>` (green glyph, accent port).
521
527
  The trailing slot shows plan-style alerts when present and is omitted otherwise;
522
- a plan chip's trailing plan file name is dropped, so the slot reads `⋮ plan`
528
+ a plan chip's trailing plan file name is dropped, so the slot reads `≡ plan`
523
529
  even though the built-in footer keeps the name. An alert (a failed job) sorts to
524
- the front of the slot. The slot trails the meters after a `╎`, so the context
530
+ the front of the slot. The slot trails the meters after a `│`, so the context
525
531
  gauge always starts the line. It keeps each extension's own colour when it
526
532
  compacts, and it is droppable: a tight bar drops the slot rather than truncating
527
533
  the model, which sits at the end of the line.
@@ -582,7 +588,7 @@ when the row is switched off in settings, `env disabled` when
582
588
  glyph and summary. The screen is drawn from the design system: a label-first
583
589
  `bytefly ────` title rule,
584
590
  the `Extensions · Settings` section bar beside the section's counts, one row
585
- grammar (`❯ ◼ ✓ name … summary`), and the shared `◼` / `◻` checkbox.
591
+ grammar (`❯ ◼ name … summary`), and the shared `◼` / `◻` checkbox.
586
592
  `/bytefly doctor [extension] [refresh]` selects the doctor report, the same text
587
593
  the `run_doctor` tool returns; in `tui` it opens the same screen. `[extension]`
588
594
  narrows the Extensions section to one extension and `refresh` forces the first
@@ -606,12 +612,13 @@ installs or fetches a package.
606
612
  manages the list is never a selectable row; its doctor report still appears in
607
613
  the trailing `Not resolved` group.
608
614
  - Shows whether each one is enabled (`◼`) or disabled (`◻`) in settings.
609
- - Gives each row one verdict from the doctor's report: the worst severity (`✓`
610
- ok, `!` degraded, `✕` missing) and its summary. A row that is `◻` reads `off`
611
- instead, because it will not load; a row the session's
615
+ - Gives each row one verdict from the doctor's report: the worst severity's
616
+ glyph (`!` degraded, `✕` missing) and its summary. A row that is `◻` reads
617
+ `off` instead, because it will not load; a row the session's
612
618
  `PI_DISABLED_EXTENSIONS` excludes reads `env disabled`, so the two axes cannot
613
- contradict each other. An extension with no doctor report leaves the glyph
614
- column and the summary blank rather than claiming `ok`.
619
+ contradict each other. The glyph marks what needs attention, so a row with
620
+ nothing to report — a clean report or none at all — leaves the glyph column
621
+ blank and says `ok` in words when a report exists.
615
622
  - Toggles the focused extension with `Space`, writing `+pattern` / `-pattern`
616
623
  into `extensions` (and package filter objects) in the active scope. A row in
617
624
  the trailing `Not resolved` group has no pattern to write, so `Space` says so
@@ -635,11 +642,11 @@ installs or fetches a package.
635
642
  without leaving the session.
636
643
 
637
644
  **Settings.** The second section uses the same frame and the same `Tab` scope
638
- switch, with its own cursor. It lists twenty-eight preferences in ten groups:
645
+ switch, with its own cursor. It lists twenty-nine preferences in ten groups:
639
646
 
640
647
  ```
641
648
  bytefly ──────────────────────────────────────────────────────────────
642
- Extensions · Settings Global · 28 settings
649
+ Extensions · Settings Global · 29 settings
643
650
 
644
651
  Startup (1)
645
652
  ❯ ◻ Bytefly startup TUI Off
@@ -662,7 +669,8 @@ bytefly ────────────────────────
662
669
  Rail glyph ╎
663
670
  Achieved goal Hide
664
671
  Output padding 1
665
- Terminal (1)
672
+ Terminal (2)
673
+ Theme system
666
674
  ◻ Hardware cursor Off
667
675
  Worktree (4)
668
676
  ◼ Fetch remote On
@@ -704,6 +712,7 @@ bytefly ────────────────────────
704
712
  | Transcript | Rail glyph | `chrome.rail` | Text, exactly one column |
705
713
  | Transcript | Achieved goal | `goal.achieved` | Show, Hide |
706
714
  | Transcript | Output padding | Pi `settings.json` | 0, 1 |
715
+ | Terminal | Theme | Pi `settings.json` (`theme`) | `system`, `bytefly-dark`, `bytefly-light` |
707
716
  | Terminal | Hardware cursor | Pi `settings.json` | Off, On |
708
717
  | Worktree | Base ref | `worktree.baseRef` | Fresh, Head |
709
718
  | Worktree | Branch prefix | `worktree.branchPrefix` | Text, trimmed, required |
@@ -720,24 +729,35 @@ bytefly ────────────────────────
720
729
  Choosing **Currency** also writes that currency's default precision. Both rows
721
730
  stay editable afterwards, so the footer follows the explicit values.
722
731
 
732
+ The **Theme** row lists `system` first, then the palettes this package ships,
733
+ `bytefly-dark` and `bytefly-light`; it cycles no further, so a bytefly screen
734
+ never proposes a palette it cannot vouch for. It writes the personal `theme`
735
+ setting and takes effect after reload. A value chosen elsewhere — Pi's built-in
736
+ `dark`, another package's theme, or an automatic pair such as
737
+ `bytefly-light/bytefly-dark` — is shown as stored (the pair with `… (automatic)`)
738
+ and is replaced by the next choice here, so manage those in Pi's `/settings` →
739
+ **Theme**.
740
+
723
741
  `Space` (or `Enter`) changes the focused row: a boolean toggles, an enum cycles
724
742
  forward, and a text row opens an inline editor on its value — the notification
725
743
  title, the probe timeout, the branch prefix, and the rail glyph. `u` deletes the
726
744
  focused row's key so its default applies again (`Already at its default.` when
727
- nothing is stored), `J` swaps the detail pane for the focused row's whole section
728
- as the active scope stores it — the only view of the keys this screen leaves to
729
- JSON — and `[` / `]` jump to the previous or next group. The detail pane names
745
+ nothing is stored in that scope; an inherited Project value says so instead),
746
+ `J` swaps the detail pane for the focused row's whole section as the active
747
+ scope stores it — the only view of the keys this screen leaves to JSON — and
748
+ `[` / `]` jump to the previous or next group. The detail pane names
730
749
  the source, describes the preference, and shows
731
- where the value is written — `Global · ~/.pi/agent/settings.json`, `Project ·
732
- .pi/bytefly.json`, or `Inherited from Global · .pi/bytefly.json`. A successful
733
- change marks the screen `Saved · reload to apply` — even when the terminal is too
734
- short for the detail body — and closing offers the same
735
- reload confirm as an extension toggle.
736
-
737
- Six of the ten groups belong to other extensions. The screen cannot import their
738
- normalizers, so it reports the leaf as the file states it and declares the
739
- loader's default; `test/settings-coverage.test.ts` holds the catalogue to every
740
- loader's defaults and bounds in both directions.
750
+ where the value comes from — `Global · ~/.pi/agent/bytefly.json`, `Project ·
751
+ .pi/bytefly.json`, `Inherited from Global · ~/.pi/agent/bytefly.json`, or
752
+ `Default · <the active scope's file>`; an app row names the personal
753
+ `settings.json`. A successful change marks the screen `Saved · reload to apply`
754
+ — even when the terminal is too short for the detail body — and closing offers
755
+ the same reload confirm as an extension toggle.
756
+
757
+ Six of the ten `bytefly.json` sections belong to other extensions. The screen
758
+ cannot import their normalizers, so it reports the leaf as the file states it and
759
+ declares the loader's default; `test/settings-coverage.test.ts` holds the
760
+ catalogue to every loader's defaults and bounds in both directions.
741
761
 
742
762
  **Bytefly startup TUI** is the one row whose label differs from the Pi key it
743
763
  writes: On writes `"quietStartup": true` (bytefly's header replaces Pi's startup
@@ -758,7 +778,7 @@ removes that one leaf through the same writer's `unsetConfigValueAt`.
758
778
  The non-`tui` form prints the headline, the groups, and `Label: Value` rows:
759
779
 
760
780
  ```
761
- bytefly: Settings · Global · 28 settings
781
+ bytefly: Settings · Global · 29 settings
762
782
 
763
783
  Startup (1)
764
784
  Bytefly startup TUI: Off
@@ -810,6 +830,9 @@ headline; a named extension prints its full findings instead.
810
830
  | `Space` | Toggle the focused extension (Extensions); change the focused setting (Settings), or open the editor on a text row. |
811
831
  | `x` | Clear the focused extension's project override (Extensions, Project scope). |
812
832
  | `Enter` | Expand or collapse the focused extension's findings (Extensions); change the focused setting, or open the editor on a text row (Settings). |
833
+ | `u` | Delete the focused setting's key so its default applies again (Settings, `bytefly.json` rows). |
834
+ | `J` | Show or hide the focused row's whole section as stored (Settings, `bytefly.json` rows). |
835
+ | `[` / `]` | Jump to the previous or next group (Settings). |
813
836
  | `←` / `→`, `Home` / `End` (`Ctrl+A` / `Ctrl+E`) | Move the editor's caret while a text row is being edited. |
814
837
  | `Backspace` / `Delete`, `Ctrl+U` | Edit the buffer: delete a character, or clear it. |
815
838
  | `Enter` / `Esc` (editing) | Save the buffer, or cancel and restore the stored value. |
@@ -901,10 +924,11 @@ control-free line before it reaches an escape sequence or a shell argument.
901
924
 
902
925
  ## Update check
903
926
 
904
- A session can be days behind npm without anyone noticing, and Pi's own startup
905
- check only covers package sources it manages — a `pi -e ./`, a linked checkout,
906
- or a git install gets nothing. So bytefly reads its own published version and
907
- reports it once, in the two places it already owns:
927
+ Pi's package manager already knows which configured packages are behind, so
928
+ bytefly does not keep a second detector. Once per interactive session, in the
929
+ background, it asks the captured `checkForAvailableUpdates` for the package list,
930
+ suppresses Pi's own `Package Updates Available` block, and draws one notice for
931
+ every row Pi returned.
908
932
 
909
933
  ```
910
934
  █▀█ Pi v1.3.0
@@ -912,47 +936,50 @@ reports it once, in the two places it already owns:
912
936
  ```
913
937
 
914
938
  ```
915
- bytefly · 0.12.0 is available
916
- ╎ ◌ current · v0.11.4
939
+ bytefly · 2 package updates available
940
+ ╎ ◌ @gavin-hu/my-pi-agent · v0.11.4 → v0.12.0
941
+ ╎ ◌ some-other-package · project
917
942
  ╎ ◌ update · pi update --extensions
918
943
  ╎ ◌ release · https://github.com/gavin-hu/my-pi-agent/releases/tag/v0.12.0
919
944
  ```
920
945
 
921
- The marker is the shared `↑` glyph and the newest version — the arrow is one cell
922
- wide, so the header's column math is unchanged — painted with the header's
923
- `accent` role: an action rather than a warning, so the muted version beside it
924
- stays quiet and the transcript is not turned yellow for a normal state. It is
925
- part of the chrome cell, so the two identity cells still line up, and it is the
926
- first thing the identity sheds when the terminal narrows.
946
+ The marker is the shared `↑` glyph and this package's newest version — the arrow
947
+ is one cell wide, so the header's column math is unchanged — painted with the
948
+ header's `accent` role: an action rather than a warning, so the muted version
949
+ beside it stays quiet and the transcript is not turned yellow for a normal state.
950
+ Other packages never change the header. The marker is part of the chrome cell, so
951
+ the two identity cells still line up, and it is the first thing the identity
952
+ sheds when the terminal narrows.
927
953
 
928
954
  The notice is the shared transcript block: the `bytefly · title` headline at the
929
- margin, then the loaded version and the fixing command behind the rail, all at
955
+ margin, one rail row per package with this package's version arrow or the other
956
+ package's scope, then the fixing command and this package's release URL, all at
930
957
  `info` severity because nothing is broken. It stays in scrollback and the model
931
- reads the same sentence in plain text.
932
-
933
- The request is `GET https://registry.npmjs.org/@gavin-hu%2Fmy-pi-agent/latest`,
934
- fired without awaiting at `session_start` (Pi awaits handlers, and a five-second
935
- registry call must not hold the first prompt) with a five-second abort. Only a
936
- strictly newer, parseable semver announces anything: a non-ok status, a body
937
- that is not JSON, a missing version, a network error, or an older or equal
938
- version is silent. `PI_OFFLINE` (Pi's own switch) turns the check off;
939
- `PI_SKIP_VERSION_CHECK` only governs Pi's `pi.dev` request.
940
-
941
- The answer is cached in `<agentDir>/bytefly/update.json` as `latest` plus the
942
- epoch milliseconds it was read at. A session that starts inside the 24-hour
943
- window reuses that entry — painting the marker and posting the notice from it —
944
- instead of repeating the request, so a working day of sessions costs one read.
945
- Past the window the check runs again and rewrites the entry; a failed read writes
946
- nothing, so the next session tries again. Offline, the cached entry is used with
947
- no request and no write, because a stale version is still the best answer
948
- available and the marker is advisory.
949
-
950
- `/bytefly update` is the on-demand half: it ignores the window, reads the registry
951
- now, records and paints the result, and prints one line — `up to date — v0.12.0
952
- is the newest published version`, `v0.13.0 is available (current 0.12.0) —
953
- https://…/releases/tag/v0.13.0`, `the update check was skipped (PI_OFFLINE is
954
- set)`, or `could not read the published version`. It resolves no packages, probes
955
- no extension, and posts no second notice, so a session open for days can see a
958
+ reads the same names, version, command, and URL in plain text.
959
+
960
+ The list is Pi's decision: npm ranges, git refs, project scope, a missing
961
+ install, and `PI_OFFLINE` all follow its package manager. The only extra read is
962
+ `GET https://registry.npmjs.org/@gavin-hu%2Fmy-pi-agent/latest` when Pi reports
963
+ this package, used just to name the version arrow and release URL. A failed
964
+ version read drops those two pieces but keeps the package list and command.
965
+
966
+ The answer is cached in `<agentDir>/bytefly/update.json` as the package list,
967
+ this package's optional version, the time it was read, and a key over the cwd and
968
+ the effective package sources. A session that starts inside the 24-hour window
969
+ reuses that snapshot — painting the marker and posting the notice from it —
970
+ without repeating Pi's npm/git checks, so a working day of sessions costs one
971
+ read. A project switch or a package install/removal changes the key and forces a
972
+ fresh read. Past the window the check runs again and rewrites the entry; a failed
973
+ read writes nothing, so the next session tries again. Offline, the cached marker
974
+ is used with no request and no notice, because a stale version is still the best
975
+ answer available and the list itself is advisory.
976
+
977
+ `/bytefly update` is the on-demand half: it ignores the window, asks Pi's package
978
+ manager now, records and paints the result, and prints one line — the package
979
+ list with this package's version and release URL when known, `up to date — no
980
+ configured package has an update`, `the update check was skipped (PI_OFFLINE is
981
+ set)`, or `could not check package updates`. It resolves no packages, probes no
982
+ extension, and posts no second notice, so a session open for days can see a
956
983
  release without a restart. Opening the control center never checks: the screen has
957
984
  no update surface, and the only request paths are `session_start` and this
958
985
  command.
@@ -964,9 +991,16 @@ command.
964
991
  rule, and transcript renderers stay on. Use `PI_DISABLED_EXTENSIONS=bytefly`
965
992
  (or `pi config`) to turn off all of bytefly.
966
993
  - **App settings are Global only.** Pi's setters for preferences such as
967
- `quietStartup` (the **Bytefly startup TUI** row), `outputPad`, and
968
- `showHardwareCursor` write the personal `settings.json`; the screen hides
969
- those rows in Project scope rather than writing a file Pi cannot manage.
994
+ `quietStartup` (the **Bytefly startup TUI** row), `outputPad`,
995
+ `showHardwareCursor`, and `theme` write the personal `settings.json`; the
996
+ screen hides those rows in Project scope rather than writing a file Pi cannot
997
+ manage.
998
+ - **The Theme row offers only this package's palettes.** `system`,
999
+ `bytefly-dark`, and `bytefly-light` are the whole cycle; Pi's built-ins and any
1000
+ user, project, or other package's theme stay in Pi's `/settings` → **Theme**,
1001
+ and a value set there shows as stored here. An automatic pair such as
1002
+ `bytefly-light/bytefly-dark` shows as stored but is replaced by the next single
1003
+ choice, and the change applies after a reload like the other app rows.
970
1004
  - **Project overrides of app settings are not shown.** App rows read the
971
1005
  personal file; a `.pi/settings.json` override still wins at runtime, and
972
1006
  `pi config` is the place to manage it.
@@ -986,19 +1020,22 @@ command.
986
1020
  their authoritative names are not exposed to extensions. Use `pi config`.
987
1021
  - **Packages are not installed, removed, or updated here**; use `pi install`,
988
1022
  `pi remove`, and `pi update`.
989
- - **The update check reads npm's `latest` only.** A private registry, a version
990
- published under another tag, or a checkout ahead of npm reports nothing, and
991
- there is no channel or prerelease preference: `0.11.4` is behind `0.12.0`
992
- whether or not a `0.12.0-beta.1` exists.
1023
+ - **The update list is Pi's.** Only packages configured in `settings.json` are
1024
+ checked; `pi -e .`, a temporary checkout, and other unmanaged loads get no
1025
+ update prompt. The version arrow and release URL still come from npm's
1026
+ `latest`, so a private registry or a version published under another tag can
1027
+ lose those two pieces while the list stays correct.
993
1028
  - **The cache window is fixed at 24 hours.** It runs at `session_start` in `tui`
994
- mode and reuses an entry younger than that; there is no setting for it, and
995
- `/bytefly` and `doctor` do not report the version. A failed or slow background
996
- request is retried by the next session, or immediately by `/bytefly update`.
1029
+ mode and reuses a snapshot younger than that whose key matches the cwd and
1030
+ package sources; there is no setting for it, and `/bytefly` and `doctor` do not
1031
+ report the version. A failed or slow background request is retried by the next
1032
+ session, or immediately by `/bytefly update`.
997
1033
  - **The marker needs the bytefly startup TUI.** It lives on the custom header,
998
1034
  so with `quietStartup` off the notice is the whole signal.
999
- - **Pi may report the same update.** An npm-installed copy can also show Pi's
1000
- own `Package Updates Available` block; the two checks are independent and
1001
- `pi update --extensions` satisfies both.
1035
+ - **Pi's block is suppressed by a host shim.** bytefly wraps
1036
+ `DefaultPackageManager.prototype.checkForAvailableUpdates` at `session_start`
1037
+ and restores it on shutdown. A future Pi that changes that method shape makes
1038
+ the shim inert, and Pi's own block can come back.
1002
1039
  - **No auto-update, and the release URL is text only.** The notice names a
1003
1040
  command instead of running one, and the notice renderer cannot open a link, so
1004
1041
  the release row prints the URL and nothing more — no changelog body, no way to
@@ -1105,15 +1142,15 @@ rather than showing a wrong value.
1105
1142
  | Notifications | `agent_settled` (skipped when aborted), `ui_prompt_start` only while `agent_start`…`agent_settled` marks a run active, and `my-pi-agent/job-finished` on `pi.events`; the `notify` section of `bytefly.json` gates each. Delivery writes an OSC 777/99 sequence to stdout or spawns `osascript`, `notify-send`, or `powershell.exe`. The bus subscription is created in `session_start` and released in `session_shutdown`, and the `job` extension never imports bytefly. |
1106
1143
  | Picker discovery | `SettingsManager.create(...)` plus `DefaultPackageManager.resolve({ onMissing: "skip" })`, both public exports; both views drop `bytefly`'s own entrypoint. |
1107
1144
  | Picker writes | `SettingsManager` setters (`setExtensionPaths`, `setPackages`, `setProjectExtensionPaths`, `setProjectPackages`) then `flush()`; `ctx.reload()` after the screen closes when changes are pending. |
1108
- | Settings writes | App rows call `SettingsManager` setters (`setQuietStartup`, `setOutputPad`, `setShowHardwareCursor`) on the global store and `flush()`, then surface `drainErrors()`; `bytefly.json` rows call `writeConfigValueAt` on the selected scope's document with the row's leaf path, so a nested key (`price.currency.decimals`) and a flat one (`notify.method`) share one writer, and `u` calls `unsetConfigValueAt` with the same path. A failed or refused write shows a notice in the screen's hint row and leaves the displayed value unchanged; a successful one marks the screen dirty. |
1145
+ | Settings writes | App rows call `SettingsManager` setters (`setQuietStartup`, `setOutputPad`, `setShowHardwareCursor`, `setTheme`) on the global store and `flush()`, then surface `drainErrors()`; `bytefly.json` rows call `writeConfigValuesAt` on the selected scope's document with the row's leaf path, so a nested key (`price.currency.decimals`) and a flat one (`notify.method`) share one writer, and a row that also writes a companion leaf (the currency and its default precision) applies both in one document pass; and `u` calls `unsetConfigValueAt` with the same path. A failed or refused write shows a notice in the screen's hint row and leaves the displayed value unchanged; a successful one marks the screen dirty. |
1109
1146
  | Doctor tool | `pi.registerTool("run_doctor")`, read-only (`readOnlyHint: true`, `openWorldHint: false`), direct exposure and active on registration (the defaults, so not declared), with a typed `outputSchema` mirroring `structuredContent`. Its `readOnlyHint` is what the shared default-deny policy in `lib/policy.ts` reads to allow it in plan mode. |
1110
1147
  | Doctor probes | `run_doctor` and the screen share the session cache created by `registerDoctor`; the screen mounts on that cache and probes on the first Extensions view (the command's `refresh` forces it), while the text modes probe before printing, and `/bytefly refresh` and `refresh: true` re-probe. Probes spawn `<binary> --version`, lazily import optional packages, and read config files through `lib/requirements.ts`; nothing is ever written. |
1111
1148
  | Status routing | Reads `footerData.getExtensionStatuses()` (a custom footer replaces the built-in one entirely); `worktree` routes to line 1, plan/alert statuses to line 2's trailing slot. |
1112
- | Update check | `session_start` in `tui` mode reads `<agentDir>/bytefly/update.json` and, when the entry is inside `UPDATE_CACHE_TTL_MS` (24 h), records it without a request; otherwise it fires `fetchLatestVersion()` without awaiting, so the handler never holds the prompt, and writes the answer back. The registry read is a fixed `GET` of npm's `latest` document over an injected `fetch`, bounded by an abort timeout; a strictly newer, parseable semver is recorded in the per-load `UpdateSource`, `onUpdate` re-installs the header, and `pi.sendMessage(updateNotice(...))` posts the one notice with its release URL. `PI_OFFLINE` uses the cache with no request and no write. `/bytefly update` calls the returned `UpdateHandle.recheck()`, which bypasses the window, records, paints, and returns an `UpdateOutcome`; a generation counter drops a background reply that lands after `session_shutdown`, and a per-load flag keeps it to one announcement. |
1149
+ | Update check | `session_start` in `tui` mode installs a suppression on `DefaultPackageManager.prototype.checkForAvailableUpdates`, then reads `<agentDir>/bytefly/update.json`. A fresh entry whose key matches the cwd and effective package sources is recorded without a package-manager call; otherwise bytefly fires `fetchAvailableUpdates()` without awaiting, resolves this package's version through `fetchLatestVersion()` when Pi reports it, writes the snapshot, and records it. `onUpdate` re-installs the header, and `pi.sendMessage(updateNotice(...))` posts the one notice with its version arrow and release URL. `PI_OFFLINE` paints the cached marker only. `/bytefly update` calls the returned `UpdateHandle.recheck()`, which bypasses the window and returns an `UpdateOutcome`; a generation counter drops a background reply that lands after `session_shutdown`, a per-load flag keeps it to one announcement, and shutdown restores Pi's method. |
1113
1150
  | Config | `bytefly.json` from the agent directory and `<cwd>/.pi`; the `price` section is read once per footer install (a new session or a session-tree change), the `notify` and `doctor` sections at `session_start`, and the `chrome` section's rail glyph at `session_start`/`session_tree`. Nothing watches the document. The update check has no section: it is on whenever `PI_OFFLINE` is off, and its 24 h window is not configurable. |
1114
1151
  | Data sources | `footerData.getGitBranch()`, `footerData.getAvailableProviderCount()`, `ctx.getContextUsage()`, `ctx.sessionManager.getEntries()`, `ctx.sessionManager.getSessionName()`, `ctx.model`; subscribes to `footerData.onBranchChange` to repaint. |
1115
- | State storage | None besides the inert separator entries. The footer reads session entries and the price config (a memoized scan) and writes no tool-result `details`; it appends `bytefly-separator` entries excluded from LLM context. The screen derives its view from the picker runtime and the current reports, and persists extension toggles through `settings.json`. The Settings section derives from a snapshot of the personal `settings.json` and both `bytefly.json` documents, and persists through the same `SettingsManager` setters or the per-leaf `bytefly.json` writer. Doctor's findings are session-scoped in memory only — cached for the session, never written to the session or a file. Notify holds only the config and the run-active flag in its closure. The update check holds one version in the per-load `UpdateSource` and writes no session entry; its one file is `<agentDir>/bytefly/update.json`, the cached `latest` plus `checkedAt`, written atomically and read back on the next session. |
1116
- | Lifecycle | `session_start` installs the header when Pi's `quietStartup` is `true` and the footer, loads the chrome rail glyph, points the doctor runtime at the session cwd, probes the extensions with hard requirements when `bytefly.json`'s `doctor.proactive` is on and `quietStartup` is `true`, loads the notify config and subscribes to the job bus, and starts the published-version check; `session_tree` reinstalls the header and footer and reloads the rail; `session_shutdown` restores Pi's built-in header and footer, disposes the branch subscription, drops doctor's cached findings, drops the notify bus subscription, and clears the recorded version. |
1152
+ | State storage | None besides the inert separator entries. The footer reads session entries and the price config (a memoized scan) and writes no tool-result `details`; it appends `bytefly-separator` entries excluded from LLM context. The screen derives its view from the picker runtime and the current reports, and persists extension toggles through `settings.json`. The Settings section derives from a snapshot of the personal `settings.json` and both `bytefly.json` documents, and persists through the same `SettingsManager` setters or the per-leaf `bytefly.json` writer. Doctor's findings are session-scoped in memory only — cached for the session, never written to the session or a file. Notify holds only the config and the run-active flag in its closure. The update check holds this package's version in the per-load `UpdateSource` and one project-scoped snapshot in `<agentDir>/bytefly/update.json`. |
1153
+ | Lifecycle | `session_start` installs the header when Pi's `quietStartup` is `true` and the footer, loads the chrome rail glyph, points the doctor runtime at the session cwd, probes the extensions with hard requirements when `bytefly.json`'s `doctor.proactive` is on and `quietStartup` is `true`, loads the notify config and subscribes to the job bus, and starts the package-update check; `session_tree` reinstalls the header and footer and reloads the rail; `session_shutdown` restores Pi's built-in header, footer, and package-update method, disposes the branch subscription, drops doctor's cached findings, drops the notify bus subscription, and clears the recorded version. |
1117
1154
 
1118
1155
  ## Design notes
1119
1156
 
@@ -1155,8 +1192,12 @@ rather than showing a wrong value.
1155
1192
  expand toggle, so no themed string outlives its theme. `indentBlockLines` only
1156
1193
  measures a line it cannot prove fits, and the header memoizes on the width,
1157
1194
  theme, shape, and the help chords it prints, so a live rebind still repaints.
1158
- The footer's context estimate joins its pricing scan in the leaf-keyed memo:
1159
- both are O(session) work the host recomputes from scratch.
1195
+ `SeparatorComponent` uses the same memo for its full-width rule, so a long
1196
+ session does not repaint every turn's rule each frame. The footer's context
1197
+ estimate and session name join its pricing scan in the leaf-keyed memo: all
1198
+ three are O(session) work the host recomputes from scratch, and
1199
+ `createRateLookup` compiles the rate table once per config instead of once per
1200
+ record.
1160
1201
  - **The frame delegates; it does not re-render.** Pi resolves tool renderers
1161
1202
  through the extension chain, and the resolved object is the definition the row
1162
1203
  reads, so a resolver can override `renderShell` and wrap `next()`'s
@@ -1190,7 +1231,8 @@ rather than showing a wrong value.
1190
1231
  themselves by that pad: the tool frame and the notice take it from Pi, and the
1191
1232
  turn separator reads the last value a sibling observed (`pad.ts`). Pi's setting
1192
1233
  is the only knob — bytefly adds no padding of its own. The footer's group
1193
- separator still draws `╎` between chips — a divider, not a rail.
1234
+ separator draws `│` between chips — a divider, not the rail (`╎`), which stays
1235
+ a transcript-only glyph.
1194
1236
  - **The header is a nameplate, not a dashboard.** Pi's built-in brand colors are
1195
1237
  fixed, so the mark ignores the active theme; bytefly's header draws a two-tone
1196
1238
  mark from the `accent`/`warn` roles, a signature rule whose accent lead sits
@@ -1211,7 +1253,7 @@ rather than showing a wrong value.
1211
1253
  dispatching process and the delegated work stays visible beside it.
1212
1254
  - **The gauge is anchored at column 0.** A leading mode slot would shift the
1213
1255
  context percentage left and right as plan mode came and went; trailing it
1214
- after a `╎` keeps the percentage in a fixed place while the mode stays
1256
+ after a `│` keeps the percentage in a fixed place while the mode stays
1215
1257
  visible as the meters drop.
1216
1258
  - **Statuses are routed, not repeated.** The built-in footer renders every
1217
1259
  `setStatus` value on its own line, so the bar sorts them by meaning instead:
@@ -1229,9 +1271,9 @@ rather than showing a wrong value.
1229
1271
  non-zero usage, so it reads full only at 100% and a small percentage never
1230
1272
  disappears; unknown usage draws no gauge at all. Only at extreme widths does
1231
1273
  `truncateToWidth` clip the remaining text.
1232
- - **Separators shrink to a single space.** Padded separators (` · `, ` ╎ `)
1274
+ - **Separators shrink to a single space.** Padded separators (` · `, ` │ `)
1233
1275
  collapse to one space before `truncateToWidth` applies, so adjacent segments
1234
- never collide and the ellipsis never replaces a `╎` with nothing after it.
1276
+ never collide and the ellipsis never replaces a `│` with nothing after it.
1235
1277
  - **Statuses compact with their colour.** A themed status's compact icon form
1236
1278
  reattaches its opening and closing SGR sequences around the visible text, so a
1237
1279
  red `✗1` stays red instead of losing its colour exactly when the bar tightens.
@@ -1335,21 +1377,27 @@ rather than showing a wrong value.
1335
1377
  - **The prerequisite table is data.** No I/O, no host access, no module state:
1336
1378
  every probe dependency arrives as a seam, so `probeExtension` is testable with
1337
1379
  no process and `lib/` stays value-only.
1338
- - **The update check is a self-report, not an update mechanism.** It answers one
1339
- question — is the version in this checkout current? — and answers it in the two
1340
- surfaces bytefly already owns, the header identity and one notice. It never
1341
- installs, never edits settings, and never fails a session: an unreachable
1342
- registry and an up-to-date one are indistinguishable to the user, which is the
1343
- only failure mode a background check is allowed to have.
1344
- - **The marker is an action, the notice is information.** The same fact is drawn
1345
- in the accent on the header, where it sits beside a control the user can act
1346
- on, and at `info` severity in the transcript, where a `warn` rail would say
1347
- something is broken. Neither surface needs per-frame polling: the value arrives
1348
- once, the memo key includes it, and one `setHeader` re-install repaints.
1380
+ - **Pi owns the update list; bytefly owns the look.** There is one detector, not
1381
+ two: `DefaultPackageManager` already resolves npm ranges, git refs, project
1382
+ scope, and offline mode, and bytefly only carries its result to a themed
1383
+ surface. The extra registry read only names this package's version for the
1384
+ marker, arrow, and release URL; it never decides whether an update exists.
1385
+ - **The suppression is a host shim, not a second checker.** Pi has no setting or
1386
+ extension event for its startup block, so bytefly replaces the public method
1387
+ that feeds it while keeping the captured original for its own read. It is
1388
+ installed only in `tui`, restored on shutdown, and made inert if the host
1389
+ changes shape.
1390
+ - **The marker is an action, the notice is information.** This package's new
1391
+ version is drawn in the accent on the header, where it sits beside a control
1392
+ the user can act on, and every package is listed at `info` severity in the
1393
+ transcript, where a `warn` rail would say something is broken. Neither surface
1394
+ needs per-frame polling: the value arrives once, the memo key includes it, and
1395
+ one `setHeader` re-install repaints.
1349
1396
  - **The transport stays local.** `web-access`'s runner does redirects, SSRF
1350
1397
  checks, HTML extraction, and caching — none of it applies to one fixed JSON
1351
1398
  endpoint, and an extension may not import another's modules, so the ~20-line
1352
- read lives beside the check with the `fetch` and timeout as seams.
1399
+ read lives beside the check with the `fetch` and timeout as seams. The package
1400
+ list itself is Pi's own command execution, not a second transport.
1353
1401
 
1354
1402
  ## Files
1355
1403
 
@@ -1359,8 +1407,8 @@ calculation), `message/` (the user-message marker), `notice/` (the shared notice
1359
1407
  run line on Pi's working row), `panel/` (the
1360
1408
  unified screen and its merged model),
1361
1409
  `picker/` (discovery and the settings store), `doctor/` (the `run_doctor` tool
1362
- and its probe runtime), `notify/`, and `update/` (the published-version check
1363
- behind the header marker, its disk cache, and the on-demand re-check). The chrome and the screen are drawn from
1410
+ and its probe runtime), `notify/`, and `update/` (Pi's package list, the host
1411
+ suppression, the cache, and the header marker). The chrome and the screen are drawn from
1364
1412
  the package's [`lib/tui/`](../../lib/tui/) design system (tokens, rules, rows,
1365
1413
  columns, gauge), so the look follows the active theme.
1366
1414
 
@@ -1376,10 +1424,10 @@ columns, gauge), so the look follows the active theme.
1376
1424
  | `footer/config.ts` | Bar tuning (`CONFIG`): thresholds, gauge, separators, icons, status keys, labels. |
1377
1425
  | `footer/types.ts` | `StatusSnapshot`, `Segment`, `LineSpec`, `UsageRecord`, and footer/TUI shapes. |
1378
1426
  | `footer/collect/entries.ts` | Session entries → usage records, each resolved to the model in effect. |
1379
- | `footer/collect/snapshot.ts` | Snapshot assembly; memoized pricing scan per branch leaf. |
1427
+ | `footer/collect/snapshot.ts` | Snapshot assembly; memoized pricing scan, context estimate, and session name per branch leaf. |
1380
1428
  | `footer/price/types.ts` | `Currency`, `ModelRates`, `PeakWindow`/`PeakSchedule`, `ByteflyConfig`. |
1381
1429
  | `footer/price/config.ts` | The `price` section of `bytefly.json`: schema, defaults, built-in DeepSeek rates/tariff, currency defaults, normalizer, loader. |
1382
- | `footer/price/rates.ts` | Model-key matching: exact and wildcard, most specific first. |
1430
+ | `footer/price/rates.ts` | Model-key matching: `createRateLookup` splits every key once, splits a key at its first slash (so an OpenRouter model id may contain one), matches exact and wildcard most-specific-first, and remembers the last record's model. |
1383
1431
  | `footer/price/schedule.ts` | Peak-window parsing and evaluation in a configured zone. |
1384
1432
  | `footer/price/price.ts` | Records → totals: rated cost in both modes, Pi's recorded cost as USD's fallback, and the peak multiplier. |
1385
1433
  | `footer/render/format.ts` | Generic formatters (tokens, cost in the configured currency, cwd, the context role, truncation). |
@@ -1400,7 +1448,7 @@ columns, gauge), so the look follows the active theme.
1400
1448
  | `block/register.ts` | The `registerToolRenderer` resolver that frames every tool; it recolours a shell call's string before the frame wraps it, rails the result body by status, and rails the call body for `edit`/`write` (the two whose call body carries the result). |
1401
1449
  | `separator/config.ts` | Turn-rule tuning: `customType`, glyph, label prefix, padding, receipt minimum duration and outcome wording, and roles. |
1402
1450
  | `separator/register.ts` | Registers the entry renderer, tracks the run's tools and failures, and appends the receipt on `agent_settled`. |
1403
- | `separator/component.ts` | `SeparatorComponent`: the stateless entry renderer. |
1451
+ | `separator/component.ts` | `SeparatorComponent`: the entry renderer, memoized on the width so a repeat frame does not rebuild the rule. |
1404
1452
  | `separator/format.ts` | Pure `receiptLabel` (the width ladder), `receiptRole`, and `separatorLine`, drawn by the kit's `centeredRule`. |
1405
1453
  | `separator/types.ts` | `TurnSeparatorData`: the turn plus the optional receipt fields. |
1406
1454
  | `working/config.ts` | Counter tuning (`WORKING_CONFIG`): the words, the tool singular/plural, and the tick interval. |
@@ -1411,7 +1459,8 @@ columns, gauge), so the look follows the active theme.
1411
1459
  | `panel/format.ts` | Non-TUI text: the extension headline and grouped listing with a per-row health suffix, plus the section segments, the Settings counts/headline, and the grouped Settings listing. |
1412
1460
  | `panel/settings.ts` | The curated Settings catalogue: descriptors, snapshot views and origins, value cycling, stored-value mapping, validation, and grouping, plus the per-section readers that resolve a row's leaf over the global and project documents. |
1413
1461
  | `panel/settings-runtime.ts` | The Settings runtime: snapshot cache, scope guards, app setters, per-leaf `bytefly.json` writes, dirty state, and change listeners. |
1414
- | `panel/tui.ts` | `PanelComponent`, the two-section dock screen (Extensions list and Settings list), drawn from the `lib/tui/` kit. |
1462
+ | `panel/themes.ts` | Which themes the Theme row offers: the host's resolved list narrowed to the palettes under this package's `themes/`, identified by directory rather than name. |
1463
+ | `panel/tui.ts` | `PanelComponent`, the two-section dock screen (Extensions list and Settings list) and its section composition; the editing key map is `tui/editing.ts` and the shared frame is `tui/frame.ts`. |
1415
1464
  | `panel/commands.ts` | `/bytefly`: one command for the screen sections, the `doctor` report, the settings listing, and their text modes. |
1416
1465
  | `picker/types.ts` | Picker host seams (`SettingsStore`, `ExtensionResolver`, `Discovery`), the app-settings setters, and row types. |
1417
1466
  | `picker/resources.ts` | Pure grouping, labels, display names, and pattern helpers. |
@@ -1429,12 +1478,14 @@ columns, gauge), so the look follows the active theme.
1429
1478
  | `notify/dispatch.ts` | Method selection and delivery: OSC 777/99, Windows toast, `osascript`, `notify-send`, over injected seams. |
1430
1479
  | `notify/format.ts` | Pure notification text: session label and the turn / input / job / watch bodies. |
1431
1480
  | `notify/register.ts` | Registers the four triggers, loads the config at `session_start`, and owns the job- and watch-bus subscriptions. |
1432
- | `update/check.ts` | The one registry read: the `latest` URL, the abort timeout, the `FetchLike` seam, and the failure-to-`undefined` mapping. |
1481
+ | `update/types.ts` | `PackageUpdate`, derived from Pi's public manager method, and `PackageUpdateCheck`. |
1482
+ | `update/suppress.ts` | The host shim: capture Pi's `checkForAvailableUpdates`, replace it with an empty result, and restore it on shutdown. |
1483
+ | `update/check.ts` | Pi's package list through `DefaultPackageManager`, plus the registry `GET` that names this package's version for the marker, arrow, and release URL. |
1433
1484
  | `update/version.ts` | Pure semver parsing and `isNewerVersion`, including prerelease precedence. |
1434
- | `update/state.ts` | `UpdateSource` and `createUpdateState` (the newest known version, one record per load), plus `UpdateOutcome`, the four answers an explicit re-check can give. |
1435
- | `update/cache.ts` | The disk cache: `<agentDir>/bytefly/update.json`, the 24 h window in `isFresh`, a validated read that degrades to a miss, and the atomic write. |
1436
- | `update/format.ts` | `updateMarker`, `updateNotice`, `releaseUrl`, and `updateOutcomeText`: the header's `↑0.12.0`, the transcript notice with its release row, and the one line `/bytefly update` prints. |
1437
- | `update/register.ts` | The lifecycle: the cache-first session-start read, the async registry call, the `PI_OFFLINE` and `tui` gates, the once-per-load announcement, the shutdown generation guard, and the `recheck()` handle `/bytefly update` calls. |
1485
+ | `update/state.ts` | `UpdateSource` and `createUpdateState` (this package's known version, one record per load), plus `UpdateOutcome`, the four answers an explicit re-check can give. |
1486
+ | `update/cache.ts` | The project-scoped snapshot: `<agentDir>/bytefly/update.json`, the cwd/package key, the 24 h window in `isFresh`, a validated read that degrades to a miss, and the atomic write. |
1487
+ | `update/format.ts` | `updateMarker`, `updateNotice`, `releaseUrl`, and `updateOutcomeText`: the header's `↑0.12.0`, the multi-package notice, and the one line `/bytefly update` prints. |
1488
+ | `update/register.ts` | The lifecycle: suppression, the cache-first session-start read, the async package-manager call, this package's version lookup, the `PI_OFFLINE` and `tui` gates, the once-per-load announcement, the shutdown generation guard, and the `recheck()` handle `/bytefly update` calls. |
1438
1489
 
1439
1490
  ## Testing
1440
1491
 
@@ -1467,7 +1518,8 @@ unified screen is covered by its pure model merge (`panel/join`), its text
1467
1518
  formatter (`panel/format`), the settings catalogue and its cycling
1468
1519
  (`panel/settings`), the scope-aware settings runtime (`panel/settings-runtime`,
1469
1520
  including a nested leaf write that keeps its siblings),
1470
- the screen's rendering and key handling (`panel/tui`), and the command through
1521
+ the screen's rendering (`panel/tui`), its editing key map (`tui/editing`),
1522
+ the shared frame (`tui/frame`), and the command through
1471
1523
  the bytefly factory (`panel/commands`).
1472
1524
  The doctor surface is covered by its pure report
1473
1525
  formatting (`doctor/format`), its config loader (`doctor/config`), and
@@ -1476,14 +1528,15 @@ through the factory (`doctor/register`). The notify capability is covered by its
1476
1528
  (`notify/config`), the pure text builders (`notify/format`), method selection and
1477
1529
  delivery over recording seams (`notify/dispatch`), and its four triggers,
1478
1530
  reload, and shutdown through `registerNotify` (`notify/register`). The update
1479
- check is covered by the version-ordering table (`update/version`), the transport
1480
- with an injected fetch (`update/check`), the per-load store (`update/state`), the
1481
- marker, notice, and release-URL wording (`update/format`), the cache read, write,
1482
- and window (`update/cache`), and its lifecycle — a fresh entry reused without a
1483
- request, a stale one re-read and rewritten, silent on failure or an older version,
1484
- cache-only offline, skipped outside `tui`, an explicit `recheck()` that ignores
1485
- the window, and immunity to a reply that lands after shutdown
1486
- (`update/register`).
1531
+ surface is covered by the version-ordering table (`update/version`), Pi's package
1532
+ list and the injected registry read (`update/check`), the host shim and its
1533
+ idempotent restore (`update/suppress`), the per-load store (`update/state`), the
1534
+ marker, multi-package notice, release URL, and recheck wording (`update/format`),
1535
+ the project-scoped snapshot and its window (`update/cache`), and its lifecycle —
1536
+ a fresh matching entry reused without a package-manager call, a stale one re-read
1537
+ and rewritten, silent on failure or an empty list, cache-only offline, skipped
1538
+ outside `tui`, an explicit `recheck()` that ignores the window, and immunity to a
1539
+ reply that lands after shutdown (`update/register`).
1487
1540
  The user-message nameplate is covered by its pure transform
1488
1541
  (`message/format`, including a render through pi-tui's real `Markdown`) and its
1489
1542
  registration (`message/register`), plus the factory check in `extension`.