@gavin-hu/my-pi-agent 0.8.1 → 0.10.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 (174) hide show
  1. package/README.md +35 -8
  2. package/README.zh-CN.md +33 -8
  3. package/assets/bytefly.schema.json +17 -1
  4. package/assets/chrome.png +0 -0
  5. package/assets/file-browse.png +0 -0
  6. package/assets/job.png +0 -0
  7. package/assets/plan-review.png +0 -0
  8. package/assets/question.png +0 -0
  9. package/assets/rewind.png +0 -0
  10. package/assets/todo.png +0 -0
  11. package/assets/tool-renderings.png +0 -0
  12. package/assets/tools/ask_user_question.png +0 -0
  13. package/assets/tools/bash.png +0 -0
  14. package/assets/tools/edit.png +0 -0
  15. package/assets/tools/enter_plan_mode.png +0 -0
  16. package/assets/tools/enter_worktree.png +0 -0
  17. package/assets/tools/exit_plan_mode.png +0 -0
  18. package/assets/tools/exit_worktree.png +0 -0
  19. package/assets/tools/find.png +0 -0
  20. package/assets/tools/goal.png +0 -0
  21. package/assets/tools/grep.png +0 -0
  22. package/assets/tools/job.png +0 -0
  23. package/assets/tools/list_worktrees.png +0 -0
  24. package/assets/tools/ls.png +0 -0
  25. package/assets/tools/memory.png +0 -0
  26. package/assets/tools/notice.png +0 -0
  27. package/assets/tools/open_path.png +0 -0
  28. package/assets/tools/prune_worktrees.png +0 -0
  29. package/assets/tools/read.png +0 -0
  30. package/assets/tools/read_doc.png +0 -0
  31. package/assets/tools/run_doctor.png +0 -0
  32. package/assets/tools/send_wechat.png +0 -0
  33. package/assets/tools/set_plan_steps.png +0 -0
  34. package/assets/tools/subagent.png +0 -0
  35. package/assets/tools/todo.png +0 -0
  36. package/assets/tools/turn-rule.png +0 -0
  37. package/assets/tools/user-message.png +0 -0
  38. package/assets/tools/watch.png +0 -0
  39. package/assets/tools/web_fetch.png +0 -0
  40. package/assets/tools/web_search.png +0 -0
  41. package/assets/tools/write.png +0 -0
  42. package/assets/tools/write_plan.png +0 -0
  43. package/extensions/bytefly/README.md +306 -68
  44. package/extensions/bytefly/block/component.ts +282 -0
  45. package/extensions/bytefly/block/config.ts +24 -0
  46. package/extensions/bytefly/block/fallback.ts +120 -0
  47. package/extensions/bytefly/block/register.ts +164 -0
  48. package/extensions/bytefly/chrome.ts +97 -0
  49. package/extensions/bytefly/doctor/format.ts +36 -10
  50. package/extensions/bytefly/doctor/tools.ts +10 -6
  51. package/extensions/bytefly/footer/config.ts +1 -1
  52. package/extensions/bytefly/footer/render/format.ts +5 -18
  53. package/extensions/bytefly/footer/render/layout.ts +2 -1
  54. package/extensions/bytefly/footer/render/segments.ts +51 -78
  55. package/extensions/bytefly/header/component.ts +49 -21
  56. package/extensions/bytefly/header/config.ts +28 -21
  57. package/extensions/bytefly/header/register.ts +6 -1
  58. package/extensions/bytefly/index.ts +32 -12
  59. package/extensions/bytefly/message/config.ts +15 -0
  60. package/extensions/bytefly/message/format.ts +42 -0
  61. package/extensions/bytefly/message/register.ts +15 -0
  62. package/extensions/bytefly/notice/component.ts +107 -0
  63. package/extensions/bytefly/notice/config.ts +41 -0
  64. package/extensions/bytefly/notice/register.ts +27 -0
  65. package/extensions/bytefly/pad.ts +42 -0
  66. package/extensions/bytefly/panel/commands.ts +69 -35
  67. package/extensions/bytefly/panel/format.ts +8 -8
  68. package/extensions/bytefly/panel/tui.ts +45 -70
  69. package/extensions/bytefly/separator/component.ts +2 -1
  70. package/extensions/bytefly/separator/config.ts +16 -8
  71. package/extensions/bytefly/separator/format.ts +18 -38
  72. package/extensions/bytefly/separator/register.ts +3 -2
  73. package/extensions/bytefly/separator/types.ts +0 -10
  74. package/extensions/bytefly/working/config.ts +20 -0
  75. package/extensions/bytefly/working/format.ts +25 -0
  76. package/extensions/bytefly/working/register.ts +88 -0
  77. package/extensions/document/README.md +12 -5
  78. package/extensions/document/paging.ts +10 -10
  79. package/extensions/document/render.ts +17 -16
  80. package/extensions/document/tool.ts +1 -1
  81. package/extensions/file-browse/README.md +54 -10
  82. package/extensions/file-browse/client.ts +110 -2
  83. package/extensions/file-browse/config.ts +12 -0
  84. package/extensions/file-browse/diff.ts +187 -0
  85. package/extensions/file-browse/git.ts +180 -8
  86. package/extensions/file-browse/html.ts +401 -25
  87. package/extensions/file-browse/index.ts +15 -4
  88. package/extensions/file-browse/markdown.ts +33 -0
  89. package/extensions/file-browse/router.ts +94 -2
  90. package/extensions/file-browse/server.ts +4 -1
  91. package/extensions/file-open/README.md +6 -0
  92. package/extensions/goal/README.md +16 -10
  93. package/extensions/goal/format.ts +13 -25
  94. package/extensions/goal/tools.ts +11 -22
  95. package/extensions/goal/tui.ts +12 -40
  96. package/extensions/job/README.md +22 -13
  97. package/extensions/job/format.ts +36 -13
  98. package/extensions/job/index.ts +26 -21
  99. package/extensions/job/tools.ts +15 -12
  100. package/extensions/job/tui.ts +148 -222
  101. package/extensions/job/ui.ts +24 -49
  102. package/extensions/memory/README.md +7 -1
  103. package/extensions/memory/format.ts +5 -3
  104. package/extensions/memory/tools.ts +4 -3
  105. package/extensions/memory/tui.ts +32 -36
  106. package/extensions/plan/README.md +86 -36
  107. package/extensions/plan/blocks.ts +44 -2
  108. package/extensions/plan/feedback.ts +20 -5
  109. package/extensions/plan/index.ts +1 -1
  110. package/extensions/plan/runtime.ts +3 -2
  111. package/extensions/plan/tools.ts +43 -27
  112. package/extensions/plan/tui.ts +21 -27
  113. package/extensions/plan/web-client.ts +386 -40
  114. package/extensions/plan/web-page.ts +138 -33
  115. package/extensions/plan/web-router.ts +46 -13
  116. package/extensions/plan/web.ts +63 -12
  117. package/extensions/question/README.md +14 -2
  118. package/extensions/question/answers.ts +25 -18
  119. package/extensions/question/tools.ts +22 -16
  120. package/extensions/question/tui.ts +132 -76
  121. package/extensions/rewind/README.md +3 -1
  122. package/extensions/rewind/format.ts +35 -2
  123. package/extensions/rewind/index.ts +3 -1
  124. package/extensions/rewind/rewind-tui.ts +66 -62
  125. package/extensions/rewind/snapshot-run.ts +1 -1
  126. package/extensions/rewind/status.ts +3 -2
  127. package/extensions/subagent/README.md +6 -0
  128. package/extensions/subagent/index.ts +1 -1
  129. package/extensions/subagent/render.ts +108 -66
  130. package/extensions/todo/README.md +20 -13
  131. package/extensions/todo/format.ts +47 -34
  132. package/extensions/todo/tools.ts +6 -4
  133. package/extensions/todo/tui.ts +46 -26
  134. package/extensions/watch/README.md +16 -8
  135. package/extensions/watch/format.ts +33 -13
  136. package/extensions/watch/index.ts +29 -24
  137. package/extensions/watch/tools.ts +16 -9
  138. package/extensions/watch/tui.ts +142 -196
  139. package/extensions/watch/ui.ts +24 -46
  140. package/extensions/web-access/README.md +16 -7
  141. package/extensions/web-access/fetch/tool.ts +12 -11
  142. package/extensions/web-access/search/tool.ts +16 -13
  143. package/extensions/wechat/README.md +8 -2
  144. package/extensions/wechat/index.ts +4 -3
  145. package/extensions/wechat/login-screen.ts +8 -5
  146. package/extensions/wechat/status-screen.ts +41 -22
  147. package/extensions/wechat/tools.ts +6 -5
  148. package/extensions/worktree/README.md +6 -0
  149. package/extensions/worktree/enter.ts +1 -1
  150. package/extensions/worktree/index.ts +1 -1
  151. package/extensions/worktree/runtime.ts +1 -1
  152. package/lib/README.md +11 -7
  153. package/lib/format.ts +17 -0
  154. package/lib/notice.ts +87 -0
  155. package/lib/requirements.ts +2 -2
  156. package/lib/{tui.ts → tui/chrome.ts} +17 -34
  157. package/lib/tui/columns.ts +96 -0
  158. package/lib/{list-cursor.ts → tui/cursor.ts} +58 -4
  159. package/lib/tui/gauge.ts +78 -0
  160. package/lib/{ui.ts → tui/glyphs.ts} +10 -11
  161. package/lib/tui/indent.ts +166 -0
  162. package/lib/tui/index.ts +27 -0
  163. package/lib/tui/line-pane.ts +173 -0
  164. package/lib/tui/rows.ts +143 -0
  165. package/lib/tui/rules.ts +94 -0
  166. package/lib/tui/status-controller.ts +107 -0
  167. package/lib/tui/tokens.ts +72 -0
  168. package/lib/web/base.ts +2 -1
  169. package/lib/web/components.ts +3 -1
  170. package/lib/web/layout.ts +5 -1
  171. package/package.json +7 -3
  172. package/themes/bytefly-dark.json +7 -8
  173. package/themes/bytefly-light.json +10 -11
  174. package/assets/bytefly-chrome.png +0 -0
