@openeryc/pi-coding-agent 0.75.28 → 0.75.30

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 (320) hide show
  1. package/CHANGELOG.md +11 -3
  2. package/dist/cli/config-selector.d.ts +13 -1
  3. package/dist/cli/config-selector.d.ts.map +1 -1
  4. package/dist/cli/config-selector.js +29 -4
  5. package/dist/cli/config-selector.js.map +1 -1
  6. package/dist/cli/list-models.d.ts.map +1 -1
  7. package/dist/cli/list-models.js +1 -1
  8. package/dist/cli/list-models.js.map +1 -1
  9. package/dist/cli/session-picker.d.ts +9 -0
  10. package/dist/cli/session-picker.d.ts.map +1 -0
  11. package/dist/cli/session-picker.js +35 -0
  12. package/dist/cli/session-picker.js.map +1 -0
  13. package/dist/core/agent-session.d.ts +3 -0
  14. package/dist/core/agent-session.d.ts.map +1 -1
  15. package/dist/core/agent-session.js +40 -12
  16. package/dist/core/agent-session.js.map +1 -1
  17. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  18. package/dist/core/export-html/tool-renderer.js.map +1 -1
  19. package/dist/core/extensions/loader.d.ts.map +1 -1
  20. package/dist/core/extensions/loader.js +1 -1
  21. package/dist/core/extensions/loader.js.map +1 -1
  22. package/dist/core/extensions/runner.d.ts +1 -1
  23. package/dist/core/extensions/runner.d.ts.map +1 -1
  24. package/dist/core/extensions/runner.js +1 -1
  25. package/dist/core/extensions/runner.js.map +1 -1
  26. package/dist/core/extensions/types.d.ts +1 -1
  27. package/dist/core/extensions/types.d.ts.map +1 -1
  28. package/dist/core/extensions/types.js.map +1 -1
  29. package/dist/core/hook-runner.d.ts +23 -0
  30. package/dist/core/hook-runner.d.ts.map +1 -0
  31. package/dist/core/hook-runner.js +116 -0
  32. package/dist/core/hook-runner.js.map +1 -0
  33. package/dist/core/keybindings.d.ts +135 -7
  34. package/dist/core/keybindings.d.ts.map +1 -1
  35. package/dist/core/keybindings.js +1 -1
  36. package/dist/core/keybindings.js.map +1 -1
  37. package/dist/core/mcp/client.d.ts +19 -28
  38. package/dist/core/mcp/client.d.ts.map +1 -1
  39. package/dist/core/mcp/client.js +169 -16
  40. package/dist/core/mcp/client.js.map +1 -1
  41. package/dist/core/mcp/index.d.ts +1 -0
  42. package/dist/core/mcp/index.d.ts.map +1 -1
  43. package/dist/core/mcp/index.js.map +1 -1
  44. package/dist/core/mcp/manager.d.ts +3 -0
  45. package/dist/core/mcp/manager.d.ts.map +1 -1
  46. package/dist/core/mcp/manager.js +38 -12
  47. package/dist/core/mcp/manager.js.map +1 -1
  48. package/dist/core/mcp/types.d.ts +49 -0
  49. package/dist/core/mcp/types.d.ts.map +1 -0
  50. package/dist/core/mcp/types.js +2 -0
  51. package/dist/core/mcp/types.js.map +1 -0
  52. package/dist/core/settings-manager.d.ts +8 -2
  53. package/dist/core/settings-manager.d.ts.map +1 -1
  54. package/dist/core/settings-manager.js.map +1 -1
  55. package/dist/core/tools/ask-user-question.d.ts +21 -0
  56. package/dist/core/tools/ask-user-question.d.ts.map +1 -0
  57. package/dist/core/tools/ask-user-question.js +75 -0
  58. package/dist/core/tools/ask-user-question.js.map +1 -0
  59. package/dist/core/tools/bash.d.ts.map +1 -1
  60. package/dist/core/tools/bash.js +1 -1
  61. package/dist/core/tools/bash.js.map +1 -1
  62. package/dist/core/tools/edit.d.ts +1 -1
  63. package/dist/core/tools/edit.d.ts.map +1 -1
  64. package/dist/core/tools/edit.js +1 -1
  65. package/dist/core/tools/edit.js.map +1 -1
  66. package/dist/core/tools/find.d.ts.map +1 -1
  67. package/dist/core/tools/find.js +1 -1
  68. package/dist/core/tools/find.js.map +1 -1
  69. package/dist/core/tools/grep.d.ts.map +1 -1
  70. package/dist/core/tools/grep.js +1 -1
  71. package/dist/core/tools/grep.js.map +1 -1
  72. package/dist/core/tools/index.d.ts +2 -1
  73. package/dist/core/tools/index.d.ts.map +1 -1
  74. package/dist/core/tools/index.js +9 -0
  75. package/dist/core/tools/index.js.map +1 -1
  76. package/dist/core/tools/ls.d.ts.map +1 -1
  77. package/dist/core/tools/ls.js +1 -1
  78. package/dist/core/tools/ls.js.map +1 -1
  79. package/dist/core/tools/read.d.ts.map +1 -1
  80. package/dist/core/tools/read.js +1 -1
  81. package/dist/core/tools/read.js.map +1 -1
  82. package/dist/core/tools/render-utils.d.ts.map +1 -1
  83. package/dist/core/tools/render-utils.js +1 -1
  84. package/dist/core/tools/render-utils.js.map +1 -1
  85. package/dist/core/tools/write.d.ts.map +1 -1
  86. package/dist/core/tools/write.js +1 -1
  87. package/dist/core/tools/write.js.map +1 -1
  88. package/dist/index.d.ts +3 -5
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +5 -7
  91. package/dist/index.js.map +1 -1
  92. package/dist/main.d.ts.map +1 -1
  93. package/dist/main.js +106 -92
  94. package/dist/main.js.map +1 -1
  95. package/dist/modes/index.d.ts +4 -0
  96. package/dist/modes/index.d.ts.map +1 -1
  97. package/dist/modes/index.js +4 -0
  98. package/dist/modes/index.js.map +1 -1
  99. package/dist/modes/interactive/assets/clankolas.png +0 -0
  100. package/dist/modes/interactive/components/armin.d.ts +34 -0
  101. package/dist/modes/interactive/components/armin.d.ts.map +1 -0
  102. package/dist/modes/interactive/components/armin.js +333 -0
  103. package/dist/modes/interactive/components/armin.js.map +1 -0
  104. package/dist/modes/interactive/components/assistant-message.d.ts +20 -0
  105. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  106. package/dist/modes/interactive/components/assistant-message.js +121 -0
  107. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  108. package/dist/modes/interactive/components/bash-execution.d.ts +34 -0
  109. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  110. package/dist/modes/interactive/components/bash-execution.js +175 -0
  111. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  112. package/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  113. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
  114. package/dist/modes/interactive/components/bordered-loader.js +54 -0
  115. package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
  116. package/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  117. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
  118. package/dist/modes/interactive/components/branch-summary-message.js +44 -0
  119. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
  120. package/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  121. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
  122. package/dist/modes/interactive/components/compaction-summary-message.js +45 -0
  123. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
  124. package/dist/modes/interactive/components/config-selector.d.ts +71 -0
  125. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
  126. package/dist/modes/interactive/components/config-selector.js +506 -0
  127. package/dist/modes/interactive/components/config-selector.js.map +1 -0
  128. package/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  129. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -0
  130. package/dist/modes/interactive/components/countdown-timer.js +33 -0
  131. package/dist/modes/interactive/components/countdown-timer.js.map +1 -0
  132. package/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  133. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
  134. package/dist/modes/interactive/components/custom-editor.js +70 -0
  135. package/dist/modes/interactive/components/custom-editor.js.map +1 -0
  136. package/dist/modes/interactive/components/custom-message.d.ts +20 -0
  137. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  138. package/dist/modes/interactive/components/custom-message.js +79 -0
  139. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  140. package/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  141. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -0
  142. package/dist/modes/interactive/components/daxnuts.js +140 -0
  143. package/dist/modes/interactive/components/daxnuts.js.map +1 -0
  144. package/dist/modes/interactive/components/diff.d.ts +8 -1
  145. package/dist/modes/interactive/components/diff.d.ts.map +1 -1
  146. package/dist/modes/interactive/components/diff.js +131 -2
  147. package/dist/modes/interactive/components/diff.js.map +1 -1
  148. package/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  149. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
  150. package/dist/modes/interactive/components/dynamic-border.js +21 -0
  151. package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
  152. package/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
  153. package/dist/modes/interactive/components/earendil-announcement.d.ts.map +1 -0
  154. package/dist/modes/interactive/components/earendil-announcement.js +40 -0
  155. package/dist/modes/interactive/components/earendil-announcement.js.map +1 -0
  156. package/dist/modes/interactive/components/extension-editor.d.ts +20 -0
  157. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  158. package/dist/modes/interactive/components/extension-editor.js +119 -0
  159. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  160. package/dist/modes/interactive/components/extension-input.d.ts +23 -0
  161. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  162. package/dist/modes/interactive/components/extension-input.js +61 -0
  163. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  164. package/dist/modes/interactive/components/extension-selector.d.ts +26 -0
  165. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  166. package/dist/modes/interactive/components/extension-selector.js +83 -0
  167. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  168. package/dist/modes/interactive/components/footer.d.ts +27 -0
  169. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  170. package/dist/modes/interactive/components/footer.js +201 -0
  171. package/dist/modes/interactive/components/footer.js.map +1 -0
  172. package/dist/modes/interactive/components/index.d.ts +32 -0
  173. package/dist/modes/interactive/components/index.d.ts.map +1 -0
  174. package/dist/modes/interactive/components/index.js +33 -0
  175. package/dist/modes/interactive/components/index.js.map +1 -0
  176. package/dist/modes/interactive/components/keybinding-hints.d.ts +12 -2
  177. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  178. package/dist/modes/interactive/components/keybinding-hints.js +33 -4
  179. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  180. package/dist/modes/interactive/components/login-dialog.d.ts +46 -0
  181. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -0
  182. package/dist/modes/interactive/components/login-dialog.js +160 -0
  183. package/dist/modes/interactive/components/login-dialog.js.map +1 -0
  184. package/dist/modes/interactive/components/model-selector.d.ts +47 -0
  185. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
  186. package/dist/modes/interactive/components/model-selector.js +278 -0
  187. package/dist/modes/interactive/components/model-selector.js.map +1 -0
  188. package/dist/modes/interactive/components/oauth-selector.d.ts +31 -0
  189. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
  190. package/dist/modes/interactive/components/oauth-selector.js +165 -0
  191. package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
  192. package/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
  193. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -0
  194. package/dist/modes/interactive/components/scoped-models-selector.js +290 -0
  195. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -0
  196. package/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  197. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
  198. package/dist/modes/interactive/components/session-selector-search.js +155 -0
  199. package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
  200. package/dist/modes/interactive/components/session-selector.d.ts +96 -0
  201. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
  202. package/dist/modes/interactive/components/session-selector.js +861 -0
  203. package/dist/modes/interactive/components/session-selector.js.map +1 -0
  204. package/dist/modes/interactive/components/settings-selector.d.ts +69 -0
  205. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -0
  206. package/dist/modes/interactive/components/settings-selector.js +390 -0
  207. package/dist/modes/interactive/components/settings-selector.js.map +1 -0
  208. package/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  209. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -0
  210. package/dist/modes/interactive/components/show-images-selector.js +39 -0
  211. package/dist/modes/interactive/components/show-images-selector.js.map +1 -0
  212. package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  213. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
  214. package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
  215. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
  216. package/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  217. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
  218. package/dist/modes/interactive/components/theme-selector.js +50 -0
  219. package/dist/modes/interactive/components/theme-selector.js.map +1 -0
  220. package/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  221. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
  222. package/dist/modes/interactive/components/thinking-selector.js +51 -0
  223. package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
  224. package/dist/modes/interactive/components/tool-execution.d.ts +63 -0
  225. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  226. package/dist/modes/interactive/components/tool-execution.js +295 -0
  227. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  228. package/dist/modes/interactive/components/tree-selector.d.ts +89 -0
  229. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
  230. package/dist/modes/interactive/components/tree-selector.js +1093 -0
  231. package/dist/modes/interactive/components/tree-selector.js.map +1 -0
  232. package/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  233. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
  234. package/dist/modes/interactive/components/user-message-selector.js +114 -0
  235. package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
  236. package/dist/modes/interactive/components/user-message.d.ts +10 -0
  237. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  238. package/dist/modes/interactive/components/user-message.js +29 -0
  239. package/dist/modes/interactive/components/user-message.js.map +1 -0
  240. package/dist/modes/interactive/components/visual-truncate.d.ts +19 -1
  241. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
  242. package/dist/modes/interactive/components/visual-truncate.js +31 -4
  243. package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
  244. package/dist/modes/interactive/interactive-mode.d.ts +375 -0
  245. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  246. package/dist/modes/interactive/interactive-mode.js +4799 -0
  247. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  248. package/dist/modes/interactive/theme/dark.json +86 -0
  249. package/dist/modes/interactive/theme/light.json +85 -0
  250. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  251. package/dist/modes/interactive/theme/theme.d.ts +89 -27
  252. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  253. package/dist/modes/interactive/theme/theme.js +992 -44
  254. package/dist/modes/interactive/theme/theme.js.map +1 -1
  255. package/dist/package-manager-cli.d.ts.map +1 -1
  256. package/dist/package-manager-cli.js +1 -1
  257. package/dist/package-manager-cli.js.map +1 -1
  258. package/examples/extensions/border-status-editor.ts +150 -0
  259. package/examples/extensions/built-in-tool-renderer.ts +249 -0
  260. package/examples/extensions/custom-footer.ts +64 -0
  261. package/examples/extensions/custom-header.ts +73 -0
  262. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  263. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  264. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  265. package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  266. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  267. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  268. package/examples/extensions/doom-overlay/README.md +46 -0
  269. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  270. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  271. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  272. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  273. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  274. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  275. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  276. package/examples/extensions/doom-overlay/index.ts +74 -0
  277. package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  278. package/examples/extensions/github-issue-autocomplete.ts +185 -0
  279. package/examples/extensions/handoff.ts +191 -0
  280. package/examples/extensions/interactive-shell.ts +196 -0
  281. package/examples/extensions/message-renderer.ts +59 -0
  282. package/examples/extensions/minimal-mode.ts +426 -0
  283. package/examples/extensions/modal-editor.ts +85 -0
  284. package/examples/extensions/overlay-qa-tests.ts +1379 -0
  285. package/examples/extensions/overlay-test.ts +153 -0
  286. package/examples/extensions/plan-mode/index.ts +340 -0
  287. package/examples/extensions/preset.ts +430 -0
  288. package/examples/extensions/qna.ts +122 -0
  289. package/examples/extensions/question.ts +264 -0
  290. package/examples/extensions/questionnaire.ts +427 -0
  291. package/examples/extensions/rainbow-editor.ts +88 -0
  292. package/examples/extensions/sandbox/index.ts +321 -0
  293. package/examples/extensions/sandbox/package-lock.json +92 -0
  294. package/examples/extensions/sandbox/package.json +19 -0
  295. package/examples/extensions/snake.ts +343 -0
  296. package/examples/extensions/space-invaders.ts +560 -0
  297. package/examples/extensions/ssh.ts +220 -0
  298. package/examples/extensions/status-line.ts +32 -0
  299. package/examples/extensions/structured-output.ts +65 -0
  300. package/examples/extensions/subagent/index.ts +1009 -0
  301. package/examples/extensions/summarize.ts +206 -0
  302. package/examples/extensions/tic-tac-toe.ts +1008 -0
  303. package/examples/extensions/todo.ts +297 -0
  304. package/examples/extensions/tools.ts +141 -0
  305. package/examples/extensions/truncated-tool.ts +195 -0
  306. package/examples/extensions/with-deps/index.ts +32 -0
  307. package/examples/extensions/with-deps/package-lock.json +31 -0
  308. package/examples/extensions/with-deps/package.json +22 -0
  309. package/examples/extensions/working-indicator.ts +123 -0
  310. package/examples/rpc-extension-ui.ts +632 -0
  311. package/npm-shrinkwrap.json +246 -240
  312. package/package.json +7 -6
  313. package/dist/core/index.d.ts +0 -12
  314. package/dist/core/index.d.ts.map +0 -1
  315. package/dist/core/index.js +0 -12
  316. package/dist/core/index.js.map +0 -1
  317. package/dist/core/tui-stubs.d.ts +0 -95
  318. package/dist/core/tui-stubs.d.ts.map +0 -1
  319. package/dist/core/tui-stubs.js +0 -90
  320. package/dist/core/tui-stubs.js.map +0 -1
