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
@@ -2,11 +2,12 @@
2
2
  * Remote Questions — Discord adapter
3
3
  */
4
4
 
5
- import type { ChannelAdapter, RemotePrompt, RemoteDispatchResult, RemoteAnswer, RemotePromptRef } from "./types.js";
5
+ import { type ChannelAdapter, type RemotePrompt, type RemoteDispatchResult, type RemoteAnswer, type RemotePromptRef } from "./types.js";
6
6
  import { formatForDiscord, parseDiscordResponse, DISCORD_NUMBER_EMOJIS } from "./format.js";
7
+ import { apiRequest } from "./http-client.js";
7
8
 
8
9
  const DISCORD_API = "https://discord.com/api/v10";
9
- const PER_REQUEST_TIMEOUT_MS = 15_000;
10
+
10
11
  export class DiscordAdapter implements ChannelAdapter {
11
12
  readonly name = "discord" as const;
12
13
  private botUserId: string | null = null;
@@ -137,23 +138,11 @@ export class DiscordAdapter implements ChannelAdapter {
137
138
  return parseDiscordResponse([], String(replies[0].content), prompt.questions);
138
139
  }
139
140
 
140
- private async discordApi(method: string, path: string, body?: unknown): Promise<any> {
141
- const headers: Record<string, string> = { Authorization: `Bot ${this.token}` };
142
- const init: RequestInit = { method, headers };
143
- if (body) {
144
- headers["Content-Type"] = "application/json";
145
- init.body = JSON.stringify(body);
146
- }
147
-
148
- init.signal = AbortSignal.timeout(PER_REQUEST_TIMEOUT_MS);
149
- const response = await fetch(`${DISCORD_API}${path}`, init);
150
- if (response.status === 204) return {};
151
- if (!response.ok) {
152
- const text = await response.text().catch(() => "");
153
- // Limit error body length to avoid leaking verbose Discord error responses
154
- const safeText = text.length > 200 ? text.slice(0, 200) + "…" : text;
155
- throw new Error(`Discord API HTTP ${response.status}: ${safeText}`);
156
- }
157
- return response.json();
141
+ private async discordApi(method: "GET" | "POST" | "PUT" | "DELETE", path: string, body?: unknown): Promise<any> {
142
+ return apiRequest(`${DISCORD_API}${path}`, method, body, {
143
+ authScheme: "Bot",
144
+ authToken: this.token,
145
+ errorLabel: "Discord API",
146
+ });
158
147
  }
159
148
  }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Remote Questions — shared HTTP client
3
+ *
4
+ * Centralizes timeout, error handling, and JSON serialization logic
5
+ * used by all channel adapters (Discord, Slack, Telegram).
6
+ */
7
+
8
+ import { PER_REQUEST_TIMEOUT_MS } from "./types.js";
9
+
10
+ export interface ApiRequestOptions {
11
+ /** Authorization header scheme. Omit to skip the Authorization header entirely. */
12
+ authScheme?: "Bearer" | "Bot";
13
+ /** Token for the Authorization header. Ignored when authScheme is omitted. */
14
+ authToken?: string;
15
+ /** Max chars of error body to include in thrown Error. Default 200. */
16
+ safeErrorLength?: number;
17
+ /** Label used in error messages (e.g. "Discord API", "Slack API"). Default "HTTP". */
18
+ errorLabel?: string;
19
+ /** Content-Type override. Default "application/json" when body is present. */
20
+ contentType?: string;
21
+ }
22
+
23
+ /**
24
+ * Makes an HTTP request with standardized timeout, error handling, and JSON
25
+ * serialization.
26
+ *
27
+ * - Sets `AbortSignal.timeout(PER_REQUEST_TIMEOUT_MS)` on every request.
28
+ * - Serializes `body` as JSON and sets Content-Type when provided.
29
+ * - Returns `{}` for 204 No Content responses.
30
+ * - Truncates error response bodies to `safeErrorLength` chars (default 200).
31
+ */
32
+ export async function apiRequest(
33
+ url: string,
34
+ method: "GET" | "POST" | "PUT" | "DELETE",
35
+ body?: unknown,
36
+ options: ApiRequestOptions = {},
37
+ ): Promise<any> {
38
+ const {
39
+ authScheme,
40
+ authToken,
41
+ safeErrorLength = 200,
42
+ errorLabel = "HTTP",
43
+ contentType,
44
+ } = options;
45
+
46
+ const headers: Record<string, string> = {};
47
+ if (authScheme && authToken) {
48
+ headers["Authorization"] = `${authScheme} ${authToken}`;
49
+ }
50
+
51
+ const init: RequestInit = {
52
+ method,
53
+ headers,
54
+ signal: AbortSignal.timeout(PER_REQUEST_TIMEOUT_MS),
55
+ };
56
+
57
+ if (body !== undefined) {
58
+ headers["Content-Type"] = contentType ?? "application/json";
59
+ init.body = JSON.stringify(body);
60
+ }
61
+
62
+ const response = await fetch(url, init);
63
+
64
+ if (response.status === 204) return {};
65
+
66
+ if (!response.ok) {
67
+ const text = await response.text().catch(() => "");
68
+ const safeText =
69
+ text.length > safeErrorLength
70
+ ? text.slice(0, safeErrorLength) + "\u2026"
71
+ : text;
72
+ throw new Error(`${errorLabel} HTTP ${response.status}: ${safeText}`);
73
+ }
74
+
75
+ return response.json();
76
+ }
@@ -9,8 +9,7 @@
9
9
 
10
10
  import { resolveRemoteConfig } from "./config.js";
11
11
  import type { ResolvedConfig } from "./config.js";
12
-
13
- const PER_REQUEST_TIMEOUT_MS = 15_000;
12
+ import { PER_REQUEST_TIMEOUT_MS } from "./types.js";
14
13
 
15
14
  /**
16
15
  * Send a one-way notification to the configured remote channel.
@@ -2,11 +2,11 @@
2
2
  * Remote Questions — Slack adapter
3
3
  */
4
4
 
5
- import type { ChannelAdapter, RemotePrompt, RemoteDispatchResult, RemoteAnswer, RemotePromptRef } from "./types.js";
5
+ import { type ChannelAdapter, type RemotePrompt, type RemoteDispatchResult, type RemoteAnswer, type RemotePromptRef } from "./types.js";
6
6
  import { formatForSlack, parseSlackReply, parseSlackReactionResponse, SLACK_NUMBER_REACTION_NAMES } from "./format.js";
7
+ import { apiRequest } from "./http-client.js";
7
8
 
8
9
  const SLACK_API = "https://slack.com/api";
9
- const PER_REQUEST_TIMEOUT_MS = 15_000;
10
10
  const SLACK_ACK_REACTION = "white_check_mark";
11
11
 
12
12
  export class SlackAdapter implements ChannelAdapter {
@@ -123,26 +123,19 @@ export class SlackAdapter implements ChannelAdapter {
123
123
  }
124
124
 
125
125
  private async slackApi(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>> {
126
- const url = `${SLACK_API}/${method}`;
127
126
  const isGet = method === "conversations.replies" || method === "auth.test" || method === "reactions.get";
127
+ const opts = { authScheme: "Bearer" as const, authToken: this.token, errorLabel: "Slack API" };
128
128
 
129
- let response: Response;
130
129
  if (isGet) {
131
- const qs = new URLSearchParams(Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)]))).toString();
132
- response = await fetch(`${url}?${qs}`, { method: "GET", headers: { Authorization: `Bearer ${this.token}` }, signal: AbortSignal.timeout(PER_REQUEST_TIMEOUT_MS) });
133
- } else {
134
- response = await fetch(url, {
135
- method: "POST",
136
- headers: {
137
- Authorization: `Bearer ${this.token}`,
138
- "Content-Type": "application/json; charset=utf-8",
139
- },
140
- body: JSON.stringify(params),
141
- signal: AbortSignal.timeout(PER_REQUEST_TIMEOUT_MS),
142
- });
130
+ const qs = new URLSearchParams(
131
+ Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)])),
132
+ ).toString();
133
+ return apiRequest(`${SLACK_API}/${method}?${qs}`, "GET", undefined, opts);
143
134
  }
