@gavin-hu/my-pi-agent 0.9.0 → 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 (132) hide show
  1. package/README.md +28 -2
  2. package/README.zh-CN.md +27 -2
  3. package/assets/bytefly.schema.json +17 -1
  4. package/assets/chrome.png +0 -0
  5. package/assets/job.png +0 -0
  6. package/assets/plan-review.png +0 -0
  7. package/assets/question.png +0 -0
  8. package/assets/rewind.png +0 -0
  9. package/assets/todo.png +0 -0
  10. package/assets/tool-renderings.png +0 -0
  11. package/assets/tools/ask_user_question.png +0 -0
  12. package/assets/tools/bash.png +0 -0
  13. package/assets/tools/edit.png +0 -0
  14. package/assets/tools/enter_plan_mode.png +0 -0
  15. package/assets/tools/enter_worktree.png +0 -0
  16. package/assets/tools/exit_plan_mode.png +0 -0
  17. package/assets/tools/exit_worktree.png +0 -0
  18. package/assets/tools/find.png +0 -0
  19. package/assets/tools/goal.png +0 -0
  20. package/assets/tools/grep.png +0 -0
  21. package/assets/tools/job.png +0 -0
  22. package/assets/tools/list_worktrees.png +0 -0
  23. package/assets/tools/ls.png +0 -0
  24. package/assets/tools/memory.png +0 -0
  25. package/assets/tools/notice.png +0 -0
  26. package/assets/tools/open_path.png +0 -0
  27. package/assets/tools/prune_worktrees.png +0 -0
  28. package/assets/tools/read.png +0 -0
  29. package/assets/tools/read_doc.png +0 -0
  30. package/assets/tools/run_doctor.png +0 -0
  31. package/assets/tools/send_wechat.png +0 -0
  32. package/assets/tools/set_plan_steps.png +0 -0
  33. package/assets/tools/subagent.png +0 -0
  34. package/assets/tools/todo.png +0 -0
  35. package/assets/tools/turn-rule.png +0 -0
  36. package/assets/tools/user-message.png +0 -0
  37. package/assets/tools/watch.png +0 -0
  38. package/assets/tools/web_fetch.png +0 -0
  39. package/assets/tools/web_search.png +0 -0
  40. package/assets/tools/write.png +0 -0
  41. package/assets/tools/write_plan.png +0 -0
  42. package/extensions/bytefly/README.md +261 -36
  43. package/extensions/bytefly/block/component.ts +282 -0
  44. package/extensions/bytefly/block/config.ts +24 -0
  45. package/extensions/bytefly/block/fallback.ts +120 -0
  46. package/extensions/bytefly/block/register.ts +164 -0
  47. package/extensions/bytefly/chrome.ts +97 -0
  48. package/extensions/bytefly/doctor/tools.ts +5 -2
  49. package/extensions/bytefly/header/component.ts +21 -10
  50. package/extensions/bytefly/header/config.ts +0 -3
  51. package/extensions/bytefly/header/register.ts +6 -1
  52. package/extensions/bytefly/index.ts +32 -12
  53. package/extensions/bytefly/message/config.ts +15 -0
  54. package/extensions/bytefly/message/format.ts +42 -0
  55. package/extensions/bytefly/message/register.ts +15 -0
  56. package/extensions/bytefly/notice/component.ts +107 -0
  57. package/extensions/bytefly/notice/config.ts +41 -0
  58. package/extensions/bytefly/notice/register.ts +27 -0
  59. package/extensions/bytefly/pad.ts +42 -0
  60. package/extensions/bytefly/separator/component.ts +2 -1
  61. package/extensions/bytefly/separator/format.ts +17 -10
  62. package/extensions/bytefly/separator/register.ts +3 -2
  63. package/extensions/bytefly/working/config.ts +20 -0
  64. package/extensions/bytefly/working/format.ts +25 -0
  65. package/extensions/bytefly/working/register.ts +88 -0
  66. package/extensions/document/README.md +11 -4
  67. package/extensions/document/paging.ts +10 -10
  68. package/extensions/document/render.ts +7 -7
  69. package/extensions/file-browse/README.md +3 -1
  70. package/extensions/file-browse/client.ts +9 -2
  71. package/extensions/file-browse/git.ts +4 -7
  72. package/extensions/file-browse/html.ts +27 -17
  73. package/extensions/file-open/README.md +6 -0
  74. package/extensions/goal/README.md +16 -10
  75. package/extensions/goal/format.ts +7 -20
  76. package/extensions/goal/tools.ts +5 -17
  77. package/extensions/goal/tui.ts +12 -40
  78. package/extensions/job/README.md +21 -12
  79. package/extensions/job/format.ts +2 -2
  80. package/extensions/job/index.ts +25 -20
  81. package/extensions/job/tools.ts +5 -3
  82. package/extensions/job/tui.ts +13 -8
  83. package/extensions/memory/README.md +6 -0
  84. package/extensions/memory/format.ts +5 -3
  85. package/extensions/plan/README.md +32 -12
  86. package/extensions/plan/tools.ts +20 -5
  87. package/extensions/plan/web-client.ts +58 -17
  88. package/extensions/plan/web-page.ts +34 -9
  89. package/extensions/plan/web-router.ts +40 -9
  90. package/extensions/plan/web.ts +22 -2
  91. package/extensions/question/README.md +13 -1
  92. package/extensions/question/answers.ts +25 -18
  93. package/extensions/question/tools.ts +11 -6
  94. package/extensions/rewind/README.md +2 -0
  95. package/extensions/rewind/format.ts +6 -1
  96. package/extensions/rewind/index.ts +3 -1
  97. package/extensions/rewind/snapshot-run.ts +1 -1
  98. package/extensions/subagent/README.md +6 -0
  99. package/extensions/subagent/index.ts +1 -1
  100. package/extensions/subagent/render.ts +56 -29
  101. package/extensions/todo/README.md +19 -12
  102. package/extensions/todo/format.ts +17 -25
  103. package/extensions/todo/tools.ts +2 -1
  104. package/extensions/todo/tui.ts +3 -2
  105. package/extensions/watch/README.md +14 -6
  106. package/extensions/watch/format.ts +2 -2
  107. package/extensions/watch/index.ts +28 -23
  108. package/extensions/watch/tools.ts +5 -3
  109. package/extensions/watch/tui.ts +12 -7
  110. package/extensions/web-access/README.md +16 -7
  111. package/extensions/web-access/fetch/tool.ts +1 -1
  112. package/extensions/web-access/search/tool.ts +3 -3
  113. package/extensions/wechat/README.md +6 -0
  114. package/extensions/worktree/README.md +6 -0
  115. package/lib/README.md +2 -1
  116. package/lib/format.ts +17 -0
  117. package/lib/notice.ts +87 -0
  118. package/lib/tui/chrome.ts +8 -4
  119. package/lib/tui/cursor.ts +2 -1
  120. package/lib/tui/glyphs.ts +10 -11
  121. package/lib/tui/indent.ts +166 -0
  122. package/lib/tui/index.ts +3 -1
  123. package/lib/tui/rows.ts +39 -5
  124. package/lib/tui/rules.ts +9 -3
  125. package/lib/tui/tokens.ts +14 -0
  126. package/lib/web/base.ts +2 -1
  127. package/lib/web/components.ts +3 -1
  128. package/lib/web/layout.ts +5 -1
  129. package/package.json +7 -3
  130. package/themes/bytefly-dark.json +7 -8
  131. package/themes/bytefly-light.json +10 -11
  132. 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,10 +18,18 @@ 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
