@gavin-hu/my-pi-agent 0.13.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +66 -41
  2. package/README.zh-CN.md +53 -36
  3. package/extensions/bytefly/README.md +321 -186
  4. package/extensions/bytefly/block/component.ts +7 -15
  5. package/extensions/bytefly/block/register.ts +6 -1
  6. package/extensions/bytefly/doctor/config.ts +4 -1
  7. package/extensions/bytefly/doctor/register.ts +24 -5
  8. package/extensions/bytefly/doctor/tools.ts +4 -0
  9. package/extensions/bytefly/footer/collect/snapshot.ts +6 -1
  10. package/extensions/bytefly/footer/config.ts +10 -6
  11. package/extensions/bytefly/footer/price/config.ts +3 -2
  12. package/extensions/bytefly/footer/price/price.ts +5 -3
  13. package/extensions/bytefly/footer/price/rates.ts +63 -26
  14. package/extensions/bytefly/footer/render/status.ts +3 -3
  15. package/extensions/bytefly/header/component.ts +33 -10
  16. package/extensions/bytefly/header/config.ts +22 -0
  17. package/extensions/bytefly/header/register.ts +2 -1
  18. package/extensions/bytefly/index.ts +6 -6
  19. package/extensions/bytefly/notice/component.ts +8 -3
  20. package/extensions/bytefly/notify/config.ts +7 -12
  21. package/extensions/bytefly/notify/dispatch.ts +13 -5
  22. package/extensions/bytefly/panel/commands.ts +22 -2
  23. package/extensions/bytefly/panel/format.ts +17 -21
  24. package/extensions/bytefly/panel/settings-runtime.ts +86 -22
  25. package/extensions/bytefly/panel/settings.ts +279 -46
  26. package/extensions/bytefly/panel/themes.ts +32 -0
  27. package/extensions/bytefly/panel/tui/editing.ts +77 -0
  28. package/extensions/bytefly/panel/tui/entries.ts +24 -0
  29. package/extensions/bytefly/panel/tui/extensions-view.ts +13 -8
  30. package/extensions/bytefly/panel/tui/frame.ts +121 -4
  31. package/extensions/bytefly/panel/tui/settings-view.ts +98 -13
  32. package/extensions/bytefly/panel/tui.ts +176 -166
  33. package/extensions/bytefly/picker/discovery.ts +1 -0
  34. package/extensions/bytefly/picker/types.ts +2 -0
  35. package/extensions/bytefly/separator/component.ts +17 -6
  36. package/extensions/bytefly/separator/config.ts +15 -0
  37. package/extensions/bytefly/separator/format.ts +60 -6
  38. package/extensions/bytefly/separator/register.ts +68 -9
  39. package/extensions/bytefly/separator/types.ts +8 -0
  40. package/extensions/bytefly/update/cache.ts +58 -19
  41. package/extensions/bytefly/update/check.ts +35 -0
  42. package/extensions/bytefly/update/format.ts +76 -35
  43. package/extensions/bytefly/update/register.ts +144 -55
  44. package/extensions/bytefly/update/state.ts +4 -2
  45. package/extensions/bytefly/update/suppress.ts +66 -0
  46. package/extensions/bytefly/update/types.ts +15 -0
  47. package/extensions/bytefly/working/config.ts +4 -5
  48. package/extensions/bytefly/working/format.ts +24 -7
  49. package/extensions/bytefly/working/register.ts +34 -3
  50. package/extensions/check/README.md +253 -0
  51. package/extensions/check/commands.ts +128 -0
  52. package/extensions/check/config.ts +39 -0
  53. package/extensions/check/detect.ts +269 -0
  54. package/extensions/check/failures.ts +230 -0
  55. package/extensions/check/format.ts +135 -0
  56. package/extensions/check/index.ts +26 -0
  57. package/extensions/check/render.ts +185 -0
  58. package/extensions/check/runner.ts +176 -0
  59. package/extensions/check/runtime.ts +244 -0
  60. package/extensions/check/source.ts +80 -0
  61. package/extensions/check/tools.ts +173 -0
  62. package/extensions/check/tui.ts +195 -0
  63. package/extensions/check/types.ts +99 -0
  64. package/extensions/document/README.md +1 -1
  65. package/extensions/document/extract/pdf.ts +8 -16
  66. package/extensions/document/formats.ts +2 -2
  67. package/extensions/file-browse/README.md +66 -32
  68. package/extensions/file-browse/client.ts +95 -32
  69. package/extensions/file-browse/config.ts +9 -9
  70. package/extensions/file-browse/git.ts +37 -47
  71. package/extensions/file-browse/html.ts +39 -58
  72. package/extensions/file-browse/markdown.ts +110 -92
  73. package/extensions/file-browse/raw-html.ts +478 -0
  74. package/extensions/file-browse/router.ts +32 -70
  75. package/extensions/file-browse/server.ts +18 -56
  76. package/extensions/file-browse/urls.ts +80 -0
  77. package/extensions/goal/README.md +103 -47
  78. package/extensions/goal/clarify-parse.ts +4 -9
  79. package/extensions/goal/clarify.ts +27 -4
  80. package/extensions/goal/commands.ts +24 -11
  81. package/extensions/goal/format.ts +41 -10
  82. package/extensions/goal/index.ts +39 -16
  83. package/extensions/goal/plan-route.ts +51 -0
  84. package/extensions/goal/schema.ts +6 -23
  85. package/extensions/goal/state.ts +39 -57
  86. package/extensions/goal/tools.ts +26 -20
  87. package/extensions/goal/types.ts +11 -1
  88. package/extensions/job/README.md +20 -6
  89. package/extensions/job/format.ts +18 -68
  90. package/extensions/job/index.ts +26 -41
  91. package/extensions/job/log-writer.ts +208 -0
  92. package/extensions/job/logs.ts +3 -2
  93. package/extensions/job/process.ts +6 -99
  94. package/extensions/job/runtime.ts +42 -181
  95. package/extensions/job/schema.ts +1 -4
  96. package/extensions/job/tools.ts +1 -15
  97. package/extensions/job/tui.ts +117 -307
  98. package/extensions/job/ui.ts +7 -33
  99. package/extensions/memory/README.md +3 -1
  100. package/extensions/memory/errors.ts +5 -6
  101. package/extensions/memory/tui.ts +14 -24
  102. package/extensions/plan/README.md +106 -18
  103. package/extensions/plan/commands.ts +9 -5
  104. package/extensions/plan/format.ts +87 -0
  105. package/extensions/plan/index.ts +31 -15
  106. package/extensions/plan/plans.ts +2 -3
  107. package/extensions/plan/policy.ts +0 -6
  108. package/extensions/plan/runtime.ts +4 -2
  109. package/extensions/plan/steps.ts +37 -8
  110. package/extensions/plan/todos.ts +44 -3
  111. package/extensions/plan/tools/enter.ts +94 -0
  112. package/extensions/plan/tools/exit.ts +296 -0
  113. package/extensions/plan/tools/nested.ts +21 -0
  114. package/extensions/plan/tools/steps.ts +136 -0
  115. package/extensions/plan/tools/todo-seed.ts +76 -0
  116. package/extensions/plan/tools/write.ts +127 -0
  117. package/extensions/plan/tools.ts +18 -703
  118. package/extensions/plan/web-client.ts +6 -22
  119. package/extensions/plan/web-page.ts +19 -30
  120. package/extensions/plan/web-router.ts +34 -71
  121. package/extensions/plan/web.ts +13 -40
  122. package/extensions/question/answers.ts +3 -8
  123. package/extensions/question/tui.ts +6 -6
  124. package/extensions/rewind/README.md +95 -22
  125. package/extensions/rewind/checklist.ts +185 -0
  126. package/extensions/rewind/commands.ts +8 -0
  127. package/extensions/rewind/config.ts +3 -11
  128. package/extensions/rewind/format.ts +220 -2
  129. package/extensions/rewind/git.ts +74 -7
  130. package/extensions/rewind/hunk-picker.ts +114 -0
  131. package/extensions/rewind/patch.ts +88 -0
  132. package/extensions/rewind/path-picker.ts +67 -100
  133. package/extensions/rewind/paths.ts +93 -19
  134. package/extensions/rewind/restore.ts +53 -10
  135. package/extensions/rewind/review-tui.ts +320 -0
  136. package/extensions/rewind/review.ts +397 -0
  137. package/extensions/rewind/rewind-tui.ts +4 -3
  138. package/extensions/rewind/runtime.ts +94 -17
  139. package/extensions/subagent/README.md +118 -8
  140. package/extensions/subagent/background-tui.ts +208 -0
  141. package/extensions/subagent/background.ts +470 -0
  142. package/extensions/subagent/commands.ts +80 -0
  143. package/extensions/subagent/config.ts +62 -0
  144. package/extensions/subagent/format.ts +24 -1
  145. package/extensions/subagent/index.ts +116 -1
  146. package/extensions/subagent/orchestrate.ts +16 -3
  147. package/extensions/subagent/render.ts +25 -0
  148. package/extensions/subagent/run.ts +113 -49
  149. package/extensions/subagent/schema.ts +12 -0
  150. package/extensions/subagent/transcripts.ts +202 -0
  151. package/extensions/subagent/types.ts +23 -0
  152. package/extensions/subagent/ui.ts +50 -0
  153. package/extensions/todo/README.md +18 -13
  154. package/extensions/todo/format.ts +3 -2
  155. package/extensions/todo/runtime.ts +3 -2
  156. package/extensions/todo/schema.ts +8 -84
  157. package/extensions/todo/state.ts +5 -46
  158. package/extensions/todo/tools.ts +2 -1
  159. package/extensions/todo/types.ts +6 -15
  160. package/extensions/watch/README.md +16 -6
  161. package/extensions/watch/child.ts +150 -0
  162. package/extensions/watch/format.ts +8 -48
  163. package/extensions/watch/index.ts +42 -58
  164. package/extensions/watch/runtime.ts +49 -128
  165. package/extensions/watch/schema.ts +1 -4
  166. package/extensions/watch/tools.ts +1 -11
  167. package/extensions/watch/tui.ts +103 -273
  168. package/extensions/watch/ui.ts +5 -32
  169. package/extensions/web-access/README.md +1 -1
  170. package/extensions/web-access/fetch/page.ts +1 -1
  171. package/extensions/web-access/fetch/tool.ts +6 -2
  172. package/extensions/web-access/search/search.ts +2 -5
  173. package/extensions/wechat/README.md +3 -1
  174. package/extensions/wechat/client.ts +23 -0
  175. package/extensions/wechat/commands.ts +57 -42
  176. package/extensions/wechat/deliver.ts +5 -9
  177. package/extensions/wechat/errors.ts +3 -6
  178. package/extensions/wechat/inbound.ts +2 -5
  179. package/extensions/wechat/index.ts +2 -8
  180. package/extensions/wechat/outbound.ts +2 -4
  181. package/extensions/wechat/peer-worker.ts +3 -8
  182. package/extensions/wechat/poll.ts +10 -9
  183. package/extensions/wechat/presence.ts +35 -62
  184. package/extensions/wechat/reply-pipeline.ts +3 -1
  185. package/extensions/wechat/status-screen.ts +1 -1
  186. package/extensions/wechat/timer.ts +57 -0
  187. package/extensions/worktree/config.ts +8 -20
  188. package/extensions/worktree/enter.ts +7 -12
  189. package/extensions/worktree/git.ts +38 -69
  190. package/extensions/worktree/index.ts +5 -4
  191. package/extensions/worktree/registry.ts +7 -7
  192. package/extensions/worktree/root-tools.ts +2 -2
  193. package/extensions/worktree/status.ts +3 -2
  194. package/lib/README.md +22 -8
  195. package/lib/config.ts +83 -12
  196. package/lib/errors.ts +12 -0
  197. package/lib/format.ts +56 -0
  198. package/lib/git/read.ts +8 -0
  199. package/lib/goal-state.ts +23 -8
  200. package/lib/http/headers.ts +54 -0
  201. package/lib/http/index.ts +13 -0
  202. package/lib/http/response.ts +46 -0
  203. package/lib/http/router.ts +52 -0
  204. package/lib/http/server.ts +91 -0
  205. package/lib/lines.ts +77 -0
  206. package/lib/pdf.ts +82 -0
  207. package/lib/plan-entry.ts +71 -0
  208. package/lib/plan-state.ts +34 -0
  209. package/lib/report.ts +99 -0
  210. package/lib/requirements.ts +25 -19
  211. package/lib/spawn.ts +170 -0
  212. package/lib/todo-state.ts +110 -23
  213. package/lib/tool-names.ts +7 -0
  214. package/lib/tool-result.ts +33 -0
  215. package/lib/tui/README.md +68 -3
  216. package/lib/tui/chrome.ts +28 -4
  217. package/lib/tui/columns.ts +6 -1
  218. package/lib/tui/confirm.ts +65 -0
  219. package/lib/tui/dock-screen.ts +410 -0
  220. package/lib/tui/glyphs.ts +2 -1
  221. package/lib/tui/indent.ts +1 -1
  222. package/lib/tui/index.ts +2 -0
  223. package/lib/tui/rows.ts +21 -0
  224. package/lib/tui/status-controller.ts +35 -1
  225. package/lib/tui/tool.ts +59 -1
  226. package/lib/web/components.ts +20 -1
  227. package/lib/web/markup.ts +32 -0
  228. package/lib/web/runtime.ts +50 -0
  229. package/package.json +23 -5
  230. package/skills/init/SKILL.md +165 -0
  231. package/skills/init/references/agents-md-quality.md +103 -0
  232. package/skills/init/references/interview-questions.md +81 -0
  233. package/skills/refactor/SKILL.md +141 -0
  234. package/themes/bytefly-dark.json +2 -3
  235. package/themes/bytefly-light.json +2 -3
  236. package/extensions/watch/process.ts +0 -63
  237. package/extensions/web-access/fetch/pdf.ts +0 -45
