gsd-pi 2.28.0-dev.e19bf89 → 2.29.0-dev.2ccf3fb

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 (256) hide show
  1. package/README.md +24 -17
  2. package/dist/cli.js +15 -9
  3. package/dist/resource-loader.js +80 -8
  4. package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
  5. package/dist/resources/extensions/gsd/auto-dashboard.ts +186 -65
  6. package/dist/resources/extensions/gsd/auto-post-unit.ts +14 -6
  7. package/dist/resources/extensions/gsd/auto-recovery.ts +33 -23
  8. package/dist/resources/extensions/gsd/auto-start.ts +25 -10
  9. package/dist/resources/extensions/gsd/auto-verification.ts +41 -7
  10. package/dist/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
  11. package/dist/resources/extensions/gsd/auto.ts +67 -22
  12. package/dist/resources/extensions/gsd/commands-handlers.ts +3 -11
  13. package/dist/resources/extensions/gsd/commands-logs.ts +536 -0
  14. package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
  15. package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
  16. package/dist/resources/extensions/gsd/commands.ts +75 -29
  17. package/dist/resources/extensions/gsd/dashboard-overlay.ts +2 -1
  18. package/dist/resources/extensions/gsd/doctor-types.ts +13 -0
  19. package/dist/resources/extensions/gsd/doctor.ts +2 -6
  20. package/dist/resources/extensions/gsd/export.ts +28 -2
  21. package/dist/resources/extensions/gsd/gsd-db.ts +19 -0
  22. package/dist/resources/extensions/gsd/index.ts +2 -1
  23. package/dist/resources/extensions/gsd/json-persistence.ts +67 -0
  24. package/dist/resources/extensions/gsd/metrics.ts +17 -31
  25. package/dist/resources/extensions/gsd/paths.ts +0 -8
  26. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
  28. package/dist/resources/extensions/gsd/queue-order.ts +10 -11
  29. package/dist/resources/extensions/gsd/routing-history.ts +13 -17
  30. package/dist/resources/extensions/gsd/session-lock.ts +284 -0
  31. package/dist/resources/extensions/gsd/session-status-io.ts +23 -41
  32. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  33. package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
  34. package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
  35. package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
  36. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
  37. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
  38. package/dist/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
  39. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
  40. package/dist/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
  41. package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
  42. package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
  43. package/dist/resources/extensions/gsd/types.ts +1 -0
  44. package/dist/resources/extensions/gsd/unit-runtime.ts +16 -13
  45. package/dist/resources/extensions/gsd/verification-evidence.ts +2 -0
  46. package/dist/resources/extensions/gsd/verification-gate.ts +13 -2
  47. package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
  48. package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
  49. package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
  50. package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
  51. package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
  52. package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
  53. package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
  54. package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
  55. package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
  56. package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
  57. package/dist/resources/extensions/mcp-client/index.ts +459 -0
  58. package/dist/resources/extensions/remote-questions/discord-adapter.ts +9 -20
  59. package/dist/resources/extensions/remote-questions/http-client.ts +76 -0
  60. package/dist/resources/extensions/remote-questions/notify.ts +1 -2
  61. package/dist/resources/extensions/remote-questions/slack-adapter.ts +11 -18
  62. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
  63. package/dist/resources/extensions/remote-questions/types.ts +3 -0
  64. package/dist/resources/extensions/shared/mod.ts +3 -0
  65. package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
  66. package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  67. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  68. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  69. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  70. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  71. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  72. package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  73. package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  74. package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  75. package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  76. package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  77. package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  78. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  79. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  80. package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  81. package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  82. package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  83. package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  84. package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  85. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  86. package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  87. package/dist/resources/skills/create-skill/SKILL.md +184 -0
  88. package/dist/resources/skills/create-skill/references/api-security.md +226 -0
  89. package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  90. package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
  91. package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
  92. package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
  93. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  94. package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  95. package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
  96. package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
  97. package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  98. package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
  99. package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
  100. package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  101. package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
  102. package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
  103. package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
  104. package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
  105. package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
  106. package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  107. package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  108. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  109. package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  110. package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  111. package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  112. package/package.json +6 -3
  113. package/packages/native/dist/native.d.ts +2 -0
  114. package/packages/native/dist/native.js +19 -5
  115. package/packages/native/src/native.ts +23 -9
  116. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/extensions/loader.js +13 -0
  118. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  120. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  122. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/system-prompt.js +10 -0
  125. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  129. package/packages/pi-coding-agent/package.json +1 -1
  130. package/packages/pi-coding-agent/scripts/copy-assets.cjs +39 -8
  131. package/packages/pi-coding-agent/src/core/extensions/loader.ts +13 -0
  132. package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
  133. package/packages/pi-coding-agent/src/core/system-prompt.ts +11 -0
  134. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -1
  135. package/packages/pi-tui/dist/autocomplete.d.ts +3 -0
  136. package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
  137. package/packages/pi-tui/dist/autocomplete.js +14 -0
  138. package/packages/pi-tui/dist/autocomplete.js.map +1 -1
  139. package/packages/pi-tui/src/autocomplete.ts +19 -1
  140. package/pkg/package.json +1 -1
  141. package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
  142. package/src/resources/extensions/gsd/auto-dashboard.ts +186 -65
  143. package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
  144. package/src/resources/extensions/gsd/auto-recovery.ts +33 -23
  145. package/src/resources/extensions/gsd/auto-start.ts +25 -10
  146. package/src/resources/extensions/gsd/auto-verification.ts +41 -7
  147. package/src/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
  148. package/src/resources/extensions/gsd/auto.ts +67 -22
  149. package/src/resources/extensions/gsd/commands-handlers.ts +3 -11
  150. package/src/resources/extensions/gsd/commands-logs.ts +536 -0
  151. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
  152. package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
  153. package/src/resources/extensions/gsd/commands.ts +75 -29
  154. package/src/resources/extensions/gsd/dashboard-overlay.ts +2 -1
  155. package/src/resources/extensions/gsd/doctor-types.ts +13 -0
  156. package/src/resources/extensions/gsd/doctor.ts +2 -6
  157. package/src/resources/extensions/gsd/export.ts +28 -2
  158. package/src/resources/extensions/gsd/gsd-db.ts +19 -0
  159. package/src/resources/extensions/gsd/index.ts +2 -1
  160. package/src/resources/extensions/gsd/json-persistence.ts +67 -0
  161. package/src/resources/extensions/gsd/metrics.ts +17 -31
  162. package/src/resources/extensions/gsd/paths.ts +0 -8
  163. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  164. package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
  165. package/src/resources/extensions/gsd/queue-order.ts +10 -11
  166. package/src/resources/extensions/gsd/routing-history.ts +13 -17
  167. package/src/resources/extensions/gsd/session-lock.ts +284 -0
  168. package/src/resources/extensions/gsd/session-status-io.ts +23 -41
  169. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  170. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
  171. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
  172. package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
  173. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
  174. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
  175. package/src/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
  176. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
  177. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
  178. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
  179. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
  180. package/src/resources/extensions/gsd/types.ts +1 -0
  181. package/src/resources/extensions/gsd/unit-runtime.ts +16 -13
  182. package/src/resources/extensions/gsd/verification-evidence.ts +2 -0
  183. package/src/resources/extensions/gsd/verification-gate.ts +13 -2
  184. package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
  185. package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
  186. package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
  187. package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
  188. package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
  189. package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
  190. package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
  191. package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
  192. package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
  193. package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
  194. package/src/resources/extensions/mcp-client/index.ts +459 -0
  195. package/src/resources/extensions/remote-questions/discord-adapter.ts +9 -20
  196. package/src/resources/extensions/remote-questions/http-client.ts +76 -0
  197. package/src/resources/extensions/remote-questions/notify.ts +1 -2
  198. package/src/resources/extensions/remote-questions/slack-adapter.ts +11 -18
  199. package/src/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
  200. package/src/resources/extensions/remote-questions/types.ts +3 -0
  201. package/src/resources/extensions/shared/mod.ts +3 -0
  202. package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
  203. package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  204. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  205. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  206. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  207. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  208. package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  209. package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  210. package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  211. package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  212. package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  213. package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  214. package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  215. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  216. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  217. package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  218. package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  219. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  220. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  221. package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  222. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  223. package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  224. package/src/resources/skills/create-skill/SKILL.md +184 -0
  225. package/src/resources/skills/create-skill/references/api-security.md +226 -0
  226. package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  227. package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
  228. package/src/resources/skills/create-skill/references/core-principles.md +437 -0
  229. package/src/resources/skills/create-skill/references/executable-code.md +175 -0
  230. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  231. package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  232. package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
  233. package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
  234. package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  235. package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
  236. package/src/resources/skills/create-skill/references/using-templates.md +112 -0
  237. package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  238. package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
  239. package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
  240. package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
  241. package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
  242. package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
  243. package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  244. package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  245. package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  246. package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  247. package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  248. package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  249. package/dist/resources/extensions/gsd/preferences-hooks.ts +0 -10
  250. package/dist/resources/extensions/mcporter/index.ts +0 -525
  251. package/dist/resources/extensions/shared/progress-widget.ts +0 -282
  252. package/dist/resources/extensions/shared/thinking-widget.ts +0 -107
  253. package/src/resources/extensions/gsd/preferences-hooks.ts +0 -10
  254. package/src/resources/extensions/mcporter/index.ts +0 -525
  255. package/src/resources/extensions/shared/progress-widget.ts +0 -282
  256. package/src/resources/extensions/shared/thinking-widget.ts +0 -107
