@vandeepunk/pi-coding-agent 0.0.1

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 (595) hide show
  1. package/CHANGELOG.md +2564 -0
  2. package/README.md +555 -0
  3. package/dist/cli/args.d.ts +47 -0
  4. package/dist/cli/args.d.ts.map +1 -0
  5. package/dist/cli/args.js +286 -0
  6. package/dist/cli/args.js.map +1 -0
  7. package/dist/cli/config-selector.d.ts +14 -0
  8. package/dist/cli/config-selector.d.ts.map +1 -0
  9. package/dist/cli/config-selector.js +31 -0
  10. package/dist/cli/config-selector.js.map +1 -0
  11. package/dist/cli/file-processor.d.ts +15 -0
  12. package/dist/cli/file-processor.d.ts.map +1 -0
  13. package/dist/cli/file-processor.js +79 -0
  14. package/dist/cli/file-processor.js.map +1 -0
  15. package/dist/cli/list-models.d.ts +9 -0
  16. package/dist/cli/list-models.d.ts.map +1 -0
  17. package/dist/cli/list-models.js +92 -0
  18. package/dist/cli/list-models.js.map +1 -0
  19. package/dist/cli/session-picker.d.ts +9 -0
  20. package/dist/cli/session-picker.d.ts.map +1 -0
  21. package/dist/cli/session-picker.js +34 -0
  22. package/dist/cli/session-picker.js.map +1 -0
  23. package/dist/cli.d.ts +3 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +11 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/config.d.ts +68 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +203 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/core/agent-session.d.ts +574 -0
  32. package/dist/core/agent-session.d.ts.map +1 -0
  33. package/dist/core/agent-session.js +2260 -0
  34. package/dist/core/agent-session.js.map +1 -0
  35. package/dist/core/auth-storage.d.ts +102 -0
  36. package/dist/core/auth-storage.d.ts.map +1 -0
  37. package/dist/core/auth-storage.js +282 -0
  38. package/dist/core/auth-storage.js.map +1 -0
  39. package/dist/core/bash-executor.d.ts +47 -0
  40. package/dist/core/bash-executor.d.ts.map +1 -0
  41. package/dist/core/bash-executor.js +212 -0
  42. package/dist/core/bash-executor.js.map +1 -0
  43. package/dist/core/compaction/branch-summarization.d.ts +86 -0
  44. package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
  45. package/dist/core/compaction/branch-summarization.js +242 -0
  46. package/dist/core/compaction/branch-summarization.js.map +1 -0
  47. package/dist/core/compaction/compaction.d.ts +121 -0
  48. package/dist/core/compaction/compaction.d.ts.map +1 -0
  49. package/dist/core/compaction/compaction.js +607 -0
  50. package/dist/core/compaction/compaction.js.map +1 -0
  51. package/dist/core/compaction/index.d.ts +7 -0
  52. package/dist/core/compaction/index.d.ts.map +1 -0
  53. package/dist/core/compaction/index.js +7 -0
  54. package/dist/core/compaction/index.js.map +1 -0
  55. package/dist/core/compaction/utils.d.ts +35 -0
  56. package/dist/core/compaction/utils.d.ts.map +1 -0
  57. package/dist/core/compaction/utils.js +138 -0
  58. package/dist/core/compaction/utils.js.map +1 -0
  59. package/dist/core/defaults.d.ts +3 -0
  60. package/dist/core/defaults.d.ts.map +1 -0
  61. package/dist/core/defaults.js +2 -0
  62. package/dist/core/defaults.js.map +1 -0
  63. package/dist/core/diagnostics.d.ts +15 -0
  64. package/dist/core/diagnostics.d.ts.map +1 -0
  65. package/dist/core/diagnostics.js +2 -0
  66. package/dist/core/diagnostics.js.map +1 -0
  67. package/dist/core/event-bus.d.ts +9 -0
  68. package/dist/core/event-bus.d.ts.map +1 -0
  69. package/dist/core/event-bus.js +25 -0
  70. package/dist/core/event-bus.js.map +1 -0
  71. package/dist/core/exec.d.ts +29 -0
  72. package/dist/core/exec.d.ts.map +1 -0
  73. package/dist/core/exec.js +71 -0
  74. package/dist/core/exec.js.map +1 -0
  75. package/dist/core/export-html/ansi-to-html.d.ts +22 -0
  76. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  77. package/dist/core/export-html/ansi-to-html.js +249 -0
  78. package/dist/core/export-html/ansi-to-html.js.map +1 -0
  79. package/dist/core/export-html/index.d.ts +34 -0
  80. package/dist/core/export-html/index.d.ts.map +1 -0
  81. package/dist/core/export-html/index.js +222 -0
  82. package/dist/core/export-html/index.js.map +1 -0
  83. package/dist/core/export-html/template.css +909 -0
  84. package/dist/core/export-html/template.html +54 -0
  85. package/dist/core/export-html/template.js +1549 -0
  86. package/dist/core/export-html/tool-renderer.d.ts +35 -0
  87. package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  88. package/dist/core/export-html/tool-renderer.js +57 -0
  89. package/dist/core/export-html/tool-renderer.js.map +1 -0
  90. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  91. package/dist/core/export-html/vendor/marked.min.js +6 -0
  92. package/dist/core/extensions/index.d.ts +11 -0
  93. package/dist/core/extensions/index.d.ts.map +1 -0
  94. package/dist/core/extensions/index.js +9 -0
  95. package/dist/core/extensions/index.js.map +1 -0
  96. package/dist/core/extensions/loader.d.ts +25 -0
  97. package/dist/core/extensions/loader.d.ts.map +1 -0
  98. package/dist/core/extensions/loader.js +400 -0
  99. package/dist/core/extensions/loader.js.map +1 -0
  100. package/dist/core/extensions/runner.d.ts +129 -0
  101. package/dist/core/extensions/runner.d.ts.map +1 -0
  102. package/dist/core/extensions/runner.js +576 -0
  103. package/dist/core/extensions/runner.js.map +1 -0
  104. package/dist/core/extensions/types.d.ts +928 -0
  105. package/dist/core/extensions/types.d.ts.map +1 -0
  106. package/dist/core/extensions/types.js +35 -0
  107. package/dist/core/extensions/types.js.map +1 -0
  108. package/dist/core/extensions/wrapper.d.ts +27 -0
  109. package/dist/core/extensions/wrapper.d.ts.map +1 -0
  110. package/dist/core/extensions/wrapper.js +102 -0
  111. package/dist/core/extensions/wrapper.js.map +1 -0
  112. package/dist/core/footer-data-provider.d.ts +32 -0
  113. package/dist/core/footer-data-provider.d.ts.map +1 -0
  114. package/dist/core/footer-data-provider.js +134 -0
  115. package/dist/core/footer-data-provider.js.map +1 -0
  116. package/dist/core/index.d.ts +9 -0
  117. package/dist/core/index.d.ts.map +1 -0
  118. package/dist/core/index.js +9 -0
  119. package/dist/core/index.js.map +1 -0
  120. package/dist/core/keybindings.d.ts +55 -0
  121. package/dist/core/keybindings.d.ts.map +1 -0
  122. package/dist/core/keybindings.js +153 -0
  123. package/dist/core/keybindings.js.map +1 -0
  124. package/dist/core/messages.d.ts +77 -0
  125. package/dist/core/messages.d.ts.map +1 -0
  126. package/dist/core/messages.js +123 -0
  127. package/dist/core/messages.js.map +1 -0
  128. package/dist/core/model-registry.d.ts +100 -0
  129. package/dist/core/model-registry.d.ts.map +1 -0
  130. package/dist/core/model-registry.js +419 -0
  131. package/dist/core/model-registry.js.map +1 -0
  132. package/dist/core/model-resolver.d.ts +76 -0
  133. package/dist/core/model-resolver.d.ts.map +1 -0
  134. package/dist/core/model-resolver.js +313 -0
  135. package/dist/core/model-resolver.js.map +1 -0
  136. package/dist/core/package-manager.d.ts +131 -0
  137. package/dist/core/package-manager.d.ts.map +1 -0
  138. package/dist/core/package-manager.js +1290 -0
  139. package/dist/core/package-manager.js.map +1 -0
  140. package/dist/core/prompt-templates.d.ts +50 -0
  141. package/dist/core/prompt-templates.d.ts.map +1 -0
  142. package/dist/core/prompt-templates.js +251 -0
  143. package/dist/core/prompt-templates.js.map +1 -0
  144. package/dist/core/resolve-config-value.d.ts +17 -0
  145. package/dist/core/resolve-config-value.d.ts.map +1 -0
  146. package/dist/core/resolve-config-value.js +59 -0
  147. package/dist/core/resolve-config-value.js.map +1 -0
  148. package/dist/core/resource-loader.d.ts +184 -0
  149. package/dist/core/resource-loader.d.ts.map +1 -0
  150. package/dist/core/resource-loader.js +673 -0
  151. package/dist/core/resource-loader.js.map +1 -0
  152. package/dist/core/sdk.d.ts +90 -0
  153. package/dist/core/sdk.d.ts.map +1 -0
  154. package/dist/core/sdk.js +234 -0
  155. package/dist/core/sdk.js.map +1 -0
  156. package/dist/core/session-manager.d.ts +323 -0
  157. package/dist/core/session-manager.d.ts.map +1 -0
  158. package/dist/core/session-manager.js +1091 -0
  159. package/dist/core/session-manager.js.map +1 -0
  160. package/dist/core/settings-manager.d.ts +187 -0
  161. package/dist/core/settings-manager.d.ts.map +1 -0
  162. package/dist/core/settings-manager.js +552 -0
  163. package/dist/core/settings-manager.js.map +1 -0
  164. package/dist/core/skills.d.ts +58 -0
  165. package/dist/core/skills.d.ts.map +1 -0
  166. package/dist/core/skills.js +310 -0
  167. package/dist/core/skills.js.map +1 -0
  168. package/dist/core/slash-commands.d.ts +15 -0
  169. package/dist/core/slash-commands.d.ts.map +1 -0
  170. package/dist/core/slash-commands.js +21 -0
  171. package/dist/core/slash-commands.js.map +1 -0
  172. package/dist/core/system-prompt.d.ts +24 -0
  173. package/dist/core/system-prompt.d.ts.map +1 -0
  174. package/dist/core/system-prompt.js +137 -0
  175. package/dist/core/system-prompt.js.map +1 -0
  176. package/dist/core/timings.d.ts +7 -0
  177. package/dist/core/timings.d.ts.map +1 -0
  178. package/dist/core/timings.js +25 -0
  179. package/dist/core/timings.js.map +1 -0
  180. package/dist/core/tools/bash.d.ts +55 -0
  181. package/dist/core/tools/bash.d.ts.map +1 -0
  182. package/dist/core/tools/bash.js +242 -0
  183. package/dist/core/tools/bash.js.map +1 -0
  184. package/dist/core/tools/edit-diff.d.ts +63 -0
  185. package/dist/core/tools/edit-diff.d.ts.map +1 -0
  186. package/dist/core/tools/edit-diff.js +243 -0
  187. package/dist/core/tools/edit-diff.js.map +1 -0
  188. package/dist/core/tools/edit.d.ts +39 -0
  189. package/dist/core/tools/edit.d.ts.map +1 -0
  190. package/dist/core/tools/edit.js +146 -0
  191. package/dist/core/tools/edit.js.map +1 -0
  192. package/dist/core/tools/find.d.ts +39 -0
  193. package/dist/core/tools/find.d.ts.map +1 -0
  194. package/dist/core/tools/find.js +206 -0
  195. package/dist/core/tools/find.js.map +1 -0
  196. package/dist/core/tools/grep.d.ts +45 -0
  197. package/dist/core/tools/grep.d.ts.map +1 -0
  198. package/dist/core/tools/grep.js +239 -0
  199. package/dist/core/tools/grep.js.map +1 -0
  200. package/dist/core/tools/index.d.ts +73 -0
  201. package/dist/core/tools/index.d.ts.map +1 -0
  202. package/dist/core/tools/index.js +61 -0
  203. package/dist/core/tools/index.js.map +1 -0
  204. package/dist/core/tools/ls.d.ts +40 -0
  205. package/dist/core/tools/ls.d.ts.map +1 -0
  206. package/dist/core/tools/ls.js +118 -0
  207. package/dist/core/tools/ls.js.map +1 -0
  208. package/dist/core/tools/path-utils.d.ts +8 -0
  209. package/dist/core/tools/path-utils.d.ts.map +1 -0
  210. package/dist/core/tools/path-utils.js +81 -0
  211. package/dist/core/tools/path-utils.js.map +1 -0
  212. package/dist/core/tools/read.d.ts +39 -0
  213. package/dist/core/tools/read.d.ts.map +1 -0
  214. package/dist/core/tools/read.js +166 -0
  215. package/dist/core/tools/read.js.map +1 -0
  216. package/dist/core/tools/truncate.d.ts +70 -0
  217. package/dist/core/tools/truncate.d.ts.map +1 -0
  218. package/dist/core/tools/truncate.js +205 -0
  219. package/dist/core/tools/truncate.js.map +1 -0
  220. package/dist/core/tools/write.d.ts +29 -0
  221. package/dist/core/tools/write.d.ts.map +1 -0
  222. package/dist/core/tools/write.js +78 -0
  223. package/dist/core/tools/write.js.map +1 -0
  224. package/dist/index.d.ts +27 -0
  225. package/dist/index.d.ts.map +1 -0
  226. package/dist/index.js +42 -0
  227. package/dist/index.js.map +1 -0
  228. package/dist/main.d.ts +8 -0
  229. package/dist/main.d.ts.map +1 -0
  230. package/dist/main.js +623 -0
  231. package/dist/main.js.map +1 -0
  232. package/dist/migrations.d.ts +33 -0
  233. package/dist/migrations.d.ts.map +1 -0
  234. package/dist/migrations.js +261 -0
  235. package/dist/migrations.js.map +1 -0
  236. package/dist/modes/index.d.ts +9 -0
  237. package/dist/modes/index.d.ts.map +1 -0
  238. package/dist/modes/index.js +8 -0
  239. package/dist/modes/index.js.map +1 -0
  240. package/dist/modes/interactive/components/armin.d.ts +34 -0
  241. package/dist/modes/interactive/components/armin.d.ts.map +1 -0
  242. package/dist/modes/interactive/components/armin.js +333 -0
  243. package/dist/modes/interactive/components/armin.js.map +1 -0
  244. package/dist/modes/interactive/components/assistant-message.d.ts +16 -0
  245. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  246. package/dist/modes/interactive/components/assistant-message.js +91 -0
  247. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  248. package/dist/modes/interactive/components/bash-execution.d.ts +35 -0
  249. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  250. package/dist/modes/interactive/components/bash-execution.js +162 -0
  251. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  252. package/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  253. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
  254. package/dist/modes/interactive/components/bordered-loader.js +51 -0
  255. package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
  256. package/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  257. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
  258. package/dist/modes/interactive/components/branch-summary-message.js +44 -0
  259. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
  260. package/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  261. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
  262. package/dist/modes/interactive/components/compaction-summary-message.js +45 -0
  263. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
  264. package/dist/modes/interactive/components/config-selector.d.ts +71 -0
  265. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
  266. package/dist/modes/interactive/components/config-selector.js +479 -0
  267. package/dist/modes/interactive/components/config-selector.js.map +1 -0
  268. package/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  269. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -0
  270. package/dist/modes/interactive/components/countdown-timer.js +33 -0
  271. package/dist/modes/interactive/components/countdown-timer.js.map +1 -0
  272. package/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  273. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
  274. package/dist/modes/interactive/components/custom-editor.js +70 -0
  275. package/dist/modes/interactive/components/custom-editor.js.map +1 -0
  276. package/dist/modes/interactive/components/custom-message.d.ts +20 -0
  277. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  278. package/dist/modes/interactive/components/custom-message.js +79 -0
  279. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  280. package/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  281. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -0
  282. package/dist/modes/interactive/components/daxnuts.js +140 -0
  283. package/dist/modes/interactive/components/daxnuts.js.map +1 -0
  284. package/dist/modes/interactive/components/diff.d.ts +12 -0
  285. package/dist/modes/interactive/components/diff.d.ts.map +1 -0
  286. package/dist/modes/interactive/components/diff.js +133 -0
  287. package/dist/modes/interactive/components/diff.js.map +1 -0
  288. package/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  289. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
  290. package/dist/modes/interactive/components/dynamic-border.js +21 -0
  291. package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
  292. package/dist/modes/interactive/components/extension-editor.d.ts +17 -0
  293. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  294. package/dist/modes/interactive/components/extension-editor.js +102 -0
  295. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  296. package/dist/modes/interactive/components/extension-input.d.ts +23 -0
  297. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  298. package/dist/modes/interactive/components/extension-input.js +61 -0
  299. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  300. package/dist/modes/interactive/components/extension-selector.d.ts +24 -0
  301. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  302. package/dist/modes/interactive/components/extension-selector.js +78 -0
  303. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  304. package/dist/modes/interactive/components/footer.d.ts +26 -0
  305. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  306. package/dist/modes/interactive/components/footer.js +220 -0
  307. package/dist/modes/interactive/components/footer.js.map +1 -0
  308. package/dist/modes/interactive/components/index.d.ts +32 -0
  309. package/dist/modes/interactive/components/index.d.ts.map +1 -0
  310. package/dist/modes/interactive/components/index.js +33 -0
  311. package/dist/modes/interactive/components/index.js.map +1 -0
  312. package/dist/modes/interactive/components/keybinding-hints.d.ts +41 -0
  313. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -0
  314. package/dist/modes/interactive/components/keybinding-hints.js +61 -0
  315. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -0
  316. package/dist/modes/interactive/components/login-dialog.d.ts +42 -0
  317. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -0
  318. package/dist/modes/interactive/components/login-dialog.js +145 -0
  319. package/dist/modes/interactive/components/login-dialog.js.map +1 -0
  320. package/dist/modes/interactive/components/model-selector.d.ts +47 -0
  321. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
  322. package/dist/modes/interactive/components/model-selector.js +266 -0
  323. package/dist/modes/interactive/components/model-selector.js.map +1 -0
  324. package/dist/modes/interactive/components/oauth-selector.d.ts +19 -0
  325. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
  326. package/dist/modes/interactive/components/oauth-selector.js +97 -0
  327. package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
  328. package/dist/modes/interactive/components/scoped-models-selector.d.ts +49 -0
  329. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -0
  330. package/dist/modes/interactive/components/scoped-models-selector.js +270 -0
  331. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -0
  332. package/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  333. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
  334. package/dist/modes/interactive/components/session-selector-search.js +155 -0
  335. package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
  336. package/dist/modes/interactive/components/session-selector.d.ts +95 -0
  337. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
  338. package/dist/modes/interactive/components/session-selector.js +851 -0
  339. package/dist/modes/interactive/components/session-selector.js.map +1 -0
  340. package/dist/modes/interactive/components/settings-selector.d.ts +53 -0
  341. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -0
  342. package/dist/modes/interactive/components/settings-selector.js +277 -0
  343. package/dist/modes/interactive/components/settings-selector.js.map +1 -0
  344. package/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  345. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -0
  346. package/dist/modes/interactive/components/show-images-selector.js +35 -0
  347. package/dist/modes/interactive/components/show-images-selector.js.map +1 -0
  348. package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  349. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
  350. package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
  351. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
  352. package/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  353. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
  354. package/dist/modes/interactive/components/theme-selector.js +46 -0
  355. package/dist/modes/interactive/components/theme-selector.js.map +1 -0
  356. package/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  357. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
  358. package/dist/modes/interactive/components/thinking-selector.js +47 -0
  359. package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
  360. package/dist/modes/interactive/components/tool-execution.d.ts +70 -0
  361. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  362. package/dist/modes/interactive/components/tool-execution.js +621 -0
  363. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  364. package/dist/modes/interactive/components/tree-selector.d.ts +68 -0
  365. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
  366. package/dist/modes/interactive/components/tree-selector.js +934 -0
  367. package/dist/modes/interactive/components/tree-selector.js.map +1 -0
  368. package/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  369. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
  370. package/dist/modes/interactive/components/user-message-selector.js +113 -0
  371. package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
  372. package/dist/modes/interactive/components/user-message.d.ts +8 -0
  373. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  374. package/dist/modes/interactive/components/user-message.js +16 -0
  375. package/dist/modes/interactive/components/user-message.js.map +1 -0
  376. package/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  377. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -0
  378. package/dist/modes/interactive/components/visual-truncate.js +33 -0
  379. package/dist/modes/interactive/components/visual-truncate.js.map +1 -0
  380. package/dist/modes/interactive/interactive-mode.d.ts +313 -0
  381. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  382. package/dist/modes/interactive/interactive-mode.js +3664 -0
  383. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  384. package/dist/modes/interactive/theme/dark.json +85 -0
  385. package/dist/modes/interactive/theme/light.json +84 -0
  386. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  387. package/dist/modes/interactive/theme/theme.d.ts +78 -0
  388. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  389. package/dist/modes/interactive/theme/theme.js +944 -0
  390. package/dist/modes/interactive/theme/theme.js.map +1 -0
  391. package/dist/modes/print-mode.d.ts +28 -0
  392. package/dist/modes/print-mode.d.ts.map +1 -0
  393. package/dist/modes/print-mode.js +98 -0
  394. package/dist/modes/print-mode.js.map +1 -0
  395. package/dist/modes/rpc/rpc-client.d.ts +217 -0
  396. package/dist/modes/rpc/rpc-client.d.ts.map +1 -0
  397. package/dist/modes/rpc/rpc-client.js +405 -0
  398. package/dist/modes/rpc/rpc-client.js.map +1 -0
  399. package/dist/modes/rpc/rpc-mode.d.ts +20 -0
  400. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
  401. package/dist/modes/rpc/rpc-mode.js +500 -0
  402. package/dist/modes/rpc/rpc-mode.js.map +1 -0
  403. package/dist/modes/rpc/rpc-types.d.ts +409 -0
  404. package/dist/modes/rpc/rpc-types.d.ts.map +1 -0
  405. package/dist/modes/rpc/rpc-types.js +8 -0
  406. package/dist/modes/rpc/rpc-types.js.map +1 -0
  407. package/dist/utils/changelog.d.ts +21 -0
  408. package/dist/utils/changelog.d.ts.map +1 -0
  409. package/dist/utils/changelog.js +87 -0
  410. package/dist/utils/changelog.js.map +1 -0
  411. package/dist/utils/clipboard-image.d.ts +11 -0
  412. package/dist/utils/clipboard-image.d.ts.map +1 -0
  413. package/dist/utils/clipboard-image.js +162 -0
  414. package/dist/utils/clipboard-image.js.map +1 -0
  415. package/dist/utils/clipboard-native.d.ts +7 -0
  416. package/dist/utils/clipboard-native.d.ts.map +1 -0
  417. package/dist/utils/clipboard-native.js +14 -0
  418. package/dist/utils/clipboard-native.js.map +1 -0
  419. package/dist/utils/clipboard.d.ts +2 -0
  420. package/dist/utils/clipboard.d.ts.map +1 -0
  421. package/dist/utils/clipboard.js +67 -0
  422. package/dist/utils/clipboard.js.map +1 -0
  423. package/dist/utils/frontmatter.d.ts +8 -0
  424. package/dist/utils/frontmatter.d.ts.map +1 -0
  425. package/dist/utils/frontmatter.js +26 -0
  426. package/dist/utils/frontmatter.js.map +1 -0
  427. package/dist/utils/git.d.ts +2 -0
  428. package/dist/utils/git.d.ts.map +1 -0
  429. package/dist/utils/git.js +6 -0
  430. package/dist/utils/git.js.map +1 -0
  431. package/dist/utils/image-convert.d.ts +9 -0
  432. package/dist/utils/image-convert.d.ts.map +1 -0
  433. package/dist/utils/image-convert.js +35 -0
  434. package/dist/utils/image-convert.js.map +1 -0
  435. package/dist/utils/image-resize.d.ts +36 -0
  436. package/dist/utils/image-resize.d.ts.map +1 -0
  437. package/dist/utils/image-resize.js +181 -0
  438. package/dist/utils/image-resize.js.map +1 -0
  439. package/dist/utils/mime.d.ts +2 -0
  440. package/dist/utils/mime.d.ts.map +1 -0
  441. package/dist/utils/mime.js +26 -0
  442. package/dist/utils/mime.js.map +1 -0
  443. package/dist/utils/photon.d.ts +21 -0
  444. package/dist/utils/photon.d.ts.map +1 -0
  445. package/dist/utils/photon.js +121 -0
  446. package/dist/utils/photon.js.map +1 -0
  447. package/dist/utils/shell.d.ts +26 -0
  448. package/dist/utils/shell.d.ts.map +1 -0
  449. package/dist/utils/shell.js +186 -0
  450. package/dist/utils/shell.js.map +1 -0
  451. package/dist/utils/sleep.d.ts +5 -0
  452. package/dist/utils/sleep.d.ts.map +1 -0
  453. package/dist/utils/sleep.js +17 -0
  454. package/dist/utils/sleep.js.map +1 -0
  455. package/dist/utils/tools-manager.d.ts +3 -0
  456. package/dist/utils/tools-manager.d.ts.map +1 -0
  457. package/dist/utils/tools-manager.js +201 -0
  458. package/dist/utils/tools-manager.js.map +1 -0
  459. package/docs/compaction.md +390 -0
  460. package/docs/custom-provider.md +539 -0
  461. package/docs/development.md +69 -0
  462. package/docs/extensions.md +1827 -0
  463. package/docs/images/doom-extension.png +0 -0
  464. package/docs/images/exy.png +0 -0
  465. package/docs/images/interactive-mode.png +0 -0
  466. package/docs/images/tree-view.png +0 -0
  467. package/docs/json.md +79 -0
  468. package/docs/keybindings.md +174 -0
  469. package/docs/models.md +254 -0
  470. package/docs/packages.md +191 -0
  471. package/docs/prompt-templates.md +67 -0
  472. package/docs/providers.md +168 -0
  473. package/docs/rpc.md +1311 -0
  474. package/docs/sdk.md +957 -0
  475. package/docs/session.md +412 -0
  476. package/docs/settings.md +221 -0
  477. package/docs/shell-aliases.md +13 -0
  478. package/docs/skills.md +227 -0
  479. package/docs/terminal-setup.md +70 -0
  480. package/docs/termux.md +127 -0
  481. package/docs/themes.md +295 -0
  482. package/docs/tree.md +219 -0
  483. package/docs/tui.md +887 -0
  484. package/docs/windows.md +17 -0
  485. package/examples/README.md +25 -0
  486. package/examples/extensions/README.md +202 -0
  487. package/examples/extensions/antigravity-image-gen.ts +413 -0
  488. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  489. package/examples/extensions/bash-spawn-hook.ts +30 -0
  490. package/examples/extensions/bookmark.ts +50 -0
  491. package/examples/extensions/claude-rules.ts +86 -0
  492. package/examples/extensions/commands.ts +72 -0
  493. package/examples/extensions/confirm-destructive.ts +59 -0
  494. package/examples/extensions/custom-compaction.ts +114 -0
  495. package/examples/extensions/custom-footer.ts +64 -0
  496. package/examples/extensions/custom-header.ts +73 -0
  497. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  498. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  499. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  500. package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  501. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  502. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  503. package/examples/extensions/custom-provider-qwen-cli/index.ts +345 -0
  504. package/examples/extensions/custom-provider-qwen-cli/package.json +16 -0
  505. package/examples/extensions/dirty-repo-guard.ts +56 -0
  506. package/examples/extensions/doom-overlay/README.md +46 -0
  507. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  508. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  509. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  510. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  511. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  512. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  513. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  514. package/examples/extensions/doom-overlay/index.ts +74 -0
  515. package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  516. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  517. package/examples/extensions/dynamic-resources/dynamic.json +79 -0
  518. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  519. package/examples/extensions/dynamic-resources/index.ts +15 -0
  520. package/examples/extensions/event-bus.ts +43 -0
  521. package/examples/extensions/file-trigger.ts +41 -0
  522. package/examples/extensions/git-checkpoint.ts +53 -0
  523. package/examples/extensions/handoff.ts +150 -0
  524. package/examples/extensions/hello.ts +25 -0
  525. package/examples/extensions/inline-bash.ts +94 -0
  526. package/examples/extensions/input-transform.ts +43 -0
  527. package/examples/extensions/interactive-shell.ts +196 -0
  528. package/examples/extensions/mac-system-theme.ts +47 -0
  529. package/examples/extensions/message-renderer.ts +59 -0
  530. package/examples/extensions/minimal-mode.ts +426 -0
  531. package/examples/extensions/modal-editor.ts +85 -0
  532. package/examples/extensions/model-status.ts +31 -0
  533. package/examples/extensions/notify.ts +55 -0
  534. package/examples/extensions/overlay-qa-tests.ts +881 -0
  535. package/examples/extensions/overlay-test.ts +150 -0
  536. package/examples/extensions/permission-gate.ts +34 -0
  537. package/examples/extensions/pirate.ts +47 -0
  538. package/examples/extensions/plan-mode/README.md +65 -0
  539. package/examples/extensions/plan-mode/index.ts +340 -0
  540. package/examples/extensions/plan-mode/utils.ts +168 -0
  541. package/examples/extensions/preset.ts +398 -0
  542. package/examples/extensions/protected-paths.ts +30 -0
  543. package/examples/extensions/qna.ts +119 -0
  544. package/examples/extensions/question.ts +264 -0
  545. package/examples/extensions/questionnaire.ts +427 -0
  546. package/examples/extensions/rainbow-editor.ts +88 -0
  547. package/examples/extensions/rpc-demo.ts +124 -0
  548. package/examples/extensions/sandbox/index.ts +318 -0
  549. package/examples/extensions/sandbox/package-lock.json +92 -0
  550. package/examples/extensions/sandbox/package.json +19 -0
  551. package/examples/extensions/send-user-message.ts +97 -0
  552. package/examples/extensions/session-name.ts +27 -0
  553. package/examples/extensions/shutdown-command.ts +63 -0
  554. package/examples/extensions/snake.ts +343 -0
  555. package/examples/extensions/space-invaders.ts +560 -0
  556. package/examples/extensions/ssh.ts +220 -0
  557. package/examples/extensions/status-line.ts +40 -0
  558. package/examples/extensions/subagent/README.md +172 -0
  559. package/examples/extensions/subagent/agents/planner.md +37 -0
  560. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  561. package/examples/extensions/subagent/agents/scout.md +50 -0
  562. package/examples/extensions/subagent/agents/worker.md +24 -0
  563. package/examples/extensions/subagent/agents.ts +127 -0
  564. package/examples/extensions/subagent/index.ts +963 -0
  565. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  566. package/examples/extensions/subagent/prompts/implement.md +10 -0
  567. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  568. package/examples/extensions/summarize.ts +195 -0
  569. package/examples/extensions/system-prompt-header.ts +17 -0
  570. package/examples/extensions/timed-confirm.ts +70 -0
  571. package/examples/extensions/titlebar-spinner.ts +58 -0
  572. package/examples/extensions/todo.ts +299 -0
  573. package/examples/extensions/tool-override.ts +143 -0
  574. package/examples/extensions/tools.ts +146 -0
  575. package/examples/extensions/trigger-compact.ts +40 -0
  576. package/examples/extensions/truncated-tool.ts +192 -0
  577. package/examples/extensions/widget-placement.ts +17 -0
  578. package/examples/extensions/with-deps/index.ts +36 -0
  579. package/examples/extensions/with-deps/package-lock.json +31 -0
  580. package/examples/extensions/with-deps/package.json +22 -0
  581. package/examples/rpc-extension-ui.ts +632 -0
  582. package/examples/sdk/01-minimal.ts +22 -0
  583. package/examples/sdk/02-custom-model.ts +49 -0
  584. package/examples/sdk/03-custom-prompt.ts +55 -0
  585. package/examples/sdk/04-skills.ts +46 -0
  586. package/examples/sdk/05-tools.ts +56 -0
  587. package/examples/sdk/06-extensions.ts +88 -0
  588. package/examples/sdk/07-context-files.ts +40 -0
  589. package/examples/sdk/08-prompt-templates.ts +47 -0
  590. package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  591. package/examples/sdk/10-settings.ts +38 -0
  592. package/examples/sdk/11-sessions.ts +48 -0
  593. package/examples/sdk/12-full-control.ts +82 -0
  594. package/examples/sdk/README.md +144 -0
  595. package/package.json +97 -0