@@ -3,13 +3,14 @@
3
3
  `bytefly` owns the terminal chrome, configuration, and diagnostics surfaces
4
4
  this package adds: one themed startup header whose two-tone nameplate carries
5
5
  the agent and chrome identities, one compact, colorful, width-adaptive two-line
6
- footer, a two-row `You` nameplate that opens each user message, a labeled rule that
7
- marks each completed turn in the transcript, one keyboard-driven screen with two
6
+ footer, a two-row `You` nameplate that opens each user message, a receipt rule that
7
+ records each completed turn in the transcript, one keyboard-driven screen with two
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
@@ -33,8 +34,8 @@ How the `run_doctor` call and its result look in the transcript — the call lin
33
34
 
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
- shared transcript notices, the tool-block frame, the live working counter, the
37
- two-section control center, notifications, and the published-version check.
37
+ shared transcript notices, the tool-block frame, the live working line, the
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
@@ -59,10 +60,15 @@ that cannot render half blocks the mark falls back to a two-tone `Pi` wordmark.
59
60
 
60
61
  As the terminal narrows, the identity sheds the update marker, then the chrome
61
62
  version, then the chrome line, then the agent name, and the help rows drop whole
62
- keys, so nothing wraps or clips mid-word. The nameplate sits on the transcript margin, which follows Pi's
63
- `outputPad` (the value the tool frame and the notice use too), and the help hangs
64
- one body indent inside it. The mark, tokens, identity, and grouped help are tuned
65
- in [`header/config.ts`](./header/config.ts).
63
+ keys, so nothing wraps or clips mid-word. Height sheds the same way: at 28 rows
64
+ all three help groups print, below 28 the middle group (the expand and model
65
+ chords) goes first, below 22 only the `/` commands row survives, and below 16 the
66
+ help block and the leading blank row go, leaving the nameplate and its rule. An
67
+ unknown height keeps the full header. The nameplate sits on the transcript
68
+ margin, which follows Pi's `outputPad` (the value the tool frame and the notice
69
+ use too), and the help hangs one body indent inside it. The mark, tokens,
70
+ identity, and grouped help are tuned in
71
+ [`header/config.ts`](./header/config.ts).
66
72
 
67
73
  **Footer.** Two lines, each with a left and right zone:
68
74
 
@@ -70,7 +76,7 @@ in [`header/config.ts`](./header/config.ts).
70
76
  (`⎇ branch · ⑂ worktree`) and the `◉` browse chip on the right.