144
135
 
145
- if (!response.ok) throw new Error(`Slack API HTTP ${response.status}: ${response.statusText}`);
146
- return (await response.json()) as Record<string, unknown>;
136
+ return apiRequest(`${SLACK_API}/${method}`, "POST", params, {
137
+ ...opts,
138
+ contentType: "application/json; charset=utf-8",
139
+ });
147
140
  }
148
141
  }
@@ -2,11 +2,11 @@
2
2
  * Remote Questions — Telegram adapter
3
3
  */
4
4
 
5
- import type { ChannelAdapter, RemotePrompt, RemoteDispatchResult, RemoteAnswer, RemotePromptRef } from "./types.js";
5
+ import { type ChannelAdapter, type RemotePrompt, type RemoteDispatchResult, type RemoteAnswer, type RemotePromptRef } from "./types.js";
6
6
  import { formatForTelegram, parseTelegramResponse } from "./format.js";
7
+ import { apiRequest } from "./http-client.js";
7
8
 
8
9
  const TELEGRAM_API = "https://api.telegram.org";
9
- const PER_REQUEST_TIMEOUT_MS = 15_000;
10
10
 
11
11
  export class TelegramAdapter implements ChannelAdapter {
12
12
  readonly name = "telegram" as const;
@@ -139,23 +139,11 @@ export class TelegramAdapter implements ChannelAdapter {
139
139
  }
140
140
 
141
141
  private async telegramApi(method: string, params?: Record<string, unknown>): Promise<any> {
142
- const url = `${TELEGRAM_API}/bot${this.token}/${method}`;
143
- const init: RequestInit = {
144
- method: "POST",
145
- headers: { "Content-Type": "application/json" },
146
- signal: AbortSignal.timeout(PER_REQUEST_TIMEOUT_MS),
147
- };
148
-
149
- if (params) {
150
- init.body = JSON.stringify(params);
151
- }
152
-
153
- const response = await fetch(url, init);
154
- if (!response.ok) {
155
- const text = await response.text().catch(() => "");
156
- const safeText = text.length > 200 ? text.slice(0, 200) + "…" : text;
157
- throw new Error(`Telegram API HTTP ${response.status}: ${safeText}`);
158
- }
159
- return response.json();
142
+ return apiRequest(
143
+ `${TELEGRAM_API}/bot${this.token}/${method}`,
144
+ "POST",
145
+ params,
146
+ { errorLabel: "Telegram API" },
147
+ );
160
148
  }
161
149
  }