@@ -1,12 +1,13 @@
1
- # bytefly — the TUI header, footer, turn rule, extension screen, and notifications
1
+ # bytefly — the TUI header, footer, user-message marker, turn rule, extension screen, and notifications
2
2
 
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 labeled rule that marks each completed turn in the transcript, one
7
- keyboard-driven screen that lists every resolved Pi extension with its enable
8
- checkbox and its health, and an opt-in notify capability that raises an OS
9
- notification when a run finishes, a job finishes, or the agent asks for input.
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 that
8
+ lists every resolved Pi extension with its enable checkbox and its health, and
9
+ an opt-in notify capability that raises an OS notification when a run finishes,
10
+ a job finishes, or the agent asks for input.
10
11
  DeepSeek is priced from the provider's published yuan rates and peak tariff by
11
12
  default, and any other model can be priced from your own rates when Pi's catalog
12
13
  does not match what a provider actually charges.
@@ -17,53 +18,63 @@ pi -e . # load the whole @gavin-hu/my-pi-agent pac
17
18
  pi install ./ # install the package
18
19
  ```
19
20
 
21
+ ## Tool rendering
22
+
23
+ How the `run_doctor` call and its result look in the transcript — the call line as a headline and the body indented under it:
24
+
25
+ <img src="../../assets/tools/run_doctor.png" alt="The run_doctor tool's result: the call line and an indented body with the doctor headline and the missing and degraded findings" width="100%">
26
+
20
27
  ## What it does
21
28
 
22
- The extension owns five surfaces: the startup header, the footer, the rule
23
- between completed turns, the extension screen, and notifications.
29
+ The extension owns nine surfaces: the startup header, the footer, the
30
+ user-message marker, the rule between completed turns, the notice block for
31
+ shared transcript notices, the tool-block frame, the live working counter, the
32
+ extension screen, and notifications.
24
33
 
25
34
  **Header.** A nameplate: a two-tone half-block mark from the active theme's
26
- `accent` / `warning` tokens, the agent identity (`Pi` and its version), the
27
- chrome identity (`enhanced by bytefly` and the package version), a `─` rule, and
28
- a grouped help block. The help keys resolve live from Pi's keybindings, so a
29
- rebound key shows the user's chord; the identity and help text are `muted`/`dim`
30
- so the mark keeps the eye. It is installed only in interactive (`tui`) sessions,
31
- and on terminals that cannot render half blocks the mark falls back to a
32
- two-tone `Pi` wordmark.
35
+ `accent` / `warn` roles, the agent identity (`Pi`, bold, and its version), the
36
+ chrome identity (`enhanced by bytefly` and the package version), a signature
37
+ rule whose `accent` lead sits under the mark, and a grouped help block. The help
38
+ keys resolve live from Pi's keybindings, so a rebound key shows the user's
39
+ chord; the identity and help text are `meta` / `faint` so the mark keeps the
40
+ eye. It is installed only in interactive (`tui`) sessions, and on terminals that
41
+ cannot render half blocks the mark falls back to a two-tone `Pi` wordmark.
33
42
 
34
43
  ```