71
77
  - **Line 2 — resources:** the context gauge and usage meters on the left, the
72
78
  child-run roll-up (`↳ 3 · ¥0.12`, present only once a subagent has finished),
73
- 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
74
80
  and thinking level on the right. The gauge is anchored at column 0, so `%` is
75
81
  always in the same place.
76
82
  The roll-up is its own number, never folded into the session's cost: a subagent
@@ -81,7 +87,7 @@ in [`header/config.ts`](./header/config.ts).
81
87
 
82
88
  ```
83
89
  ~/repo/my-pi-agent ⎇ main · ⑂ smoke
84
- ▰▰▰▰▰▰▱▱▱▱ 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
85
91
  ```
86
92
 
87
93
  (The examples show the default `¥` symbol. A model that neither `rates` nor the
@@ -89,7 +95,7 @@ built-in table covers contributes nothing in `CNY` mode, so the bar shows no
89
95
  figure rather than a guessed one; `USD` mode adds Pi's recorded dollars for that
90
96
  record instead — see the currency note under [Configuration](#configuration).)
91
97
 
92
- 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:
93
99
 
94
100
  ```
95
101
  ~/repo/my-pi-agent ⎇ main · ⑂ smoke
@@ -102,19 +108,19 @@ fits, rather than collapsing to a bare glyph:
102
108
 
103
109
  ```
104
110
  ~/repo/project ⎇ main · ⑂ smoke
105
- ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 ╎ ⋮ plan opus-4.5 · high
111
+ ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 │ ≡ plan opus-4.5 · high
106
112
  ```
107
113
 
108
114
  ```
109
115
  project ⎇ main
110
- 62% ⋮ opus-4.5
116
+ 62% ≡ opus-4.5
111
117
  ```
112
118
 
113
119
  With a named session, the name follows the path and truncates before it drops:
114
120
 
115
121
  ```
116
122
  ~/repo/project · feature-refac… ⎇ main
117
- ▰▰▰▰▰▰▱▱▱▱ 62% ╎ ⋮ plan opus-4.5
123
+ ▰▰▰▰▰▰▱▱▱▱ 62% │ ≡ plan opus-4.5
118
124
  ```
119
125
 
120
126
  Zero-value meters are omitted, so a fresh session shows only `?/window` instead
@@ -168,26 +174,36 @@ as it would without it, and a soft wrap of one long line stays at the margin
168
174
  because the renderer wraps it, not bytefly. The label and the rule are tuned
169
175
  in [`message/config.ts`](./message/config.ts).
170
176
 
171
- **Working timer.** Pi's working row shows a static `Working`, so a long run
177
+ **Working line.** Pi's working row shows a static `Working`, so a long run
172
178
  reads the same as a short one. While the row is up, bytefly advances its
173
- message once a second and hands it back to Pi when the row comes down:
179
+ message once a second and hands it back to Pi when the row comes down. Before
180
+ the first tool starts it is a count:
174
181
 
175
182
  ```
176
183
  Working… 12s
177
184
  ```
178
185
 
179
- The count is tied to the row's visibility: it starts on `turn_start`, the event
180
- the host shows the working indicator on, and stops on `agent_end`, where the
181
- host clears it, then again on `session_shutdown`. Turns inside one run do not
182
- restart it — the host keeps the same row up across turns — so the count covers
183
- the whole run, tool calls included. A retry is a new run, so the count restarts
184
- when its row reappears. The word and the interval are tuned in
185
- [`working/config.ts`](./working/config.ts).
186
+ Once a top-level tool starts, the row becomes a live run line: the tool, how
187
+ many top-level tools this run has started, and the elapsed time.
188
+
189
+ ```
190
+ edit · 3 tools · 12s
191
+ ```
192
+
193
+ A nested tool call (one carrying `parentToolCallId`, such as a codemode script's
194
+ inner call) neither counts nor takes over the line, so the number matches the
195
+ tool blocks in the transcript. The line keeps the last tool name while the
196
+ model streams after it, and a tool event repaints the row immediately instead
197
+ of waiting for the next tick. The line covers the whole run, tool calls
198
+ included. It is tied to the row's visibility: it starts on `turn_start`, stops
199
+ on `agent_end`, and is reset on `session_shutdown`; turns inside one run do not
200
+ restart it, and a retry starts a new run. The words and the interval are tuned
201
+ in [`working/config.ts`](./working/config.ts).
186
202
 
187
203
  **Turn separator.** After each completed turn the extension appends an inert
188
- entry and draws a centered `╌╌╌ turn N ╌╌╌` rule across the transcript; see
189
- [Turn separator](#turn-separator). No config file is needed and there is no
190
- command.
204
+ entry and draws a centered receipt rule — `╌╌╌ turn N · duration · N tools ╌╌╌`
205
+ — across the transcript; see [Turn separator](#turn-separator). No config file is
206
+ needed and there is no command.
191
207
 
192
208
  **Notices.** Any extension in the package can post a transcript notice under the
193
209
  shared `my-pi-agent/notice` type; bytefly draws it at the transcript's margin —
@@ -219,6 +235,15 @@ are tuned in [`notice/config.ts`](./notice/config.ts). Every field is sanitized
219
235
  and clipped at the boundary, and a message without `details` keeps Pi's default
220
236
  custom-message box.
221
237
 
238
+ **Compaction.** `/compact` is Pi's own surface: its summary block is a built-in
239
+ message component, so bytefly cannot replace, hide, or shorten it. The shipped
240
+ themes only remove its fill — `customMessageBg` is the terminal default — and
241
+ paint the `[compaction]` label in the accent, so the block sits on the canvas
242
+ like the rest of the transcript. Pi's branch summaries and skill invocations
243
+ share the same tokens.
244
+
245
+ <img src="../../assets/compaction.png" alt="The /compact summary block: a [compaction] label, the compacted token count, and the summary Markdown on the terminal canvas, with no background band" width="100%">
246
+
222
247
  **Tool blocks.** Every tool block leads with the tool's own call line as the
223
248
  headline — the tool name in the theme's tool-title colour — and its result body
224
249
  sits two columns in directly beneath it, led by the transcript rail, instead of
@@ -294,15 +319,17 @@ method follows the terminal (OSC 777, OSC 99, a Windows toast) or falls back to
294
319
  the platform notifier (`osascript`, `notify-send`); see
295
320
  [Notifications](#notifications).
296
321
 
297
- **Update check.** In the background at an interactive session's start, the loaded
298
- version is compared with the published `@gavin-hu/my-pi-agent` on npm. A newer
299
- one puts `↑0.12.0` after the header's chrome version and posts one transcript
300
- notice naming `pi update --extensions` and its release URL. The answer is cached
301
- in `<agentDir>/bytefly/update.json` with the time it was read, so a session
302
- starting inside the 24-hour window reuses it instead of repeating the request,
303
- and `/bytefly update` re-runs the check on demand. The check is skipped when
304
- `PI_OFFLINE` is set, makes no request outside `tui`, and a failed background
305
- 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).
306
333
 
307
334
  ## Commands
308
335
 
@@ -311,7 +338,7 @@ request is silent; see [Update check](#update-check).
311
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. |
312
339
  | `/bytefly settings` | Open the control center on Settings. Outside `tui` it prints the grouped settings listing with each row's value. |
313
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. |
314
- | `/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. |
315
342
 
316
343
  Tab completes the subcommands, extension names after `doctor`, and `refresh`;
317
344
  `/bytefly settings` and `/bytefly update` take no name.
@@ -348,10 +375,10 @@ identity tokens, tagline, grouped help), the footer in
348
375
  [`footer/config.ts`](./footer/config.ts) (thresholds, gauge glyphs and widths,
349
376
  separators, icons, status keys), and the turn separator in
350
377
  [`separator/config.ts`](./separator/config.ts) (glyph, label prefix, padding,
351
- colors).
378
+ minimum duration, outcome wording, colors).
352
379
 
