@gavin-hu/my-pi-agent 0.0.0-stage → 0.7.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 (314) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -2
  3. package/assets/bytefly-chrome.png +0 -0
  4. package/assets/bytefly.schema.json +769 -0
  5. package/assets/file-browse.png +0 -0
  6. package/assets/plan-review.png +0 -0
  7. package/extensions/bytefly/README.md +820 -0
  8. package/extensions/bytefly/doctor/commands.ts +68 -0
  9. package/extensions/bytefly/doctor/config.ts +47 -0
  10. package/extensions/bytefly/doctor/format.ts +188 -0
  11. package/extensions/bytefly/doctor/register.ts +54 -0
  12. package/extensions/bytefly/doctor/runtime.ts +87 -0
  13. package/extensions/bytefly/doctor/schema.ts +51 -0
  14. package/extensions/bytefly/doctor/tools.ts +113 -0
  15. package/extensions/bytefly/doctor/tui.ts +140 -0
  16. package/extensions/bytefly/footer/collect/entries.ts +137 -0
  17. package/extensions/bytefly/footer/collect/snapshot.ts +64 -0
  18. package/extensions/bytefly/footer/config.ts +35 -0
  19. package/extensions/bytefly/footer/price/config.ts +152 -0
  20. package/extensions/bytefly/footer/price/price.ts +59 -0
  21. package/extensions/bytefly/footer/price/rates.ts +54 -0
  22. package/extensions/bytefly/footer/price/schedule.ts +112 -0
  23. package/extensions/bytefly/footer/price/types.ts +53 -0
  24. package/extensions/bytefly/footer/register.ts +116 -0
  25. package/extensions/bytefly/footer/render/footer.ts +51 -0
  26. package/extensions/bytefly/footer/render/format.ts +106 -0
  27. package/extensions/bytefly/footer/render/layout.ts +139 -0
  28. package/extensions/bytefly/footer/render/lines.ts +50 -0
  29. package/extensions/bytefly/footer/render/segments.ts +281 -0
  30. package/extensions/bytefly/footer/render/status.ts +42 -0
  31. package/extensions/bytefly/footer/types.ts +104 -0
  32. package/extensions/bytefly/header/component.ts +112 -0
  33. package/extensions/bytefly/header/config.ts +77 -0
  34. package/extensions/bytefly/header/register.ts +53 -0
  35. package/extensions/bytefly/index.ts +48 -0
  36. package/extensions/bytefly/notify/config.ts +60 -0
  37. package/extensions/bytefly/notify/dispatch.ts +138 -0
  38. package/extensions/bytefly/notify/format.ts +39 -0
  39. package/extensions/bytefly/notify/register.ts +85 -0
  40. package/extensions/bytefly/notify/types.ts +28 -0
  41. package/extensions/bytefly/picker/commands.ts +73 -0
  42. package/extensions/bytefly/picker/discovery.ts +128 -0
  43. package/extensions/bytefly/picker/resources.ts +226 -0
  44. package/extensions/bytefly/picker/runtime.ts +108 -0
  45. package/extensions/bytefly/picker/toggle.ts +145 -0
  46. package/extensions/bytefly/picker/tui.ts +260 -0
  47. package/extensions/bytefly/picker/types.ts +55 -0
  48. package/extensions/bytefly/separator/component.ts +26 -0
  49. package/extensions/bytefly/separator/config.ts +24 -0
  50. package/extensions/bytefly/separator/format.ts +50 -0
  51. package/extensions/bytefly/separator/register.ts +42 -0
  52. package/extensions/bytefly/separator/types.ts +22 -0
  53. package/extensions/document/README.md +281 -0
  54. package/extensions/document/config.ts +58 -0
  55. package/extensions/document/extract/doc.ts +19 -0
  56. package/extensions/document/extract/docx.ts +18 -0
  57. package/extensions/document/extract/file.ts +45 -0
  58. package/extensions/document/extract/odt.ts +101 -0
  59. package/extensions/document/extract/pdf.ts +25 -0
  60. package/extensions/document/extract/rtf.ts +45 -0
  61. package/extensions/document/extract/vendor.d.ts +39 -0
  62. package/extensions/document/extract/xlsx.ts +88 -0
  63. package/extensions/document/formats.ts +59 -0
  64. package/extensions/document/index.ts +17 -0
  65. package/extensions/document/paging.ts +110 -0
  66. package/extensions/document/render.ts +105 -0
  67. package/extensions/document/schema.ts +50 -0
  68. package/extensions/document/tool.ts +152 -0
  69. package/extensions/file-browse/README.md +269 -0
  70. package/extensions/file-browse/client.ts +550 -0
  71. package/extensions/file-browse/config.ts +82 -0
  72. package/extensions/file-browse/files.ts +433 -0
  73. package/extensions/file-browse/fuzzy.ts +75 -0
  74. package/extensions/file-browse/git.ts +74 -0
  75. package/extensions/file-browse/html.ts +346 -0
  76. package/extensions/file-browse/icons.ts +119 -0
  77. package/extensions/file-browse/index.ts +149 -0
  78. package/extensions/file-browse/markdown.ts +592 -0
  79. package/extensions/file-browse/paths.ts +101 -0
  80. package/extensions/file-browse/router.ts +289 -0
  81. package/extensions/file-browse/server.ts +104 -0
  82. package/extensions/file-open/README.md +135 -0
  83. package/extensions/file-open/index.ts +93 -0
  84. package/extensions/file-open/open.ts +102 -0
  85. package/extensions/file-open/schema.ts +31 -0
  86. package/extensions/goal/README.md +241 -0
  87. package/extensions/goal/clarify-parse.ts +203 -0
  88. package/extensions/goal/clarify-prompt.ts +80 -0
  89. package/extensions/goal/clarify.ts +234 -0
  90. package/extensions/goal/commands.ts +220 -0
  91. package/extensions/goal/config.ts +35 -0
  92. package/extensions/goal/format.ts +111 -0
  93. package/extensions/goal/index.ts +77 -0
  94. package/extensions/goal/runtime.ts +90 -0
  95. package/extensions/goal/schema.ts +105 -0
  96. package/extensions/goal/state.ts +69 -0
  97. package/extensions/goal/tools.ts +136 -0
  98. package/extensions/goal/tui.ts +97 -0
  99. package/extensions/goal/types.ts +42 -0
  100. package/extensions/job/README.md +309 -0
  101. package/extensions/job/commands.ts +74 -0
  102. package/extensions/job/config.ts +68 -0
  103. package/extensions/job/format.ts +256 -0
  104. package/extensions/job/index.ts +140 -0
  105. package/extensions/job/logs.ts +56 -0
  106. package/extensions/job/paths.ts +31 -0
  107. package/extensions/job/process.ts +106 -0
  108. package/extensions/job/registry.ts +265 -0
  109. package/extensions/job/reservations.ts +82 -0
  110. package/extensions/job/runtime.ts +846 -0
  111. package/extensions/job/schema.ts +148 -0
  112. package/extensions/job/session.ts +116 -0
  113. package/extensions/job/status.ts +69 -0
  114. package/extensions/job/store.ts +103 -0
  115. package/extensions/job/tools.ts +295 -0
  116. package/extensions/job/tui.ts +486 -0
  117. package/extensions/job/types.ts +92 -0
  118. package/extensions/job/ui.ts +99 -0
  119. package/extensions/job/waiters.ts +46 -0
  120. package/extensions/memory/README.md +221 -0
  121. package/extensions/memory/commands.ts +190 -0
  122. package/extensions/memory/config.ts +35 -0
  123. package/extensions/memory/errors.ts +9 -0
  124. package/extensions/memory/format.ts +141 -0
  125. package/extensions/memory/index.ts +59 -0
  126. package/extensions/memory/runtime.ts +199 -0
  127. package/extensions/memory/schema.ts +86 -0
  128. package/extensions/memory/store.ts +231 -0
  129. package/extensions/memory/tools.ts +165 -0
  130. package/extensions/memory/tui.ts +430 -0
  131. package/extensions/memory/types.ts +38 -0
  132. package/extensions/plan/README.md +417 -0
  133. package/extensions/plan/blocks.ts +197 -0
  134. package/extensions/plan/commands.ts +134 -0
  135. package/extensions/plan/feedback.ts +157 -0
  136. package/extensions/plan/git-bash.ts +320 -0
  137. package/extensions/plan/index.ts +179 -0
  138. package/extensions/plan/path-guard.ts +101 -0
  139. package/extensions/plan/plans.ts +267 -0
  140. package/extensions/plan/policy.ts +73 -0
  141. package/extensions/plan/runtime.ts +195 -0
  142. package/extensions/plan/steps.ts +228 -0
  143. package/extensions/plan/tools.ts +605 -0
  144. package/extensions/plan/tui.ts +391 -0
  145. package/extensions/plan/types.ts +67 -0
  146. package/extensions/plan/web-client.ts +433 -0
  147. package/extensions/plan/web-page.ts +169 -0
  148. package/extensions/plan/web-router.ts +155 -0
  149. package/extensions/plan/web.ts +267 -0
  150. package/extensions/question/README.md +189 -0
  151. package/extensions/question/answers.ts +119 -0
  152. package/extensions/question/dialogs.ts +109 -0
  153. package/extensions/question/index.ts +28 -0
  154. package/extensions/question/schema.ts +119 -0
  155. package/extensions/question/tools.ts +122 -0
  156. package/extensions/question/tui-state.ts +240 -0
  157. package/extensions/question/tui.ts +324 -0
  158. package/extensions/question/types.ts +61 -0
  159. package/extensions/rewind/README.md +202 -0
  160. package/extensions/rewind/commands.ts +74 -0
  161. package/extensions/rewind/config.ts +71 -0
  162. package/extensions/rewind/format.ts +110 -0
  163. package/extensions/rewind/git.ts +177 -0
  164. package/extensions/rewind/index.ts +60 -0
  165. package/extensions/rewind/policy.ts +42 -0
  166. package/extensions/rewind/queue.ts +27 -0
  167. package/extensions/rewind/restore.ts +96 -0
  168. package/extensions/rewind/rewind-tui.ts +135 -0
  169. package/extensions/rewind/rewind.ts +146 -0
  170. package/extensions/rewind/runtime.ts +174 -0
  171. package/extensions/rewind/snapshot-run.ts +103 -0
  172. package/extensions/rewind/snapshot.ts +80 -0
  173. package/extensions/rewind/status.ts +67 -0
  174. package/extensions/rewind/store.ts +142 -0
  175. package/extensions/rewind/temp-index.ts +87 -0
  176. package/extensions/rewind/timeline.ts +119 -0
  177. package/extensions/rewind/types.ts +58 -0
  178. package/extensions/subagent/README.md +310 -0
  179. package/extensions/subagent/agents.ts +454 -0
  180. package/extensions/subagent/format.ts +126 -0
  181. package/extensions/subagent/index.ts +181 -0
  182. package/extensions/subagent/invocation.ts +57 -0
  183. package/extensions/subagent/orchestrate.ts +182 -0
  184. package/extensions/subagent/render.ts +491 -0
  185. package/extensions/subagent/run.ts +212 -0
  186. package/extensions/subagent/schema.ts +102 -0
  187. package/extensions/subagent/stream.ts +144 -0
  188. package/extensions/subagent/types.ts +79 -0
  189. package/extensions/todo/README.md +230 -0
  190. package/extensions/todo/commands.ts +41 -0
  191. package/extensions/todo/config.ts +29 -0
  192. package/extensions/todo/format.ts +142 -0
  193. package/extensions/todo/index.ts +100 -0
  194. package/extensions/todo/nudge.ts +64 -0
  195. package/extensions/todo/runtime.ts +149 -0
  196. package/extensions/todo/schema.ts +125 -0
  197. package/extensions/todo/state.ts +65 -0
  198. package/extensions/todo/tools.ts +136 -0
  199. package/extensions/todo/tui.ts +170 -0
  200. package/extensions/todo/types.ts +33 -0
  201. package/extensions/watch/README.md +214 -0
  202. package/extensions/watch/commands.ts +144 -0
  203. package/extensions/watch/config.ts +59 -0
  204. package/extensions/watch/format.ts +212 -0
  205. package/extensions/watch/index.ts +129 -0
  206. package/extensions/watch/process.ts +63 -0
  207. package/extensions/watch/runtime.ts +525 -0
  208. package/extensions/watch/schema.ts +177 -0
  209. package/extensions/watch/tools.ts +278 -0
  210. package/extensions/watch/tui.ts +427 -0
  211. package/extensions/watch/types.ts +83 -0
  212. package/extensions/watch/ui.ts +97 -0
  213. package/extensions/web-access/README.md +347 -0
  214. package/extensions/web-access/config.ts +36 -0
  215. package/extensions/web-access/fetch/cache.ts +78 -0
  216. package/extensions/web-access/fetch/config.ts +81 -0
  217. package/extensions/web-access/fetch/extract.ts +124 -0
  218. package/extensions/web-access/fetch/find.ts +158 -0
  219. package/extensions/web-access/fetch/format.ts +117 -0
  220. package/extensions/web-access/fetch/page.ts +169 -0
  221. package/extensions/web-access/fetch/pdf.ts +45 -0
  222. package/extensions/web-access/fetch/schema.ts +192 -0
  223. package/extensions/web-access/fetch/ssrf.ts +191 -0
  224. package/extensions/web-access/fetch/tool.ts +238 -0
  225. package/extensions/web-access/fetch/types.ts +26 -0
  226. package/extensions/web-access/html.ts +41 -0
  227. package/extensions/web-access/http.ts +133 -0
  228. package/extensions/web-access/index.ts +28 -0
  229. package/extensions/web-access/search/config.ts +115 -0
  230. package/extensions/web-access/search/format.ts +65 -0
  231. package/extensions/web-access/search/json.ts +75 -0
  232. package/extensions/web-access/search/provider.ts +38 -0
  233. package/extensions/web-access/search/providers/brave.ts +76 -0
  234. package/extensions/web-access/search/providers/duckduckgo.ts +109 -0
  235. package/extensions/web-access/search/providers/searxng.ts +98 -0
  236. package/extensions/web-access/search/registry.ts +49 -0
  237. package/extensions/web-access/search/schema.ts +65 -0
  238. package/extensions/web-access/search/search.ts +129 -0
  239. package/extensions/web-access/search/tool.ts +112 -0
  240. package/extensions/web-access/search/types.ts +32 -0
  241. package/extensions/web-access/text.ts +26 -0
  242. package/extensions/web-access/transcript.ts +20 -0
  243. package/extensions/wechat/README.md +273 -0
  244. package/extensions/wechat/bridge.ts +577 -0
  245. package/extensions/wechat/client.ts +411 -0
  246. package/extensions/wechat/commands.ts +220 -0
  247. package/extensions/wechat/config.ts +88 -0
  248. package/extensions/wechat/credentials.ts +89 -0
  249. package/extensions/wechat/format.ts +76 -0
  250. package/extensions/wechat/index.ts +175 -0
  251. package/extensions/wechat/interaction.ts +130 -0
  252. package/extensions/wechat/lock.ts +86 -0
  253. package/extensions/wechat/login-screen.ts +91 -0
  254. package/extensions/wechat/login.ts +130 -0
  255. package/extensions/wechat/media.ts +103 -0
  256. package/extensions/wechat/outbound.ts +102 -0
  257. package/extensions/wechat/prompt.ts +104 -0
  258. package/extensions/wechat/qrcode.ts +46 -0
  259. package/extensions/wechat/remote-ui.ts +97 -0
  260. package/extensions/wechat/slash.ts +114 -0
  261. package/extensions/wechat/state.ts +71 -0
  262. package/extensions/wechat/status-screen.ts +176 -0
  263. package/extensions/wechat/tools.ts +109 -0
  264. package/extensions/wechat/types.ts +170 -0
  265. package/extensions/worktree/README.md +278 -0
  266. package/extensions/worktree/commands.ts +110 -0
  267. package/extensions/worktree/config.ts +107 -0
  268. package/extensions/worktree/enter.ts +273 -0
  269. package/extensions/worktree/errors.ts +18 -0
  270. package/extensions/worktree/exit.ts +122 -0
  271. package/extensions/worktree/git.ts +429 -0
  272. package/extensions/worktree/guard.ts +566 -0
  273. package/extensions/worktree/include.ts +147 -0
  274. package/extensions/worktree/index.ts +221 -0
  275. package/extensions/worktree/lifecycle.ts +14 -0
  276. package/extensions/worktree/prune.ts +99 -0
  277. package/extensions/worktree/registry.ts +221 -0
  278. package/extensions/worktree/root-tools.ts +162 -0
  279. package/extensions/worktree/runtime.ts +135 -0
  280. package/extensions/worktree/state.ts +65 -0
  281. package/extensions/worktree/status.ts +115 -0
  282. package/extensions/worktree/tools.ts +147 -0
  283. package/lib/README.md +49 -0
  284. package/lib/browser.ts +40 -0
  285. package/lib/config.ts +186 -0
  286. package/lib/env.ts +41 -0
  287. package/lib/escape.ts +27 -0
  288. package/lib/events.ts +40 -0
  289. package/lib/format.ts +76 -0
  290. package/lib/git/index.ts +7 -0
  291. package/lib/git/read.ts +76 -0
  292. package/lib/git/runner.ts +80 -0
  293. package/lib/highlight.ts +1000 -0
  294. package/lib/interaction.ts +132 -0
  295. package/lib/list-cursor.ts +222 -0
  296. package/lib/path.ts +49 -0
  297. package/lib/pi-invocation.ts +71 -0
  298. package/lib/policy.ts +67 -0
  299. package/lib/rails.ts +91 -0
  300. package/lib/requirements.ts +431 -0
  301. package/lib/shell.ts +33 -0
  302. package/lib/tool-names.ts +32 -0
  303. package/lib/tui.ts +184 -0
  304. package/lib/ui.ts +105 -0
  305. package/lib/web/base.ts +18 -0
  306. package/lib/web/components.ts +122 -0
  307. package/lib/web/index.ts +12 -0
  308. package/lib/web/layout.ts +30 -0
  309. package/lib/web/markup.ts +99 -0
  310. package/lib/web/runtime.ts +68 -0
  311. package/lib/web/tokens.ts +128 -0
  312. package/package.json +134 -4
  313. package/themes/bytefly-dark.json +91 -0
  314. package/themes/bytefly-light.json +91 -0