35
44
  █▀█ Pi v1.2.0
36
- █▀ █ enhanced by bytefly v0.7.0
45
+ █▀ █ enhanced by bytefly v0.8.1
37
46
  ─────────────────────────────────────────────────
38
- escape to interrupt · ctrl+c to clear · ctrl+d to exit (empty)
39
- ctrl+o to expand tools · ctrl+t to expand thinking · ctrl+l to select model
40
- / commands · ! bash · !! bash (no context) · /bytefly extensions
47
+ escape to interrupt · ctrl+c to clear · ctrl+d to exit (empty)
48
+ ctrl+o to expand tools · ctrl+t to expand thinking · ctrl+l to select model
49
+ / commands · ! bash · !! bash (no context) · /bytefly extensions
41
50
  ```
42
51
 
43
52
  As the terminal narrows, the identity sheds the chrome version, then the chrome
44
53
  line, then the agent name, and the help rows drop whole keys, so nothing wraps or
45
- clips mid-word. The mark, tokens, identity, and grouped help are tuned in
46
- [`header/config.ts`](./header/config.ts).
54
+ clips mid-word. The nameplate sits on the transcript margin, which follows Pi's
55
+ `outputPad` (the value the tool frame and the notice use too), and the help hangs
56
+ one body indent inside it. The mark, tokens, identity, and grouped help are tuned
57
+ in [`header/config.ts`](./header/config.ts).
47
58
 
48
59
  **Footer.** Two lines, each with a left and right zone:
49
60
 
50
61
  - **Line 1 — identity:** `pwd` and the session name on the left; git state
51
62
  (`⎇ branch · ⑂ worktree`) and the `◉` browse chip on the right.
52
63
  - **Line 2 — resources:** the context gauge and usage meters on the left, the
53
- mode/alert slot (`⋮ plan`) trailing after a `│` when present; the model and
64
+ mode/alert slot (`⋮ plan`) trailing after a `╎` when present; the model and
54
65
  thinking level on the right. The gauge is anchored at column 0, so `%` is
55
66
  always in the same place.
56
67
 
57
68
  ```
58
69
  ~/repo/my-pi-agent ⎇ main · ⑂ smoke
59
- ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% │ ⋮ plan opus-4.5 · high
70
+ ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% ╎ ⋮ plan opus-4.5 · high
60
71
  ```
61
72
 
62
73
  (The examples show the default `¥` symbol. For a model the built-in table does
63
74
  not cover, the number stays Pi's recorded USD amount — see the currency note
64
75
  under [Configuration](#configuration).)
65
76
 
66
- With no plan mode there is no mode slot, so the `│` is omitted:
77
+ With no plan mode there is no mode slot, so the `╎` is omitted:
67
78
 
68
79
  ```
69
80
  ~/repo/my-pi-agent ⎇ main · ⑂ smoke
@@ -76,7 +87,7 @@ fits, rather than collapsing to a bare glyph:
76
87
 
77
88
  ```
78
89
  ~/repo/project ⎇ main · ⑂ smoke
79
- ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 │ ⋮ plan opus-4.5 · high
90
+ ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 ╎ ⋮ plan opus-4.5 · high
80
91
  ```
81
92
 
82
93
  ```
@@ -88,7 +99,7 @@ With a named session, the name follows the path and truncates before it drops:
88
99
 
89
100
  ```
90
101
  ~/repo/project · feature-refac… ⎇ main
91
- ▰▰▰▰▰▰▱▱▱▱ 62% │ ⋮ plan opus-4.5
102
+ ▰▰▰▰▰▰▱▱▱▱ 62% ╎ ⋮ plan opus-4.5
92
103
  ```
93
104
 
94
105
  Zero-value meters are omitted, so a fresh session shows only `?/window` instead
@@ -107,11 +118,122 @@ rates and peak tariff built into [`footer/price/config.ts`](./footer/price/confi
107
118
  ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥2.50 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
108
119
  ```
109
120
 
121
+ **User message.** Every user message opens with a two-row `You` nameplate —
122
+ the label over a rule that spans the width — above the message:
123
+
124
+ ```
125
+ You
126
+ ────────────────────────────────────
127
+
128
+ the message the user typed
129
+ ```
130
+
131
+ It is always on in interactive sessions and has no config file. Pi renders a
132
+ user message as Markdown and owns its colors, so bytefly's only lever is a
133
+ `registerMarkdownTransformer`: the nameplate is a single level-2 heading whose
134
+ text is padded so the rule wraps onto the second row, and both rows carry the
135
+ accent because the themes point `mdHeading` at it. The block carries no fill —
136
+ the shipped themes set `userMessageBg` to the terminal default, so it reads like
137
+ the tool blocks. The nameplate is separated from the message by a blank line, so
138
+ the message's own Markdown (a fence, a list, a second paragraph) parses exactly
139
+ as it would without it, and a soft wrap of one long line stays at the margin
140
+ because the renderer wraps it, not bytefly. The label and the rule are tuned
141
+ in [`message/config.ts`](./message/config.ts).
142
+
143
+ **Working timer.** Pi's working row shows a static `Working`, so a long run
144
+ reads the same as a short one. While the row is up, bytefly advances its
145
+ message once a second and hands it back to Pi when the row comes down:
146
+
147
+ ```
148
+ Working… 12s
149
+ ```
150
+
151
+ The count is tied to the row's visibility: it starts on `turn_start`, the event
152
+ the host shows the working indicator on, and stops on `agent_end`, where the
153
+ host clears it, then again on `session_shutdown`. Turns inside one run do not
154
+ restart it — the host keeps the same row up across turns — so the count covers
155
+ the whole run, tool calls included. A retry is a new run, so the count restarts
156
+ when its row reappears. The word and the interval are tuned in
157
+ [`working/config.ts`](./working/config.ts).
158
+
110
159
  **Turn separator.** After each completed turn the extension appends an inert
111
160
  entry and draws a centered `╌╌╌ turn N ╌╌╌` rule across the transcript; see