353
- Every key of every section is listed with its type, default, and bounds in the
354
- [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
355
382
  covers what that table cannot — when each section is read, and what the cost
356
383
  does when no rate matches.
357
384
 
@@ -448,7 +475,10 @@ it with its own rates for the models they match:
448
475
  - **Which rate.** The most specific key wins, with the model segment outranking
449
476
  the provider one: `provider/model`, then `*/model`, then `provider/*`, then
450
477
  `*`. A segment ending in a star is a prefix match (`*/gpt-*`); equally
451
- 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`.
452
482
  - **Which formula.** `tokens × rate ÷ 1,000,000`, summed over the four token
453
483
  classes. Cache tokens are never billed again as fresh input.
454
484
  - **Which tariff.** Each record is dated by its session entry, so a window is
@@ -495,9 +525,9 @@ The worktree status is read from the `worktree` status key, so the
495
525
  from the `browse` status key published by [file-browse](../file-browse/) while
496
526
  its server runs, and shows the two-tone `◉ <port>` (green glyph, accent port).
497
527
  The trailing slot shows plan-style alerts when present and is omitted otherwise;
498
- 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`
499
529
  even though the built-in footer keeps the name. An alert (a failed job) sorts to
500
- 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
501
531
  gauge always starts the line. It keeps each extension's own colour when it
502
532
  compacts, and it is droppable: a tight bar drops the slot rather than truncating
503
533
  the model, which sits at the end of the line.
@@ -507,32 +537,46 @@ the model, which sits at the end of the line.
507
537
  `agent_settled` fires once after an agent run fully settles — exactly one user
508
538
  turn — which is why it is used instead of `turn_end` (that fires once per
509
539
  assistant message, so tool-calling rounds would each get a line). On each
510
- settled run the extension appends an inert custom entry and a renderer draws
511
- it:
540
+ settled run the extension appends an inert custom entry and a renderer draws it
541
+ as a receipt: the turn number, then the run's duration, top-level tool count,
542
+ and outcome as the width allows.
512
543
 
513
544
  ```
514
- ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ turn 3 ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
545
+ ╌╌╌╌╌╌╌╌╌╌╌╌ turn 3 · 1m12s · 7 tools ╌╌╌╌╌╌╌╌╌╌╌╌
515
546
  ```
516
547
 
517
548
  - The turn number is `count(separators on the current branch) + 1`, so a
518
549
  resumed or forked session continues the parent's numbering and abandoned
519
550
  branches do not leak counts.
551
+ - The receipt opens on the run's first `agent_start` and closes on
552
+ `agent_settled`, so a retry stays inside one duration. It counts only
553
+ top-level tool calls; a nested call (one carrying `parentToolCallId`) is an
554
+ implementation detail of its parent and does not inflate the number.
555
+ - The outcome is part of the receipt: a failed tool adds `✕N`, and an aborted
556
+ run adds `aborted` instead of the failure count. A sub-second run prints no
557
+ duration, so quick turns stay quiet. Entries written before the receipt
558
+ existed have only a turn number and still render as `turn N`.
559
+ - The label sheds fields rather than overflowing: the tool count first, then
560
+ the duration, then the outcome. When even the turn number does not fit, the
561
+ line becomes a plain dashed rule instead of clipping.
562
+ - A clean turn paints the label `meta`, a turn with a failed tool paints it
563
+ `bad`, and an aborted turn paints it `warn`.
520
564
  - The entry is excluded from LLM context, so it costs no tokens, and it
521
565
  survives resume and fork. It is drawn under the `bytefly-separator` custom
522
566
  entry type.
523
- - The line is centered and width-adaptive: the label is filled out with the
524
- glyph, and when it cannot fit the line becomes a plain dashed rule rather
525
- than overflowing.
526
567
  - The rule is installed only in interactive (`tui`) sessions; RPC, JSON, and
527
568
  print sessions append nothing.
528
569
  - Separators recorded by the retired `turn-separator` extension are not
529
570
  rendered, and `PI_DISABLED_EXTENSIONS=turn-separator` no longer affects the
530
571
  rule; disable the whole extension with `bytefly` instead.
531
- - There is no runtime command; the glyph, label prefix, padding, and colors are
532
- tuned in [`separator/config.ts`](./separator/config.ts).
572
+ - There is no runtime command; the glyph, label prefix, padding, colors, and
573
+ minimum duration are tuned in
574
+ [`separator/config.ts`](./separator/config.ts).
533
575
 
534
576
  ## The control center
535
577
 
578
+ <img src="../../assets/bytefly.png" alt="The /bytefly control center: the Extensions and Settings section bar with the `Global · 10 extensions · 2 off · 1 missing` counts, one checkbox row per extension with its verdict, the focused extension's report, and the key hints" width="100%">
579
+
536
580
  `/bytefly [extension] [refresh]` opens the control center on **Extensions**: a
537
581
  keyboard-driven dock list of every extension Pi resolves for the session, so the
538
582
  list matches what actually loads. `←`/`→` switch to **Settings**, where the same
@@ -544,7 +588,7 @@ when the row is switched off in settings, `env disabled` when
544
588
  glyph and summary. The screen is drawn from the design system: a label-first
545
589
  `bytefly ────` title rule,
546
590
  the `Extensions · Settings` section bar beside the section's counts, one row
547
- grammar (`❯ ◼ ✓ name … summary`), and the shared `◼` / `◻` checkbox.
591
+ grammar (`❯ ◼ name … summary`), and the shared `◼` / `◻` checkbox.
548
592
  `/bytefly doctor [extension] [refresh]` selects the doctor report, the same text
549
593
  the `run_doctor` tool returns; in `tui` it opens the same screen. `[extension]`
550
594
  narrows the Extensions section to one extension and `refresh` forces the first
@@ -568,12 +612,13 @@ installs or fetches a package.
568
612
  manages the list is never a selectable row; its doctor report still appears in
569
613
  the trailing `Not resolved` group.
570
614
  - Shows whether each one is enabled (`◼`) or disabled (`◻`) in settings.
571
- - Gives each row one verdict from the doctor's report: the worst severity (`✓`
572
- ok, `!` degraded, `✕` missing) and its summary. A row that is `◻` reads `off`
573
- 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
574
618
  `PI_DISABLED_EXTENSIONS` excludes reads `env disabled`, so the two axes cannot
575
- contradict each other. An extension with no doctor report leaves the glyph
576
- 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.
577
622
  - Toggles the focused extension with `Space`, writing `+pattern` / `-pattern`
578
623
  into `extensions` (and package filter objects) in the active scope. A row in
579
624
  the trailing `Not resolved` group has no pattern to write, so `Space` says so
@@ -597,11 +642,11 @@ installs or fetches a package.
597
642
  without leaving the session.
598
643
 
599
644
  **Settings.** The second section uses the same frame and the same `Tab` scope
600
- switch, with its own cursor. It lists sixteen preferences in six groups:
645
+ switch, with its own cursor. It lists twenty-nine preferences in ten groups:
601
646
 
602
647
  ```
603
648
  bytefly ──────────────────────────────────────────────────────────────
604
- Extensions · Settings Global · 16 settings
649
+ Extensions · Settings Global · 29 settings
605
650
 
606
651
  Startup (1)
607
652
  ❯ ◻ Bytefly startup TUI Off
@@ -620,16 +665,33 @@ bytefly ────────────────────────
620
665
  Currency CNY (¥)
621
666
  Cost decimals 2
622
667
  Session budget 0
623
- Transcript (2)
668
+ Transcript (3)
624
669
  Rail glyph ╎
670
+ Achieved goal Hide
625
671
  Output padding 1
626
- Terminal (1)
672
+ Terminal (2)
673
+ Theme system
627
674
  ◻ Hardware cursor Off
675
+ Worktree (4)
676
+ ◼ Fetch remote On
677
+ Base ref Fresh
678
+ Branch prefix worktree-
679
+ Prune after (days) 7
680
+ Snapshots (3)
681
+ ◼ Automatic snapshots On
682
+ Snapshots kept 20
683
+ ◼ Safety snapshot On
684
+ Jobs and watchers (3)
685
+ ◻ Wake when a job finishes Off
686
+ ◻ Keep jobs after exit Off
687
+ ◻ Wake on a match Off
688
+ Memory (1)
689
+ ◼ Inject notes On
628
690
  Bytefly startup TUI — Pi setting
629
691
  Use bytefly's startup header in place of Pi's startup TUI.
630
692
  Default · ~/.pi/agent/settings.json
631
693
 
632
- Esc close · ←/→ · Space change · Tab scope · ↑/↓ select
694
+ Esc close · ←/→ · Space change · Tab scope · u reset · J JSON · [ ] group
633
695
  ```