@@ -2,6 +2,9 @@
2
2
  * Remote Questions — shared types
3
3
  */
4
4
 
5
+ /** Timeout applied to every outbound HTTP request across all channel adapters. */
6
+ export const PER_REQUEST_TIMEOUT_MS = 15_000;
7
+
5
8
  export type RemoteChannel = "slack" | "discord" | "telegram";
6
9
 
7
10
  export interface RemoteQuestionOption {
@@ -28,3 +28,6 @@ export { showInterviewRound } from "./interview-ui.js";
28
28
  export type { Question, QuestionOption, RoundResult } from "./interview-ui.js";
29
29
  export { showNextAction } from "./next-action-ui.js";
30
30
  export { showConfirm } from "./confirm-ui.js";
31
+ export { sanitizeError } from "./sanitize.js";
32
+ export { formatDateShort, truncateWithEllipsis } from "./format-utils.js";
33
+ export { splitFrontmatter, parseFrontmatterMap } from "./frontmatter.js";
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: create-gsd-extension
3
+ description: Create, debug, and iterate on GSD extensions (TypeScript modules that add tools, commands, event hooks, custom UI, and providers to GSD). Use when asked to build an extension, add a tool the LLM can call, register a slash command, hook into GSD events, create custom TUI components, or modify GSD behavior. Triggers on "create extension", "build extension", "add a tool", "register command", "hook into gsd", "custom tool", "gsd plugin", "gsd extension".
4
+ ---
5
+
6
+ <essential_principles>
7
+
8
+ **Extensions are TypeScript modules** that hook into GSD's runtime (built on pi). They export a default function receiving `ExtensionAPI` and use it to subscribe to events, register tools/commands/shortcuts, and interact with the session.
9
+
10
+ **GSD extension paths:**
11
+ - Global extensions: `~/.gsd/agent/extensions/*.ts` or `~/.gsd/agent/extensions/*/index.ts`
12
+ - Project-local extensions: `.gsd/extensions/*.ts` or `.gsd/extensions/*/index.ts`
13
+
14
+ **The three primitives:**
15
+ 1. **Events** — Listen and react (`pi.on("event", handler)`). Can block tool calls, modify messages, inject context.
16
+ 2. **Tools** — Give the LLM new abilities (`pi.registerTool()`). LLM calls them autonomously.
17
+ 3. **Commands** — Give users slash commands (`pi.registerCommand()`). Users type `/mycommand`.
18
+
19
+ **Non-negotiable rules:**
20
+ - Use `StringEnum` from `@mariozechner/pi-ai` for string enum params (NOT `Type.Union`/`Type.Literal` — breaks Google's API)
21
+ - Truncate tool output to 50KB / 2000 lines max (use `truncateHead`/`truncateTail` from `@mariozechner/pi-coding-agent`)
22
+ - Store stateful tool state in `details` for branching support
23
+ - Check `signal?.aborted` in long-running tool executions
24
+ - Use `pi.exec()` not `child_process` for shell commands
25
+ - Check `ctx.hasUI` before dialog methods (non-interactive modes exist)
26
+ - Session control methods (`waitForIdle`, `newSession`, `fork`, `navigateTree`, `reload`) are ONLY available in command handlers — they deadlock in event handlers
27
+ - Lines from `render()` must not exceed `width` — use `truncateToWidth()`
28
+ - Use theme from callback params, never import directly
29
+ - Strip leading `@` from path params in custom tools (some models add it)
30
+
31
+ **Available imports:**
32
+
33
+ | Package | Purpose |
34
+ |---------|---------|
35
+ | `@mariozechner/pi-coding-agent` | `ExtensionAPI`, `ExtensionContext`, `Theme`, event types, tool utilities, `DynamicBorder`, `BorderedLoader`, `CustomEditor`, `highlightCode` |
36
+ | `@sinclair/typebox` | `Type.Object`, `Type.String`, `Type.Number`, `Type.Optional`, `Type.Boolean`, `Type.Array` |
37
+ | `@mariozechner/pi-ai` | `StringEnum` (required for string enums), `Type` re-export |
38
+ | `@mariozechner/pi-tui` | `Text`, `Box`, `Container`, `Spacer`, `Markdown`, `SelectList`, `Input`, `matchesKey`, `Key`, `truncateToWidth`, `visibleWidth` |
39
+ | Node.js built-ins | `node:fs`, `node:path`, `node:child_process`, etc. |
40
+
41
+ </essential_principles>
42
+
43
+ <routing>
44
+ Based on user intent, route to the appropriate workflow:
45
+
46
+ **Building a new extension:**
47
+ - "Create an extension", "build a tool", "I want to add a command" → `workflows/create-extension.md`
48
+
49
+ **Adding capabilities to an existing extension:**
50
+ - "Add a tool to my extension", "add event hook", "add custom rendering" → `workflows/add-capability.md`
51
+
52
+ **Debugging an extension:**
53
+ - "My extension doesn't work", "tool not showing up", "event not firing" → `workflows/debug-extension.md`
54
+
55
+ **If user intent is clear from context, skip the question and go directly to the workflow.**
56
+ </routing>
57
+
58
+ <reference_index>
59
+ All domain knowledge in `references/`:
60
+
61
+ **Core architecture:** extension-lifecycle.md, events-reference.md
62
+ **API surface:** extensionapi-reference.md, extensioncontext-reference.md
63
+ **Capabilities:** custom-tools.md, custom-commands.md, custom-ui.md, custom-rendering.md
64
+ **Patterns:** state-management.md, system-prompt-modification.md, compaction-session-control.md
65
+ **Infrastructure:** model-provider-management.md, remote-execution-overrides.md, packaging-distribution.md, mode-behavior.md
66
+ **Gotchas:** key-rules-gotchas.md
67
+ </reference_index>
68
+
69
+ <workflows_index>
70
+ | Workflow | Purpose |
71
+ |----------|---------|
72
+ | create-extension.md | Build a new extension from scratch |
73
+ | add-capability.md | Add tools, commands, hooks, UI to an existing extension |
74
+ | debug-extension.md | Diagnose and fix extension issues |
75
+ </workflows_index>
76
+
77
+ <success_criteria>
78
+ Extension is complete when:
79
+ - TypeScript compiles without errors (jiti handles this at runtime)
80
+ - Extension loads on GSD startup or `/reload` without errors
81
+ - Tools appear in the LLM's system prompt and are callable
82
+ - Commands respond to `/command` input
83
+ - Event hooks fire at the expected lifecycle points
84
+ - Custom UI renders correctly within terminal width
85
+ - State persists correctly across session restarts (if stateful)
86
+ - Output is truncated to safe limits (if tools produce variable output)
87
+ </success_criteria>
@@ -0,0 +1,77 @@
1
+ <overview>
2
+ Custom compaction hooks, triggering compaction, and session control methods available only in command handlers.
3
+ </overview>
4
+
5
+ <custom_compaction>
6
+ Override default compaction behavior:
7
+
8
+ ```typescript
9
+ pi.on("session_before_compact", async (event, ctx) => {
10
+ const { preparation, branchEntries, customInstructions, signal } = event;
11
+
12
+ // Option 1: Cancel
13
+ return { cancel: true };
14
+
15
+ // Option 2: Custom summary
16
+ return {
17
+ compaction: {
18
+ summary: "Custom summary of conversation so far...",
19
+ firstKeptEntryId: preparation.firstKeptEntryId,
20
+ tokensBefore: preparation.tokensBefore,
21
+ }
22
+ };
23
+ });
24
+ ```
25
+ </custom_compaction>
26
+
27
+ <trigger_compaction>
28
+ Trigger compaction programmatically from any handler:
29
+
30
+ ```typescript
31
+ ctx.compact({
32
+ customInstructions: "Focus on the authentication changes",
33
+ onComplete: (result) => ctx.ui.notify("Compacted!", "info"),
34
+ onError: (error) => ctx.ui.notify(`Failed: ${error.message}`, "error"),
35
+ });
36
+ ```
37
+ </trigger_compaction>
38
+
39
+ <session_control>
40
+ **Only available in command handlers** (deadlocks in event handlers):
41
+
42
+ ```typescript
43
+ pi.registerCommand("handoff", {
44
+ handler: async (args, ctx) => {
45
+ await ctx.waitForIdle();
46
+
47
+ // Create new session with initial context
48
+ const result = await ctx.newSession({
49
+ parentSession: ctx.sessionManager.getSessionFile(),
50
+ setup: async (sm) => {
51
+ sm.appendMessage({
52
+ role: "user",
53
+ content: [{ type: "text", text: `Context: ${args}` }],
54
+ timestamp: Date.now(),
55
+ });
56
+ },
57
+ });
58
+
59
+ if (result.cancelled) { /* extension cancelled via session_before_switch */ }
60
+ },
61
+ });
62
+ ```
63
+
64
+ | Method | Purpose |
65
+ |--------|---------|
66
+ | `ctx.waitForIdle()` | Wait for agent to finish streaming |
67
+ | `ctx.newSession(options?)` | Create a new session |
68
+ | `ctx.fork(entryId)` | Fork from a specific entry |
69
+ | `ctx.navigateTree(targetId, options?)` | Navigate session tree (with optional summary) |
70
+ | `ctx.reload()` | Hot-reload everything (treat as terminal — code after runs pre-reload version) |
71
+
72
+ `navigateTree` options:
73
+ - `summarize: boolean` — generate summary of abandoned branch
74
+ - `customInstructions: string` — instructions for summarizer
75
+ - `replaceInstructions: boolean` — replace default prompt entirely
76
+ - `label: string` — label to attach to branch summary
77
+ </session_control>
@@ -0,0 +1,139 @@
1
+ <overview>
2
+ Custom slash commands — registration, argument completions, subcommand patterns, and the extended command context.
3
+ </overview>
4
+
5
+ <basic_registration>
6
+ ```typescript
7
+ pi.registerCommand("deploy", {
8
+ description: "Deploy to an environment",
9
+ handler: async (args, ctx) => {
10
+ // args = everything after "/deploy "
11
+ // ctx = ExtensionCommandContext (has session control methods)
12
+ ctx.ui.notify(`Deploying to ${args || "production"}`, "info");
13
+ },
14
+ });
15
+ ```
16
+ </basic_registration>
17
+
18
+ <argument_completions>
19
+ Add tab-completion for command arguments:
20
+
21
+ ```typescript
22
+ import type { AutocompleteItem } from "@mariozechner/pi-tui";
23
+
24
+ pi.registerCommand("deploy", {
25
+ description: "Deploy to an environment",
26
+ getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
27
+ const envs = ["dev", "staging", "prod"];
28
+ const items = envs.map(e => ({ value: e, label: e }));
29
+ const filtered = items.filter(i => i.value.startsWith(prefix));
30
+ return filtered.length > 0 ? filtered : null;
31
+ },
32
+ handler: async (args, ctx) => {
33
+ ctx.ui.notify(`Deploying to ${args}`, "info");
34
+ },
35
+ });
36
+ ```
37
+ </argument_completions>
38
+
39
+ <subcommand_pattern>
40
+ Fake nested commands via first-argument parsing. Used by `/wt new|ls|switch|merge|rm`.
41
+
42
+ ```typescript
43
+ pi.registerCommand("foo", {
44
+ description: "Manage foo items: /foo new|list|delete [name]",
45
+
46
+ getArgumentCompletions: (prefix: string) => {
47
+ const parts = prefix.trim().split(/\s+/);
48
+
49
+ // First arg: subcommand
50
+ if (parts.length <= 1) {
51
+ return ["new", "list", "delete"]
52
+ .filter(cmd => cmd.startsWith(parts[0] ?? ""))
53
+ .map(cmd => ({ value: cmd, label: cmd }));
54
+ }
55
+
56
+ // Second arg: depends on subcommand
57
+ if (parts[0] === "delete") {
58
+ const items = getItemsSomehow();
59
+ return items
60
+ .filter(name => name.startsWith(parts[1] ?? ""))
61
+ .map(name => ({ value: `delete ${name}`, label: name }));
62
+ }
63
+
64
+ return [];
65
+ },
66
+
67
+ handler: async (args, ctx) => {
68
+ const parts = args.trim().split(/\s+/);
69
+ const sub = parts[0];
70
+
71
+ switch (sub) {
72
+ case "new": /* ... */ return;
73
+ case "list": /* ... */ return;
74
+ case "delete": /* handle parts[1] */ return;
75
+ default:
76
+ ctx.ui.notify("Usage: /foo <new|list|delete> [name]", "info");
77
+ }
78
+ },
79
+ });
80
+ ```
81
+
82
+ **Gotcha:** `"".trim().split(/\s+/)` produces `['']`, not `[]`. That's why `parts.length <= 1` handles both empty and partial first arg.
83
+ </subcommand_pattern>
84
+
85
+ <command_context>
86
+ Command handlers get `ExtensionCommandContext` which extends `ExtensionContext` with session control methods:
87
+
88
+ | Method | Purpose |
89
+ |--------|---------|
90
+ | `ctx.waitForIdle()` | Wait for agent to finish streaming |
91
+ | `ctx.newSession(options?)` | Create a new session |
92
+ | `ctx.fork(entryId)` | Fork from an entry |
93
+ | `ctx.navigateTree(targetId, options?)` | Navigate session tree |
94
+ | `ctx.reload()` | Hot-reload everything |
95
+
96
+ **⚠️ These methods are ONLY available in command handlers.** Calling them from event handlers causes deadlocks.
97
+
98
+ ```typescript
99
+ pi.registerCommand("handoff", {
100
+ handler: async (args, ctx) => {
101
+ await ctx.waitForIdle();
102
+ await ctx.newSession({
103
+ setup: async (sm) => {
104
+ sm.appendMessage({
105
+ role: "user",
106
+ content: [{ type: "text", text: `Context: ${args}` }],
107
+ timestamp: Date.now(),
108
+ });
109
+ },
110
+ });
111
+ },
112
+ });
113
+ ```
114
+ </command_context>
115
+
116
+ <reload_pattern>
117
+ Expose reload as both a command and a tool the LLM can call:
118
+
119
+ ```typescript
120
+ pi.registerCommand("reload-runtime", {
121
+ description: "Reload extensions, skills, prompts, and themes",
122
+ handler: async (_args, ctx) => {
123
+ await ctx.reload();
124
+ return; // Treat reload as terminal
125
+ },
126
+ });
127
+
128
+ pi.registerTool({
129
+ name: "reload_runtime",
130
+ label: "Reload Runtime",
131
+ description: "Reload extensions, skills, prompts, and themes",
132
+ parameters: Type.Object({}),
133
+ async execute() {
134
+ pi.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
135
+ return { content: [{ type: "text", text: "Queued /reload-runtime as follow-up." }] };
136
+ },
137
+ });
138
+ ```
139
+ </reload_pattern>
@@ -0,0 +1,108 @@
1
+ <overview>
2
+ Custom rendering for tools and messages — control how they appear in the TUI.
3
+ </overview>
4
+
5
+ <tool_rendering>
6
+ Tools can provide `renderCall` (how the call looks) and `renderResult` (how the result looks):
7
+
8
+ ```typescript
9
+ import { Text } from "@mariozechner/pi-tui";
10
+ import { keyHint } from "@mariozechner/pi-coding-agent";
11
+
12
+ pi.registerTool({
13
+ name: "my_tool",
14
+ // ...
15
+
16
+ renderCall(args, theme) {
17
+ let text = theme.fg("toolTitle", theme.bold("my_tool "));
18
+ text += theme.fg("muted", args.action);
19
+ if (args.text) text += " " + theme.fg("dim", `"${args.text}"`);
20
+ return new Text(text, 0, 0); // 0,0 padding — Box handles it
21
+ },
22
+
23
+ renderResult(result, { expanded, isPartial }, theme) {
24
+ // isPartial = true during streaming (onUpdate was called)
25
+ if (isPartial) {
26
+ return new Text(theme.fg("warning", "Processing..."), 0, 0);
27
+ }
28
+
29
+ // expanded = user toggled expand (Ctrl+O)
30
+ if (result.details?.error) {
31
+ return new Text(theme.fg("error", `Error: ${result.details.error}`), 0, 0);
32
+ }
33
+
34
+ let text = theme.fg("success", "✓ Done");
35
+ if (!expanded) {
36
+ text += ` (${keyHint("expandTools", "to expand")})`;
37
+ }
38
+ if (expanded && result.details?.items) {
39
+ for (const item of result.details.items) {
40
+ text += "\n " + theme.fg("dim", item);
41
+ }
42
+ }
43
+ return new Text(text, 0, 0);
44
+ },
45
+ });
46
+ ```
47
+
48
+ If you omit `renderCall`/`renderResult`, the built-in renderer is used. Useful for tool overrides where you just wrap logic without reimplementing UI.
49
+
50
+ **Fallback:** If render methods throw, `renderCall` shows tool name, `renderResult` shows raw `content` text.
51
+ </tool_rendering>
52
+
53
+ <key_hints>
54
+ Key hint helpers for showing keybinding info in render output:
55
+
56
+ ```typescript
57
+ import { keyHint, appKeyHint, editorKey, rawKeyHint } from "@mariozechner/pi-coding-agent";
58
+
59
+ // Editor action hint (respects user keybinding config)
60
+ keyHint("expandTools", "to expand") // e.g., "Ctrl+O to expand"
61
+ keyHint("selectConfirm", "to select")
62
+
63
+ // Raw key hint (always shows literal key)
64
+ rawKeyHint("Ctrl+O", "to expand")
65
+ ```
66
+ </key_hints>
67
+
68
+ <message_rendering>
69
+ Register a renderer for custom message types:
70
+
71
+ ```typescript
72
+ import { Text } from "@mariozechner/pi-tui";
73
+
74
+ pi.registerMessageRenderer("my-extension", (message, options, theme) => {
75
+ const { expanded } = options;
76
+ let text = theme.fg("accent", `[${message.customType}] `) + message.content;
77
+ if (expanded && message.details) {
78
+ text += "\n" + theme.fg("dim", JSON.stringify(message.details, null, 2));
79
+ }
80
+ return new Text(text, 0, 0);
81
+ });
82
+
83
+ // Send messages that use this renderer:
84
+ pi.sendMessage({
85
+ customType: "my-extension", // Matches renderer name
86
+ content: "Status update",
87
+ display: true,
88
+ details: { foo: "bar" },
89
+ });
90
+ ```
91
+ </message_rendering>
92
+
93
+ <syntax_highlighting>
94
+ ```typescript
95
+ import { highlightCode, getLanguageFromPath } from "@mariozechner/pi-coding-agent";
96
+
97
+ const lang = getLanguageFromPath("/path/to/file.rs"); // "rust"
98
+ const highlighted = highlightCode(code, lang, theme);
99
+ ```
100
+ </syntax_highlighting>
101
+
102
+ <best_practices>
103
+ - Return `Text` with padding `(0, 0)` — the wrapping `Box` handles padding
104
+ - Support `expanded` for detail on demand
105
+ - Handle `isPartial` for streaming progress
106
+ - Keep collapsed view compact
107
+ - Use `\n` for multi-line content within a single `Text`
108
+ </best_practices>