112
161
  [Turn separator](#turn-separator). No config file is needed and there is no
113
162
  command.
114
163
 
164
+ **Notices.** Any extension in the package can post a transcript notice under the
165
+ shared `my-pi-agent/notice` type; bytefly draws it at the transcript's margin —
166
+ a `source · title` headline at the margin, then rail-led rows, each with its own
167
+ severity glyph:
168
+
169
+ ```
170
+ job · 2 background jobs finished
171
+ ╎ ✓ j1 build · exit 0
172
+ ╎ ✕ j2 test · exit 1
173
+ ```
174
+
175
+ The rail is painted by the notice's worst severity, so a failure reads red at the
176
+ edge like an errored tool result.
177
+
178
+ `job` and `watch` post one when a background job finishes or a watched line
179
+ matches, so the report the model reads is visible too. The type and the
180
+ `NoticeDetails` shape are the shared contract in
181
+ [`lib/notice.ts`](../../lib/notice.ts), and the severity glyphs and item cap
182
+ are tuned in [`notice/config.ts`](./notice/config.ts). Every field is sanitized
183
+ and clipped at the boundary, and a message without `details` keeps Pi's default
184
+ custom-message box.
185
+
186
+ **Tool blocks.** Every tool block leads with the tool's own call line as the
187
+ headline — the tool name in the theme's tool-title colour — and its result body
188
+ sits two columns in directly beneath it, led by the transcript rail, instead of
189
+ Pi's filled status background:
190
+
191
+ ```
192
+ read src/app.ts
193
+ ╎ const answer = 42;
194
+ ```
195
+
196
+ The two tools that mutate a file, `edit` and `write`, carry their result on the
197
+ call body — the edit diff, the written file content — so that body is railed
198
+ like any other result. There is no status band: the diff's own red/green line
199
+ colours, with the changed words in reverse video, and the `edit` / `write`
200
+ headline are what mark a file change.
201
+
202
+ A `registerToolRenderer` resolver switches the row to `renderShell: "self"`,
203
+ leaves the call's own first line as the headline, and indents the rest of the
204
+ call and the whole result under it. A shell call's `$` / `>` prompt becomes the
205
+ tool name and the command is repainted in the body colour, and a primary subject
206
+ (a path, URL, query, pattern) is demoted to the body colour too, so the name is
207
+ the call line's only accent (`bash bun run check`, `read src/app.ts`). The
208
+ tool's own
209
+ `renderCall`/`renderResult` are passed through, so syntax highlighting, diffs,
210
+ previews, and truncation notices are unchanged — only the frame is bytefly's.
211
+ The body carries the transcript rail, painted by status (a running result takes
212
+ the accent, a failure red, a finished one neutral) and drawn with the glyph from
213
+ the `chrome.rail` setting. A result frame is always railed; a call frame is railed
214
+ only for `edit` and `write`, whose call body carries the result — every other
215
+ call's headline and continuation lines stay plain-indented. A call line that
216
+ does not fit wraps, and Pi drops the tool name onto its own line when the
217
+ subject it carries (a long path) is one unbreakable token; those continuation
218
+ lines are still the call, so they stay unrailed and lose the accent too, and the
219
+ rail begins with the result underneath. A tool with
220
+ no renderer of its own is framed around a generic call/result fallback, whose
221
+ call line reads like a built-in — the leading string argument bare, the rest as
222
+ trailing options (`open_path src/app.ts`) — rather than dumping raw `key=value`
223
+ pairs. A tool that draws its own shell (`edit`) is framed too:
224
+ `renderShell: "self"` only tells Pi not to draw its box around the renderer, so
225
+ its diff would otherwise sit at the wrong indent; the frame drops the blank
226
+ edges its own `Box` pads itself with, and hands it a theme whose `bg` is inert so
227
+ it never paints a band the rest of the transcript does not have.
228
+
229
+ The call line is one colour grammar, shared with Pi's built-ins: the tool name is
230
+ `toolTitle` and bold and is the line's only accent; a primary subject (a query, a
231
+ URL, a path, a status) reads in the body role (`label`), counts and summaries are
232
+ `muted`, trailing options are `dim`, and the result body is `toolOutput`. The
233
+ shipped themes point `toolTitle` at the accent and `label` at the body colour, so
234
+ the name is the call line's one accent and the subject follows as body text; a
235
+ theme that maps `toolTitle` elsewhere is respected rather than overridden.
236
+
115
237
  **Extension screen.** `/bytefly [extension] [refresh]` opens a keyboard-driven
116
238
  dock list of every Pi extension the session resolves — across personal settings
117
239
  and, when trusted, the project — one row per extension with its enable checkbox
@@ -133,19 +255,18 @@ the platform notifier (`osascript`, `notify-send`); see
133
255
  | Command | What it does |
134
256
  |---|---|
135
257
  | `/bytefly [extension] [refresh]` | Open the extension screen. In `tui` it mounts a docked list; every other mode prints the grouped listing, or one extension's full findings when named. |