634
696
 
635
697
  | Group | Row | Source | Values |
@@ -648,20 +710,54 @@ bytefly ────────────────────────
648
710
  | Pricing | Cost decimals | `price.currency.decimals` | 0, 1, 2, 3, 4 |
649
711
  | Pricing | Session budget | `price.budget` | Number, `0` = off |
650
712
  | Transcript | Rail glyph | `chrome.rail` | Text, exactly one column |
713
+ | Transcript | Achieved goal | `goal.achieved` | Show, Hide |
651
714
  | Transcript | Output padding | Pi `settings.json` | 0, 1 |
715
+ | Terminal | Theme | Pi `settings.json` (`theme`) | `system`, `bytefly-dark`, `bytefly-light` |
652
716
  | Terminal | Hardware cursor | Pi `settings.json` | Off, On |
717
+ | Worktree | Base ref | `worktree.baseRef` | Fresh, Head |
718
+ | Worktree | Branch prefix | `worktree.branchPrefix` | Text, trimmed, required |
719
+ | Worktree | Fetch remote | `worktree.fetchRemote` | Off, On |
720
+ | Worktree | Prune after (days) | `worktree.pruneAfterDays` | Number, 0–3650 |
721
+ | Snapshots | Automatic snapshots | `rewind.autoSnapshots` | Off, On |
722
+ | Snapshots | Snapshots kept | `rewind.max` | Number, 0–1000 |
723
+ | Snapshots | Safety snapshot | `rewind.safetySnapshot` | Off, On |
724
+ | Jobs and watchers | Wake when a job finishes | `job.wakeOnFinish` | Off, On |
725
+ | Jobs and watchers | Keep jobs after exit | `job.detachedByDefault` | Off, On |
726
+ | Jobs and watchers | Wake on a match | `watch.wakeOnMatch` | Off, On |
727
+ | Memory | Inject notes | `memory.inject` | Off, On |
653
728
 
654
729
  Choosing **Currency** also writes that currency's default precision. Both rows
655
730
  stay editable afterwards, so the footer follows the explicit values.
656
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
+
657
741
  `Space` (or `Enter`) changes the focused row: a boolean toggles, an enum cycles
658
742
  forward, and a text row opens an inline editor on its value — the notification
659
- title, the probe timeout, and the rail glyph. The detail pane names the source, describes the preference, and shows
660
- where the value is written — `Global · ~/.pi/agent/settings.json`, `Project ·
661
- .pi/bytefly.json`, or `Inherited from Global · .pi/bytefly.json`. A successful
662
- change marks the screen `Saved · reload to apply` — even when the terminal is too
663
- short for the detail body — and closing offers the same
664
- reload confirm as an extension toggle.
743
+ title, the probe timeout, the branch prefix, and the rail glyph. `u` deletes the
744
+ focused row's key so its default applies again (`Already at its default.` when
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
749
+ the source, describes the preference, and shows
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.
665
761
 
666
762
  **Bytefly startup TUI** is the one row whose label differs from the Pi key it