package/docs/tui.md ADDED
@@ -0,0 +1,887 @@
1
+ > pi can create TUI components. Ask it to build one for your use case.
2
+
3
+ # TUI Components
4
+
5
+ Extensions and custom tools can render custom TUI components for interactive user interfaces. This page covers the component system and available building blocks.
6
+
7
+ **Source:** [`@mariozechner/pi-tui`](https://github.com/badlogic/pi-mono/tree/main/packages/tui)
8
+
9
+ ## Component Interface
10
+
11
+ All components implement:
12
+
13
+ ```typescript
14
+ interface Component {
15
+ render(width: number): string[];
16
+ handleInput?(data: string): void;
17
+ wantsKeyRelease?: boolean;
18
+ invalidate(): void;
19
+ }
20
+ ```
21
+
22
+ | Method | Description |
23
+ |--------|-------------|
24
+ | `render(width)` | Return array of strings (one per line). Each line **must not exceed `width`**. |
25
+ | `handleInput?(data)` | Receive keyboard input when component has focus. |
26
+ | `wantsKeyRelease?` | If true, component receives key release events (Kitty protocol). Default: false. |
27
+ | `invalidate()` | Clear cached render state. Called on theme changes. |
28
+
29
+ The TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.
30
+
31
+ ## Focusable Interface (IME Support)
32
+
33
+ Components that display a text cursor and need IME (Input Method Editor) support should implement the `Focusable` interface:
34
+
35
+ ```typescript
36
+ import { CURSOR_MARKER, type Component, type Focusable } from "@mariozechner/pi-tui";
37
+
38
+ class MyInput implements Component, Focusable {
39
+ focused: boolean = false; // Set by TUI when focus changes
40
+
41
+ render(width: number): string[] {
42
+ const marker = this.focused ? CURSOR_MARKER : "";
43
+ // Emit marker right before the fake cursor
44
+ return [`> ${beforeCursor}${marker}\x1b[7m${atCursor}\x1b[27m${afterCursor}`];
45
+ }
46
+ }
47
+ ```
48
+
49
+ When a `Focusable` component has focus, TUI:
50
+ 1. Sets `focused = true` on the component
51
+ 2. Scans rendered output for `CURSOR_MARKER` (a zero-width APC escape sequence)
52
+ 3. Positions the hardware terminal cursor at that location
53
+ 4. Shows the hardware cursor
54
+
55
+ This enables IME candidate windows to appear at the correct position for CJK input methods. The `Editor` and `Input` built-in components already implement this interface.
56
+
57
+ ### Container Components with Embedded Inputs
58
+
59
+ When a container component (dialog, selector, etc.) contains an `Input` or `Editor` child, the container must implement `Focusable` and propagate the focus state to the child. Otherwise, the hardware cursor won't be positioned correctly for IME input.
60
+
61
+ ```typescript
62
+ import { Container, type Focusable, Input } from "@mariozechner/pi-tui";
63
+
64
+ class SearchDialog extends Container implements Focusable {
65
+ private searchInput: Input;
66
+
67
+ // Focusable implementation - propagate to child input for IME cursor positioning
68
+ private _focused = false;
69
+ get focused(): boolean {
70
+ return this._focused;
71
+ }
72
+ set focused(value: boolean) {
73
+ this._focused = value;
74
+ this.searchInput.focused = value;
75
+ }
76
+
77
+ constructor() {
78
+ super();
79
+ this.searchInput = new Input();
80
+ this.addChild(this.searchInput);
81
+ }
82
+ }
83
+ ```
84
+
85
+ Without this propagation, typing with an IME (Chinese, Japanese, Korean, etc.) will show the candidate window in the wrong position on screen.
86
+
87
+ ## Using Components
88
+
89
+ **In extensions** via `ctx.ui.custom()`:
90
+
91
+ ```typescript
92
+ pi.on("session_start", async (_event, ctx) => {
93
+ const handle = ctx.ui.custom(myComponent);
94
+ // handle.requestRender() - trigger re-render
95
+ // handle.close() - restore normal UI
96
+ });
97
+ ```
98
+
99
+ **In custom tools** via `pi.ui.custom()`:
100
+
101
+ ```typescript
102
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
103
+ const handle = pi.ui.custom(myComponent);
104
+ // ...
105
+ handle.close();
106
+ }
107
+ ```
108
+
109
+ ## Overlays
110
+
111
+ Overlays render components on top of existing content without clearing the screen. Pass `{ overlay: true }` to `ctx.ui.custom()`:
112
+
113
+ ```typescript
114
+ const result = await ctx.ui.custom<string | null>(
115
+ (tui, theme, keybindings, done) => new MyDialog({ onClose: done }),
116
+ { overlay: true }
117
+ );
118
+ ```
119
+
120
+ For positioning and sizing, use `overlayOptions`:
121
+
122
+ ```typescript
123
+ const result = await ctx.ui.custom<string | null>(
124
+ (tui, theme, keybindings, done) => new SidePanel({ onClose: done }),
125
+ {
126
+ overlay: true,
127
+ overlayOptions: {
128
+ // Size: number or percentage string
129
+ width: "50%", // 50% of terminal width
130
+ minWidth: 40, // minimum 40 columns
131
+ maxHeight: "80%", // max 80% of terminal height
132
+
133
+ // Position: anchor-based (default: "center")
134
+ anchor: "right-center", // 9 positions: center, top-left, top-center, etc.
135
+ offsetX: -2, // offset from anchor
136
+ offsetY: 0,
137
+
138
+ // Or percentage/absolute positioning
139
+ row: "25%", // 25% from top
140
+ col: 10, // column 10
141
+
142
+ // Margins
143
+ margin: 2, // all sides, or { top, right, bottom, left }
144
+
145
+ // Responsive: hide on narrow terminals
146
+ visible: (termWidth, termHeight) => termWidth >= 80,
147
+ },
148
+ // Get handle for programmatic visibility control
149
+ onHandle: (handle) => {
150
+ // handle.setHidden(true/false) - toggle visibility
151
+ // handle.hide() - permanently remove
152
+ },
153
+ }
154
+ );
155
+ ```
156
+
157
+ ### Overlay Lifecycle
158
+
159
+ Overlay components are disposed when closed. Don't reuse references - create fresh instances:
160
+
161
+ ```typescript
162
+ // Wrong - stale reference
163
+ let menu: MenuComponent;
164
+ await ctx.ui.custom((_, __, ___, done) => {
165
+ menu = new MenuComponent(done);
166
+ return menu;
167
+ }, { overlay: true });
168
+ setActiveComponent(menu); // Disposed
169
+
170
+ // Correct - re-call to re-show
171
+ const showMenu = () => ctx.ui.custom((_, __, ___, done) =>
172
+ new MenuComponent(done), { overlay: true });
173
+
174
+ await showMenu(); // First show
175
+ await showMenu(); // "Back" = just call again
176
+ ```
177
+
178
+ See [overlay-qa-tests.ts](../examples/extensions/overlay-qa-tests.ts) for comprehensive examples covering anchors, margins, stacking, responsive visibility, and animation.
179
+
180
+ ## Built-in Components
181
+
182
+ Import from `@mariozechner/pi-tui`:
183
+
184
+ ```typescript
185
+ import { Text, Box, Container, Spacer, Markdown } from "@mariozechner/pi-tui";
186
+ ```
187
+
188
+ ### Text
189
+
190
+ Multi-line text with word wrapping.
191
+
192
+ ```typescript
193
+ const text = new Text(
194
+ "Hello World", // content
195
+ 1, // paddingX (default: 1)
196
+ 1, // paddingY (default: 1)
197
+ (s) => bgGray(s) // optional background function
198
+ );
199
+ text.setText("Updated");
200
+ ```
201
+
202
+ ### Box
203
+
204
+ Container with padding and background color.
205
+
206
+ ```typescript
207
+ const box = new Box(
208
+ 1, // paddingX
209
+ 1, // paddingY
210
+ (s) => bgGray(s) // background function
211
+ );
212
+ box.addChild(new Text("Content", 0, 0));
213
+ box.setBgFn((s) => bgBlue(s));
214
+ ```
215
+
216
+ ### Container
217
+
218
+ Groups child components vertically.
219
+
220
+ ```typescript
221
+ const container = new Container();
222
+ container.addChild(component1);
223
+ container.addChild(component2);
224
+ container.removeChild(component1);
225
+ ```
226
+
227
+ ### Spacer
228
+
229
+ Empty vertical space.
230
+
231
+ ```typescript
232
+ const spacer = new Spacer(2); // 2 empty lines
233
+ ```
234
+
235
+ ### Markdown
236
+
237
+ Renders markdown with syntax highlighting.
238
+
239
+ ```typescript
240
+ const md = new Markdown(
241
+ "# Title\n\nSome **bold** text",
242
+ 1, // paddingX
243
+ 1, // paddingY
244
+ theme // MarkdownTheme (see below)
245
+ );
246
+ md.setText("Updated markdown");
247
+ ```
248
+
249
+ ### Image
250
+
251
+ Renders images in supported terminals (Kitty, iTerm2, Ghostty, WezTerm).
252
+
253
+ ```typescript
254
+ const image = new Image(
255
+ base64Data, // base64-encoded image
256
+ "image/png", // MIME type
257
+ theme, // ImageTheme
258
+ { maxWidthCells: 80, maxHeightCells: 24 }
259
+ );
260
+ ```
261
+
262
+ ## Keyboard Input
263
+
264
+ Use `matchesKey()` for key detection:
265
+
266
+ ```typescript
267
+ import { matchesKey, Key } from "@mariozechner/pi-tui";
268
+
269
+ handleInput(data: string) {
270
+ if (matchesKey(data, Key.up)) {
271
+ this.selectedIndex--;
272
+ } else if (matchesKey(data, Key.enter)) {
273
+ this.onSelect?.(this.selectedIndex);
274
+ } else if (matchesKey(data, Key.escape)) {
275
+ this.onCancel?.();
276
+ } else if (matchesKey(data, Key.ctrl("c"))) {
277
+ // Ctrl+C
278
+ }
279
+ }
280
+ ```
281
+
282
+ **Key identifiers** (use `Key.*` for autocomplete, or string literals):
283
+ - Basic keys: `Key.enter`, `Key.escape`, `Key.tab`, `Key.space`, `Key.backspace`, `Key.delete`, `Key.home`, `Key.end`
284
+ - Arrow keys: `Key.up`, `Key.down`, `Key.left`, `Key.right`
285
+ - With modifiers: `Key.ctrl("c")`, `Key.shift("tab")`, `Key.alt("left")`, `Key.ctrlShift("p")`
286
+ - String format also works: `"enter"`, `"ctrl+c"`, `"shift+tab"`, `"ctrl+shift+p"`
287
+
288
+ ## Line Width
289
+
290
+ **Critical:** Each line from `render()` must not exceed the `width` parameter.
291
+
292
+ ```typescript
293
+ import { visibleWidth, truncateToWidth } from "@mariozechner/pi-tui";
294
+
295
+ render(width: number): string[] {
296
+ // Truncate long lines
297
+ return [truncateToWidth(this.text, width)];
298
+ }
299
+ ```
300
+
301
+ Utilities:
302
+ - `visibleWidth(str)` - Get display width (ignores ANSI codes)
303
+ - `truncateToWidth(str, width, ellipsis?)` - Truncate with optional ellipsis
304
+ - `wrapTextWithAnsi(str, width)` - Word wrap preserving ANSI codes
305
+
306
+ ## Creating Custom Components
307
+
308
+ Example: Interactive selector
309
+
310
+ ```typescript
311
+ import {
312
+ matchesKey, Key,
313
+ truncateToWidth, visibleWidth
314
+ } from "@mariozechner/pi-tui";
315
+
316
+ class MySelector {
317
+ private items: string[];
318
+ private selected = 0;
319
+ private cachedWidth?: number;
320
+ private cachedLines?: string[];
321
+
322
+ public onSelect?: (item: string) => void;
323
+ public onCancel?: () => void;
324
+
325
+ constructor(items: string[]) {
326
+ this.items = items;
327
+ }
328
+
329
+ handleInput(data: string): void {
330
+ if (matchesKey(data, Key.up) && this.selected > 0) {
331
+ this.selected--;
332
+ this.invalidate();
333
+ } else if (matchesKey(data, Key.down) && this.selected < this.items.length - 1) {
334
+ this.selected++;
335
+ this.invalidate();
336
+ } else if (matchesKey(data, Key.enter)) {
337
+ this.onSelect?.(this.items[this.selected]);
338
+ } else if (matchesKey(data, Key.escape)) {
339
+ this.onCancel?.();
340
+ }
341
+ }
342
+
343
+ render(width: number): string[] {
344
+ if (this.cachedLines && this.cachedWidth === width) {
345
+ return this.cachedLines;
346
+ }
347
+
348
+ this.cachedLines = this.items.map((item, i) => {
349
+ const prefix = i === this.selected ? "> " : " ";
350
+ return truncateToWidth(prefix + item, width);
351
+ });
352
+ this.cachedWidth = width;
353
+ return this.cachedLines;
354
+ }
355
+
356
+ invalidate(): void {
357
+ this.cachedWidth = undefined;
358
+ this.cachedLines = undefined;
359
+ }
360
+ }
361
+ ```
362
+
363
+ Usage in an extension:
364
+
365
+ ```typescript
366
+ pi.registerCommand("pick", {
367
+ description: "Pick an item",
368
+ handler: async (args, ctx) => {
369
+ const items = ["Option A", "Option B", "Option C"];
370
+ const selector = new MySelector(items);
371
+
372
+ let handle: { close: () => void; requestRender: () => void };
373
+
374
+ await new Promise<void>((resolve) => {
375
+ selector.onSelect = (item) => {
376
+ ctx.ui.notify(`Selected: ${item}`, "info");
377
+ handle.close();
378
+ resolve();
379
+ };
380
+ selector.onCancel = () => {
381
+ handle.close();
382
+ resolve();
383
+ };
384
+ handle = ctx.ui.custom(selector);
385
+ });
386
+ }
387
+ });
388
+ ```
389
+
390
+ ## Theming
391
+
392
+ Components accept theme objects for styling.
393
+
394
+ **In `renderCall`/`renderResult`**, use the `theme` parameter:
395
+
396
+ ```typescript
397
+ renderResult(result, options, theme) {
398
+ // Use theme.fg() for foreground colors
399
+ return new Text(theme.fg("success", "Done!"), 0, 0);
400
+
401
+ // Use theme.bg() for background colors
402
+ const styled = theme.bg("toolPendingBg", theme.fg("accent", "text"));
403
+ }
404
+ ```
405
+
406
+ **Foreground colors** (`theme.fg(color, text)`):
407
+
408
+ | Category | Colors |
409
+ |----------|--------|
410
+ | General | `text`, `accent`, `muted`, `dim` |
411
+ | Status | `success`, `error`, `warning` |
412
+ | Borders | `border`, `borderAccent`, `borderMuted` |
413
+ | Messages | `userMessageText`, `customMessageText`, `customMessageLabel` |
414
+ | Tools | `toolTitle`, `toolOutput` |
415
+ | Diffs | `toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext` |
416
+ | Markdown | `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |
417
+ | Syntax | `syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation` |
418
+ | Thinking | `thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh` |
419
+ | Modes | `bashMode` |
420
+
421
+ **Background colors** (`theme.bg(color, text)`):
422
+
423
+ `selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`
424
+
425
+ **For Markdown**, use `getMarkdownTheme()`:
426
+
427
+ ```typescript
428
+ import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
429
+ import { Markdown } from "@mariozechner/pi-tui";
430
+
431
+ renderResult(result, options, theme) {
432
+ const mdTheme = getMarkdownTheme();
433
+ return new Markdown(result.details.markdown, 0, 0, mdTheme);
434
+ }
435
+ ```
436
+
437
+ **For custom components**, define your own theme interface:
438
+
439
+ ```typescript
440
+ interface MyTheme {
441
+ selected: (s: string) => string;
442
+ normal: (s: string) => string;
443
+ }
444
+ ```
445
+
446
+ ## Debug logging
447
+
448
+ Set `PI_TUI_WRITE_LOG` to capture the raw ANSI stream written to stdout.
449
+
450
+ ```bash
451
+ PI_TUI_WRITE_LOG=/tmp/tui-ansi.log npx tsx packages/tui/test/chat-simple.ts
452
+ ```
453
+
454
+ ## Performance
455
+
456
+ Cache rendered output when possible:
457
+
458
+ ```typescript
459
+ class CachedComponent {
460
+ private cachedWidth?: number;
461
+ private cachedLines?: string[];
462
+
463
+ render(width: number): string[] {
464
+ if (this.cachedLines && this.cachedWidth === width) {
465
+ return this.cachedLines;
466
+ }
467
+ // ... compute lines ...
468
+ this.cachedWidth = width;
469
+ this.cachedLines = lines;
470
+ return lines;
471
+ }
472
+
473
+ invalidate(): void {
474
+ this.cachedWidth = undefined;
475
+ this.cachedLines = undefined;
476
+ }
477
+ }
478
+ ```
479
+
480
+ Call `invalidate()` when state changes, then `handle.requestRender()` to trigger re-render.
481
+
482
+ ## Invalidation and Theme Changes
483
+
484
+ When the theme changes, the TUI calls `invalidate()` on all components to clear their caches. Components must properly implement `invalidate()` to ensure theme changes take effect.
485
+
486
+ ### The Problem
487
+
488
+ If a component pre-bakes theme colors into strings (via `theme.fg()`, `theme.bg()`, etc.) and caches them, the cached strings contain ANSI escape codes from the old theme. Simply clearing the render cache isn't enough if the component stores the themed content separately.
489
+
490
+ **Wrong approach** (theme colors won't update):
491
+
492
+ ```typescript
493
+ class BadComponent extends Container {
494
+ private content: Text;
495
+
496
+ constructor(message: string, theme: Theme) {
497
+ super();
498
+ // Pre-baked theme colors stored in Text component
499
+ this.content = new Text(theme.fg("accent", message), 1, 0);
500
+ this.addChild(this.content);
501
+ }
502
+ // No invalidate override - parent's invalidate only clears
503
+ // child render caches, not the pre-baked content
504
+ }
505
+ ```
506
+
507
+ ### The Solution
508
+
509
+ Components that build content with theme colors must rebuild that content when `invalidate()` is called:
510
+
511
+ ```typescript
512
+ class GoodComponent extends Container {
513
+ private message: string;
514
+ private content: Text;
515
+
516
+ constructor(message: string) {
517
+ super();
518
+ this.message = message;
519
+ this.content = new Text("", 1, 0);
520
+ this.addChild(this.content);
521
+ this.updateDisplay();
522
+ }
523
+
524
+ private updateDisplay(): void {
525
+ // Rebuild content with current theme
526
+ this.content.setText(theme.fg("accent", this.message));
527
+ }
528
+
529
+ override invalidate(): void {
530
+ super.invalidate(); // Clear child caches
531
+ this.updateDisplay(); // Rebuild with new theme
532
+ }
533
+ }
534
+ ```
535
+
536
+ ### Pattern: Rebuild on Invalidate
537
+
538
+ For components with complex content:
539
+
540
+ ```typescript
541
+ class ComplexComponent extends Container {
542
+ private data: SomeData;
543
+
544
+ constructor(data: SomeData) {
545
+ super();
546
+ this.data = data;
547
+ this.rebuild();
548
+ }
549
+
550
+ private rebuild(): void {
551
+ this.clear(); // Remove all children
552
+
553
+ // Build UI with current theme
554
+ this.addChild(new Text(theme.fg("accent", theme.bold("Title")), 1, 0));
555
+ this.addChild(new Spacer(1));
556
+
557
+ for (const item of this.data.items) {
558
+ const color = item.active ? "success" : "muted";
559
+ this.addChild(new Text(theme.fg(color, item.label), 1, 0));
560
+ }
561
+ }
562
+
563
+ override invalidate(): void {
564
+ super.invalidate();
565
+ this.rebuild();
566
+ }
567
+ }
568
+ ```
569
+
570
+ ### When This Matters
571
+
572
+ This pattern is needed when:
573
+
574
+ 1. **Pre-baking theme colors** - Using `theme.fg()` or `theme.bg()` to create styled strings stored in child components
575
+ 2. **Syntax highlighting** - Using `highlightCode()` which applies theme-based syntax colors
576
+ 3. **Complex layouts** - Building child component trees that embed theme colors
577
+
578
+ This pattern is NOT needed when:
579
+
580
+ 1. **Using theme callbacks** - Passing functions like `(text) => theme.fg("accent", text)` that are called during render
581
+ 2. **Simple containers** - Just grouping other components without adding themed content
582
+ 3. **Stateless render** - Computing themed output fresh in every `render()` call (no caching)
583
+
584
+ ## Common Patterns
585
+
586
+ These patterns cover the most common UI needs in extensions. **Copy these patterns instead of building from scratch.**
587
+
588
+ ### Pattern 1: Selection Dialog (SelectList)
589
+
590
+ For letting users pick from a list of options. Use `SelectList` from `@mariozechner/pi-tui` with `DynamicBorder` for framing.
591
+
592
+ ```typescript
593
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
594
+ import { DynamicBorder } from "@mariozechner/pi-coding-agent";
595
+ import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
596
+
597
+ pi.registerCommand("pick", {
598
+ handler: async (_args, ctx) => {
599
+ const items: SelectItem[] = [
600
+ { value: "opt1", label: "Option 1", description: "First option" },
601
+ { value: "opt2", label: "Option 2", description: "Second option" },
602
+ { value: "opt3", label: "Option 3" }, // description is optional
603
+ ];
604
+
605
+ const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
606
+ const container = new Container();
607
+
608
+ // Top border
609
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
610
+
611
+ // Title
612
+ container.addChild(new Text(theme.fg("accent", theme.bold("Pick an Option")), 1, 0));
613
+
614
+ // SelectList with theme
615
+ const selectList = new SelectList(items, Math.min(items.length, 10), {
616
+ selectedPrefix: (t) => theme.fg("accent", t),
617
+ selectedText: (t) => theme.fg("accent", t),
618
+ description: (t) => theme.fg("muted", t),
619
+ scrollInfo: (t) => theme.fg("dim", t),
620
+ noMatch: (t) => theme.fg("warning", t),
621
+ });
622
+ selectList.onSelect = (item) => done(item.value);
623
+ selectList.onCancel = () => done(null);
624
+ container.addChild(selectList);
625
+
626
+ // Help text
627
+ container.addChild(new Text(theme.fg("dim", "↑↓ navigate • enter select • esc cancel"), 1, 0));
628
+
629
+ // Bottom border
630
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
631
+
632
+ return {
633
+ render: (w) => container.render(w),
634
+ invalidate: () => container.invalidate(),
635
+ handleInput: (data) => { selectList.handleInput(data); tui.requestRender(); },
636
+ };
637
+ });
638
+
639
+ if (result) {
640
+ ctx.ui.notify(`Selected: ${result}`, "info");
641
+ }
642
+ },
643
+ });
644
+ ```
645
+
646
+ **Examples:** [preset.ts](../examples/extensions/preset.ts), [tools.ts](../examples/extensions/tools.ts)
647
+
648
+ ### Pattern 2: Async Operation with Cancel (BorderedLoader)
649
+
650
+ For operations that take time and should be cancellable. `BorderedLoader` shows a spinner and handles escape to cancel.
651
+
652
+ ```typescript
653
+ import { BorderedLoader } from "@mariozechner/pi-coding-agent";
654
+
655
+ pi.registerCommand("fetch", {
656
+ handler: async (_args, ctx) => {
657
+ const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
658
+ const loader = new BorderedLoader(tui, theme, "Fetching data...");
659
+ loader.onAbort = () => done(null);
660
+
661
+ // Do async work
662
+ fetchData(loader.signal)
663
+ .then((data) => done(data))
664
+ .catch(() => done(null));
665
+
666
+ return loader;
667
+ });
668
+
669
+ if (result === null) {
670
+ ctx.ui.notify("Cancelled", "info");
671
+ } else {
672
+ ctx.ui.setEditorText(result);
673
+ }
674
+ },
675
+ });
676
+ ```
677
+
678
+ **Examples:** [qna.ts](../examples/extensions/qna.ts), [handoff.ts](../examples/extensions/handoff.ts)
679
+
680
+ ### Pattern 3: Settings/Toggles (SettingsList)
681
+
682
+ For toggling multiple settings. Use `SettingsList` from `@mariozechner/pi-tui` with `getSettingsListTheme()`.
683
+
684
+ ```typescript
685
+ import { getSettingsListTheme } from "@mariozechner/pi-coding-agent";
686
+ import { Container, type SettingItem, SettingsList, Text } from "@mariozechner/pi-tui";
687
+
688
+ pi.registerCommand("settings", {
689
+ handler: async (_args, ctx) => {
690
+ const items: SettingItem[] = [
691
+ { id: "verbose", label: "Verbose mode", currentValue: "off", values: ["on", "off"] },
692
+ { id: "color", label: "Color output", currentValue: "on", values: ["on", "off"] },
693
+ ];
694
+
695
+ await ctx.ui.custom((_tui, theme, _kb, done) => {
696
+ const container = new Container();
697
+ container.addChild(new Text(theme.fg("accent", theme.bold("Settings")), 1, 1));
698
+
699
+ const settingsList = new SettingsList(
700
+ items,
701
+ Math.min(items.length + 2, 15),
702
+ getSettingsListTheme(),
703
+ (id, newValue) => {
704
+ // Handle value change
705
+ ctx.ui.notify(`${id} = ${newValue}`, "info");
706
+ },
707
+ () => done(undefined), // On close
708
+ { enableSearch: true }, // Optional: enable fuzzy search by label
709
+ );
710
+ container.addChild(settingsList);
711
+
712
+ return {
713
+ render: (w) => container.render(w),
714
+ invalidate: () => container.invalidate(),
715
+ handleInput: (data) => settingsList.handleInput?.(data),
716
+ };
717
+ });
718
+ },
719
+ });
720
+ ```
721
+
722
+ **Examples:** [tools.ts](../examples/extensions/tools.ts)
723
+
724
+ ### Pattern 4: Persistent Status Indicator
725
+
726
+ Show status in the footer that persists across renders. Good for mode indicators.
727
+
728
+ ```typescript
729
+ // Set status (shown in footer)
730
+ ctx.ui.setStatus("my-ext", ctx.ui.theme.fg("accent", "● active"));
731
+
732
+ // Clear status
733
+ ctx.ui.setStatus("my-ext", undefined);
734
+ ```
735
+
736
+ **Examples:** [status-line.ts](../examples/extensions/status-line.ts), [plan-mode.ts](../examples/extensions/plan-mode.ts), [preset.ts](../examples/extensions/preset.ts)
737
+
738
+ ### Pattern 5: Widgets Above/Below Editor
739
+
740
+ Show persistent content above or below the input editor. Good for todo lists, progress.
741
+
742
+ ```typescript
743
+ // Simple string array (above editor by default)
744
+ ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]);
745
+
746
+ // Render below the editor
747
+ ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"], { placement: "belowEditor" });
748
+
749
+ // Or with theme
750
+ ctx.ui.setWidget("my-widget", (_tui, theme) => {
751
+ const lines = items.map((item, i) =>
752
+ item.done
753
+ ? theme.fg("success", "✓ ") + theme.fg("muted", item.text)
754
+ : theme.fg("dim", "○ ") + item.text
755
+ );
756
+ return {
757
+ render: () => lines,
758
+ invalidate: () => {},
759
+ };
760
+ });
761
+
762
+ // Clear
763
+ ctx.ui.setWidget("my-widget", undefined);
764
+ ```
765
+
766
+ **Examples:** [plan-mode.ts](../examples/extensions/plan-mode.ts)
767
+
768
+ ### Pattern 6: Custom Footer
769
+
770
+ Replace the footer. `footerData` exposes data not otherwise accessible to extensions.
771
+
772
+ ```typescript
773
+ ctx.ui.setFooter((tui, theme, footerData) => ({
774
+ invalidate() {},
775
+ render(width: number): string[] {
776
+ // footerData.getGitBranch(): string | null
777
+ // footerData.getExtensionStatuses(): ReadonlyMap<string, string>
778
+ return [`${ctx.model?.id} (${footerData.getGitBranch() || "no git"})`];
779
+ },
780
+ dispose: footerData.onBranchChange(() => tui.requestRender()), // reactive
781
+ }));
782
+
783
+ ctx.ui.setFooter(undefined); // restore default
784
+ ```
785
+
786
+ Token stats available via `ctx.sessionManager.getBranch()` and `ctx.model`.
787
+
788
+ **Examples:** [custom-footer.ts](../examples/extensions/custom-footer.ts)
789
+
790
+ ### Pattern 7: Custom Editor (vim mode, etc.)
791
+
792
+ Replace the main input editor with a custom implementation. Useful for modal editing (vim), different keybindings (emacs), or specialized input handling.
793
+
794
+ ```typescript
795
+ import { CustomEditor, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
796
+ import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
797
+
798
+ type Mode = "normal" | "insert";
799
+
800
+ class VimEditor extends CustomEditor {
801
+ private mode: Mode = "insert";
802
+
803
+ handleInput(data: string): void {
804
+ // Escape: switch to normal mode, or pass through for app handling
805
+ if (matchesKey(data, "escape")) {
806
+ if (this.mode === "insert") {
807
+ this.mode = "normal";
808
+ return;
809
+ }
810
+ // In normal mode, escape aborts agent (handled by CustomEditor)
811
+ super.handleInput(data);
812
+ return;
813
+ }
814
+
815
+ // Insert mode: pass everything to CustomEditor
816
+ if (this.mode === "insert") {
817
+ super.handleInput(data);
818
+ return;
819
+ }
820
+
821
+ // Normal mode: vim-style navigation
822
+ switch (data) {
823
+ case "i": this.mode = "insert"; return;
824
+ case "h": super.handleInput("\x1b[D"); return; // Left
825
+ case "j": super.handleInput("\x1b[B"); return; // Down
826
+ case "k": super.handleInput("\x1b[A"); return; // Up
827
+ case "l": super.handleInput("\x1b[C"); return; // Right
828
+ }
829
+ // Pass unhandled keys to super (ctrl+c, etc.), but filter printable chars
830
+ if (data.length === 1 && data.charCodeAt(0) >= 32) return;
831
+ super.handleInput(data);
832
+ }
833
+
834
+ render(width: number): string[] {
835
+ const lines = super.render(width);
836
+ // Add mode indicator to bottom border (use truncateToWidth for ANSI-safe truncation)
837
+ if (lines.length > 0) {
838
+ const label = this.mode === "normal" ? " NORMAL " : " INSERT ";
839
+ const lastLine = lines[lines.length - 1]!;
840
+ // Pass "" as ellipsis to avoid adding "..." when truncating
841
+ lines[lines.length - 1] = truncateToWidth(lastLine, width - label.length, "") + label;
842
+ }
843
+ return lines;
844
+ }
845
+ }
846
+
847
+ export default function (pi: ExtensionAPI) {
848
+ pi.on("session_start", (_event, ctx) => {
849
+ // Factory receives theme and keybindings from the app
850
+ ctx.ui.setEditorComponent((tui, theme, keybindings) =>
851
+ new VimEditor(theme, keybindings)
852
+ );
853
+ });
854
+ }
855
+ ```
856
+
857
+ **Key points:**
858
+
859
+ - **Extend `CustomEditor`** (not base `Editor`) to get app keybindings (escape to abort, ctrl+d to exit, model switching, etc.)
860
+ - **Call `super.handleInput(data)`** for keys you don't handle
861
+ - **Factory pattern**: `setEditorComponent` receives a factory function that gets `tui`, `theme`, and `keybindings`
862
+ - **Pass `undefined`** to restore the default editor: `ctx.ui.setEditorComponent(undefined)`
863
+
864
+ **Examples:** [modal-editor.ts](../examples/extensions/modal-editor.ts)
865
+
866
+ ## Key Rules
867
+
868
+ 1. **Always use theme from callback** - Don't import theme directly. Use `theme` from the `ctx.ui.custom((tui, theme, keybindings, done) => ...)` callback.
869
+
870
+ 2. **Always type DynamicBorder color param** - Write `(s: string) => theme.fg("accent", s)`, not `(s) => theme.fg("accent", s)`.
871
+
872
+ 3. **Call tui.requestRender() after state changes** - In `handleInput`, call `tui.requestRender()` after updating state.
873
+
874
+ 4. **Return the three-method object** - Custom components need `{ render, invalidate, handleInput }`.
875
+
876
+ 5. **Use existing components** - `SelectList`, `SettingsList`, `BorderedLoader` cover 90% of cases. Don't rebuild them.
877
+
878
+ ## Examples
879
+
880
+ - **Selection UI**: [examples/extensions/preset.ts](../examples/extensions/preset.ts) - SelectList with DynamicBorder framing
881
+ - **Async with cancel**: [examples/extensions/qna.ts](../examples/extensions/qna.ts) - BorderedLoader for LLM calls
882
+ - **Settings toggles**: [examples/extensions/tools.ts](../examples/extensions/tools.ts) - SettingsList for tool enable/disable
883
+ - **Status indicators**: [examples/extensions/plan-mode.ts](../examples/extensions/plan-mode.ts) - setStatus and setWidget
884
+ - **Custom footer**: [examples/extensions/custom-footer.ts](../examples/extensions/custom-footer.ts) - setFooter with stats
885
+ - **Custom editor**: [examples/extensions/modal-editor.ts](../examples/extensions/modal-editor.ts) - Vim-like modal editing
886
+ - **Snake game**: [examples/extensions/snake.ts](../examples/extensions/snake.ts) - Full game with keyboard input, game loop
887
+ - **Custom tool rendering**: [examples/extensions/todo.ts](../examples/extensions/todo.ts) - renderCall and renderResult