@@ -0,0 +1,820 @@
1
+ # bytefly — the TUI header, footer, turn rule, extensions picker, doctor, and notifications
2
+
3
+ `bytefly` owns the terminal chrome, configuration, and diagnostics surfaces
4
+ this package adds: one themed startup header that recolors Pi's brand mark and
5
+ version, one compact, colorful, width-adaptive two-line footer, a labeled rule
6
+ that marks each completed turn in the transcript, the `/extensions` picker that
7
+ lists and toggles every resolved Pi extension, the `/doctor` surface that
8
+ reports whether each extension has what it needs, and an opt-in notify
9
+ capability that raises an OS notification when a run finishes, a job finishes,
10
+ or the agent asks for input. DeepSeek is priced from the provider's published
11
+ yuan rates and peak tariff by default, and any other model can be priced from
12
+ your own rates when Pi's catalog does not match what a provider actually
13
+ charges.
14
+
15
+ ```
16
+ pi --extension ./extensions/bytefly # load just this extension
17
+ pi -e . # load the whole @gavin-hu/my-pi-agent package
18
+ pi install ./ # install the package
19
+ ```
20
+
21
+ ## What it does
22
+
23
+ The extension owns five surfaces: the startup header, the footer, the rule
24
+ between completed turns, the `/extensions` picker, and the `/doctor` report.
25
+
26
+ **Header.** A nameplate: a two-tone half-block mark from the active theme's
27
+ `accent` / `warning` tokens, the agent identity (`Pi` and its version), the
28
+ chrome identity (`enhanced by bytefly` and the package version), a `─` rule, and
29
+ a grouped help block. The help keys resolve live from Pi's keybindings, so a
30
+ rebound key shows the user's chord; the identity and help text are `muted`/`dim`
31
+ so the mark keeps the eye. It is installed only in interactive (`tui`) sessions,
32
+ and on terminals that cannot render half blocks the mark falls back to a
33
+ two-tone `Pi` wordmark.
34
+
35
+ ```
36
+ █▀█ Pi v1.2.0
37
+ █▀ █ enhanced by bytefly v0.5.1
38
+ ─────────────────────────────────────────────────
39
+ escape to interrupt · ctrl+c to clear · ctrl+d to exit
40
+ ctrl+o more · ctrl+t to think · ctrl+l models · ctrl+g editor
41
+ / commands · ! bash · /bytefly chrome · /doctor checks
42
+ ```
43
+
44
+ As the terminal narrows, the identity sheds the chrome version, then the chrome
45
+ line, then the agent name, and the help rows drop whole keys, so nothing wraps or
46
+ clips mid-word. The mark, tokens, identity, and grouped help are tuned in
47
+ [`header/config.ts`](./header/config.ts).
48
+
49
+ **Footer.** Two lines, each with a left and right zone:
50
+
51
+ - **Line 1 — identity:** `pwd` and the session name on the left; git state
52
+ (`⎇ branch · ⑂ worktree`) and the `◉` browse chip on the right.
53
+ - **Line 2 — resources:** the context gauge and usage meters on the left, the
54
+ mode/alert slot (`⋮ plan`) trailing after a `│` when present; the model and
55
+ thinking level on the right. The gauge is anchored at column 0, so `%` is
56
+ always in the same place.
57
+
58
+ ```
59
+ ~/repo/my-pi-agent ⎇ main · ⑂ smoke
60
+ ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% │ ⋮ plan opus-4.5 · high
61
+ ```
62
+
63
+ (The examples show the default `¥` symbol. For a model the built-in table does
64
+ not cover, the number stays Pi's recorded USD amount — see the currency note
65
+ under [Configuration](#configuration).)
66
+
67
+ With no plan mode there is no mode slot, so the `│` is omitted:
68
+
69
+ ```
70
+ ~/repo/my-pi-agent ⎇ main · ⑂ smoke
71
+ ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
72
+ ```
73
+
74
+ As the terminal narrows, segments shrink and drop by priority rather than
75
+ clipping. The branch keeps its name and is dropped when the name no longer
76
+ fits, rather than collapsing to a bare glyph:
77
+
78
+ ```
79
+ ~/repo/project ⎇ main · ⑂ smoke
80
+ ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 │ ⋮ plan opus-4.5 · high
81
+ ```
82
+
83
+ ```
84
+ project ⎇ main
85
+ 62% ⋮ opus-4.5
86
+ ```
87
+
88
+ With a named session, the name follows the path and truncates before it drops:
89
+
90
+ ```
91
+ ~/repo/project · feature-refac… ⎇ main
92
+ ▰▰▰▰▰▰▱▱▱▱ 62% │ ⋮ plan opus-4.5
93
+ ```
94
+
95
+ Zero-value meters are omitted, so a fresh session shows only `?/window` instead
96
+ of a row of `¥0.00 · ↑0 ↓0 · R0 W0`. Unknown context usage shows a muted `?`
97
+ with no gauge (known `0%` still draws the empty gauge), so unknown and zero stay
98
+ distinct. Cost keeps the configured precision — the segment drops rather than
99
+ rounding to a misleading `¥0.3` — and a positive amount below one minor unit
100
+ shows as `¥<0.01` rather than `¥0.00`.
101
+
102
+ DeepSeek needs no config file at all: the bar prices it from the published yuan
103
+ rates and peak tariff built into [`footer/price/config.ts`](./footer/price/config.ts). A
104
+ `bytefly.json` adds models of your own, or replaces a built-in entry:
105
+
106
+ ```
107
+ ~/repo/project ⎇ main
108
+ ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥2.50 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
109
+ ```
110
+
111
+ **Turn separator.** After each completed turn the extension appends an inert
112
+ entry and draws a centered `╌╌╌ turn N ╌╌╌` rule across the transcript; see
113
+ [Turn separator](#turn-separator). No config file is needed and there is no
114
+ command.
115
+
116
+ **Extensions picker.** `/extensions` opens a keyboard-driven dock list of every
117
+ Pi extension the session resolves — across personal settings and, when trusted,
118
+ the project — and writes a `+pattern` / `-pattern` toggle into the matching
119
+ `settings.json`; see [Extensions picker](#extensions-picker). There is no
120
+ config file, and changes apply on reload.
121
+
122
+ **Doctor.** `/doctor` reports whether each extension has what it needs at
123
+ runtime — a binary on `PATH`, an optional package, a login/state file, or a
124
+ config file that fails to parse — and `run_doctor` returns the same report as
125
+ text and typed data. Only a missing *hard* requirement warns at session start;
126
+ everything else waits in the report. See [Doctor](#doctor).
127
+
128
+ **Notify.** Off by default, one `notify` section in `bytefly.json` turns on OS
129
+ notifications for unattended runs: when a full agent run settles, when a
130
+ background job finishes, and when the agent opens a prompt asking for input. The
131
+ method follows the terminal (OSC 777, OSC 99, a Windows toast) or falls back to
132
+ the platform notifier (`osascript`, `notify-send`); see
133
+ [Notifications](#notifications).
134
+
135
+ ## Commands
136
+
137
+ | Command | What it does |
138
+ |---|---|
139
+ | `/bytefly` | Toggle the bytefly chrome — header and footer — on or off. |
140
+ | `/bytefly header` | Toggle the header alone. |
141
+ | `/bytefly footer` | Toggle the footer alone. |
142
+ | `/bytefly status` | Report whether the header and footer are on. |
143
+ | `/extensions` | Open the extensions picker in the TUI, or print the grouped list elsewhere. |
144
+ | `/doctor [extension] [refresh]` | Probe the extensions and show the report; in the TUI this opens a docked list screen. |
145
+
146
+ The `status` subcommand completes with Tab.
147
+
148
+ ## Behaviour by mode
149
+
150
+ The header, footer, and turn rule are installed only in interactive (`tui`)
151
+ sessions; `/bytefly` is a no-op elsewhere. The picker is available in every
152
+ mode: in `tui` it opens the dock screen, while in RPC, JSON, and print modes
153
+ `/extensions` prints the same grouped list and does not toggle. `/doctor` probes
154
+ in every mode: in `tui` it opens its list screen, while RPC, JSON, and print
155
+ modes print the same report, and the proactive session-start warning is skipped
156
+ when there is no UI. Notify runs in every mode and picks its method from the
157
+ environment; the OSC methods need a TTY, so a print/JSON/RPC run gets an
158
+ OS-level notification or none.
159
+
160
+ ## Configuration
161
+
162
+ The header is tuned in [`header/config.ts`](./header/config.ts) — the mark and
163
+ identity theme tokens, the padding and tagline, and the grouped help entries
164
+ (each naming a Pi keybinding resolved live or a raw command prefix). The footer's
165
+ content and layout are tuned in [`footer/config.ts`](./footer/config.ts) —
166
+ thresholds, gauge
167
+ glyphs and widths, separators, icons, and status keys. The turn separator has no
168
+ JSON either: its glyph, label prefix, padding, and colors are tuned in
169
+ [`separator/config.ts`](./separator/config.ts).
170
+
171
+ Everything JSON-configurable lives in `bytefly.json`, one shared document with a
172
+ top-level section per extension (see [Configuration](../../docs/configuration.md)).
173
+ It is read from the global document, then `<cwd>/.pi/bytefly.json`. The old
174
+ per-extension files
175
+ (`document.json`, `jobs.json`, …) are no longer read; run
176
+ `bun run config:migrate` to move them into the shared document.
177
+
178
+ The price
179
+ calculation is configured by the `price` section of the shared `bytefly.json`
180
+ (see [Configuration](../../docs/configuration.md)), read from
181
+ `~/.pi/agent/bytefly.json`, then `<cwd>/.pi/bytefly.json` (project wins).
182
+
183
+ Both documents are optional, and neither is needed for DeepSeek. The built-in
184
+ table is the base layer for every session: DeepSeek is priced in yuan at the
185
+ published peak and off-peak rates, and every other model keeps Pi's recorded
186
+ cost, whose number comes from Pi's catalog. A document can change the currency,
187
+ replace a built-in rate, or add rates of your own. A malformed document is
188
+ ignored, a wrong-typed value falls back to its default, and an unknown timezone
189
+ drops the schedule rather than the session.
190
+
191
+ ```json
192
+ {
193
+ "price": {
194
+ "currency": { "symbol": "¥", "decimals": 2 },
195
+ "fallbackToRecordedCost": true,
196
+ "rates": {
197
+ "deepseek/deepseek-flash": { "input": 2, "output": 8, "cacheRead": 0.04, "cacheWrite": 0, "offPeakMultiplier": 0.5 },
198
+ "deepseek/deepseek-v4-pro": { "input": 9, "output": 27, "cacheRead": 0.3, "cacheWrite": 0, "offPeakMultiplier": 0.5 }
199
+ },
200
+ "peak": {
201
+ "timezone": "Asia/Shanghai",
202
+ "windows": [
203
+ { "days": [1, 2, 3, 4, 5], "from": "09:00", "to": "12:00" },
204
+ { "days": [1, 2, 3, 4, 5], "from": "14:00", "to": "18:00" }
205
+ ]
206
+ }
207
+ }
208
+ }
209
+ ```
210
+
211
+ That file restates the built-in defaults, so on its own it changes nothing; it
212
+ is shown to document every key at once.
213
+
214
+ | Key | Meaning |
215
+ |---|---|
216
+ | `currency.symbol` | Printed before the number; at most four characters. Default `¥`. |
217
+ | `currency.decimals` | Digits after the point, 0–4. Default `2`. |
218
+ | `fallbackToRecordedCost` | Use Pi's recorded cost for a record no rate matches. Default `true`. |
219
+ | `rates` | Per-million-token prices in `currency`, keyed by `provider/model`; the built-in DeepSeek table is the base, and a key stated here replaces its entry. |
220
+ | `rates.<key>.input` · `output` · `cacheRead` · `cacheWrite` | Rate per token class; a class left out is free. |
221
+ | `rates.<key>.offPeakMultiplier` | Multiplier outside every peak window, 0–1. Default `1`. |
222
+ | `peak.timezone` | The IANA zone the windows are stated in. |
223
+ | `peak.windows` | `days` (1 = Monday … 7 = Sunday; omitted means every day), `from` inclusive, `to` exclusive, `HH:MM` local; a `to` before `from` wraps midnight. An empty list means every moment is peak. |
224
+
225
+ Rates merge key by key across the two documents, so a project document can
226
+ override one model without restating the rest; a key present in both is replaced,
227
+ not merged field by field. No windows means every moment is peak, so a rate with
228
+ `offPeakMultiplier: 0.5` and no schedule stays flat. The `price` section is read
229
+ when the footer is installed, so a new session — or `/bytefly` toggled off and on
230
+ — picks up an edit.
231
+
232
+ **Mixing currencies is the sharp edge of the built-in default.** A record priced
233
+ from `rates` or the built-in table is in `currency.symbol` units — yuan for
234
+ DeepSeek — while a record that fell back carries Pi's USD number under that same
235
+ symbol. A session mixing DeepSeek with another provider therefore sums two
236
+ currencies into one number. Either cover every model you use (a `"*"` catch-all
237
+ helps) and set `fallbackToRecordedCost: false`, or set the symbol to `"$"` and
238
+ treat the rates purely as an off-peak correction.
239
+
240
+ ### The `notify` section
241
+
242
+ The notify capability reads the same shared `bytefly.json`, under its own
243
+ top-level `notify` key.
244
+ It is **off by default**; `enabled: true` is the whole setup needed for an
245
+ unattended run, and the three trigger flags then narrow what is announced. The
246
+ price section and the notify section are parsed independently, so a wrong-typed
247
+ value in one never reaches the other.
248
+
249
+ ```json
250
+ {
251
+ "notify": {
252
+ "enabled": true,
253
+ "turnFinished": true,
254
+ "needsInput": true,
255
+ "jobFinished": true,
256
+ "method": "auto",
257
+ "title": "Pi"
258
+ }
259
+ }
260
+ ```
261
+
262
+ | Key | Default | Meaning |
263
+ |---|---|---|
264
+ | `enabled` | `false` | Master switch. A notification on every turn is intrusive, so it is opt-in. |
265
+ | `turnFinished` | `true` | Notify when a full agent run settles and Pi waits for the next message. An aborted run is never announced. |
266
+ | `needsInput` | `true` | Notify when the agent opens a prompt mid-run (`select`, `confirm`, `input`, `editor`, `custom`). A prompt the user typed while idle does not fire. |
267
+ | `jobFinished` | `true` | Notify when a background `job` reaches a terminal state. |
268
+ | `method` | `auto` | `auto`, `osascript`, `notify-send`, `toast`, `osc777`, or `osc99`. An explicit method that the environment cannot deliver is a no-op. |
269
+ | `title` | `Pi` | Notification title, clamped to 40 characters. |
270
+
271
+ A `notify` section is read at `session_start`, so a new session or `/reload`
272
+ picks up an edit.
273
+
274
+ ### The `doctor` section
275
+
276
+ The doctor surface reads the same shared `bytefly.json`, under its own top-level
277
+ `doctor` key:
278
+
279
+ ```json
280
+ {
281
+ "doctor": {
282
+ "proactive": true,
283
+ "timeoutMs": 2000,
284
+ "skip": []
285
+ }
286
+ }
287
+ ```
288
+
289
+ | Key | Default | Meaning |
290
+ |---|---|---|
291
+ | `proactive` | `true` | Probe the extensions with hard requirements at session start and warn once when something is missing. |
292
+ | `timeoutMs` | `2000` | Per-probe timeout for one spawned command, clamped to 100–30000. |
293
+ | `skip` | `[]` | Extension names to leave out of every report. |
294
+
295
+ A malformed `bytefly.json` is ignored like every other config file — and named
296
+ by the report `doctor` produces. The section is read at `session_start`, so a
297
+ new session or `/reload` picks up an edit.
298
+
299
+ ## Security
300
+
301
+ - Project settings are only read or written when the project is trusted
302
+ (`ctx.isProjectTrusted()`); an untrusted session offers Global scope only.
303
+ - Discovery never installs or fetches a package: missing sources are skipped.
304
+ - The picker registers no model-facing tool, so the model cannot change resource
305
+ configuration.
306
+
307
+ ## Cost calculation
308
+
309
+ Pi prices every model call from its bundled catalog in USD, and the provider API
310
+ never returns a price. Built-in and configured rates replace that for the models
311
+ they match:
312
+
313
+ - **Which built-in rates.** [`footer/price/config.ts`](./footer/price/config.ts) ships DeepSeek's published
314
+ yuan prices, with the peak schedule that doubles the price during peak hours
315
+ ([DeepSeek's pricing page](https://api-docs.deepseek.com/quick_start/pricing)).
316
+ They are ordinary rate entries, so a `bytefly.json` key replaces one of
317
+ them, and the schedule is the default `peak` every rate is judged against.
318
+ - **Which rate.** The most specific key wins, with the model segment outranking
319
+ the provider one: `provider/model`, then `*/model`, then `provider/*`, then
320
+ `*`. A segment ending in a star is a prefix match (`*/gpt-*`); equally
321
+ specific keys are resolved in file order.
322
+ - **Which formula.** `tokens × rate ÷ 1,000,000`, summed over the four token
323
+ classes. Cache tokens are never billed again as fresh input.
324
+ - **Which tariff.** Each record is dated by its session entry, so a window is
325
+ applied at the local time the tokens were actually spent: an off-peak turn
326
+ stays off-peak when peak starts. A record with no usable timestamp is priced
327
+ at peak, because it cannot be dated.
328
+ - **Which source.** A record with no matching rate — built-in or configured —
329
+ keeps Pi's recorded cost, or contributes nothing when
330
+ `fallbackToRecordedCost` is `false`.
331
+
332
+ For example, DeepSeek flash at `¥2` in and `¥8` out per million, with the
333
+ published peak windows and `offPeakMultiplier: 0.5`, prices a 1M-in + 1M-out
334
+ turn at `¥10.00` at peak and `¥5.00` off-peak. That example is worked end to end
335
+ in [`footer/price/price.test.ts`](./footer/price/price.test.ts), which also prices the built-in
336
+ table with no config file at all.
337
+
338
+ ## Content and colors
339
+
340
+ | Segment | Line / zone | Color | Drops |
341
+ |---|---|---|---|
342
+ | pwd | 1 left | dim | last |
343
+ | session | 1 left | muted, bracketed | after worktree |
344
+ | browse | 1 right | success glyph + accent port | first |
345
+ | branch | 1 right | muted (warning when detached) | last |
346
+ | worktree | 1 right | success | first |
347
+ | context gauge + `%` | 2 left | muted ≤70 · warning 71–90 · error >90 | never |
348
+ | `/window` | 2 left | dim | before cost |
349
+ | cost | 2 left | muted | after tokens |
350
+ | tokens `↑in ↓out` | 2 left | dim | after cache |
351
+ | cache `R… CH …%` | 2 left | dim | first |
352
+ | statuses | 2 left | as emitted by each extension | before the model clips |
353
+ | model | 2 right | accent | never |
354
+ | thinking level | 2 right | thinking token | before cost |
355
+
356
+ The worktree status is read from the `worktree` status key, so the
357
+ [worktree](../worktree/) extension and the bar agree. The browse chip is read
358
+ from the `browse` status key published by [file-browse](../file-browse/) while
359
+ its server runs, and shows the two-tone `◉ <port>` (green glyph, accent port).
360
+ The trailing slot shows plan-style alerts when present and is omitted otherwise;
361
+ a plan chip's trailing plan file name is dropped, so the slot reads `⋮ plan`
362
+ even though the built-in footer keeps the name. An alert (a failed job) sorts to
363
+ the front of the slot. The slot trails the meters after a `│`, so the context
364
+ gauge always starts the line. It keeps each extension's own colour when it
365
+ compacts, and it is droppable: a tight bar drops the slot rather than truncating
366
+ the model, which sits at the end of the line.
367
+
368
+ ## Turn separator
369
+
370
+ `agent_settled` fires once after an agent run fully settles — exactly one user
371
+ turn — which is why it is used instead of `turn_end` (that fires once per
372
+ assistant message, so tool-calling rounds would each get a line). On each
373
+ settled run the extension appends an inert custom entry and a renderer draws
374
+ it:
375
+
376
+ ```
377
+ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ turn 3 ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
378
+ ```
379
+
380
+ - The turn number is `count(separators on the current branch) + 1`, so a
381
+ resumed or forked session continues the parent's numbering and abandoned
382
+ branches do not leak counts.
383
+ - The entry is excluded from LLM context, so it costs no tokens, and it
384
+ survives resume and fork. It is drawn under the `bytefly-separator` custom
385
+ entry type.
386
+ - The line is centered and width-adaptive: the label is filled out with the
387
+ glyph, and when it cannot fit the line becomes a plain dashed rule rather
388
+ than overflowing.
389
+ - The rule is installed only in interactive (`tui`) sessions; RPC, JSON, and
390
+ print sessions append nothing.
391
+ - Separators recorded by the retired `turn-separator` extension are not
392
+ rendered, and `PI_DISABLED_EXTENSIONS=turn-separator` no longer affects the
393
+ rule; disable the whole extension with `bytefly` instead.
394
+ - There is no runtime command; the glyph, label prefix, padding, and colors are
395
+ tuned in [`separator/config.ts`](./separator/config.ts).
396
+
397
+ ## Extensions picker
398
+
399
+ `/extensions` is a keyboard-driven dock list of every extension Pi resolves for
400
+ the session, so the list matches what actually loads. It reuses Pi's own settings
401
+ and package resolution (`SettingsManager` plus `DefaultPackageManager` with
402
+ `onMissing: "skip"`), so opening the screen never installs or fetches a package.
403
+
404
+ - Lists every resolved extension, grouped by source (each package, user
405
+ settings, project settings), sorted packages-first and then by display name.
406
+ - Omits `bytefly` itself, so the tool that manages the list is never a row.
407
+ Turn the chrome off with `/bytefly`; use `pi config` to disable bytefly
408
+ entirely.
409
+ - Shows whether each one is enabled (`[x]`) or disabled (`[ ]`).
410
+ - Toggles the focused extension with `Space` / `Enter`, writing `+pattern` /
411
+ `-pattern` into `extensions` (and package filter objects) in the active
412
+ scope.
413
+ - Switches the write scope with `Tab`: **Global** (personal
414
+ `settings.json`) or **Project** (`.pi/settings.json`, only when the project
415
+ is trusted).
416
+ - Offers a reload after pending changes, so the new selection takes effect
417
+ without leaving the session.
418
+
419
+ ### Keys
420
+
421
+ | Key | Effect |
422
+ |---|---|
423
+ | `Space` / `Enter` | Toggle the focused extension. |
424
+ | `Tab` | Switch between Global and Project scope. |
425
+ | `↑`/`↓`, `j`/`k` | Move the selection. |
426
+ | `PgUp`/`PgDn` | Move a page. |
427
+ | `g` / `G`, `Home` / `End` | Jump to the first or last extension. |
428
+ | `Esc` / `Ctrl+C` | Close the screen. |
429
+
430
+ Mouse wheel scrolling works; every action also has a keyboard path.
431
+
432
+ ## Doctor
433
+
434
+ `/doctor` probes every declared extension and reports each check as `ok`,
435
+ `missing`, `degraded`, or `info`; `run_doctor` returns the same report as text
436
+ plus a typed `structuredContent` report. Only `missing` and `degraded` are
437
+ problems: `missing` means an enabled extension cannot do its job, `degraded`
438
+ means reduced capability — an optional package that is absent, a config file
439
+ that fails to parse, a login that has not happened. `doctor` never changes
440
+ anything and never installs anything.
441
+
442
+ `/doctor <extension>` narrows the report to one extension — including the
443
+ checks that passed — and `/doctor refresh` ignores the findings cached earlier
444
+ in the session. In the TUI the report is a docked list screen: one row per
445
+ extension with a severity glyph, the focused extension's findings underneath.
446
+ `Enter` switches between problems only and every check, `r` re-probes, `Esc`
447
+ closes; while a re-probe is in flight the hint shows `refreshing…` and a second
448
+ `r` is ignored, so probes cannot stack. Every other mode prints the same report
449
+ as text.
450
+
451
+ ### Keys
452
+
453
+ | Key | Effect |
454
+ |---|---|
455
+ | `↑`/`↓`, `j`/`k` | Move the selection. |
456
+ | `PgUp`/`PgDn` | Move a page. |
457
+ | `g` / `G`, `Home` / `End` | Jump to the first or last extension. |
458
+ | `Enter` | Toggle between problems only and every check. |
459
+ | `r` | Re-probe. |
460
+ | `Esc` | Close the screen. |
461
+
462
+ ### What is checked
463
+
464
+ | Extension | Hard requirement | Optional / degraded |
465
+ |---|---|---|
466
+ | worktree | `git` | `.worktreeinclude` symlinks need symlink support |
467
+ | rewind | `git` | — |
468
+ | document | — | — |
469
+ | subagent | a runnable nested `pi` | project-local agents need a trusted project |
470
+ | plan | — | `git` for project-local `.pi/plans`; an interactive UI |
471
+ | memory | — | `git` for the project-scope root |
472
+ | file-browse | — | `git` for the branch chip; the browser opener is best-effort |
473
+ | web-access | — | `unpdf` |
474
+ | wechat | — | `wechat/credentials.json` (not logged in) |
475
+ | job | — | Windows has no `EXIT` trap; `ps` is POSIX-only |
476
+ | todo, goal | — | their config files |
477
+ | bytefly | — | TUI only for the header, footer, and turn rule; the picker's project scope needs a trusted project; `bytefly.json` |
478
+ | question | — | needs an interactive UI |
479
+
480
+ Every extension that reads a config file also gets the malformed-JSON check:
481
+ the file and the parse error are named instead of being silently ignored. The
482
+ install guidance a tool error shows and the hint this report prints come from
483
+ the same entry in `lib/requirements.ts`, so the two cannot drift.
484
+
485
+ ## Notifications
486
+
487
+ The notify capability exists for unattended runs: start a long turn, leave the
488
+ terminal, and get a desktop notification when Pi needs you — or when it is done.
489
+ It fires on three events, each independently switchable in the `notify` section
490
+ of `bytefly.json` (see [Configuration](#configuration)):
491
+
492
+ - **A run settled** (`agent_settled`). Pi finished the turn and waits for the
493
+ next message. An aborted run is skipped: interrupting with Escape means the
494
+ user is already at the keyboard.
495
+ - **The agent needs input.** Pi opened a prompt while a run was active — an
496
+ `ask_user_question`, a plan review, a subagent approval. A prompt opened while
497
+ idle is a command the user just typed, so it does not fire; the run boundary
498
+ is what separates "the agent is asking" from "the user is asking".
499
+ - **A background job finished.** The [job](../job/) extension announces each
500
+ terminal transition on the shared `pi.events` bus
501
+ (`my-pi-agent/job-finished`); bytefly listens and never imports it.
502
+
503
+ ### Delivery method
504
+
505
+ `method: "auto"` (the default) chooses from the environment, terminal first so
506
+ it works over SSH:
507
+
508
+ | Detected | Method |
509
+ |---|---|
510
+ | `WT_SESSION` (Windows Terminal / WSL) | `toast` — a PowerShell Windows toast |
511
+ | `KITTY_WINDOW_ID` | `osc99` |
512
+ | `TERM_PROGRAM` is `WezTerm` / `iTerm.app`, or `GHOSTTY_RESOURCES_DIR` is set | `osc777` |
513
+ | a plain `win32` host | `toast` |
514
+ | a TTY on any other host | `osc777` |
515
+ | headless macOS | `osascript` |
516
+ | headless Linux | `notify-send` |
517
+
518
+ Set `method` to any of `osc777`, `osc99`, `toast`, `osascript`, or `notify-send`
519
+ to force one. OSC 777 reaches Ghostty, iTerm2, WezTerm, and rxvt-unicode;
520
+ OSC 99 reaches Kitty. Off a TTY the OSC methods are a no-op — writing an escape
521
+ sequence into captured output would corrupt it — so a headless run needs an
522
+ OS-level method.
523
+
524
+ ### Text
525
+
526
+ A notification body names the session (the session name, else the working
527
+ directory's folder) so concurrent runs can be told apart, and a finished job
528
+ adds its id, status, exit code, and label. Every piece that can carry model- or
529
+ user-authored text — a prompt title, a job label — is sanitized to one
530
+ control-free line before it reaches an escape sequence or a shell argument.
531
+
532
+ ## Limitations
533
+
534
+ - **The picker manages only the `extensions` resource type.** Skills, prompts,
535
+ and themes are handled by Pi's own `pi config`.
536
+ - **Built-in extensions are not listed** (`builtin:mcp`, `builtin:codemode`, …):
537
+ their authoritative names are not exposed to extensions. Use `pi config`.
538
+ - **Packages are not installed, removed, or updated here**; use `pi install`,
539
+ `pi remove`, and `pi update`.
540
+ - **Project toggles are two-state** (`enable`/`disable`). Resetting an override
541
+ back to "inherit" is done in `pi config`.
542
+ - **One built-in provider.** DeepSeek is the only provider with a built-in
543
+ table; another provider's currency and tariff have to be stated in
544
+ `bytefly.json`.
545
+ - **Holidays.** A tariff that excludes public holidays — DeepSeek's peak window
546
+ does — cannot be expressed. A weekend can, by leaving `days` 6 and 7 out.
547
+ - **One schedule.** A single `peak` section applies to every rate. Only a rate
548
+ with an `offPeakMultiplier` below 1 is affected by it, so two providers with
549
+ different time-of-day tariffs cannot both be described.
550
+ - **No tiers, no special cache writes.** Per-request input tiers and
551
+ Anthropic's 2× one-hour cache write are not modelled; Pi's recorded cost
552
+ covers those models when no rate is configured.
553
+ - **No FX conversion.** A fallback record is USD and is never converted into
554
+ the configured currency.
555
+ - **The displayed number is the total, rounded once** — not the sum of rounded
556
+ records.
557
+ - **`doctor` can only check what is declared.** A new extension with no
558
+ `REQUIREMENTS` entry is invisible to it — `test/structure.test.ts` fails on
559
+ exactly that, which is why the table cannot rot quietly.
560
+ - **The disabled check reads `PI_DISABLED_EXTENSIONS`** through `lib/env.ts`; it
561
+ does not read Pi's own `settings.json` disable list, so an extension disabled
562
+ only there may still be probed.
563
+ - **A probe proves a binary runs, not that it works:** a `git` that crashes on a
564
+ specific repository is still `ok`.
565
+ - **Config scope is checked for `<agent-dir>/bytefly.json` and
566
+ `<cwd>/.pi/bytefly.json` only**; a `job.registryDir` override is not
567
+ followed.
568
+ - **Notifications are not focus-aware.** They fire whether or not the terminal
569
+ has focus, and once per trigger rather than once per unseen event. There is
570
+ no focus API to consult.
571
+ - **A background job cannot ask for input.** The `needsInput` trigger is a
572
+ prompt raised during a run, not a process waiting on stdin: jobs are
573
+ fire-and-observe, so that state does not exist.
574
+ - **An explicit `method` is honored blindly.** Point `method` at a notifier the
575
+ host lacks (or an OSC method off a TTY) and the notification is silently
576
+ dropped; only `auto` inspects the environment.
577
+
578
+ ## Non-goals
579
+
580
+ - No per-segment CLI: the bar's look lives in `footer/config.ts`, the price in
581
+ `bytefly.json`.
582
+ - No per-model cost breakdown, no `/bytefly price`, and no on-screen marker
583
+ for which tariff or which source produced a number.
584
+ - No todo-count segment; the [todo](../todo/) widget already shows progress.
585
+ - No shelling out for git ahead/behind; the footer data provider exposes only
586
+ the branch.
587
+ - No session or model data in the header: it shows Pi's brand mark and version
588
+ only. The footer owns the session, model, and working-directory data.
589
+ - No auto-fix: `doctor` reports the install command; it never runs it.
590
+ - No aggregation of runtime failures: a tool that errored earlier in the session
591
+ is not a prerequisite problem, and `doctor` does not pretend to know about it.
592
+ - No status chip for the doctor report: it is on demand plus one warning, not a
593
+ permanent footer segment.
594
+ - No notification for individual tool calls, message turns, or subagent steps:
595
+ the three triggers — a settled run, a prompt mid-run, a finished job — are the
596
+ whole surface.
597
+ - No sound, action buttons, notification history, or de-duplication; a
598
+ notification is a fire-and-forget message, not an inbox.
599
+
600
+ The built-in footer's `(auto)` auto-compact indicator and `(sub)`
601
+ subscription-cost marker are not reachable from `ExtensionContext` (there is no
602
+ `autoCompactEnabled` or provider subscription check), so the bar omits them
603
+ rather than showing a wrong value.
604
+
605
+ ## Pi integration
606
+
607
+ | Integration point | Details |
608
+ |---|---|
609
+ | 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. |
610
+ | Footer | `ctx.ui.setFooter` with a custom component, replacing Pi's built-in footer; installed only when `ctx.mode === "tui"` and the footer is on. |
611
+ | Command | `/bytefly [header|footer|status]` toggles the header and footer together (no argument) or one surface, and reports their state (tui only; a no-op elsewhere), with Tab completion. |
612
+ | Turn separator | `registerEntryRenderer("bytefly-separator")` draws each rule; `agent_settled` appends `pi.appendEntry("bytefly-separator", { turn })` in `tui` mode only. |
613
+ | 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. |
614
+ | Picker command | `pi.registerCommand("extensions")`; `tui` opens a dock screen via `ctx.ui.custom()` with the rails suppressed, other modes print through `ctx.ui.notify()`. |
615
+ | Picker discovery | `SettingsManager.create(...)` plus `DefaultPackageManager.resolve({ onMissing: "skip" })`, both public exports; both views drop `bytefly`'s own entrypoint. |
616
+ | Picker writes | `SettingsManager` setters (`setExtensionPaths`, `setPackages`, `setProjectExtensionPaths`, `setProjectPackages`) then `flush()`; `ctx.reload()` after the screen closes when changes are pending. |
617
+ | 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. |
618
+ | Doctor command | `pi.registerCommand("doctor")`; `tui` opens a dock screen via `ctx.ui.custom()` with the rails suppressed, other modes print through `ctx.ui.notify()`. |
619
+ | Doctor probes | `run_doctor` and `/doctor` share the session cache; `/doctor 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. |
620
+ | 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. |
621
+ | Config | `bytefly.json` from the agent directory and `<cwd>/.pi`; the `price` section is read when the footer is installed (a new session, or `/bytefly` toggled back on), and the `notify` and `doctor` sections at `session_start`. Nothing watches the document. |
622
+ | Data sources | `footerData.getGitBranch()`, `footerData.getAvailableProviderCount()`, `ctx.getContextUsage()`, `ctx.sessionManager.getEntries()`, `ctx.sessionManager.getSessionName()`, `ctx.model`; subscribes to `footerData.onBranchChange` to repaint. |
623
+ | 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 picker derives its screen from live runtime state 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. |
624
+ | 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. |
625
+
626
+ ## Design notes
627
+
628
+ - **The header is a nameplate, not a dashboard.** Pi's built-in brand colors are
629
+ fixed, so the mark ignores the active theme; bytefly's header draws a two-tone
630
+ mark from `accent`/`warning` and pairs it with the agent identity, the chrome
631
+ identity, and a grouped help block. It adds no session or model data — that is
632
+ the footer's job — and it stays a fixed four-plus rows so the startup
633
+ transcript never reflows.
634
+ - **Two lines, four zones.** Line 1 is identity (`pwd` left, branch + worktree
635
+ right); line 2 is resources (meters left, model + level right). This keeps
636
+ "where am I / what state" separate from "how much am I burning".
637
+ - **The gauge is anchored at column 0.** A leading mode slot would shift the
638
+ context percentage left and right as plan mode came and went; trailing it
639
+ after a `│` keeps the percentage in a fixed place while the mode stays
640
+ visible as the meters drop.
641
+ - **Statuses are routed, not repeated.** The built-in footer renders every
642
+ `setStatus` value on its own line; the bar splits them by meaning (`worktree`
643
+ and `browse` are location, plan/alert is mode). The [wechat](../wechat/)
644
+ extension publishes `✆ wechat` while its bridge runs, joining the mode/alert
645
+ slot; this is why the bar must render `getExtensionStatuses()` at all.
646
+ - **Browse joins the git state.** The `◉ <port>` chip is routed beside `⎇` branch
647
+ and `⑂` worktree rather than after `pwd`, so the left zone stays the
648
+ filesystem identity (path + session name) and every "where am I / what is
649
+ running" fact sits on line 1 right.
650
+ - **Segments degrade; they do not clip.** Each `Segment` carries progressively
651
+ shorter `forms` and a `weight`; the layout advances the lowest-priority form
652
+ or drops the lowest-priority segment, and only truncates once everything is
653
+ minimal. The gauge steps `10 → 5 → 3 → 0` blocks, tokens drop before cost,
654
+ and a branch that no longer fits its name drops rather than collapsing to a
655
+ glyph. The gauge floors its fill and shows at least one block for any
656
+ non-zero usage, so it reads full only at 100% and a small percentage never
657
+ disappears; unknown usage draws no gauge at all. Only at extreme widths does
658
+ `truncateToWidth` clip the remaining text.
659
+ - **Separators shrink to a single space.** Padded separators (` · `, ` │ `)
660
+ collapse to one space before `truncateToWidth` applies, so adjacent segments
661
+ never collide and the ellipsis never replaces a `│` with nothing after it.
662
+ - **Statuses compact with their colour.** A themed status's compact icon form
663
+ reattaches its opening and closing SGR sequences around the visible text, so a
664
+ red `✗1` stays red instead of losing its colour exactly when the bar tightens.
665
+ A two-token `icon count` badge (for `rewind`: `↺ 2`) compacts to `↺2`, keeping
666
+ the count. The slot sorts an alert (a failed job) ahead of the modes and is
667
+ droppable, so it goes before the model is clipped.
668
+ - **Neutral by default; color means something.** Only the context gauge carries
669
+ status color, and it stays `muted` below 70%; cost is `muted`, the session and
670
+ branch are `muted`, and tokens/cache/window are `dim`, so yellow and red are
671
+ reserved for the gauge crossing a threshold rather than always on.
672
+ - **Data is read fresh, except the usage scan.** The component resolves the
673
+ theme through a getter and reads a fresh snapshot each render. The expensive
674
+ part — summing token/cost totals across the session — is memoized by
675
+ `sessionId:leafId` (every append moves the leaf), avoiding
676
+ `getEntryCount()`, which is not exposed on the read-only session manager.
677
+ - **TTY-only, fail-safe.** The bytefly is installed only in `tui` mode;
678
+ non-interactive runs load the extension and change nothing.
679
+ - **Notify splits when, what, and how.** The trigger policy lives in
680
+ `notify/register.ts`, the wording in `notify/format.ts`, and the delivery in
681
+ `notify/dispatch.ts`, so each is testable without a terminal or a child
682
+ process and the sanitize-at-the-boundary rule has one home. The `job`
683
+ extension publishes completions on `pi.events` rather than being imported,
684
+ because extensions load with isolated caches and may not import each other;
685
+ the channel and its payload live in `lib/events.ts`.
686
+ - **The price is a stage, not a branch in the renderer.** Entries are reduced to
687
+ usage records (`collect/`), records are priced into totals (`price/`), and the
688
+ snapshot is drawn (`render/`). Pricing is a pure function of records and rates
689
+ — no session, terminal, or theme to stub — which is what lets the published
690
+ DeepSeek example be a unit test. The recorded-cost fallback keeps the stage
691
+ opt-in per model: a record with no matching rate is exactly Pi's own number,
692
+ in Pi's catalog units. The built-in DeepSeek table is only the default value
693
+ of `rates`, so nothing in the pricer is provider-specific.
694
+ - **DeepSeek is built in, not bundled as a catalog.** Its published price is in
695
+ yuan at two tariffs, while Pi's catalog holds one USD number per model, so an
696
+ unconfigured DeepSeek session showed a bill that was neither the right
697
+ currency nor the right amount outside peak. The table sits beside `CONFIG` as
698
+ the default `rates` and `peak`, which keeps config in charge and leaves the
699
+ pricer a pure function of records and rates.
700
+ - **A record is dated, not the frame.** The peak schedule is evaluated against
701
+ each entry's own timestamp, so a total is stable as the clock crosses a
702
+ boundary — and memoizing the scan by `sessionId:leafId` stays valid.
703
+ - **The picker reuses Pi's resolution; only the toggle is ported.** Reading
704
+ `SettingsManager` and `DefaultPackageManager` (public exports) makes the list
705
+ match what Pi loads; the `+`/`-` pattern arithmetic is a trimmed port of Pi's
706
+ config selector, kept pure and separately tested so a future drift is easy to
707
+ diff.
708
+ - **Two picker views, one write scope.** Personal (`global`) and effective
709
+ (`project`) resources are resolved separately, mirroring `pi config`; the
710
+ screen shows the view for the active scope and writes through the matching
711
+ store. A project package override is written with `autoload: false`, so it
712
+ filters the personal entry instead of replacing it.
713
+ - **Doctor declarations live in `lib/`, probes in the extension, presentation in
714
+ a third layer.** An extension cannot import another extension's modules, so
715
+ the only legal home for a shared prerequisite table is `lib/requirements.ts`;
716
+ the report itself is a pure function of the probe results.
717
+ - **The install hint has one home.** `document` and `web-access` format their
718
+ tool errors from the same `REQUIREMENTS` entries `doctor` prints, so a user
719
+ reads the same command whether a tool failed or the report named it.
720
+ - **The doctor is quiet by default at startup.** Only `missing` findings warn,
721
+ only for enabled extensions, only with a UI, and only once per session;
722
+ `degraded` findings — including a malformed config — wait in `/doctor`, because
723
+ a session that nags about an optional package is worse than one that never
724
+ mentions it.
725
+ - **Probe the real thing.** `subagent`'s requirement resolves through the same
726
+ `getPiInvocation` the extension uses rather than re-guessing whether a `pi`
727
+ binary exists.
728
+ - **The prerequisite table is data.** No I/O, no host access, no module state:
729
+ every probe dependency arrives as a seam, so `probeExtension` is testable with
730
+ no process and `lib/` stays value-only.
731
+
732
+ ## Files
733
+
734
+ The six surfaces are folders: `header/`, `footer/` (the status bar and its
735
+ price calculation), `separator/`, `picker/`, `doctor/`, and `notify/`.
736
+
737
+ | File | Responsibility |
738
+ |---|---|
739
+ | `index.ts` | Composition root: the disable guard and the six `register*` calls. |
740
+ | `header/config.ts` | Header tuning (`HEADER_CONFIG`): mark and identity tokens, padding, tagline, and the grouped help entries. |
741
+ | `header/component.ts` | `ByteflyHeader`: the mark, identity rows, rule, and grouped help, plus its factory. |
742
+ | `header/register.ts` | Installs/restores the header, exposes its enabled state, and owns its session lifecycle. |
743
+ | `footer/register.ts` | Installs/restores the footer, registers `/bytefly [header|footer|status]`, and owns its session lifecycle. |
744
+ | `footer/config.ts` | Bar tuning (`CONFIG`): thresholds, gauge, separators, icons, status keys, labels. |
745
+ | `footer/types.ts` | `StatusSnapshot`, `Segment`, `LineSpec`, `UsageRecord`, and footer/TUI shapes. |
746
+ | `footer/collect/entries.ts` | Session entries → usage records, each resolved to the model in effect. |
747
+ | `footer/collect/snapshot.ts` | Snapshot assembly; memoized pricing scan per branch leaf. |
748
+ | `footer/price/types.ts` | `Currency`, `ModelRates`, `PeakWindow`/`PeakSchedule`, `ByteflyConfig`. |
749
+ | `footer/price/config.ts` | The `price` section of `bytefly.json`: schema, defaults, built-in DeepSeek rates/tariff, normalizer, loader. |
750
+ | `footer/price/rates.ts` | Model-key matching: exact and wildcard, most specific first. |
751
+ | `footer/price/schedule.ts` | Peak-window parsing and evaluation in a configured zone. |
752
+ | `footer/price/price.ts` | Records → totals: rates, peak multiplier, recorded-cost fallback. |
753
+ | `footer/render/format.ts` | Generic formatters (tokens, cost in the configured currency, cwd, gauge, colors). |
754
+ | `footer/render/status.ts` | Status-chip transforms: `compactStatus` and `dropStatusDetail`. |
755
+ | `footer/render/segments.ts` | The segment builders: forms, weights, separators, colors. |
756
+ | `footer/render/lines.ts` | Snapshot → two `LineSpec`s; status routing. |
757
+ | `footer/render/layout.ts` | Width reduction and right-zone folding. |
758
+ | `footer/render/footer.ts` | `StatusBar` component and footer factory. |
759
+ | `separator/config.ts` | Turn-rule tuning: `customType`, glyph, label prefix, padding, and colors. |
760
+ | `separator/register.ts` | Registers the entry renderer and appends a separator on `agent_settled`. |
761
+ | `separator/component.ts` | `SeparatorComponent`: the stateless entry renderer. |
762
+ | `separator/format.ts` | Pure `separatorLabel` / `separatorParts` / `separatorLine`. |
763
+ | `separator/types.ts` | `TurnSeparatorData`, `SeparatorParts`. |
764
+ | `picker/types.ts` | Picker host seams (`SettingsStore`, `ExtensionResolver`, `Discovery`) and row types. |
765
+ | `picker/resources.ts` | Pure grouping, labels, display names, pattern helpers, text listing. |
766
+ | `picker/toggle.ts` | Pure next-settings transforms for global and project toggles. |
767
+ | `picker/discovery.ts` | Host wiring: builds the global and effective views from Pi's managers. |
768
+ | `picker/runtime.ts` | Picker scope, live view copies, dirty tracking, and persistence. |
769
+ | `picker/tui.ts` | `ExtensionsListComponent`, the dock screen. |
770
+ | `picker/commands.ts` | `/extensions`: TUI path and text fallback. |
771
+ | `doctor/register.ts` | Registers `run_doctor`, `/doctor`, and the proactive session-start check; returns the runtime. |
772
+ | `doctor/runtime.ts` | Host seams, the session cache, and the two probe modes. |
773
+ | `doctor/tools.ts` | The `run_doctor` tool and its renderers. |
774
+ | `doctor/commands.ts` | The `/doctor` command and its TUI wiring. |
775
+ | `doctor/schema.ts` | Tool parameters and the structured output schema. |
776
+ | `doctor/config.ts` | `DEFAULT_CONFIG`, `normalizeConfig`, `loadConfig` for the `doctor` section of `bytefly.json`. |
777
+ | `doctor/format.ts` | Pure report text, rows, glyphs, and the proactive warning sentence. |
778
+ | `doctor/tui.ts` | `DoctorScreen`, the docked list screen with its detail pane. |
779
+ | `notify/types.ts` | `NotifyConfig`, `NotifyMethod`, and the method list. |
780
+ | `notify/config.ts` | The `notify` section of `bytefly.json`: defaults, validation, loader. |
781
+ | `notify/dispatch.ts` | Method selection and delivery: OSC 777/99, Windows toast, `osascript`, `notify-send`, over injected seams. |
782
+ | `notify/format.ts` | Pure notification text: session label and the turn / input / job bodies. |
783
+ | `notify/register.ts` | Registers the three triggers, loads the config at `session_start`, and owns the job-bus subscription. |
784
+
785
+ ## Testing
786
+
787
+ `extensions/bytefly/` covers the install/restore/toggle lifecycle
788
+ (`extension`), the header's rendering, width shedding, and live theme
789
+ (`header/component`),
790
+ the formatters (`footer/render/format`), the status-chip transforms
791
+ (`footer/render/status`), entry
792
+ reduction and model inheritance (`footer/collect/entries`), snapshot assembly
793
+ and its memoization (`footer/collect/snapshot`), rate matching
794
+ (`footer/price/rates`), window parsing
795
+ and peak evaluation in two zones (`footer/price/schedule`), the pricing formula,
796
+ the off-peak multiplier, the built-in DeepSeek table with no config file, and the
797
+ fallback (`footer/price/price`), the config schema, its clamping, the built-in
798
+ defaults and their replacement, and the two file layers
799
+ (`footer/price/config`), zone/priority
800
+ assignment
801
+ (`footer/render/lines`), the reduction, separator collapse, and right-zone
802
+ folding across a width sweep from 6 to 120 plus wide characters
803
+ (`footer/render/layout`), exact
804
+ golden output at fixed widths (`footer/render/render`), and the install/skip/
805
+ restore/toggle lifecycle plus a config-driven price (`extension`). The turn
806
+ separator
807
+ is covered by its pure formatting (`separator/format`) and its
808
+ register/append/render/disable lifecycle (`separator/register`). The picker is
809
+ covered by the pure helpers (`picker/resources`), the toggle transforms
810
+ (`picker/toggle`), discovery with an injected factory (`picker/discovery`), the
811
+ runtime state machine (`picker/runtime`), the screen's rendering and key
812
+ handling (`picker/tui`), and the command through the bytefly factory
813
+ (`picker/commands`). The doctor surface is covered by its pure report
814
+ formatting (`doctor/format`), the screen's rendering and key handling
815
+ (`doctor/tui`), its config loader (`doctor/config`), and registration, the
816
+ proactive warning, the session cache, and `run_doctor` through the factory
817
+ (`doctor/register`). The notify capability is covered by its section loader
818
+ (`notify/config`), the pure text builders (`notify/format`), method selection and
819
+ delivery over recording seams (`notify/dispatch`), and its three triggers,
820
+ reload, and shutdown through `registerNotify` (`notify/register`).