@@ -0,0 +1,51 @@
1
+ import { existsSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ // Get the bundled WAD path (relative to this module)
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const BUNDLED_WAD = join(__dirname, "doom1.wad");
8
+ const WAD_URL = "https://distro.ibiblio.org/slitaz/sources/packages/d/doom1.wad";
9
+
10
+ const DEFAULT_WAD_PATHS = ["./doom1.wad", "./DOOM1.WAD", "~/doom1.wad", "~/.doom/doom1.wad"];
11
+
12
+ export function findWadFile(customPath?: string): string | null {
13
+ if (customPath) {
14
+ const resolved = resolve(customPath.replace(/^~/, process.env.HOME || ""));
15
+ if (existsSync(resolved)) return resolved;
16
+ return null;
17
+ }
18
+
19
+ // Check bundled WAD first
20
+ if (existsSync(BUNDLED_WAD)) {
21
+ return BUNDLED_WAD;
22
+ }
23
+
24
+ // Fall back to default paths
25
+ for (const p of DEFAULT_WAD_PATHS) {
26
+ const resolved = resolve(p.replace(/^~/, process.env.HOME || ""));
27
+ if (existsSync(resolved)) return resolved;
28
+ }
29
+
30
+ return null;
31
+ }
32
+
33
+ /** Download the shareware WAD if not present. Returns path or null on failure. */
34
+ export async function ensureWadFile(): Promise<string | null> {
35
+ // Check if already exists
36
+ const existing = findWadFile();
37
+ if (existing) return existing;
38
+
39
+ // Download to bundled location
40
+ try {
41
+ const response = await fetch(WAD_URL);
42
+ if (!response.ok) {
43
+ throw new Error(`HTTP ${response.status}`);
44
+ }
45
+ const buffer = await response.arrayBuffer();
46
+ writeFileSync(BUNDLED_WAD, Buffer.from(buffer));
47
+ return BUNDLED_WAD;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
@@ -0,0 +1,185 @@
1
+ // Requires GitHub CLI (`gh`) and a GitHub repository checkout.
2
+ // Preloads the latest open issues once per session, then filters them locally for fast `#...` completion.
3
+
4
+ import type { ExtensionAPI } from "@openeryc/pi-coding-agent";
5
+ import {
6
+ type AutocompleteItem,
7
+ type AutocompleteProvider,
8
+ type AutocompleteSuggestions,
9
+ fuzzyFilter,
10
+ } from "@openeryc/pi-tui";
11
+
12
+ type GitHubIssue = {
13
+ number: number;
14
+ title: string;
15
+ state: string;
16
+ };
17
+
18
+ type RepoResolution = { ok: true; repo: string } | { ok: false; error: string };
19
+
20
+ const MAX_ISSUES = 100;
21
+ const MAX_SUGGESTIONS = 20;
22
+
23
+ function extractIssueToken(textBeforeCursor: string): string | undefined {
24
+ const match = textBeforeCursor.match(/(?:^|[ \t])#([^\s#]*)$/);
25
+ return match?.[1];
26
+ }
27
+
28
+ function parseGitHubRepo(remoteUrl: string): string | undefined {
29
+ const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+\/[^/]+?)(?:\.git)?$/);
30
+ if (sshMatch) {
31
+ return sshMatch[1];
32
+ }
33
+
34
+ const httpsMatch = remoteUrl.match(/^https?:\/\/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/);
35
+ if (httpsMatch) {
36
+ return httpsMatch[1];
37
+ }
38
+
39
+ return undefined;
40
+ }
41
+
42
+ async function resolveGitHubRepo(pi: ExtensionAPI, cwd: string): Promise<RepoResolution> {
43
+ const result = await pi.exec("git", ["remote", "-v"], { cwd, timeout: 5_000 });
44
+ if (result.code !== 0) {
45
+ return { ok: false, error: "github-issue-autocomplete: cwd is not a git repository" };
46
+ }
47
+
48
+ for (const line of result.stdout.split("\n")) {
49
+ const columns = line.trim().split(/\s+/);
50
+ const remoteUrl = columns[1];
51
+ if (!remoteUrl) {
52
+ continue;
53
+ }
54
+ const repo = parseGitHubRepo(remoteUrl);
55
+ if (repo) {
56
+ return { ok: true, repo };
57
+ }
58
+ }
59
+
60
+ return { ok: false, error: "github-issue-autocomplete: cwd is not a GitHub repository" };
61
+ }
62
+
63
+ function formatIssueItem(issue: GitHubIssue): AutocompleteItem {
64
+ return {
65
+ value: `#${issue.number}`,
66
+ label: `#${issue.number}`,
67
+ description: `[${issue.state.toLowerCase()}] ${issue.title}`,
68
+ };
69
+ }
70
+
71
+ function filterIssues(issues: GitHubIssue[], query: string): AutocompleteItem[] {
72
+ if (!query.trim()) {
73
+ return issues.slice(0, MAX_SUGGESTIONS).map(formatIssueItem);
74
+ }
75
+
76
+ if (/^\d+$/.test(query)) {
77
+ const numericMatches = issues
78
+ .filter((issue) => String(issue.number).startsWith(query))
79
+ .slice(0, MAX_SUGGESTIONS)
80
+ .map(formatIssueItem);
81
+ if (numericMatches.length > 0) {
82
+ return numericMatches;
83
+ }
84
+ }
85
+
86
+ return fuzzyFilter(issues, query, (issue) => `${issue.number} ${issue.title}`)
87
+ .slice(0, MAX_SUGGESTIONS)
88
+ .map(formatIssueItem);
89
+ }
90
+
91
+ function createIssueAutocompleteProvider(
92
+ current: AutocompleteProvider,
93
+ getIssues: () => Promise<GitHubIssue[] | undefined>,
94
+ ): AutocompleteProvider {
95
+ return {
96
+ async getSuggestions(lines, cursorLine, cursorCol, options): Promise<AutocompleteSuggestions | null> {
97
+ const currentLine = lines[cursorLine] ?? "";
98
+ const textBeforeCursor = currentLine.slice(0, cursorCol);
99
+ const token = extractIssueToken(textBeforeCursor);
100
+ if (token === undefined) {
101
+ return current.getSuggestions(lines, cursorLine, cursorCol, options);
102
+ }
103
+
104
+ const issues = await getIssues();
105
+ if (options.signal.aborted || !issues || issues.length === 0) {
106
+ return current.getSuggestions(lines, cursorLine, cursorCol, options);
107
+ }
108
+
109
+ const suggestions = filterIssues(issues, token);
110
+ if (suggestions.length === 0) {
111
+ return current.getSuggestions(lines, cursorLine, cursorCol, options);
112
+ }
113
+
114
+ return {
115
+ items: suggestions,
116
+ prefix: `#${token}`,
117
+ };
118
+ },
119
+
120
+ applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
121
+ return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
122
+ },
123
+
124
+ shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
125
+ return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
126
+ },
127
+ };
128
+ }
129
+
130
+ export default function (pi: ExtensionAPI): void {
131
+ pi.on("session_start", async (_event, ctx) => {
132
+ const resolvedRepo = await resolveGitHubRepo(pi, ctx.cwd);
133
+ if (!resolvedRepo.ok) {
134
+ ctx.ui.notify(resolvedRepo.error, "error");
135
+ return;
136
+ }
137
+
138
+ const repo = resolvedRepo.repo;
139
+ let issuesPromise: Promise<GitHubIssue[] | undefined> | undefined;
140
+ let loadErrorShown = false;
141
+
142
+ const getIssues = async (): Promise<GitHubIssue[] | undefined> => {
143
+ issuesPromise ||= (async () => {
144
+ const result = await pi.exec(
145
+ "gh",
146
+ [
147
+ "issue",
148
+ "list",
149
+ "--repo",
150
+ repo,
151
+ "--state",
152
+ "open",
153
+ "--limit",
154
+ String(MAX_ISSUES),
155
+ "--json",
156
+ "number,title,state",
157
+ ],
158
+ { cwd: ctx.cwd, timeout: 5_000 },
159
+ );
160
+ if (result.code !== 0) {
161
+ if (!loadErrorShown) {
162
+ loadErrorShown = true;
163
+ const details = result.stderr.trim() || `exit code ${result.code}`;
164
+ ctx.ui.notify(`github-issue-autocomplete: failed to load issues: ${details}`, "error");
165
+ }
166
+ return undefined;
167
+ }
168
+
169
+ try {
170
+ return JSON.parse(result.stdout) as GitHubIssue[];
171
+ } catch {
172
+ if (!loadErrorShown) {
173
+ loadErrorShown = true;
174
+ ctx.ui.notify("github-issue-autocomplete: failed to parse gh issue list output", "error");
175
+ }
176
+ return undefined;
177
+ }
178
+ })();
179
+ return issuesPromise;
180
+ };
181
+
182
+ void getIssues();
183
+ ctx.ui.addAutocompleteProvider((current) => createIssueAutocompleteProvider(current, getIssues));
184
+ });
185
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Handoff extension - transfer context to a new focused session
3
+ *
4
+ * Instead of compacting (which is lossy), handoff extracts what matters
5
+ * for your next task and creates a new session with a generated prompt.
6
+ *
7
+ * Usage:
8
+ * /handoff now implement this for teams as well
9
+ * /handoff execute phase one of the plan
10
+ * /handoff check other places that need this fix
11
+ *
12
+ * The generated prompt appears as a draft in the editor for review/editing.
13
+ */
14
+
15
+ import type { AgentMessage } from "@openeryc/pi-agent-core";
16
+ import { complete, type Message } from "@openeryc/pi-ai";
17
+ import type { ExtensionAPI, SessionEntry } from "@openeryc/pi-coding-agent";
18
+ import { BorderedLoader, convertToLlm, serializeConversation } from "@openeryc/pi-coding-agent";
19
+
20
+ const SYSTEM_PROMPT = `You are a context transfer assistant. Given a conversation history and the user's goal for a new thread, generate a focused prompt that:
21
+
22
+ 1. Summarizes relevant context from the conversation (decisions made, approaches taken, key findings)
23
+ 2. Lists any relevant files that were discussed or modified
24
+ 3. Clearly states the next task based on the user's goal
25
+ 4. Is self-contained - the new thread should be able to proceed without the old conversation
26
+
27
+ Format your response as a prompt the user can send to start the new thread. Be concise but include all necessary context. Do not include any preamble like "Here's the prompt" - just output the prompt itself.
28
+
29
+ Example output format:
30
+ ## Context
31
+ We've been working on X. Key decisions:
32
+ - Decision 1
33
+ - Decision 2
34
+
35
+ Files involved:
36
+ - path/to/file1.ts
37
+ - path/to/file2.ts
38
+
39
+ ## Task
40
+ [Clear description of what to do next based on user's goal]`;
41
+
42
+ function entryToMessage(entry: SessionEntry): AgentMessage | undefined {
43
+ if (entry.type === "message") {
44
+ return entry.message;
45
+ }
46
+ if (entry.type === "compaction") {
47
+ return {
48
+ role: "compactionSummary",
49
+ summary: entry.summary,
50
+ tokensBefore: entry.tokensBefore,
51
+ timestamp: new Date(entry.timestamp).getTime(),
52
+ };
53
+ }
54
+ return undefined;
55
+ }
56
+
57
+ function getHandoffMessages(branch: SessionEntry[]): AgentMessage[] {
58
+ let compactionIndex = -1;
59
+ for (let i = branch.length - 1; i >= 0; i--) {
60
+ if (branch[i].type === "compaction") {
61
+ compactionIndex = i;
62
+ break;
63
+ }
64
+ }
65
+ if (compactionIndex < 0) {
66
+ return branch.map(entryToMessage).filter((message) => message !== undefined);
67
+ }
68
+
69
+ const compaction = branch[compactionIndex];
70
+ const firstKeptIndex =
71
+ compaction.type === "compaction" ? branch.findIndex((entry) => entry.id === compaction.firstKeptEntryId) : -1;
72
+ const compactedBranch = [
73
+ compaction,
74
+ ...(firstKeptIndex >= 0 ? branch.slice(firstKeptIndex, compactionIndex) : []),
75
+ ...branch.slice(compactionIndex + 1),
76
+ ];
77
+ return compactedBranch.map(entryToMessage).filter((message) => message !== undefined);
78
+ }
79
+
80
+ export default function (pi: ExtensionAPI) {
81
+ pi.registerCommand("handoff", {
82
+ description: "Transfer context to a new focused session",
83
+ handler: async (args, ctx) => {
84
+ if (!ctx.hasUI) {
85
+ ctx.ui.notify("handoff requires interactive mode", "error");
86
+ return;
87
+ }
88
+
89
+ if (!ctx.model) {
90
+ ctx.ui.notify("No model selected", "error");
91
+ return;
92
+ }
93
+
94
+ const goal = args.trim();
95
+ if (!goal) {
96
+ ctx.ui.notify("Usage: /handoff <goal for new thread>", "error");
97
+ return;
98
+ }
99
+
100
+ // Gather conversation context from current branch. If the branch was compacted,
101
+ // include the compaction summary plus entries from firstKeptEntryId onward.
102
+ const messages = getHandoffMessages(ctx.sessionManager.getBranch());
103
+
104
+ if (messages.length === 0) {
105
+ ctx.ui.notify("No conversation to hand off", "error");
106
+ return;
107
+ }
108
+
109
+ // Convert to LLM format and serialize
110
+ const llmMessages = convertToLlm(messages);
111
+ const conversationText = serializeConversation(llmMessages);
112
+ const currentSessionFile = ctx.sessionManager.getSessionFile();
113
+
114
+ // Generate the handoff prompt with loader UI
115
+ const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
116
+ const loader = new BorderedLoader(tui, theme, `Generating handoff prompt...`);
117
+ loader.onAbort = () => done(null);
118
+
119
+ const doGenerate = async () => {
120
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
121
+ if (!auth.ok || !auth.apiKey) {
122
+ throw new Error(auth.ok ? `No API key for ${ctx.model!.provider}` : auth.error);
123
+ }
124
+
125
+ const userMessage: Message = {
126
+ role: "user",
127
+ content: [
128
+ {
129
+ type: "text",
130
+ text: `## Conversation History\n\n${conversationText}\n\n## User's Goal for New Thread\n\n${goal}`,
131
+ },
132
+ ],
133
+ timestamp: Date.now(),
134
+ };
135
+
136
+ const response = await complete(
137
+ ctx.model!,
138
+ { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
139
+ { apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal },
140
+ );
141
+
142
+ if (response.stopReason === "aborted") {
143
+ return null;
144
+ }
145
+
146
+ return response.content
147
+ .filter((c): c is { type: "text"; text: string } => c.type === "text")
148
+ .map((c) => c.text)
149
+ .join("\n");
150
+ };
151
+
152
+ doGenerate()
153
+ .then(done)
154
+ .catch((err) => {
155
+ console.error("Handoff generation failed:", err);
156
+ done(null);
157
+ });
158
+
159
+ return loader;
160
+ });
161
+
162
+ if (result === null) {
163
+ ctx.ui.notify("Cancelled", "info");
164
+ return;
165
+ }
166
+
167
+ // Let user edit the generated prompt
168
+ const editedPrompt = await ctx.ui.editor("Edit handoff prompt", result);
169
+
170
+ if (editedPrompt === undefined) {
171
+ ctx.ui.notify("Cancelled", "info");
172
+ return;
173
+ }
174
+
175
+ // Create new session with parent tracking. Use the replacement-session
176
+ // context for post-switch UI work; the original ctx is stale after a
177
+ // successful session replacement.
178
+ const newSessionResult = await ctx.newSession({
179
+ parentSession: currentSessionFile,
180
+ withSession: async (replacementCtx) => {
181
+ replacementCtx.ui.setEditorText(editedPrompt);
182
+ replacementCtx.ui.notify("Handoff ready. Submit when ready.", "info");
183
+ },
184
+ });
185
+
186
+ if (newSessionResult.cancelled) {
187
+ ctx.ui.notify("New session cancelled", "info");
188
+ }
189
+ },
190
+ });
191
+ }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Interactive Shell Commands Extension
3
+ *
4
+ * Enables running interactive commands (vim, git rebase -i, htop, etc.)
5
+ * with full terminal access. The TUI suspends while they run.
6
+ *
7
+ * Usage:
8
+ * pi -e examples/extensions/interactive-shell.ts
9
+ *
10
+ * !vim file.txt # Auto-detected as interactive
11
+ * !i any-command # Force interactive mode with !i prefix
12
+ * !git rebase -i HEAD~3
13
+ * !htop
14
+ *
15
+ * Configuration via environment variables:
16
+ * INTERACTIVE_COMMANDS - Additional commands (comma-separated)
17
+ * INTERACTIVE_EXCLUDE - Commands to exclude (comma-separated)
18
+ *
19
+ * Note: This only intercepts user `!` commands, not agent bash tool calls.
20
+ * If the agent runs an interactive command, it will fail (which is fine).
21
+ */
22
+
23
+ import { spawnSync } from "node:child_process";
24
+ import type { ExtensionAPI } from "@openeryc/pi-coding-agent";
25
+
26
+ // Default interactive commands - editors, pagers, git ops, TUIs
27
+ const DEFAULT_INTERACTIVE_COMMANDS = [
28
+ // Editors
29
+ "vim",
30
+ "nvim",
31
+ "vi",
32
+ "nano",
33
+ "emacs",
34
+ "pico",
35
+ "micro",
36
+ "helix",
37
+ "hx",
38
+ "kak",
39
+ // Pagers
40
+ "less",
41
+ "more",
42
+ "most",
43
+ // Git interactive
44
+ "git commit",
45
+ "git rebase",
46
+ "git merge",
47
+ "git cherry-pick",
48
+ "git revert",
49
+ "git add -p",
50
+ "git add --patch",
51
+ "git add -i",
52
+ "git add --interactive",
53
+ "git stash -p",
54
+ "git stash --patch",
55
+ "git reset -p",
56
+ "git reset --patch",
57
+ "git checkout -p",
58
+ "git checkout --patch",
59
+ "git difftool",
60
+ "git mergetool",
61
+ // System monitors
62
+ "htop",
63
+ "top",
64
+ "btop",
65
+ "glances",
66
+ // File managers
67
+ "ranger",
68
+ "nnn",
69
+ "lf",
70
+ "mc",
71
+ "vifm",
72
+ // Git TUIs
73
+ "tig",
74
+ "lazygit",
75
+ "gitui",
76
+ // Fuzzy finders
77
+ "fzf",
78
+ "sk",
79
+ // Remote sessions
80
+ "ssh",
81
+ "telnet",
82
+ "mosh",
83
+ // Database clients
84
+ "psql",
85
+ "mysql",
86
+ "sqlite3",
87
+ "mongosh",
88
+ "redis-cli",
89
+ // Kubernetes/Docker
90
+ "kubectl edit",
91
+ "kubectl exec -it",
92
+ "docker exec -it",
93
+ "docker run -it",
94
+ // Other
95
+ "tmux",
96
+ "screen",
97
+ "ncdu",
98
+ ];
99
+
100
+ function getInteractiveCommands(): string[] {
101
+ const additional =
102
+ process.env.INTERACTIVE_COMMANDS?.split(",")
103
+ .map((s) => s.trim())
104
+ .filter(Boolean) ?? [];
105
+ const excluded = new Set(process.env.INTERACTIVE_EXCLUDE?.split(",").map((s) => s.trim().toLowerCase()) ?? []);
106
+ return [...DEFAULT_INTERACTIVE_COMMANDS, ...additional].filter((cmd) => !excluded.has(cmd.toLowerCase()));
107
+ }
108
+
109
+ function isInteractiveCommand(command: string): boolean {
110
+ const trimmed = command.trim().toLowerCase();
111
+ const commands = getInteractiveCommands();
112
+
113
+ for (const cmd of commands) {
114
+ const cmdLower = cmd.toLowerCase();
115
+ // Match at start
116
+ if (trimmed === cmdLower || trimmed.startsWith(`${cmdLower} `) || trimmed.startsWith(`${cmdLower}\t`)) {
117
+ return true;
118
+ }
119
+ // Match after pipe: "cat file | less"
120
+ const pipeIdx = trimmed.lastIndexOf("|");
121
+ if (pipeIdx !== -1) {
122
+ const afterPipe = trimmed.slice(pipeIdx + 1).trim();
123
+ if (afterPipe === cmdLower || afterPipe.startsWith(`${cmdLower} `)) {
124
+ return true;
125
+ }
126
+ }
127
+ }
128
+ return false;
129
+ }
130
+
131
+ export default function (pi: ExtensionAPI) {
132
+ pi.on("user_bash", async (event, ctx) => {
133
+ let command = event.command;
134
+ let forceInteractive = false;
135
+
136
+ // Check for !i prefix (command comes without the leading !)
137
+ // The prefix parsing happens before this event, so we check if command starts with "i "
138
+ if (command.startsWith("i ") || command.startsWith("i\t")) {
139
+ forceInteractive = true;
140
+ command = command.slice(2).trim();
141
+ }
142
+
143
+ const shouldBeInteractive = forceInteractive || isInteractiveCommand(command);
144
+ if (!shouldBeInteractive) {
145
+ return; // Let normal handling proceed
146
+ }
147
+
148
+ // No UI available (print mode, RPC, etc.)
149
+ if (!ctx.hasUI) {
150
+ return {
151
+ result: { output: "(interactive commands require TUI)", exitCode: 1, cancelled: false, truncated: false },
152
+ };
153
+ }
154
+
155
+ // Use ctx.ui.custom() to get TUI access, then run the command
156
+ const exitCode = await ctx.ui.custom<number | null>((tui, _theme, _kb, done) => {
157
+ // Stop TUI to release terminal
158
+ tui.stop();
159
+
160
+ // Clear screen
161
+ process.stdout.write("\x1b[2J\x1b[H");
162
+
163
+ // Run command with full terminal access
164
+ const shell = process.env.SHELL || "/bin/sh";
165
+ const result = spawnSync(shell, ["-c", command], {
166
+ stdio: "inherit",
167
+ env: process.env,
168
+ });
169
+
170
+ // Restart TUI
171
+ tui.start();
172
+ tui.requestRender(true);
173
+
174
+ // Signal completion
175
+ done(result.status);
176
+
177
+ // Return empty component (immediately disposed since done() was called)
178
+ return { render: () => [], invalidate: () => {} };
179
+ });
180
+
181
+ // Return result to prevent default bash handling
182
+ const output =
183
+ exitCode === 0
184
+ ? "(interactive command completed successfully)"
185
+ : `(interactive command exited with code ${exitCode})`;
186
+
187
+ return {
188
+ result: {
189
+ output,
190
+ exitCode: exitCode ?? 1,
191
+ cancelled: false,
192
+ truncated: false,
193
+ },
194
+ };
195
+ });
196
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Custom message rendering example.
3
+ *
4
+ * Shows how to use registerMessageRenderer to control how custom messages
5
+ * appear in the TUI, with colors, formatting, and expandable details.
6
+ *
7
+ * Usage: /status [message] - sends a status message with custom rendering
8
+ */
9
+
10
+ import type { ExtensionAPI } from "@openeryc/pi-coding-agent";
11
+ import { Box, Text } from "@openeryc/pi-tui";
12
+
13
+ export default function (pi: ExtensionAPI) {
14
+ // Register custom renderer for "status-update" messages
15
+ pi.registerMessageRenderer("status-update", (message, { expanded }, theme) => {
16
+ const details = message.details as { level: string; timestamp: number } | undefined;
17
+ const level = details?.level ?? "info";
18
+
19
+ // Color based on level
20
+ const color = level === "error" ? "error" : level === "warn" ? "warning" : "success";
21
+ const prefix = theme.fg(color, `[${level.toUpperCase()}]`);
22
+
23
+ let text = `${prefix} ${message.content}`;
24
+
25
+ // Show timestamp when expanded
26
+ if (expanded && details?.timestamp) {
27
+ const time = new Date(details.timestamp).toLocaleTimeString();
28
+ text += `\n${theme.fg("dim", ` at ${time}`)}`;
29
+ }
30
+
31
+ // Use Box with customMessageBg for consistent styling
32
+ const box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
33
+ box.addChild(new Text(text, 0, 0));
34
+ return box;
35
+ });
36
+
37
+ // Command to send status messages
38
+ pi.registerCommand("status", {
39
+ description: "Send a status message (usage: /status [warn|error] message)",
40
+ handler: async (args, _ctx) => {
41
+ const parts = args.trim().split(/\s+/);
42
+ let level = "info";
43
+ let content = args.trim();
44
+
45
+ // Check for level prefix
46
+ if (parts[0] === "warn" || parts[0] === "error") {
47
+ level = parts[0];
48
+ content = parts.slice(1).join(" ") || "Status update";
49
+ }
50
+
51
+ pi.sendMessage({
52
+ customType: "status-update",
53
+ content,
54
+ display: true,
55
+ details: { level, timestamp: Date.now() },
56
+ });
57
+ },
58
+ });
59
+ }