136
- | `/extensions` | Alias for `/bytefly`: the same screen and the same handler. |
137
- | `/doctor [extension] [refresh]` | Alias for `/bytefly`: the same screen and the same handler. |
258
+ | `/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. |
138
259
 
139
- Tab completes extension names and `refresh` for all three commands.
260
+ Tab completes `doctor`, extension names, and `refresh`.
140
261
 
141
262
  ## Behaviour by mode
142
263
 
143
264
  The header, footer, and turn rule are installed only in interactive (`tui`)
144
265
  sessions and are always on; every mode loads the extension, and non-TUI modes
145
266
  change nothing. The screen is available in every mode: in `tui`, `/bytefly` and
146
- its `/extensions` and `/doctor` aliases mount the docked screen, while RPC,
147
- JSON, and print modes print the grouped listing with a per-row health suffix and
148
- the headline, or that extension's full findings when one is named. The
267
+ `/bytefly doctor` mount the docked screen, while RPC, JSON, and print modes
268
+ print the grouped listing with a per-row health suffix and the headline, or that
269
+ extension's full findings when one is named. The
149
270
  proactive session-start warning is skipped when there is no UI. Notify runs in
150
271
  every mode and picks its method from the environment; the OSC methods need a
151
272
  TTY, so a print/JSON/RPC run gets an OS-level notification or none.
@@ -153,14 +274,19 @@ TTY, so a print/JSON/RPC run gets an OS-level notification or none.
153
274
  ## Configuration
154
275
 
155
276
  The header is tuned in [`header/config.ts`](./header/config.ts) — the mark and
156
- identity theme tokens, the padding and tagline, and the grouped help entries
157
- (each naming a Pi keybinding resolved live or a raw command prefix). The footer's
277
+ identity theme tokens, the tagline, and the grouped help entries (each naming a
278
+ Pi keybinding resolved live or a raw command prefix). The footer's
158
279
  content and layout are tuned in [`footer/config.ts`](./footer/config.ts) —
159
280
  thresholds, gauge
160
281
  glyphs and widths, separators, icons, and status keys. The turn separator has no
161
282
  JSON either: its glyph, label prefix, padding, and colors are tuned in
162
283
  [`separator/config.ts`](./separator/config.ts).
163
284
 
285
+ The transcript rail's glyph is the one chrome value that is JSON-configurable:
286
+ the `chrome` section's `rail` key (default `╎`) picks a single-column rail glyph,
287
+ read at `session_start`/`session_tree`. Everything else about the chrome is tuned
288
+ in code.
289
+
164
290
  Everything JSON-configurable lives in `bytefly.json`, one shared document with a
165
291
  top-level section per extension (see [Configuration](../../docs/configuration.md)).
166
292
  It is read from the global document, then `<cwd>/.pi/bytefly.json`. The old
@@ -354,7 +480,7 @@ its server runs, and shows the two-tone `◉ <port>` (green glyph, accent port).
354
480
  The trailing slot shows plan-style alerts when present and is omitted otherwise;
355
481
  a plan chip's trailing plan file name is dropped, so the slot reads `⋮ plan`
356
482
  even though the built-in footer keeps the name. An alert (a failed job) sorts to
357
- the front of the slot. The slot trails the meters after a `│`, so the context
483
+ the front of the slot. The slot trails the meters after a `╎`, so the context
358
484
  gauge always starts the line. It keeps each extension's own colour when it
359
485
  compacts, and it is droppable: a tight bar drops the slot rather than truncating
360
486
  the model, which sits at the end of the line.
@@ -394,10 +520,13 @@ it:
394
520
  extension Pi resolves for the session, so the list matches what actually loads.
395
521
  It merges the former extensions picker and doctor report into one row per
396
522
  extension: the picker's enable checkbox and label, the doctor's worst-severity
397
- glyph and summary. `/extensions` and `/doctor [extension] [refresh]` are
398
- registered against the same handler, so all three names open the same screen;
399
- `[extension]` narrows it to one extension and `refresh` re-probes the doctor
400
- before it opens. Tab completes extension names and `refresh`.
523
+ glyph and summary. The screen is drawn from the design system: a label-first
524
+ `bytefly ────` title rule, one row grammar (`❯ ◼ ✓ name … summary`), and the
525
+ shared `◼` / `◻` checkbox. `/bytefly doctor [extension] [refresh]` selects the doctor
526
+ report, the same text the `run_doctor` tool returns; in `tui` it opens the same
527
+ screen. `[extension]` narrows either surface to one extension and `refresh`
528
+ re-probes the doctor before it opens. Tab completes `doctor`, extension names,
529
+ and `refresh`.
401
530
 
402
531
  The screen reuses Pi's own settings and package resolution (`SettingsManager`
403
532
  plus `DefaultPackageManager` with `onMissing: "skip"`), so opening it never
@@ -408,7 +537,7 @@ installs or fetches a package.
408
537
  - Omits `bytefly`'s own entrypoint from the picker groups, so the tool that
409
538
  manages the list is never a selectable row; its doctor report still appears in
410
539
  the trailing `Not resolved` group.
411
- - Shows whether each one is enabled (`[x]`) or disabled (`[ ]`).
540
+ - Shows whether each one is enabled (`◼`) or disabled (`◻`).
412
541
  - Shows the worst severity for a reported extension (`✓` ok, `!` degraded, `✕`
413
542
  missing, `∅` disabled) and its summary; an extension with no doctor report
414
543
  leaves the glyph column and the summary blank rather than claiming `ok`.
@@ -524,6 +653,9 @@ control-free line before it reaches an escape sequence or a shell argument.
524
653
 
525
654
  ## Limitations
526
655
 
656
+ - **`doctor` is a reserved first argument.** `/bytefly doctor` always selects the
657
+ report, so an extension literally named `doctor` cannot be narrowed to by
658
+ name.
527
659
  - **The picker manages only the `extensions` resource type.** Skills, prompts,
528
660
  and themes are handled by Pi's own `pi config`.
529
661
  - **Built-in extensions are not listed** (`builtin:mcp`, `builtin:codemode`, …):
@@ -567,6 +699,11 @@ control-free line before it reaches an escape sequence or a shell argument.
567
699
  - **An explicit `method` is honored blindly.** Point `method` at a notifier the
568
700
  host lacks (or an OSC method off a TTY) and the notification is silently
569
701
  dropped; only `auto` inspects the environment.
702
+ - **The working counter is one global row.** `setWorkingMessage` drives the
703
+ single working row, so bytefly and any other extension setting it race — last
704
+ writer wins — and the message is reset to Pi's default at `agent_end`. If the
705
+ row is hidden (`ctx.ui.setWorkingVisible(false)`) the counter has no surface,
706
+ and a reload mid-run does not resume it until the next turn.
570
707
 
571
708
  ## Non-goals
572
709
 
@@ -590,6 +727,9 @@ control-free line before it reaches an escape sequence or a shell argument.
590
727
  whole surface.
591
728
  - No sound, action buttons, notification history, or de-duplication; a
592
729
  notification is a fire-and-forget message, not an inbox.
730
+ - No frozen per-run working duration: the counter is live and is handed back to
731
+ Pi when the working row comes down, because the extension API exposes no hook
732
+ to keep a finished run's total.
593
733
 
594
734
  The built-in footer's `(auto)` auto-compact indicator and `(sub)`
595
735
  subscription-cost marker are not reachable from `ExtensionContext` (there is no
@@ -600,35 +740,100 @@ rather than showing a wrong value.
600
740
 
601
741
  | Integration point | Details |
602
742
  |---|---|
603
- | Header | `ctx.ui.setHeader` with a custom component, replacing Pi's built-in header; installed only when `ctx.mode === "tui"`. 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. |
743
+ | Header | `ctx.ui.setHeader` with a custom component, replacing Pi's built-in header; installed only when `ctx.mode === "tui"`. 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. |
604
744
  | Footer | `ctx.ui.setFooter` with a custom component, replacing Pi's built-in footer; installed only when `ctx.mode === "tui"`. |
605
- | Screen command | `pi.registerCommand("bytefly"`, `"extensions"`, and `"doctor"`) share one handler: `tui` mounts a dock screen via `ctx.ui.custom()` with the rails suppressed, other modes print through `ctx.ui.notify()`. Tab completes extension names and `refresh`. |
745
+ | 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. |
746
+ | 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`. The type and shape come from `lib/notice.ts`. A message without `details` falls back to Pi's default custom-message box. |
747
+ | 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. |
748
+ | Screen command | `pi.registerCommand("bytefly")` takes `[doctor] [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, or `run_doctor`'s report for `doctor`. Tab completes `doctor`, extension names, and `refresh`. |
606
749
  | Turn separator | `registerEntryRenderer("bytefly-separator")` draws each rule; `agent_settled` appends `pi.appendEntry("bytefly-separator", { turn })` in `tui` mode only. |
750
+ | 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. |
607
751
  | 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. |
608
752
  | Picker discovery | `SettingsManager.create(...)` plus `DefaultPackageManager.resolve({ onMissing: "skip" })`, both public exports; both views drop `bytefly`'s own entrypoint. |
609
753
  | Picker writes | `SettingsManager` setters (`setExtensionPaths`, `setPackages`, `setProjectExtensionPaths`, `setProjectPackages`) then `flush()`; `ctx.reload()` after the screen closes when changes are pending. |
610
754
  | Doctor tool | `pi.registerTool("run_doctor")`, read-only (`readOnlyHint: true`), `exposure: "direct"`, active by default, 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. |
611
755
  | Doctor probes | `run_doctor` and the screen share the session cache created by `registerDoctor`; `/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. |
612
756
  | 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. |
613
- | 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), and the `notify` and `doctor` sections at `session_start`. Nothing watches the document. |
757
+ | 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. |
614
758
  | Data sources | `footerData.getGitBranch()`, `footerData.getAvailableProviderCount()`, `ctx.getContextUsage()`, `ctx.sessionManager.getEntries()`, `ctx.sessionManager.getSessionName()`, `ctx.model`; subscribes to `footerData.onBranchChange` to repaint. |