35
  `accent` / `warn` roles, the agent identity (`Pi`, bold, and its version), the
@@ -35,35 +44,37 @@ cannot render half blocks the mark falls back to a two-tone `Pi` wordmark.
35
44
  █▀█ Pi v1.2.0
36
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
@@ -152,14 +274,19 @@ TTY, so a print/JSON/RPC run gets an OS-level notification or none.
152
274
  ## Configuration
153
275
 
154
276
  The header is tuned in [`header/config.ts`](./header/config.ts) — the mark and
155
- identity theme tokens, the padding and tagline, and the grouped help entries
156
- (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
157
279
  content and layout are tuned in [`footer/config.ts`](./footer/config.ts) —
158
280
  thresholds, gauge
159
281
  glyphs and widths, separators, icons, and status keys. The turn separator has no
160
282
  JSON either: its glyph, label prefix, padding, and colors are tuned in
161
283
  [`separator/config.ts`](./separator/config.ts).
162
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
+
163
290
  Everything JSON-configurable lives in `bytefly.json`, one shared document with a
164
291
  top-level section per extension (see [Configuration](../../docs/configuration.md)).
165
292
  It is read from the global document, then `<cwd>/.pi/bytefly.json`. The old
@@ -353,7 +480,7 @@ its server runs, and shows the two-tone `◉ <port>` (green glyph, accent port).
353
480
  The trailing slot shows plan-style alerts when present and is omitted otherwise;
354
481
  a plan chip's trailing plan file name is dropped, so the slot reads `⋮ plan`
355
482
  even though the built-in footer keeps the name. An alert (a failed job) sorts to
356
- 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
357
484
  gauge always starts the line. It keeps each extension's own colour when it
358
485
  compacts, and it is droppable: a tight bar drops the slot rather than truncating
359
486
  the model, which sits at the end of the line.
@@ -572,6 +699,11 @@ control-free line before it reaches an escape sequence or a shell argument.
572
699
  - **An explicit `method` is honored blindly.** Point `method` at a notifier the
573
700
  host lacks (or an OSC method off a TTY) and the notification is silently
574
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.
575
707
 
576
708
  ## Non-goals
577
709
 
@@ -595,6 +727,9 @@ control-free line before it reaches an escape sequence or a shell argument.
595
727
  whole surface.
596
728
  - No sound, action buttons, notification history, or de-duplication; a
597
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.
598
733
 
599
734
  The built-in footer's `(auto)` auto-compact indicator and `(sub)`
600
735
  subscription-cost marker are not reachable from `ExtensionContext` (there is no
@@ -605,20 +740,24 @@ rather than showing a wrong value.
605
740
 
606
741
  | Integration point | Details |
607
742
  |---|---|
608
- | 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. |
609
744
  | Footer | `ctx.ui.setFooter` with a custom component, replacing Pi's built-in footer; installed only when `ctx.mode === "tui"`. |
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. |
610
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`. |
611
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. |
612
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. |
613
752
  | Picker discovery | `SettingsManager.create(...)` plus `DefaultPackageManager.resolve({ onMissing: "skip" })`, both public exports; both views drop `bytefly`'s own entrypoint. |
614
753
  | Picker writes | `SettingsManager` setters (`setExtensionPaths`, `setPackages`, `setProjectExtensionPaths`, `setProjectPackages`) then `flush()`; `ctx.reload()` after the screen closes when changes are pending. |
615
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. |
616
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. |
617
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. |
618
- | 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. |
619
758
  | Data sources | `footerData.getGitBranch()`, `footerData.getAvailableProviderCount()`, `ctx.getContextUsage()`, `ctx.sessionManager.getEntries()`, `ctx.sessionManager.getSessionName()`, `ctx.model`; subscribes to `footerData.onBranchChange` to repaint. |
620
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. |
621
- | 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. |
622
761
 
623
762
  ## Design notes
624
763
 
@@ -627,19 +766,74 @@ rather than showing a wrong value.
627
766
  from `rules.ts`, a list row from `rows.ts`, a meter from `gauge.ts`, and a
628
767
  width from `columns.ts`. Nothing in bytefly names a literal color, and a
629
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.
630
822
  - **The header is a nameplate, not a dashboard.** Pi's built-in brand colors are
631
823
  fixed, so the mark ignores the active theme; bytefly's header draws a two-tone
632
824
  mark from the `accent`/`warn` roles, a signature rule whose accent lead sits
633
825
  under the mark, and pairs them with the agent identity, the chrome identity,
634
- and a grouped help block. It adds no session or model data — that is the
635
- footer's job — and it stays a fixed four-plus rows so the startup transcript
636
- never reflows.
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.
637
831
  - **Two lines, four zones.** Line 1 is identity (`pwd` left, branch + worktree
638
832
  right); line 2 is resources (meters left, model + level right). This keeps
639
833
  "where am I / what state" separate from "how much am I burning".
640
834
  - **The gauge is anchored at column 0.** A leading mode slot would shift the
641
835
  context percentage left and right as plan mode came and went; trailing it
642
- 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
643
837
  visible as the meters drop.
644
838
  - **Statuses are routed, not repeated.** The built-in footer renders every
645
839
  `setStatus` value on its own line; the bar splits them by meaning (`worktree`
@@ -659,19 +853,23 @@ rather than showing a wrong value.
659
853
  non-zero usage, so it reads full only at 100% and a small percentage never
660
854
  disappears; unknown usage draws no gauge at all. Only at extreme widths does
661
855
  `truncateToWidth` clip the remaining text.
662
- - **Separators shrink to a single space.** Padded separators (` · `, ` │ `)
856
+ - **Separators shrink to a single space.** Padded separators (` · `, ` ╎ `)
663
857
  collapse to one space before `truncateToWidth` applies, so adjacent segments
664
- 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.
665
859
  - **Statuses compact with their colour.** A themed status's compact icon form
666
860
  reattaches its opening and closing SGR sequences around the visible text, so a
667
861
  red `✗1` stays red instead of losing its colour exactly when the bar tightens.
668
862
  A two-token `icon count` badge (for `rewind`: `↺ 2`) compacts to `↺2`, keeping
669
863
  the count. The slot sorts an alert (a failed job) ahead of the modes and is
670
864
  droppable, so it goes before the model is clipped.
671
- - **Neutral by default; color means something.** Only the context gauge carries
672
- status color, and it stays `muted` below 70%; cost is `muted`, the session and
673
- branch are `muted`, and tokens/cache/window are `dim`, so yellow and red are
674
- 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.
675
873
  - **Data is read fresh, except the usage scan.** The component resolves the
676
874
  theme through a getter and reads a fresh snapshot each render. The expensive
677
875
  part — summing token/cost totals across the session — is memoized by
@@ -746,7 +944,10 @@ rather than showing a wrong value.
746
944
  ## Files
747
945
 
748
946
  The surfaces are folders: `header/`, `footer/` (the status bar and its price
749
- 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),
750
951
  `picker/` (discovery and the settings store), `doctor/` (the `run_doctor` tool
751
952
  and its probe runtime), and `notify/`. The chrome and the screen are drawn from
752
953
  the package's [`lib/tui/`](../../lib/tui/) design system (tokens, rules, rows,
@@ -754,8 +955,10 @@ columns, gauge), so the look follows the active theme.
754
955
 
755
956
  | File | Responsibility |
756
957
  |---|---|
757
- | `index.ts` | Composition root: the disable guard and the six `register*` calls. |
758
- | `header/config.ts` | Header tuning (`HEADER_CONFIG`): mark and identity roles, padding, tagline, and the grouped help entries. |
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. |
759
962
  | `header/component.ts` | `ByteflyHeader`: the mark, identity rows, signature rule, and grouped help, plus its factory. |
760
963
  | `header/register.ts` | Installs/restores the header and owns its session lifecycle. |
761
964
  | `footer/register.ts` | Installs/restores the footer and owns its session lifecycle. |
@@ -774,11 +977,24 @@ columns, gauge), so the look follows the active theme.
774
977
  | `footer/render/lines.ts` | Snapshot → two `LineSpec`s; status routing. |
775
978
  | `footer/render/layout.ts` | Width reduction and right-zone folding. |
776
979
  | `footer/render/footer.ts` | `StatusBar` component and footer factory. |
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). |
777
990
  | `separator/config.ts` | Turn-rule tuning: `customType`, glyph, label prefix, padding, and roles. |
778
991
  | `separator/register.ts` | Registers the entry renderer and appends a separator on `agent_settled`. |
779
992
  | `separator/component.ts` | `SeparatorComponent`: the stateless entry renderer. |
780
993
  | `separator/format.ts` | Pure `separatorLine`: the centered turn rule, drawn by the kit's `centeredRule`. |
781
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`. |
782
998
  | `panel/types.ts` | `PanelRow` and `PanelModel`, the merged row model. |
783
999
  | `panel/join.ts` | The item/report union with the unambiguous-name health rule and unique row keys, groups, and ordering. |
784
1000
  | `panel/format.ts` | Non-TUI text: the headline and the grouped listing with a per-row health suffix. |
@@ -838,3 +1054,12 @@ through the factory (`doctor/register`). The notify capability is covered by its
838
1054
  (`notify/config`), the pure text builders (`notify/format`), method selection and
839
1055
  delivery over recording seams (`notify/dispatch`), and its three triggers,
840
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`).