667
763
  writes: On writes `"quietStartup": true` (bytefly's header replaces Pi's startup
@@ -671,17 +767,18 @@ with `true` or `false`.
671
767
 
672
768
  App rows are **Global only**: Pi's setters for them write the personal
673
769
  `settings.json`, and only resource paths have public project setters. Project
674
- scope therefore lists the thirteen `bytefly.json` rows and a
770
+ scope therefore lists the twenty-five `bytefly.json` rows and a
675
771
  `App settings are Global only.` note rather than promising a write that cannot
676
772
  happen. A `bytefly.json` row writes one leaf in the selected scope (a depth-1
677
773
  key, or a nested one such as `price.currency.decimals`) through a per-key
678
774
  writer, so every sibling key, section, `$schema`, and `version` is preserved. A
679
- malformed document is refused with a notice instead of overwritten.
775
+ malformed document is refused with a notice instead of overwritten, and `u`
776
+ removes that one leaf through the same writer's `unsetConfigValueAt`.
680
777
 
681
778
  The non-`tui` form prints the headline, the groups, and `Label: Value` rows:
682
779
 
683
780
  ```
684
- bytefly: Settings · Global · 16 settings
781
+ bytefly: Settings · Global · 29 settings
685
782
 
686
783
  Startup (1)
687
784
  Bytefly startup TUI: Off
@@ -733,6 +830,9 @@ headline; a named extension prints its full findings instead.
733
830
  | `Space` | Toggle the focused extension (Extensions); change the focused setting (Settings), or open the editor on a text row. |
734
831
  | `x` | Clear the focused extension's project override (Extensions, Project scope). |
735
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). |
736
836
  | `←` / `→`, `Home` / `End` (`Ctrl+A` / `Ctrl+E`) | Move the editor's caret while a text row is being edited. |
737
837
  | `Backspace` / `Delete`, `Ctrl+U` | Edit the buffer: delete a character, or clear it. |
738
838
  | `Enter` / `Esc` (editing) | Save the buffer, or cancel and restore the stored value. |
@@ -824,10 +924,11 @@ control-free line before it reaches an escape sequence or a shell argument.
824
924
 
825
925
  ## Update check
826
926
 
827
- A session can be days behind npm without anyone noticing, and Pi's own startup
828
- check only covers package sources it manages — a `pi -e ./`, a linked checkout,
829
- or a git install gets nothing. So bytefly reads its own published version and
830
- 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.
831
932
 
832
933
  ```
833
934
  █▀█ Pi v1.3.0
@@ -835,47 +936,50 @@ reports it once, in the two places it already owns:
835
936
  ```
836
937
 
837
938
  ```
838
- bytefly · 0.12.0 is available
839
- ╎ ◌ 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
840
942
  ╎ ◌ update · pi update --extensions
841
943
  ╎ ◌ release · https://github.com/gavin-hu/my-pi-agent/releases/tag/v0.12.0
842
944
  ```
843
945
 
844
- The marker is the shared `↑` glyph and the newest version — the arrow is one cell
845
- wide, so the header's column math is unchanged — painted with the header's
846
- `accent` role: an action rather than a warning, so the muted version beside it
847
- stays quiet and the transcript is not turned yellow for a normal state. It is
848
- part of the chrome cell, so the two identity cells still line up, and it is the
849
- 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.
850
953
 
851
954
  The notice is the shared transcript block: the `bytefly · title` headline at the
852
- 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
853
957
  `info` severity because nothing is broken. It stays in scrollback and the model
854
- reads the same sentence in plain text.
855
-
856
- The request is `GET https://registry.npmjs.org/@gavin-hu%2Fmy-pi-agent/latest`,
857
- fired without awaiting at `session_start` (Pi awaits handlers, and a five-second
858
- registry call must not hold the first prompt) with a five-second abort. Only a
859
- strictly newer, parseable semver announces anything: a non-ok status, a body
860
- that is not JSON, a missing version, a network error, or an older or equal
861
- version is silent. `PI_OFFLINE` (Pi's own switch) turns the check off;
862
- `PI_SKIP_VERSION_CHECK` only governs Pi's `pi.dev` request.
863
-
864
- The answer is cached in `<agentDir>/bytefly/update.json` as `latest` plus the
865
- epoch milliseconds it was read at. A session that starts inside the 24-hour
866
- window reuses that entry — painting the marker and posting the notice from it —
867
- instead of repeating the request, so a working day of sessions costs one read.
868
- Past the window the check runs again and rewrites the entry; a failed read writes
869
- nothing, so the next session tries again. Offline, the cached entry is used with
870
- no request and no write, because a stale version is still the best answer
871
- available and the marker is advisory.
872
-
873
- `/bytefly update` is the on-demand half: it ignores the window, reads the registry
874
- now, records and paints the result, and prints one line — `up to date — v0.12.0
875
- is the newest published version`, `v0.13.0 is available (current 0.12.0) —
876
- https://…/releases/tag/v0.13.0`, `the update check was skipped (PI_OFFLINE is
877
- set)`, or `could not read the published version`. It resolves no packages, probes
878
- 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
879
983
  release without a restart. Opening the control center never checks: the screen has
880
984
  no update surface, and the only request paths are `session_start` and this
881
985
  command.
@@ -887,9 +991,16 @@ command.
887
991
  rule, and transcript renderers stay on. Use `PI_DISABLED_EXTENSIONS=bytefly`
888
992
  (or `pi config`) to turn off all of bytefly.
889
993
  - **App settings are Global only.** Pi's setters for preferences such as
890
- `quietStartup` (the **Bytefly startup TUI** row), `outputPad`, and
891
- `showHardwareCursor` write the personal `settings.json`; the screen hides
892
- 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.
893
1004
  - **Project overrides of app settings are not shown.** App rows read the
894
1005
  personal file; a `.pi/settings.json` override still wins at runtime, and
895
1006
  `pi config` is the place to manage it.
@@ -909,19 +1020,22 @@ command.
909
1020
  their authoritative names are not exposed to extensions. Use `pi config`.
910
1021
  - **Packages are not installed, removed, or updated here**; use `pi install`,
911
1022
  `pi remove`, and `pi update`.
912
- - **The update check reads npm's `latest` only.** A private registry, a version
913
- published under another tag, or a checkout ahead of npm reports nothing, and
914
- there is no channel or prerelease preference: `0.11.4` is behind `0.12.0`
915
- 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.
916
1028
  - **The cache window is fixed at 24 hours.** It runs at `session_start` in `tui`
917
- mode and reuses an entry younger than that; there is no setting for it, and
918
- `/bytefly` and `doctor` do not report the version. A failed or slow background
919
- 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`.
920
1033
  - **The marker needs the bytefly startup TUI.** It lives on the custom header,
921
1034
  so with `quietStartup` off the notice is the whole signal.
922
- - **Pi may report the same update.** An npm-installed copy can also show Pi's
923
- own `Package Updates Available` block; the two checks are independent and
924
- `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.
925
1039
  - **No auto-update, and the release URL is text only.** The notice names a
926
1040
  command instead of running one, and the notice renderer cannot open a link, so
927
1041
  the release row prints the URL and nothing more — no changelog body, no way to
@@ -966,6 +1080,9 @@ command.
966
1080
  - **Notifications are not focus-aware.** They fire whether or not the terminal
967
1081
  has focus, and once per trigger rather than once per unseen event. There is
968
1082
  no focus API to consult.
1083
+ - **`/compact` is Pi's block.** The summary component is built in, so bytefly
1084
+ cannot render, hide, or shorten it; the themes only choose its fill and label
1085
+ color. Branch summaries and skill invocations share the same theme tokens.
969
1086
  - **A background job cannot ask for input.** The `needsInput` trigger is a
970
1087
  prompt raised during a run, not a process waiting on stdin: jobs are
971
1088
  fire-and-observe, so that state does not exist.
@@ -973,11 +1090,11 @@ command.
973
1090
  host lacks (or an OSC method off a TTY) and the notification is dropped; only
974
1091
  `auto` inspects the environment. `doctor` now reports the mismatch as a
975
1092
  `degraded` finding, which says what to change but does not fix it for you.
976
- - **The working counter is one global row.** `setWorkingMessage` drives the
1093
+ - **The working line is one global row.** `setWorkingMessage` drives the
977
1094
  single working row, so bytefly and any other extension setting it race — last
978
1095
  writer wins — and the message is reset to Pi's default at `agent_end`. If the
979
- row is hidden (`ctx.ui.setWorkingVisible(false)`) the counter has no surface,
980
- and a reload mid-run does not resume it until the next turn.
1096
+ row is hidden (`ctx.ui.setWorkingVisible(false)`) the line has no surface,
1097
+ and a reload mid-run does not resume the count until the next turn.
981
1098
 
982
1099
  ## Non-goals
983
1100
 
@@ -1001,7 +1118,7 @@ command.
1001
1118
  match — are the whole surface.
1002
1119
  - No sound, action buttons, notification history, or de-duplication; a
1003
1120
  notification is a fire-and-forget message, not an inbox.
1004
- - No frozen per-run working duration: the counter is live and is handed back to
1121
+ - No frozen per-run working duration: the line is live and is handed back to
1005
1122
  Pi when the working row comes down, because the extension API exposes no hook
1006
1123
  to keep a finished run's total.
1007
1124
 
@@ -1014,26 +1131,26 @@ rather than showing a wrong value.
1014
1131
 
1015
1132
  | Integration point | Details |
1016
1133
  |---|---|
1017
- | Header | `ctx.ui.setHeader` with a custom component, replacing Pi's built-in header; installed only when `ctx.mode === "tui"` and `pi.getSettings().quietStartup === true` (the **Bytefly startup TUI** row). `false`, `"header"`, and unset call `setHeader(undefined)`, leaving Pi's built-in header in place. The nameplate insets by Pi's `outputPad` (read through `pi.getSettings()`), so it sits on the transcript margin, and the help hangs one body indent inside it. The mark and text are drawn from the active theme's tokens, the help keys resolve live through `keyText`, and the mark falls back to a wordmark on Apple Terminal (`isAppleTerminalSession`), mirroring Pi's built-in. The chrome version gains `↑<latest>` from the `UpdateSource`, and `registerHeader` returns a `refresh()` handle that re-installs the last context's header when that value arrives. |
1134
+ | Header | `ctx.ui.setHeader` with a custom component, replacing Pi's built-in header; installed only when `ctx.mode === "tui"` and `pi.getSettings().quietStartup === true` (the **Bytefly startup TUI** row). `false`, `"header"`, and unset call `setHeader(undefined)`, leaving Pi's built-in header in place. The nameplate insets by Pi's `outputPad` (read through `pi.getSettings()`), so it sits on the transcript margin, and the help hangs one body indent inside it. The mark and text are drawn from the active theme's tokens, the help keys resolve live through `keyText`, and the mark falls back to a wordmark on Apple Terminal (`isAppleTerminalSession`), mirroring Pi's built-in. The factory reads `tui.terminal.rows` on every render, so the height ladder in `header/config.ts` sheds help groups and then the leading blank row as the terminal shortens (unknown height keeps the full header). The chrome version gains `↑<latest>` from the `UpdateSource`, and `registerHeader` returns a `refresh()` handle that re-installs the last context's header when that value arrives. |
1018
1135
  | Footer | `ctx.ui.setFooter` with a custom component, replacing Pi's built-in footer; installed only when `ctx.mode === "tui"`. |
1019
1136
  | User message | `pi.registerMarkdownTransformer` puts a two-row `You` nameplate (the label over a full-width rule) above each user message; one padded level-2 heading carries the accent through `mdHeading` for both rows, and the shipped themes set `userMessageBg` to the terminal default, so the block has no fill. Pi renders the result as Markdown, so the surface adds no component or color of its own. |
1020
1137
  | Notice | `registerMessageRenderer(NOTICE_TYPE)` draws every shared transcript notice at the margin from its `details`; the headline stays at the margin and its rows hang from the transcript rail, painted by the notice's worst severity, whose glyph comes from the shared `RailSource`. `job` and `watch` post one when their work settles, and `doctor` posts one at `session_start` for a missing hard requirement. The type and shape come from `lib/notice.ts`. A message without `details` falls back to Pi's default custom-message box. |
1021
1138
  | Tool blocks | `registerToolRenderer` returns, for every tool, `renderShell: "self"`; `renderCall` leaves the tool's own first line as the block's headline (a shell prompt shown as the tool name, its command repainted in the body colour, and a primary subject demoted to the body colour) and indents the rest of the call two columns under it, `renderResult` indents its body the same and leads it with the status-coloured rail, and each wraps the tool's own renderer (or a generic fallback). `edit` and `write` carry their result on the call body, so that body is railed too. |
1022
1139
  | Screen command | `pi.registerCommand("bytefly")` takes `[doctor] [settings] [extension] [refresh]`: `tui` mounts a dock screen via `ctx.ui.custom()` with the rails suppressed; other modes print through `ctx.ui.notify()` — the grouped listing, `run_doctor`'s report for `doctor`, or the grouped settings listing with values for `settings`. A named extension is checked against the discovered set before any probe. Tab completes the subcommands, extension names after `doctor`, and `refresh`; `/bytefly settings` rejects a name. |
1023
- | Turn separator | `registerEntryRenderer("bytefly-separator")` draws each rule; `agent_settled` appends `pi.appendEntry("bytefly-separator", { turn })` in `tui` mode only. |
1024
- | Working timer | `turn_start` starts a 1s tick that drives `ctx.ui.setWorkingMessage("Working… Ns")` in `tui` mode; `agent_end` restores Pi's default and cancels the tick (also on `session_shutdown`). No config file and no stored state. |
1140
+ | Turn separator | `registerEntryRenderer("bytefly-separator")` draws each stored receipt; `agent_start` opens it, top-level `tool_execution_start`/`tool_execution_end` count tools and failures, and `agent_settled` appends `pi.appendEntry("bytefly-separator", { turn, durationMs, tools, failures, aborted })` in `tui` mode only. The receipt fields are omitted when no `agent_start` was seen, so an older or partially observed entry stays `{ turn }`. |
1141
+ | Working line | `turn_start` starts a 1s tick that drives `ctx.ui.setWorkingMessage` in `tui` mode; before a tool runs the message is `Working… Ns`, and each top-level `tool_execution_start` repaints it as `tool · N tools · Ns` (nested calls carrying `parentToolCallId` are skipped). `agent_end` restores Pi's default and cancels the tick (also on `session_shutdown`). No config file and no persisted state; the count lives in the register's closure. |
1025
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. |
1026
1143
  | Picker discovery | `SettingsManager.create(...)` plus `DefaultPackageManager.resolve({ onMissing: "skip" })`, both public exports; both views drop `bytefly`'s own entrypoint. |
1027
1144
  | Picker writes | `SettingsManager` setters (`setExtensionPaths`, `setPackages`, `setProjectExtensionPaths`, `setProjectPackages`) then `flush()`; `ctx.reload()` after the screen closes when changes are pending. |
1028
- | 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. 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. |
1029
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. |
1030
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. |
1031
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. |
1032
- | 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. |
1033
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. |
1034
1151
  | Data sources | `footerData.getGitBranch()`, `footerData.getAvailableProviderCount()`, `ctx.getContextUsage()`, `ctx.sessionManager.getEntries()`, `ctx.sessionManager.getSessionName()`, `ctx.model`; subscribes to `footerData.onBranchChange` to repaint. |
1035
- | 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. |
1036
- | 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. |
1037
1154
 
1038
1155
  ## Design notes
1039
1156
 
@@ -1075,8 +1192,12 @@ rather than showing a wrong value.
1075
1192
  expand toggle, so no themed string outlives its theme. `indentBlockLines` only
1076
1193
  measures a line it cannot prove fits, and the header memoizes on the width,
1077
1194
  theme, shape, and the help chords it prints, so a live rebind still repaints.
1078
- The footer's context estimate joins its pricing scan in the leaf-keyed memo:
1079
- 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.
1080
1201
  - **The frame delegates; it does not re-render.** Pi resolves tool renderers
1081
1202
  through the extension chain, and the resolved object is the definition the row
1082
1203
  reads, so a resolver can override `renderShell` and wrap `next()`'s
@@ -1110,7 +1231,8 @@ rather than showing a wrong value.
1110
1231
  themselves by that pad: the tool frame and the notice take it from Pi, and the
1111
1232
  turn separator reads the last value a sibling observed (`pad.ts`). Pi's setting
1112
1233
  is the only knob — bytefly adds no padding of its own. The footer's group
1113
- 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.
1114
1236
  - **The header is a nameplate, not a dashboard.** Pi's built-in brand colors are
1115
1237
  fixed, so the mark ignores the active theme; bytefly's header draws a two-tone
1116
1238
  mark from the `accent`/`warn` roles, a signature rule whose accent lead sits
@@ -1118,8 +1240,10 @@ rather than showing a wrong value.
1118
1240
  and a grouped help block. It takes its margin from Pi's `outputPad`, like the
1119
1241
  tool frame and the notice, and hangs the help one body indent inside it, so the
1120
1242
  header follows a non-default pad instead of assuming a column. It adds no
1121
- session or model data — that is the footer's job — and it stays a fixed
1122
- four-plus rows so the startup transcript never reflows.
1243
+ session or model data — that is the footer's job — and its shape follows the
1244
+ terminal: the height ladder drops whole help groups, then the leading blank
1245
+ row, before the nameplate gives up anything, so a short pane spends its rows
1246
+ on the transcript instead of chrome.
1123
1247
  - **Two lines, four zones.** Line 1 is identity (`pwd` left, branch + worktree
1124
1248
  right); line 2 is resources (meters left, model + level right). This keeps
1125
1249
  "where am I / what state" separate from "how much am I burning".
@@ -1129,7 +1253,7 @@ rather than showing a wrong value.
1129
1253
  dispatching process and the delegated work stays visible beside it.
1130
1254
  - **The gauge is anchored at column 0.** A leading mode slot would shift the
1131
1255
  context percentage left and right as plan mode came and went; trailing it
1132
- 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
1133
1257
  visible as the meters drop.
1134
1258
  - **Statuses are routed, not repeated.** The built-in footer renders every
1135
1259
  `setStatus` value on its own line, so the bar sorts them by meaning instead:
@@ -1147,9 +1271,9 @@ rather than showing a wrong value.
1147
1271
  non-zero usage, so it reads full only at 100% and a small percentage never
1148
1272
  disappears; unknown usage draws no gauge at all. Only at extreme widths does
1149
1273
  `truncateToWidth` clip the remaining text.
1150
- - **Separators shrink to a single space.** Padded separators (` · `, ` ╎ `)
1274
+ - **Separators shrink to a single space.** Padded separators (` · `, ` │ `)
1151
1275
  collapse to one space before `truncateToWidth` applies, so adjacent segments
1152
- 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.
1153
1277
  - **Statuses compact with their colour.** A themed status's compact icon form
1154
1278
  reattaches its opening and closing SGR sequences around the visible text, so a
1155
1279
  red `✗1` stays red instead of losing its colour exactly when the bar tightens.
@@ -1253,32 +1377,38 @@ rather than showing a wrong value.
1253
1377
  - **The prerequisite table is data.** No I/O, no host access, no module state:
1254
1378
  every probe dependency arrives as a seam, so `probeExtension` is testable with
1255
1379
  no process and `lib/` stays value-only.
1256
- - **The update check is a self-report, not an update mechanism.** It answers one
1257
- question — is the version in this checkout current? — and answers it in the two
1258
- surfaces bytefly already owns, the header identity and one notice. It never
1259
- installs, never edits settings, and never fails a session: an unreachable
1260
- registry and an up-to-date one are indistinguishable to the user, which is the
1261
- only failure mode a background check is allowed to have.
1262
- - **The marker is an action, the notice is information.** The same fact is drawn
1263
- in the accent on the header, where it sits beside a control the user can act
1264
- on, and at `info` severity in the transcript, where a `warn` rail would say
1265
- something is broken. Neither surface needs per-frame polling: the value arrives
1266
- 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.
1267
1396
  - **The transport stays local.** `web-access`'s runner does redirects, SSRF
1268
1397
  checks, HTML extraction, and caching — none of it applies to one fixed JSON
1269
1398
  endpoint, and an extension may not import another's modules, so the ~20-line
1270
- 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.
1271
1401
 
1272
1402
  ## Files
1273
1403
 
1274
1404
  The surfaces are folders: `header/`, `footer/` (the status bar and its price
1275
1405
  calculation), `message/` (the user-message marker), `notice/` (the shared notice),
1276
1406
  `block/` (the tool-block frame), `separator/`, `working/` (the live
1277
- elapsed counter on Pi's working row), `panel/` (the
1407
+ run line on Pi's working row), `panel/` (the
1278
1408
  unified screen and its merged model),
1279
1409
  `picker/` (discovery and the settings store), `doctor/` (the `run_doctor` tool
1280
- and its probe runtime), `notify/`, and `update/` (the published-version check
1281
- 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
1282
1412
  the package's [`lib/tui/`](../../lib/tui/) design system (tokens, rules, rows,
1283
1413
  columns, gauge), so the look follows the active theme.
1284
1414
 
@@ -1294,10 +1424,10 @@ columns, gauge), so the look follows the active theme.
1294
1424
  | `footer/config.ts` | Bar tuning (`CONFIG`): thresholds, gauge, separators, icons, status keys, labels. |
1295
1425
  | `footer/types.ts` | `StatusSnapshot`, `Segment`, `LineSpec`, `UsageRecord`, and footer/TUI shapes. |
1296
1426
  | `footer/collect/entries.ts` | Session entries → usage records, each resolved to the model in effect. |
1297
- | `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. |
1298
1428
  | `footer/price/types.ts` | `Currency`, `ModelRates`, `PeakWindow`/`PeakSchedule`, `ByteflyConfig`. |
1299
1429
  | `footer/price/config.ts` | The `price` section of `bytefly.json`: schema, defaults, built-in DeepSeek rates/tariff, currency defaults, normalizer, loader. |
1300
- | `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. |
1301
1431
  | `footer/price/schedule.ts` | Peak-window parsing and evaluation in a configured zone. |
1302
1432
  | `footer/price/price.ts` | Records → totals: rated cost in both modes, Pi's recorded cost as USD's fallback, and the peak multiplier. |
1303
1433
  | `footer/render/format.ts` | Generic formatters (tokens, cost in the configured currency, cwd, the context role, truncation). |
@@ -1316,20 +1446,21 @@ columns, gauge), so the look follows the active theme.
1316
1446
  | `block/component.ts` | Pure `toolBlockLines`, `headlineWithToolName`, `recolorShellCall`, `recolorCallHeadline`, and `ToolBlock`: the headline, the body's two-column indent (optionally rail-led), the reduced-width inner render, the leading-blank trim, the shell-prompt rewrite (the name keeps the accent, the command reopens in `label`), the subject demotion (a path, URL, query, or pattern reads in `label` so the name is the line's only accent, including the tail a bare `edit <path>` headline wraps onto the body), and the image-line passthrough. |