615
759
  | 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 only through `settings.json`. 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. |
616
- | Lifecycle | `session_start` installs the header and footer, points the doctor runtime at the session cwd, probes the extensions with hard requirements when `bytefly.json`'s `doctor.proactive` is on, and loads the notify config and subscribes to the job bus; `session_tree` reinstalls the header and footer; `session_shutdown` restores Pi's built-in header and footer, disposes the branch subscription, drops doctor's cached findings, and drops the notify bus subscription. |
760
+ | Lifecycle | `session_start` installs the header and 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 loads the notify config and subscribes to the job bus; `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, and drops the notify bus subscription. |
617
761
 
618
762
  ## Design notes
619
763
 
764
+ - **One kit, one look.** Every bytefly surface is drawn from the package's
765
+ `lib/tui/` design system: a role is resolved by the active theme, a rule comes
766
+ from `rules.ts`, a list row from `rows.ts`, a meter from `gauge.ts`, and a
767
+ width from `columns.ts`. Nothing in bytefly names a literal color, and a
768
+ theme swap re-skins the whole chrome.
769
+ - **The user message is shaped, not painted.** Pi renders a user message as
770
+ Markdown and owns its colors, so bytefly's only lever is a Markdown
771
+ transformer: the two-row nameplate is one level-2 heading whose text is padded
772
+ to the width so the rule wraps below the label, and both rows take the accent
773
+ from the theme's `mdHeading`, because no extension API can color it directly.
774
+ Markdown has no multi-line heading and Pi inserts a blank line after a
775
+ heading block, so a single wrapped heading is the only way to keep the two
776
+ rows adjacent and in the accent. The block has no fill: the shipped themes set
777
+ `userMessageBg` to the terminal default, so it matches the tool blocks. The
778
+ nameplate is separated from the message by a blank line, so the message's own
779
+ Markdown parses unchanged and a soft wrap stays at the margin; hard-wrapping
780
+ Markdown here would corrupt a break inside an inline construct.
781
+ - **A notice is one shared contract, not a foreign type.** Pi resolves a
782
+ custom-message renderer by exact type with no wildcard, so `lib/notice.ts`
783
+ owns the type and the `NoticeDetails` shape: a producer states model text and
784
+ renderer details once, and bytefly owns only the look. One type for every
785
+ producer also lets `job` and `watch` share one prune
786
+ (`pruneStaleNotices`) keyed on `details.source`, so neither drops the other's
787
+ note from context.
788
+ - **The frame delegates; it does not re-render.** Pi resolves tool renderers
789
+ through the extension chain, and the resolved object is the definition the row
790
+ reads, so a resolver can override `renderShell` and wrap `next()`'s
791
+ `renderCall`/`renderResult`. bytefly replaces only the frame — Pi's filled
792
+ status box for an indent — and keeps every built-in's diff, syntax
793
+ highlighting, and truncation. The call and result are sibling components with
794
+ no spacer between them, so the call's headline and the result read as one
795
+ block; the result body hangs from the rail, and `edit`/`write` rail their call
796
+ body because it carries the result, except for the lines of a call line that
797
+ wrapped onto the body — a 121-column path under a bare `edit` is still the
798
+ call, not diff content, and the rail starts at the diff. A tool that renders
799
+ its own shell (`edit`)
800
+ is framed too: its blank edge lines are trimmed so the body starts under the
801
+ call, and it is handed a theme whose `bg` is inert so it paints no band. Each base renderer is handed its own
802
+ previous component (tracked in the row's `state`), never the frame: Pi gives a
803
+ renderer the component it returned last time, and a built-in calls `setText` on
804
+ it, so handing back the frame would break it. A base renderer that throws is
805
+ caught here too, and the frame falls back to its own call/result line at the
806
+ frame's pad; letting the throw reach Pi would swap in Pi's generic call line,
807
+ which sits a column to the left of the framed one.
808
+ - **One frame, one left edge.** Every transcript block that is not prose shares
809
+ the same frame (`lib/tui/indent.ts`): a tool block, a notice, and a subagent's
810
+ quoted output take one indent, so they hang from one left edge instead of four,
811
+ and a tool result's body sits `BODY_INDENT` columns in under its headline. A
812
+ body and a notice row lead that body with the transcript rail, whose
813
+ glyph is the one configurable part (`chrome.rail`, default `╎`); a result frame
814
+ is always railed, and a call frame only for `edit`/`write`, whose call body
815
+ carries the result. Pi pads its own messages and hands the value to tool renderers
816
+ (`context.outputPad`) and message renderers (`options.outputPad`), but gives an
817
+ entry renderer none and adds its component raw, so bytefly's blocks inset
818
+ themselves by that pad: the tool frame and the notice take it from Pi, and the
819
+ turn separator reads the last value a sibling observed (`pad.ts`). Pi's setting
820
+ is the only knob — bytefly adds no padding of its own. The footer's group
821
+ separator still draws `╎` between chips — a divider, not a rail.
620
822
  - **The header is a nameplate, not a dashboard.** Pi's built-in brand colors are
621
823
  fixed, so the mark ignores the active theme; bytefly's header draws a two-tone
622
- mark from `accent`/`warning` and pairs it with the agent identity, the chrome
623
- identity, and a grouped help block. It adds no session or model data — that is
624
- the footer's job — and it stays a fixed four-plus rows so the startup
625
- transcript never reflows.
824
+ mark from the `accent`/`warn` roles, a signature rule whose accent lead sits
825
+ under the mark, and pairs them with the agent identity, the chrome identity,
826
+ and a grouped help block. It takes its margin from Pi's `outputPad`, like the
827
+ tool frame and the notice, and hangs the help one body indent inside it, so the
828
+ header follows a non-default pad instead of assuming a column. It adds no
829
+ session or model data — that is the footer's job — and it stays a fixed
830
+ four-plus rows so the startup transcript never reflows.
626
831
  - **Two lines, four zones.** Line 1 is identity (`pwd` left, branch + worktree
627
832
  right); line 2 is resources (meters left, model + level right). This keeps
628
833
  "where am I / what state" separate from "how much am I burning".
629
834
  - **The gauge is anchored at column 0.** A leading mode slot would shift the
630
835
  context percentage left and right as plan mode came and went; trailing it
631
- after a `│` keeps the percentage in a fixed place while the mode stays
836
+ after a `╎` keeps the percentage in a fixed place while the mode stays
632
837
  visible as the meters drop.
633
838
  - **Statuses are routed, not repeated.** The built-in footer renders every
634
839
  `setStatus` value on its own line; the bar splits them by meaning (`worktree`
@@ -648,19 +853,23 @@ rather than showing a wrong value.
648
853
  non-zero usage, so it reads full only at 100% and a small percentage never
649
854
  disappears; unknown usage draws no gauge at all. Only at extreme widths does
650
855
  `truncateToWidth` clip the remaining text.
651
- - **Separators shrink to a single space.** Padded separators (` · `, ` │ `)
856
+ - **Separators shrink to a single space.** Padded separators (` · `, ` ╎ `)
652
857
  collapse to one space before `truncateToWidth` applies, so adjacent segments
653
- never collide and the ellipsis never replaces a `│` with nothing after it.
858
+ never collide and the ellipsis never replaces a `╎` with nothing after it.
654
859
  - **Statuses compact with their colour.** A themed status's compact icon form
655
860
  reattaches its opening and closing SGR sequences around the visible text, so a
656
861
  red `✗1` stays red instead of losing its colour exactly when the bar tightens.
657
862
  A two-token `icon count` badge (for `rewind`: `↺ 2`) compacts to `↺2`, keeping
658
863
  the count. The slot sorts an alert (a failed job) ahead of the modes and is
659
864
  droppable, so it goes before the model is clipped.
660
- - **Neutral by default; color means something.** Only the context gauge carries
661
- status color, and it stays `muted` below 70%; cost is `muted`, the session and
662
- branch are `muted`, and tokens/cache/window are `dim`, so yellow and red are
663
- reserved for the gauge crossing a threshold rather than always on.
865
+ - **Neutral by default; color means something.** A tool block carries no status
866
+ colour except the accent tool name and the status-coloured result rail; an
867
+ informational notice leads with `muted`; a failure is red only where the
868
+ tool's own output says so, so the transcript is not a wall of green.
869
+ The context gauge is the other coloured surface: it stays `muted` below 70%,
870
+ cost is `muted`, the session and branch are `muted`, and tokens/cache/window are
871
+ `dim`, so yellow and red are reserved for a threshold or a failure rather than
872
+ always on.
664
873
  - **Data is read fresh, except the usage scan.** The component resolves the
665
874
  theme through a getter and reads a fresh snapshot each render. The expensive
666
875
  part — summing token/cost totals across the session — is memoized by
@@ -722,7 +931,7 @@ rather than showing a wrong value.
722
931
  reads the same command whether a tool failed or the report named it.
723
932
  - **The doctor is quiet by default at startup.** Only `missing` findings warn,
724
933
  only for enabled extensions, only with a UI, and only once per session;
725
- `degraded` findings — including a malformed config — wait in `/doctor`, because
934
+ `degraded` findings — including a malformed config — wait in `/bytefly doctor`, because
726
935
  a session that nags about an optional package is worse than one that never
727
936
  mentions it.
728
937
  - **Probe the real thing.** `subagent`'s requirement resolves through the same
@@ -735,15 +944,22 @@ rather than showing a wrong value.
735
944
  ## Files
736
945
 
737
946
  The surfaces are folders: `header/`, `footer/` (the status bar and its price
738
- calculation), `separator/`, `panel/` (the unified screen and its merged model),
947
+ calculation), `message/` (the user-message marker), `notice/` (the shared notice),
948
+ `block/` (the tool-block frame), `separator/`, `working/` (the live
949
+ elapsed counter on Pi's working row), `panel/` (the
950
+ unified screen and its merged model),
739
951
  `picker/` (discovery and the settings store), `doctor/` (the `run_doctor` tool
740
- and its probe runtime), and `notify/`.
952
+ and its probe runtime), and `notify/`. The chrome and the screen are drawn from
953
+ the package's [`lib/tui/`](../../lib/tui/) design system (tokens, rules, rows,
954
+ columns, gauge), so the look follows the active theme.
741
955
 
742
956
  | File | Responsibility |
743
957
  |---|---|
744
- | `index.ts` | Composition root: the disable guard and the six `register*` calls. |
745
- | `header/config.ts` | Header tuning (`HEADER_CONFIG`): mark and identity tokens, padding, tagline, and the grouped help entries. |
746
- | `header/component.ts` | `ByteflyHeader`: the mark, identity rows, rule, and grouped help, plus its factory. |
958
+ | `index.ts` | Composition root: the disable guard, the eleven `register*` calls, the one shared output-pad store, and the rail source. |
959
+ | `pad.ts` | `PadSource` and `createPadStore`: one record of Pi's output pad, so the tool frame, the notice, and the turn separator inset by the same margin. |
960
+ | `chrome.ts` | The transcript rail (`RAIL_ROLES`, `railGlyph`), the `chrome` section's normalizer and loader, and the `RailSource` that `registerChrome` keeps current on `session_start`/`session_tree`. |
961
+ | `header/config.ts` | Header tuning (`HEADER_CONFIG`): mark and identity roles, tagline, and the grouped help entries. |
962
+ | `header/component.ts` | `ByteflyHeader`: the mark, identity rows, signature rule, and grouped help, plus its factory. |
747
963
  | `header/register.ts` | Installs/restores the header and owns its session lifecycle. |
748
964
  | `footer/register.ts` | Installs/restores the footer and owns its session lifecycle. |
749
965
  | `footer/config.ts` | Bar tuning (`CONFIG`): thresholds, gauge, separators, icons, status keys, labels. |
@@ -755,22 +971,35 @@ and its probe runtime), and `notify/`.
755
971
  | `footer/price/rates.ts` | Model-key matching: exact and wildcard, most specific first. |
756
972
  | `footer/price/schedule.ts` | Peak-window parsing and evaluation in a configured zone. |
757
973
  | `footer/price/price.ts` | Records → totals: rates, peak multiplier, recorded-cost fallback. |
758
- | `footer/render/format.ts` | Generic formatters (tokens, cost in the configured currency, cwd, gauge, colors). |
974
+ | `footer/render/format.ts` | Generic formatters (tokens, cost in the configured currency, cwd, the context role, truncation). |
759
975
  | `footer/render/status.ts` | Status-chip transforms: `compactStatus` and `dropStatusDetail`. |
760
- | `footer/render/segments.ts` | The segment builders: forms, weights, separators, colors. |
976
+ | `footer/render/segments.ts` | The segment builders: forms, weights, separators, roles. |
761
977
  | `footer/render/lines.ts` | Snapshot → two `LineSpec`s; status routing. |
762
978
  | `footer/render/layout.ts` | Width reduction and right-zone folding. |
763
979
  | `footer/render/footer.ts` | `StatusBar` component and footer factory. |
764
- | `separator/config.ts` | Turn-rule tuning: `customType`, glyph, label prefix, padding, and colors. |
980
+ | `message/config.ts` | Nameplate tuning (`MESSAGE_CONFIG`): the label and the rule. |
981
+ | `message/format.ts` | Pure `nameplate` and `styleUserMessage`: the two-row nameplate (label over a full-width rule) and its placement above the message. |
982
+ | `message/register.ts` | Registers the Markdown transformer. |
983
+ | `notice/config.ts` | Notice tuning (`NOTICE_CONFIG`): source/title/detail roles, the severity glyphs and roles, the shared rail roles, and the item cap. |
984
+ | `notice/component.ts` | Pure `noticeLines` and `NoticeComponent`: the headline at the margin, the rail-led severity rows, sanitized and width-clipped. |
985
+ | `notice/register.ts` | Registers the message renderer for the shared notice type and hands it the rail source. |
986
+ | `block/config.ts` | `BlockStatus` and `BLOCK_CONFIG.roles`: the design role a result's rail takes for each state. |
987
+ | `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. |
988
+ | `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. |
989
+ | `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). |
990
+ | `separator/config.ts` | Turn-rule tuning: `customType`, glyph, label prefix, padding, and roles. |
765
991
  | `separator/register.ts` | Registers the entry renderer and appends a separator on `agent_settled`. |