@@ -0,0 +1,183 @@
1
+ <overview>
2
+ Complete custom tools reference — registration, parameters, execution, output truncation, overrides, rendering, and dynamic registration.
3
+ </overview>
4
+
5
+ <registration>
6
+ ```typescript
7
+ import { Type } from "@sinclair/typebox";
8
+ import { StringEnum } from "@mariozechner/pi-ai";
9
+
10
+ pi.registerTool({
11
+ name: "my_tool", // Unique identifier (snake_case)
12
+ label: "My Tool", // Display name in TUI
13
+ description: "What this does", // Full description shown to LLM
14
+
15
+ // Optional: one-liner for system prompt "Available tools" section
16
+ promptSnippet: "Manage project todo items",
17
+
18
+ // Optional: bullets added to system prompt "Guidelines" when tool is active
19
+ promptGuidelines: [
20
+ "Use my_tool for task management instead of file edits."
21
+ ],
22
+
23
+ // Parameter schema (MUST use TypeBox)
24
+ parameters: Type.Object({
25
+ action: StringEnum(["list", "add", "remove"] as const),
26
+ text: Type.Optional(Type.String({ description: "Item text" })),
27
+ id: Type.Optional(Type.Number({ description: "Item ID" })),
28
+ }),
29
+
30
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
31
+ // 1. Check cancellation
32
+ if (signal?.aborted) {
33
+ return { content: [{ type: "text", text: "Cancelled" }] };
34
+ }
35
+
36
+ // 2. Stream progress (optional)
37
+ onUpdate?.({
38
+ content: [{ type: "text", text: "Working..." }],
39
+ details: { progress: 50 },
40
+ });
41
+
42
+ // 3. Do the work
43
+ const result = await doWork(params);
44
+
45
+ // 4. Return result
46
+ return {
47
+ content: [{ type: "text", text: "Result text for LLM" }], // Sent to LLM context
48
+ details: { data: result }, // For rendering & state
49
+ };
50
+ },
51
+
52
+ // Optional: custom TUI rendering
53
+ renderCall(args, theme) { ... },
54
+ renderResult(result, { expanded, isPartial }, theme) { ... },
55
+ });
56
+ ```
57
+ </registration>
58
+
59
+ <critical_stringenum>
60
+ **⚠️ MUST use `StringEnum` for string enum parameters:**
61
+
62
+ ```typescript
63
+ import { StringEnum } from "@mariozechner/pi-ai";
64
+
65
+ // ✅ Correct — works with all providers including Google
66
+ action: StringEnum(["list", "add", "remove"] as const)
67
+
68
+ // ❌ BROKEN with Google's API
69
+ action: Type.Union([Type.Literal("list"), Type.Literal("add")])
70
+ ```
71
+ </critical_stringenum>
72
+
73
+ <output_truncation>
74
+ Tools MUST truncate output to avoid context overflow. Built-in limit: 50KB / 2000 lines.
75
+
76
+ ```typescript
77
+ import {
78
+ truncateHead, truncateTail, formatSize,
79
+ DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES,
80
+ } from "@mariozechner/pi-coding-agent";
81
+
82
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
83
+ const output = await runCommand();
84
+ const truncation = truncateHead(output, {
85
+ maxLines: DEFAULT_MAX_LINES,
86
+ maxBytes: DEFAULT_MAX_BYTES,
87
+ });
88
+
89
+ let result = truncation.content;
90
+ if (truncation.truncated) {
91
+ const tempFile = writeTempFile(output);
92
+ result += `\n\n[Output truncated: ${truncation.outputLines}/${truncation.totalLines} lines`;
93
+ result += ` (${formatSize(truncation.outputBytes)}/${formatSize(truncation.totalBytes)}).`;
94
+ result += ` Full output: ${tempFile}]`;
95
+ }
96
+ return { content: [{ type: "text", text: result }] };
97
+ }
98
+ ```
99
+
100
+ Use `truncateHead` when beginning matters (search results, file reads). Use `truncateTail` when end matters (logs, command output).
101
+ </output_truncation>
102
+
103
+ <signaling_errors>
104
+ Throw to signal an error (sets `isError: true`). Returning a value never sets error flag.
105
+
106
+ ```typescript
107
+ async execute(toolCallId, params) {
108
+ if (!isValid(params.input)) {
109
+ throw new Error(`Invalid input: ${params.input}`);
110
+ }
111
+ return { content: [{ type: "text", text: "OK" }], details: {} };
112
+ }
113
+ ```
114
+ </signaling_errors>
115
+
116
+ <dynamic_registration>
117
+ Tools can be registered at any time — during load, in `session_start`, in command handlers. Available immediately without `/reload`.
118
+
119
+ ```typescript
120
+ pi.on("session_start", async (_event, ctx) => {
121
+ pi.registerTool({ name: "dynamic_tool", ... });
122
+ });
123
+ ```
124
+
125
+ Use `pi.setActiveTools(names)` to enable/disable tools at runtime.
126
+ </dynamic_registration>
127
+
128
+ <overriding_builtins>
129
+ Register a tool with the same name as a built-in (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) to override it. **Must match exact result shape including `details` type.**
130
+
131
+ ```typescript
132
+ import { createReadTool } from "@mariozechner/pi-coding-agent";
133
+
134
+ pi.registerTool({
135
+ name: "read",
136
+ label: "Read (Logged)",
137
+ description: "Read file contents with logging",
138
+ parameters: Type.Object({
139
+ path: Type.String(),
140
+ offset: Type.Optional(Type.Number()),
141
+ limit: Type.Optional(Type.Number()),
142
+ }),
143
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
144
+ console.log(`[AUDIT] Reading: ${params.path}`);
145
+ const builtIn = createReadTool(ctx.cwd);
146
+ return builtIn.execute(toolCallId, params, signal, onUpdate);
147
+ },
148
+ // Omit renderCall/renderResult to use built-in renderer
149
+ });
150
+ ```
151
+
152
+ Start with no built-in tools: `gsd --no-tools -e ./my-extension.ts`
153
+ </overriding_builtins>
154
+
155
+ <multiple_tools>
156
+ One extension can register multiple tools with shared state:
157
+
158
+ ```typescript
159
+ export default function (pi: ExtensionAPI) {
160
+ let connection = null;
161
+
162
+ pi.registerTool({ name: "db_connect", ... });
163
+ pi.registerTool({ name: "db_query", ... });
164
+ pi.registerTool({ name: "db_close", ... });
165
+
166
+ pi.on("session_shutdown", async () => {
167
+ connection?.close();
168
+ });
169
+ }
170
+ ```
171
+ </multiple_tools>
172
+
173
+ <path_normalization>
174
+ Some models add `@` prefix to path arguments. Strip it:
175
+
176
+ ```typescript
177
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
178
+ let path = params.path;
179
+ if (path.startsWith("@")) path = path.slice(1);
180
+ // ...
181
+ }
182
+ ```
183
+ </path_normalization>
@@ -0,0 +1,490 @@
1
+ <overview>
2
+ Complete custom UI reference — dialogs, persistent elements, custom components, overlays, custom editors, built-in components, keyboard input, performance, theming, and common mistakes.
3
+ </overview>
4
+
5
+ <ui_architecture>
6
+ ```
7
+ ┌─────────────────────────────────────────────────┐
8
+ │ Custom Header (ctx.ui.setHeader) │
9
+ ├─────────────────────────────────────────────────┤
10
+ │ Message Area │
11
+ │ - User/assistant messages │
12
+ │ - Tool calls ◄── renderCall/renderResult │
13
+ │ - Custom messages ◄── registerMessageRenderer │
14
+ ├─────────────────────────────────────────────────┤
15
+ │ Widgets (above editor) ◄── ctx.ui.setWidget │
16
+ ├─────────────────────────────────────────────────┤
17
+ │ Editor ◄── ctx.ui.custom() / setEditorComponent│
18
+ ├─────────────────────────────────────────────────┤
19
+ │ Widgets (below editor) ◄── ctx.ui.setWidget │
20
+ ├─────────────────────────────────────────────────┤
21
+ │ Footer ◄── ctx.ui.setFooter / setStatus │
22
+ └─────────────────────────────────────────────────┘
23
+ ┌─────────────────────┐
24
+ │ Overlay (floating) │ ◄── ctx.ui.custom({ overlay })
25
+ └─────────────────────┘
26
+ ```
27
+
28
+ **11 ways to get UI on screen:**
29
+
30
+ | Method | Blocks? | Replaces editor? |
31
+ |--------|---------|-------------------|
32
+ | `ctx.ui.select/confirm/input/editor` | Yes | Temporarily |
33
+ | `ctx.ui.notify` | No | No |
34
+ | `ctx.ui.setStatus` | No | No (footer) |
35
+ | `ctx.ui.setWidget` | No | No |
36
+ | `ctx.ui.setFooter` | No | No (replaces footer) |
37
+ | `ctx.ui.setHeader` | No | No (replaces header) |
38
+ | `ctx.ui.custom()` | Yes | Temporarily |
39
+ | `ctx.ui.custom({overlay})` | Yes | No (renders on top) |
40
+ | `ctx.ui.setEditorComponent` | No | Yes (permanently) |
41
+ | `renderCall/renderResult` | No | No (inline in messages) |
42
+ | `registerMessageRenderer` | No | No (inline in messages) |
43
+ </ui_architecture>
44
+
45
+ <component_interface>
46
+ Every visual element implements:
47
+
48
+ ```typescript
49
+ interface Component {
50
+ render(width: number): string[]; // Required — each line ≤ width visible chars
51
+ handleInput?(data: string): void; // Optional — receive keyboard input
52
+ wantsKeyRelease?: boolean; // Optional — receive key release events (Kitty protocol)
53
+ invalidate(): void; // Required — clear cached render state
54
+ }
55
+ ```
56
+
57
+ **Render contract:**
58
+ - Return array of strings, one per line
59
+ - Each string MUST NOT exceed `width` in visible characters
60
+ - ANSI escape codes don't count toward visible width
61
+ - **Styles are reset at end of each line** — reapply per line
62
+ - Return `[]` for zero-height component
63
+
64
+ **Invalidation contract:**
65
+ - Clear ALL cached render output
66
+ - Clear any pre-baked themed strings
67
+ - Call `super.invalidate()` if extending a built-in component
68
+ </component_interface>
69
+
70
+ <dialogs>
71
+ Blocking dialog methods on `ctx.ui`:
72
+
73
+ ```typescript
74
+ const choice = await ctx.ui.select("Pick one:", ["A", "B", "C"]); // string | undefined
75
+ const ok = await ctx.ui.confirm("Delete?", "This cannot be undone"); // boolean
76
+ const name = await ctx.ui.input("Name:", "placeholder"); // string | undefined
77
+ const text = await ctx.ui.editor("Edit:", "prefilled text"); // string | undefined
78
+
79
+ // Timed auto-dismiss with countdown
80
+ const ok = await ctx.ui.confirm("Proceed?", "Auto-continues in 5s", { timeout: 5000 });
81
+ // Returns false on timeout, undefined for select/input
82
+
83
+ // Manual dismissal with AbortSignal (distinguish timeout from cancel)
84
+ const controller = new AbortController();
85
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
86
+ const ok = await ctx.ui.confirm("Timed", "Auto-cancels in 5s", { signal: controller.signal });
87
+ clearTimeout(timeoutId);
88
+ if (controller.signal.aborted) { /* timed out */ }
89
+ ```
90
+ </dialogs>
91
+
92
+ <persistent_ui>
93
+ ```typescript
94
+ // Footer status (multiple extensions can set independent entries)
95
+ ctx.ui.setStatus("my-ext", "● Active");
96
+ ctx.ui.setStatus("my-ext", undefined); // Clear
97
+
98
+ // Widgets
99
+ ctx.ui.setWidget("my-id", ["Line 1", "Line 2"]); // Above editor
100
+ ctx.ui.setWidget("my-id", ["Below"], { placement: "belowEditor" }); // Below editor
101
+ ctx.ui.setWidget("my-id", (_tui, theme) => ({ // Themed
102
+ render: () => [theme.fg("accent", "Styled")],
103
+ invalidate: () => {},
104
+ }));
105
+ ctx.ui.setWidget("my-id", undefined); // Clear
106
+
107
+ // Working message during streaming
108
+ ctx.ui.setWorkingMessage("Analyzing code...");
109
+ ctx.ui.setWorkingMessage(); // Restore default
110
+
111
+ // Custom footer (full replacement)
112
+ ctx.ui.setFooter((tui, theme, footerData) => ({
113
+ render(width) {
114
+ const branch = footerData.getGitBranch(); // Only available here
115
+ const statuses = footerData.getExtensionStatuses(); // All setStatus values
116
+ return [truncateToWidth(`${branch} | model`, width)];
117
+ },
118
+ invalidate() {},
119
+ dispose: footerData.onBranchChange(() => tui.requestRender()), // Reactive
120
+ }));
121
+ ctx.ui.setFooter(undefined); // Restore default
122
+
123
+ // Custom header
124
+ ctx.ui.setHeader((tui, theme) => ({
125
+ render(width) { return [theme.fg("accent", theme.bold("My Header"))]; },
126
+ invalidate() {},
127
+ }));
128
+
129
+ // Editor control
130
+ ctx.ui.setEditorText("Prefill");
131
+ const current = ctx.ui.getEditorText();
132
+ ctx.ui.pasteToEditor("pasted content"); // Triggers paste handling
133
+
134
+ // Tool expansion
135
+ ctx.ui.setToolsExpanded(true);
136
+ const expanded = ctx.ui.getToolsExpanded();
137
+
138
+ // Theme management
139
+ const themes = ctx.ui.getAllThemes();
140
+ ctx.ui.setTheme("light");
141
+ ctx.ui.theme.fg("accent", "text"); // Access current theme
142
+ ```
143
+ </persistent_ui>
144
+
145
+ <custom_components>
146
+ `ctx.ui.custom()` temporarily replaces the editor. Returns a value when `done()` is called.
147
+
148
+ **Factory callback args:**
149
+
150
+ | Argument | Type | Purpose |
151
+ |----------|------|---------|
152
+ | `tui` | `TUI` | `tui.requestRender()` triggers re-render after state changes |
153
+ | `theme` | `Theme` | Current theme for styling |
154
+ | `keybindings` | `KeybindingsManager` | App keybinding config |
155
+ | `done` | `(value: T) => void` | Close component and return value |
156
+
157
+ **Inline pattern:**
158
+ ```typescript
159
+ const result = await ctx.ui.custom<string | null>((tui, theme, keybindings, done) => ({
160
+ render(width: number): string[] {
161
+ return [truncateToWidth("Press Enter to confirm, Escape to cancel", width)];
162
+ },
163
+ handleInput(data: string) {
164
+ if (matchesKey(data, Key.enter)) done("confirmed");
165
+ if (matchesKey(data, Key.escape)) done(null);
166
+ },
167
+ invalidate() {},
168
+ }));
169
+ ```
170
+
171
+ **Class-based pattern (recommended for complex UI):**
172
+ ```typescript
173
+ class MyComponent {
174
+ private selected = 0;
175
+ private cachedWidth?: number;
176
+ private cachedLines?: string[];
177
+
178
+ constructor(
179
+ private tui: { requestRender: () => void },
180
+ private theme: Theme,
181
+ private items: string[],
182
+ private done: (value: string | null) => void,
183
+ ) {}
184
+
185
+ handleInput(data: string) {
186
+ if (matchesKey(data, Key.up) && this.selected > 0) this.selected--;
187
+ else if (matchesKey(data, Key.down) && this.selected < this.items.length - 1) this.selected++;
188
+ else if (matchesKey(data, Key.enter)) { this.done(this.items[this.selected]); return; }
189
+ else if (matchesKey(data, Key.escape)) { this.done(null); return; }
190
+ else return;
191
+ this.invalidate();
192
+ this.tui.requestRender();
193
+ }
194
+
195
+ render(width: number): string[] {
196
+ if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
197
+ this.cachedLines = this.items.map((item, i) =>
198
+ truncateToWidth((i === this.selected ? "> " : " ") + item, width)
199
+ );
200
+ this.cachedWidth = width;
201
+ return this.cachedLines;
202
+ }
203
+
204
+ invalidate() { this.cachedWidth = undefined; this.cachedLines = undefined; }
205
+ }
206
+
207
+ const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) =>
208
+ new MyComponent(tui, theme, ["A", "B", "C"], done)
209
+ );
210
+ ```
211
+
212
+ **Composing with built-in components:**
213
+ ```typescript
214
+ const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
215
+ const container = new Container();
216
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
217
+ container.addChild(new Text(theme.fg("accent", theme.bold("Title")), 1, 0));
218
+
219
+ const selectList = new SelectList(items, 10, {
220
+ selectedPrefix: (t) => theme.fg("accent", t),
221
+ selectedText: (t) => theme.fg("accent", t),
222
+ description: (t) => theme.fg("muted", t),
223
+ scrollInfo: (t) => theme.fg("dim", t),
224
+ noMatch: (t) => theme.fg("warning", t),
225
+ });
226
+ selectList.onSelect = (item) => done(item.value);
227
+ selectList.onCancel = () => done(null);
228
+ container.addChild(selectList);
229
+
230
+ return {
231
+ render: (w) => container.render(w),
232
+ invalidate: () => container.invalidate(),
233
+ handleInput: (data) => { selectList.handleInput(data); tui.requestRender(); },
234
+ };
235
+ });
236
+ ```
237
+ </custom_components>
238
+
239
+ <overlays>
240
+ Floating modals rendered on top of everything:
241
+
242
+ ```typescript
243
+ const result = await ctx.ui.custom<string | null>(
244
+ (tui, theme, _kb, done) => new MyDialog({ onClose: done }),
245
+ {
246
+ overlay: true,
247
+ overlayOptions: {
248
+ anchor: "center", // 9 positions (see below)
249
+ width: "50%", // number = columns, string = percentage
250
+ minWidth: 40,
251
+ maxHeight: "80%",
252
+ margin: 2, // All sides, or { top, right, bottom, left }
253
+ offsetX: 0, offsetY: 0, // Fine-tune position
254
+ visible: (w, h) => w >= 80, // Hide on narrow terminals
255
+ },
256
+ onHandle: (handle) => {
257
+ // handle.setHidden(true/false) — temporarily hide
258
+ // handle.hide() — permanently remove
259
+ },
260
+ }
261
+ );
262
+ ```
263
+
264
+ **Anchor positions:**
265
+ ```
266
+ top-left top-center top-right
267
+ left-center center right-center
268
+ bottom-left bottom-center bottom-right
269
+ ```
270
+
271
+ **Stacked overlays:** Multiple overlays stack (newest on top). Closing one gives focus to the one below.
272
+
273
+ **⚠️ Overlay lifecycle:** Components are disposed when closed. Never reuse references — create fresh instances each time.
274
+ </overlays>
275
+
276
+ <custom_editor>
277
+ Replace the main input editor permanently:
278
+
279
+ ```typescript
280
+ import { CustomEditor } from "@mariozechner/pi-coding-agent";
281
+
282
+ class VimEditor extends CustomEditor {
283
+ private mode: "normal" | "insert" = "insert";
284
+
285
+ handleInput(data: string): void {
286
+ if (matchesKey(data, "escape") && this.mode === "insert") {
287
+ this.mode = "normal"; return;
288
+ }
289
+ if (this.mode === "insert") { super.handleInput(data); return; }
290
+ switch (data) {
291
+ case "i": this.mode = "insert"; return;
292
+ case "h": super.handleInput("\x1b[D"); return; // Left
293
+ case "j": super.handleInput("\x1b[B"); return; // Down
294
+ case "k": super.handleInput("\x1b[A"); return; // Up
295
+ case "l": super.handleInput("\x1b[C"); return; // Right
296
+ }
297
+ if (data.length === 1 && data.charCodeAt(0) >= 32) return; // Block printable in normal
298
+ super.handleInput(data);
299
+ }
300
+ }
301
+
302
+ ctx.ui.setEditorComponent((_tui, theme, keybindings) => new VimEditor(theme, keybindings));
303
+ ctx.ui.setEditorComponent(undefined); // Restore default
304
+ ```
305
+
306
+ **Critical:** Extend `CustomEditor` (NOT `Editor`) to get app keybindings (escape to abort, ctrl+d, model switching).
307
+ </custom_editor>
308
+
309
+ <built_in_components>
310
+ **From `@mariozechner/pi-tui`:**
311
+
312
+ | Component | Constructor | Purpose |
313
+ |-----------|-------------|---------|
314
+ | `Text` | `new Text(content, paddingX, paddingY, bgFn?)` | Multi-line text with word wrap |
315
+ | `Box` | `new Box(paddingX, paddingY, bgFn)` | Container with padding+background, `.addChild()` |
316
+ | `Container` | `new Container()` | Vertical stack, `.addChild()`, `.removeChild()`, `.clear()` |
317
+ | `Spacer` | `new Spacer(lines)` | Empty vertical space |
318
+ | `Markdown` | `new Markdown(content, padX, padY, getMarkdownTheme())` | Rendered markdown with syntax highlighting |
319
+ | `Image` | `new Image(base64, mimeType, theme, opts?)` | Image rendering (Kitty, iTerm2) |
320
+ | `SelectList` | `new SelectList(items, maxVisible, themeOpts)` | Interactive selection with search and scrolling |
321
+ | `SettingsList` | `new SettingsList(items, maxVisible, theme, onChange, onClose, opts?)` | Toggle settings with left/right arrows |
322
+ | `Input` | `new Input()` | Text input field |
323
+ | `Editor` | `new Editor(tui, editorTheme)` | Multi-line editor with undo |
324
+
325
+ **SelectList usage:**
326
+ ```typescript
327
+ const items: SelectItem[] = [
328
+ { value: "opt1", label: "Option 1", description: "First option" },
329
+ { value: "opt2", label: "Option 2" },
330
+ ];
331
+ const selectList = new SelectList(items, 10, {
332
+ selectedPrefix: (t) => theme.fg("accent", t),
333
+ selectedText: (t) => theme.fg("accent", t),
334
+ description: (t) => theme.fg("muted", t),
335
+ scrollInfo: (t) => theme.fg("dim", t),
336
+ noMatch: (t) => theme.fg("warning", t),
337
+ });
338
+ selectList.onSelect = (item) => { /* item.value */ };
339
+ selectList.onCancel = () => { /* escape pressed */ };
340
+ ```
341
+
342
+ **SettingsList usage:**
343
+ ```typescript
344
+ const items: SettingItem[] = [
345
+ { id: "verbose", label: "Verbose mode", currentValue: "off", values: ["on", "off"] },
346
+ { id: "theme", label: "Theme", currentValue: "dark", values: ["dark", "light", "auto"] },
347
+ ];
348
+ const settings = new SettingsList(items, 15, getSettingsListTheme(),
349
+ (id, newValue) => { /* setting changed */ },
350
+ () => { /* close requested */ },
351
+ { enableSearch: true },
352
+ );
353
+ ```
354
+
355
+ **From `@mariozechner/pi-coding-agent`:**
356
+
357
+ | Component | Constructor | Purpose |
358
+ |-----------|-------------|---------|
359
+ | `DynamicBorder` | `new DynamicBorder((s: string) => theme.fg("accent", s))` | Border line |
360
+ | `BorderedLoader` | — | Spinner with cancel support |
361
+ | `CustomEditor` | `new CustomEditor(theme, keybindings)` | Base class for custom editors |
362
+ </built_in_components>
363
+
364
+ <keyboard_input>
365
+ ```typescript
366
+ import { matchesKey, Key } from "@mariozechner/pi-tui";
367
+
368
+ handleInput(data: string) {
369
+ // Basic keys
370
+ if (matchesKey(data, Key.up)) {}
371
+ if (matchesKey(data, Key.down)) {}
372
+ if (matchesKey(data, Key.enter)) {}
373
+ if (matchesKey(data, Key.escape)) {}
374
+ if (matchesKey(data, Key.tab)) {}
375
+ if (matchesKey(data, Key.space)) {}
376
+ if (matchesKey(data, Key.backspace)) {}
377
+ if (matchesKey(data, Key.home)) {}
378
+ if (matchesKey(data, Key.end)) {}
379
+
380
+ // With modifiers
381
+ if (matchesKey(data, Key.ctrl("c"))) {}
382
+ if (matchesKey(data, Key.shift("tab"))) {}
383
+ if (matchesKey(data, Key.alt("left"))) {}
384
+ if (matchesKey(data, Key.ctrlShift("p"))) {}
385
+
386
+ // String format also works: "enter", "ctrl+c", "shift+tab"
387
+
388
+ // Printable character detection
389
+ if (data.length === 1 && data.charCodeAt(0) >= 32) {
390
+ // Letter, number, symbol
391
+ }
392
+ }
393
+ ```
394
+
395
+ **handleInput contract:**
396
+ 1. Check for your keys
397
+ 2. Update state
398
+ 3. Call `this.invalidate()` if render output changes
399
+ 4. Call `tui.requestRender()` to trigger re-render
400
+ </keyboard_input>
401
+
402
+ <line_width_rule>
403
+ **Cardinal rule: each line from render() must not exceed `width` visible characters.**
404
+
405
+ ```typescript
406
+ import { visibleWidth, truncateToWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
407
+
408
+ visibleWidth("\x1b[32mHello\x1b[0m"); // Returns 5 (ignores ANSI codes)
409
+ truncateToWidth("Very long text here", 10); // "Very lo..."
410
+ truncateToWidth("Very long text here", 10, ""); // "Very long " (no ellipsis)
411
+ wrapTextWithAnsi("\x1b[32mLong green text\x1b[0m", 10); // Word wrap preserving ANSI
412
+ ```
413
+
414
+ If lines exceed `width`, terminal wraps cause visual corruption.
415
+ </line_width_rule>
416
+
417
+ <performance_caching>
418
+ Always cache render output:
419
+
420
+ ```typescript
421
+ class CachedComponent {
422
+ private cachedWidth?: number;
423
+ private cachedLines?: string[];
424
+
425
+ render(width: number): string[] {
426
+ if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
427
+ const lines = this.computeLines(width);
428
+ this.cachedWidth = width;
429
+ this.cachedLines = lines;
430
+ return lines;
431
+ }
432
+
433
+ invalidate() { this.cachedWidth = undefined; this.cachedLines = undefined; }
434
+ }
435
+ ```
436
+
437
+ **Update cycle:** State changes → `invalidate()` → `tui.requestRender()` → `render(width)` called
438
+
439
+ **Game loop pattern** (real-time updates):
440
+ ```typescript
441
+ this.interval = setInterval(() => {
442
+ this.tick();
443
+ this.version++;
444
+ this.tui.requestRender();
445
+ }, 100); // 10 FPS
446
+
447
+ // Clean up in dispose()
448
+ clearInterval(this.interval);
449
+ ```
450
+ </performance_caching>
451
+
452
+ <theme_colors>
453
+ Always use theme from callback params, never import directly.
454
+
455
+ **All foreground colors:**
456
+
457
+ | Category | Colors |
458
+ |----------|--------|
459
+ | General | `text`, `accent`, `muted`, `dim` |
460
+ | Status | `success`, `error`, `warning` |
461
+ | Borders | `border`, `borderAccent`, `borderMuted` |
462
+ | Messages | `userMessageText`, `customMessageText`, `customMessageLabel` |
463
+ | Tools | `toolTitle`, `toolOutput` |
464
+ | Diffs | `toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext` |
465
+ | Markdown | `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |
466
+ | Syntax | `syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation` |
467
+ | Thinking | `thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh` |
468
+
469
+ **All background colors:** `selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`
470
+
471
+ **Syntax highlighting:**
472
+ ```typescript
473
+ import { highlightCode, getLanguageFromPath } from "@mariozechner/pi-coding-agent";
474
+ const lang = getLanguageFromPath("/file.rs"); // "rust"
475
+ const highlighted = highlightCode(code, lang, theme);
476
+ ```
477
+ </theme_colors>
478
+
479
+ <common_mistakes>
480
+ 1. **Lines exceed width** → Visual corruption. Use `truncateToWidth()` on every line.
481
+ 2. **Forgetting `tui.requestRender()`** → UI doesn't update. Call after invalidate().
482
+ 3. **Importing theme directly** → Wrong colors after theme switch. Use theme from callback.
483
+ 4. **Not typing DynamicBorder param** → `new DynamicBorder((s: string) => theme.fg("accent", s))`.
484
+ 5. **Reusing disposed overlay components** → Create fresh instances each time.
485
+ 6. **Styles bleeding across lines** → TUI resets per line. Reapply styles, or use `wrapTextWithAnsi()`.
486
+ 7. **Not implementing invalidate()** → Theme changes don't take effect.
487
+ 8. **Forgetting super.invalidate()** → `override invalidate() { super.invalidate(); /* cleanup */ }`
488
+ 9. **Timer not cleaned up** → Call `clearInterval` before `done()`.
489
+ 10. **Using ctx.ui in non-interactive mode** → Check `ctx.hasUI` first.
490
+ </common_mistakes>