1317
1447
  | `block/fallback.ts` | Generic `callFallback` / `resultFallback` for a tool with no renderer: the subject in the body colour, its options faint, and a capped result preview. |
1318
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). |
1319
- | `separator/config.ts` | Turn-rule tuning: `customType`, glyph, label prefix, padding, and roles. |
1320
- | `separator/register.ts` | Registers the entry renderer and appends a separator on `agent_settled`. |
1321
- | `separator/component.ts` | `SeparatorComponent`: the stateless entry renderer. |
1322
- | `separator/format.ts` | Pure `separatorLine`: the centered turn rule, drawn by the kit's `centeredRule`. |
1323
- | `separator/types.ts` | `TurnSeparatorData`. |
1324
- | `working/config.ts` | Counter tuning (`WORKING_CONFIG`): the word, the ellipsis, and the tick interval. |
1325
- | `working/format.ts` | Pure `formatElapsed` (whole seconds) and the working-message shape. |
1326
- | `working/register.ts` | Starts the tick on `turn_start` and cancels it on `agent_end`/`session_shutdown`. |
1449
+ | `separator/config.ts` | Turn-rule tuning: `customType`, glyph, label prefix, padding, receipt minimum duration and outcome wording, and roles. |
1450
+ | `separator/register.ts` | Registers the entry renderer, tracks the run's tools and failures, and appends the receipt on `agent_settled`. |
1451
+ | `separator/component.ts` | `SeparatorComponent`: the entry renderer, memoized on the width so a repeat frame does not rebuild the rule. |
1452
+ | `separator/format.ts` | Pure `receiptLabel` (the width ladder), `receiptRole`, and `separatorLine`, drawn by the kit's `centeredRule`. |
1453
+ | `separator/types.ts` | `TurnSeparatorData`: the turn plus the optional receipt fields. |
1454
+ | `working/config.ts` | Counter tuning (`WORKING_CONFIG`): the words, the tool singular/plural, and the tick interval. |
1455
+ | `working/format.ts` | Pure `formatElapsed` (whole seconds) and the working-message shape: the plain count and the `tool · N tools · elapsed` run line. |
1456
+ | `working/register.ts` | Starts the tick on `turn_start`, tracks top-level tools on `tool_execution_start`, and cancels on `agent_end`/`session_shutdown`. |
1327
1457
  | `panel/types.ts` | `PanelSection` (`extensions` | `settings`), `PanelRow`, and `PanelModel`, the merged row model. |