766
992
  | `separator/component.ts` | `SeparatorComponent`: the stateless entry renderer. |
767
- | `separator/format.ts` | Pure `separatorLabel` / `separatorParts` / `separatorLine`. |
768
- | `separator/types.ts` | `TurnSeparatorData`, `SeparatorParts`. |
993
+ | `separator/format.ts` | Pure `separatorLine`: the centered turn rule, drawn by the kit's `centeredRule`. |
994
+ | `separator/types.ts` | `TurnSeparatorData`. |
995
+ | `working/config.ts` | Counter tuning (`WORKING_CONFIG`): the word, the ellipsis, and the tick interval. |
996
+ | `working/format.ts` | Pure `formatElapsed` (whole seconds) and the working-message shape. |
997
+ | `working/register.ts` | Starts the tick on `turn_start` and cancels it on `agent_end`/`session_shutdown`. |
769
998
  | `panel/types.ts` | `PanelRow` and `PanelModel`, the merged row model. |
770
999
  | `panel/join.ts` | The item/report union with the unambiguous-name health rule and unique row keys, groups, and ordering. |
771
1000
  | `panel/format.ts` | Non-TUI text: the headline and the grouped listing with a per-row health suffix. |
772
- | `panel/tui.ts` | `PanelComponent`, the unified dock screen. |
773
- | `panel/commands.ts` | `/bytefly`, `/extensions`, and `/doctor`: one runner for the screen and its text modes. |
1001
+ | `panel/tui.ts` | `PanelComponent`, the unified dock screen, drawn from the `lib/tui/` kit. |
1002
+ | `panel/commands.ts` | `/bytefly`: one command for the screen, the `doctor` report, and their text modes. |
774
1003
  | `picker/types.ts` | Picker host seams (`SettingsStore`, `ExtensionResolver`, `Discovery`) and row types. |
