@vaclav-synacek/pi-coding-agent-termux 0.45.7

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