1328
1458
  | `panel/join.ts` | The item/report union with the unambiguous-name health rule and unique row keys, groups, and ordering. |
1329
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. |
1330
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. |
1331
1461
  | `panel/settings-runtime.ts` | The Settings runtime: snapshot cache, scope guards, app setters, per-leaf `bytefly.json` writes, dirty state, and change listeners. |
1332
- | `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`. |
1333
1464
  | `panel/commands.ts` | `/bytefly`: one command for the screen sections, the `doctor` report, the settings listing, and their text modes. |
1334
1465
  | `picker/types.ts` | Picker host seams (`SettingsStore`, `ExtensionResolver`, `Discovery`), the app-settings setters, and row types. |
1335
1466
  | `picker/resources.ts` | Pure grouping, labels, display names, and pattern helpers. |
@@ -1347,12 +1478,14 @@ columns, gauge), so the look follows the active theme.
1347
1478
  | `notify/dispatch.ts` | Method selection and delivery: OSC 777/99, Windows toast, `osascript`, `notify-send`, over injected seams. |
1348
1479
  | `notify/format.ts` | Pure notification text: session label and the turn / input / job / watch bodies. |
1349
1480
  | `notify/register.ts` | Registers the four triggers, loads the config at `session_start`, and owns the job- and watch-bus subscriptions. |
1350
- | `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. |
1351
1484
  | `update/version.ts` | Pure semver parsing and `isNewerVersion`, including prerelease precedence. |
1352
- | `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. |
1353
- | `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. |
1354
- | `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. |
1355
- | `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. |
1356
1489
 
1357
1490
  ## Testing
1358
1491
 
@@ -1375,17 +1508,18 @@ folding across a width sweep from 6 to 120 plus wide characters
1375
1508
  (`footer/render/layout`), exact
1376
1509
  golden output at fixed widths (`footer/render/render`), and the install/skip/
1377
1510
  restore/toggle lifecycle plus a config-driven price (`extension`). The turn
1378
- separator
1379
- is covered by its pure formatting (`separator/format`) and its
1380
- register/append/render/disable lifecycle (`separator/register`). The picker
1381
- helpers are covered by the pure helpers (`picker/resources`), the toggle
1511
+ separator is covered by its pure formatting (`separator/format`, including the
1512
+ receipt's width ladder), its register/append/render/disable lifecycle
1513
+ (`separator/register`), and the run stats it records. The picker helpers are
1514
+ covered by the pure helpers (`picker/resources`), the toggle
1382
1515
  transforms (`picker/toggle`), discovery with an injected factory
1383
1516
  (`picker/discovery`), and the runtime state machine (`picker/runtime`). The
1384
1517
  unified screen is covered by its pure model merge (`panel/join`), its text
1385
1518
  formatter (`panel/format`), the settings catalogue and its cycling
1386
1519
  (`panel/settings`), the scope-aware settings runtime (`panel/settings-runtime`,
1387
1520
  including a nested leaf write that keeps its siblings),
1388
- 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
1389
1523
  the bytefly factory (`panel/commands`).
1390
1524
  The doctor surface is covered by its pure report
1391
1525
  formatting (`doctor/format`), its config loader (`doctor/config`), and
@@ -1394,14 +1528,15 @@ through the factory (`doctor/register`). The notify capability is covered by its
1394
1528
  (`notify/config`), the pure text builders (`notify/format`), method selection and
1395
1529
  delivery over recording seams (`notify/dispatch`), and its four triggers,
1396
1530
  reload, and shutdown through `registerNotify` (`notify/register`). The update
1397
- check is covered by the version-ordering table (`update/version`), the transport
1398
- with an injected fetch (`update/check`), the per-load store (`update/state`), the
1399
- marker, notice, and release-URL wording (`update/format`), the cache read, write,
1400
- and window (`update/cache`), and its lifecycle — a fresh entry reused without a
1401
- request, a stale one re-read and rewritten, silent on failure or an older version,
1402
- cache-only offline, skipped outside `tui`, an explicit `recheck()` that ignores
1403
- the window, and immunity to a reply that lands after shutdown
1404
- (`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`).
1405
1540
  The user-message nameplate is covered by its pure transform
1406
1541
  (`message/format`, including a render through pi-tui's real `Markdown`) and its
1407
1542
  registration (`message/register`), plus the factory check in `extension`.