775
1004
  | `picker/resources.ts` | Pure grouping, labels, display names, and pattern helpers. |
776
1005
  | `picker/toggle.ts` | Pure next-settings transforms for global and project toggles. |
@@ -781,7 +1010,7 @@ and its probe runtime), and `notify/`.
781
1010
  | `doctor/tools.ts` | The `run_doctor` tool and its renderers. |
782
1011
  | `doctor/schema.ts` | Tool parameters and the structured output schema. |
783
1012
  | `doctor/config.ts` | `DEFAULT_CONFIG`, `normalizeConfig`, `loadConfig` for the `doctor` section of `bytefly.json`. |
784
- | `doctor/format.ts` | Pure report text, summary, glyphs, detail lines, and the proactive warning sentence. |
1013
+ | `doctor/format.ts` | Pure report text, summary, severity roles and glyphs, detail lines, and the proactive warning sentence. |
785
1014
  | `notify/types.ts` | `NotifyConfig`, `NotifyMethod`, and the method list. |
786
1015
  | `notify/config.ts` | The `notify` section of `bytefly.json`: defaults, validation, loader. |
787
1016
  | `notify/dispatch.ts` | Method selection and delivery: OSC 777/99, Windows toast, `osascript`, `notify-send`, over injected seams. |
@@ -825,3 +1054,12 @@ through the factory (`doctor/register`). The notify capability is covered by its
825
1054
  (`notify/config`), the pure text builders (`notify/format`), method selection and
826
1055
  delivery over recording seams (`notify/dispatch`), and its three triggers,
827
1056
  reload, and shutdown through `registerNotify` (`notify/register`).
1057
+ The user-message nameplate is covered by its pure transform
1058
+ (`message/format`, including a render through pi-tui's real `Markdown`) and its
1059
+ registration (`message/register`), plus the factory check in `extension`.
1060
+ The notice surface is covered by its pure rows (`notice/component`), its
1061
+ renderer registration (`notice/register`), and the shared contract
1062
+ (`lib/notice`), plus the factory check in `extension`.
1063
+ The tool-block frame is covered by its pure `toolBlockLines` (`block/component`)
1064
+ and its generic fallback (`block/fallback`), plus the resolver's shell override,
1065
+ self-shell carve-out, and fallback framing (`block/register`).