@oh-my-pi/pi-coding-agent 0.1.0

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 (337) hide show
  1. package/CHANGELOG.md +1629 -0
  2. package/README.md +1041 -0
  3. package/docs/compaction.md +403 -0
  4. package/docs/config-usage.md +113 -0
  5. package/docs/custom-tools.md +541 -0
  6. package/docs/extension-loading.md +1004 -0
  7. package/docs/hooks.md +867 -0
  8. package/docs/rpc.md +1040 -0
  9. package/docs/sdk.md +994 -0
  10. package/docs/session-tree-plan.md +441 -0
  11. package/docs/session.md +240 -0
  12. package/docs/skills.md +290 -0
  13. package/docs/theme.md +670 -0
  14. package/docs/tree.md +197 -0
  15. package/docs/tui.md +341 -0
  16. package/examples/README.md +21 -0
  17. package/examples/custom-tools/README.md +124 -0
  18. package/examples/custom-tools/hello/index.ts +20 -0
  19. package/examples/custom-tools/question/index.ts +84 -0
  20. package/examples/custom-tools/subagent/README.md +172 -0
  21. package/examples/custom-tools/subagent/agents/planner.md +37 -0
  22. package/examples/custom-tools/subagent/agents/scout.md +50 -0
  23. package/examples/custom-tools/subagent/agents/worker.md +24 -0
  24. package/examples/custom-tools/subagent/agents.ts +156 -0
  25. package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
  26. package/examples/custom-tools/subagent/commands/implement.md +10 -0
  27. package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
  28. package/examples/custom-tools/subagent/index.ts +1002 -0
  29. package/examples/custom-tools/todo/index.ts +212 -0
  30. package/examples/hooks/README.md +56 -0
  31. package/examples/hooks/auto-commit-on-exit.ts +49 -0
  32. package/examples/hooks/confirm-destructive.ts +59 -0
  33. package/examples/hooks/custom-compaction.ts +116 -0
  34. package/examples/hooks/dirty-repo-guard.ts +52 -0
  35. package/examples/hooks/file-trigger.ts +41 -0
  36. package/examples/hooks/git-checkpoint.ts +53 -0
  37. package/examples/hooks/handoff.ts +150 -0
  38. package/examples/hooks/permission-gate.ts +34 -0
  39. package/examples/hooks/protected-paths.ts +30 -0
  40. package/examples/hooks/qna.ts +119 -0
  41. package/examples/hooks/snake.ts +343 -0
  42. package/examples/hooks/status-line.ts +40 -0
  43. package/examples/sdk/01-minimal.ts +22 -0
  44. package/examples/sdk/02-custom-model.ts +49 -0
  45. package/examples/sdk/03-custom-prompt.ts +44 -0
  46. package/examples/sdk/04-skills.ts +44 -0
  47. package/examples/sdk/05-tools.ts +90 -0
  48. package/examples/sdk/06-hooks.ts +61 -0
  49. package/examples/sdk/07-context-files.ts +36 -0
  50. package/examples/sdk/08-slash-commands.ts +42 -0
  51. package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
  52. package/examples/sdk/10-settings.ts +38 -0
  53. package/examples/sdk/11-sessions.ts +48 -0
  54. package/examples/sdk/12-full-control.ts +95 -0
  55. package/examples/sdk/README.md +154 -0
  56. package/package.json +89 -0
  57. package/src/bun-imports.d.ts +16 -0
  58. package/src/capability/context-file.ts +40 -0
  59. package/src/capability/extension.ts +48 -0
  60. package/src/capability/hook.ts +40 -0
  61. package/src/capability/index.ts +616 -0
  62. package/src/capability/instruction.ts +37 -0
  63. package/src/capability/mcp.ts +52 -0
  64. package/src/capability/prompt.ts +35 -0
  65. package/src/capability/rule.ts +56 -0
  66. package/src/capability/settings.ts +35 -0
  67. package/src/capability/skill.ts +49 -0
  68. package/src/capability/slash-command.ts +40 -0
  69. package/src/capability/system-prompt.ts +35 -0
  70. package/src/capability/tool.ts +38 -0
  71. package/src/capability/types.ts +166 -0
  72. package/src/cli/args.ts +259 -0
  73. package/src/cli/file-processor.ts +121 -0
  74. package/src/cli/list-models.ts +104 -0
  75. package/src/cli/plugin-cli.ts +661 -0
  76. package/src/cli/session-picker.ts +41 -0
  77. package/src/cli/update-cli.ts +274 -0
  78. package/src/cli.ts +10 -0
  79. package/src/config.ts +391 -0
  80. package/src/core/agent-session.ts +2178 -0
  81. package/src/core/auth-storage.ts +258 -0
  82. package/src/core/bash-executor.ts +197 -0
  83. package/src/core/compaction/branch-summarization.ts +315 -0
  84. package/src/core/compaction/compaction.ts +664 -0
  85. package/src/core/compaction/index.ts +7 -0
  86. package/src/core/compaction/utils.ts +153 -0
  87. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  88. package/src/core/custom-commands/index.ts +15 -0
  89. package/src/core/custom-commands/loader.ts +226 -0
  90. package/src/core/custom-commands/types.ts +112 -0
  91. package/src/core/custom-tools/index.ts +22 -0
  92. package/src/core/custom-tools/loader.ts +248 -0
  93. package/src/core/custom-tools/types.ts +185 -0
  94. package/src/core/custom-tools/wrapper.ts +29 -0
  95. package/src/core/exec.ts +139 -0
  96. package/src/core/export-html/index.ts +159 -0
  97. package/src/core/export-html/template.css +774 -0
  98. package/src/core/export-html/template.generated.ts +2 -0
  99. package/src/core/export-html/template.html +45 -0
  100. package/src/core/export-html/template.js +1185 -0
  101. package/src/core/export-html/template.macro.ts +24 -0
  102. package/src/core/file-mentions.ts +54 -0
  103. package/src/core/hooks/index.ts +16 -0
  104. package/src/core/hooks/loader.ts +288 -0
  105. package/src/core/hooks/runner.ts +434 -0
  106. package/src/core/hooks/tool-wrapper.ts +98 -0
  107. package/src/core/hooks/types.ts +770 -0
  108. package/src/core/index.ts +53 -0
  109. package/src/core/logger.ts +112 -0
  110. package/src/core/mcp/client.ts +185 -0
  111. package/src/core/mcp/config.ts +248 -0
  112. package/src/core/mcp/index.ts +45 -0
  113. package/src/core/mcp/loader.ts +99 -0
  114. package/src/core/mcp/manager.ts +235 -0
  115. package/src/core/mcp/tool-bridge.ts +156 -0
  116. package/src/core/mcp/transports/http.ts +316 -0
  117. package/src/core/mcp/transports/index.ts +6 -0
  118. package/src/core/mcp/transports/stdio.ts +252 -0
  119. package/src/core/mcp/types.ts +228 -0
  120. package/src/core/messages.ts +211 -0
  121. package/src/core/model-registry.ts +334 -0
  122. package/src/core/model-resolver.ts +494 -0
  123. package/src/core/plugins/doctor.ts +67 -0
  124. package/src/core/plugins/index.ts +38 -0
  125. package/src/core/plugins/installer.ts +189 -0
  126. package/src/core/plugins/loader.ts +339 -0
  127. package/src/core/plugins/manager.ts +672 -0
  128. package/src/core/plugins/parser.ts +105 -0
  129. package/src/core/plugins/paths.ts +37 -0
  130. package/src/core/plugins/types.ts +190 -0
  131. package/src/core/sdk.ts +900 -0
  132. package/src/core/session-manager.ts +1837 -0
  133. package/src/core/settings-manager.ts +860 -0
  134. package/src/core/skills.ts +352 -0
  135. package/src/core/slash-commands.ts +132 -0
  136. package/src/core/system-prompt.ts +442 -0
  137. package/src/core/timings.ts +25 -0
  138. package/src/core/title-generator.ts +110 -0
  139. package/src/core/tools/ask.ts +193 -0
  140. package/src/core/tools/bash-interceptor.ts +120 -0
  141. package/src/core/tools/bash.ts +91 -0
  142. package/src/core/tools/context.ts +32 -0
  143. package/src/core/tools/edit-diff.ts +487 -0
  144. package/src/core/tools/edit.ts +140 -0
  145. package/src/core/tools/exa/company.ts +59 -0
  146. package/src/core/tools/exa/index.ts +63 -0
  147. package/src/core/tools/exa/linkedin.ts +59 -0
  148. package/src/core/tools/exa/mcp-client.ts +368 -0
  149. package/src/core/tools/exa/render.ts +200 -0
  150. package/src/core/tools/exa/researcher.ts +90 -0
  151. package/src/core/tools/exa/search.ts +338 -0
  152. package/src/core/tools/exa/types.ts +167 -0
  153. package/src/core/tools/exa/websets.ts +248 -0
  154. package/src/core/tools/find.ts +244 -0
  155. package/src/core/tools/grep.ts +584 -0
  156. package/src/core/tools/index.ts +283 -0
  157. package/src/core/tools/ls.ts +142 -0
  158. package/src/core/tools/lsp/client.ts +767 -0
  159. package/src/core/tools/lsp/clients/biome-client.ts +207 -0
  160. package/src/core/tools/lsp/clients/index.ts +49 -0
  161. package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
  162. package/src/core/tools/lsp/config.ts +845 -0
  163. package/src/core/tools/lsp/edits.ts +110 -0
  164. package/src/core/tools/lsp/index.ts +1364 -0
  165. package/src/core/tools/lsp/render.ts +560 -0
  166. package/src/core/tools/lsp/rust-analyzer.ts +145 -0
  167. package/src/core/tools/lsp/types.ts +495 -0
  168. package/src/core/tools/lsp/utils.ts +526 -0
  169. package/src/core/tools/notebook.ts +182 -0
  170. package/src/core/tools/output.ts +198 -0
  171. package/src/core/tools/path-utils.ts +61 -0
  172. package/src/core/tools/read.ts +507 -0
  173. package/src/core/tools/renderers.ts +820 -0
  174. package/src/core/tools/review.ts +275 -0
  175. package/src/core/tools/rulebook.ts +124 -0
  176. package/src/core/tools/task/agents.ts +158 -0
  177. package/src/core/tools/task/artifacts.ts +114 -0
  178. package/src/core/tools/task/commands.ts +157 -0
  179. package/src/core/tools/task/discovery.ts +217 -0
  180. package/src/core/tools/task/executor.ts +531 -0
  181. package/src/core/tools/task/index.ts +548 -0
  182. package/src/core/tools/task/model-resolver.ts +176 -0
  183. package/src/core/tools/task/parallel.ts +38 -0
  184. package/src/core/tools/task/render.ts +502 -0
  185. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  186. package/src/core/tools/task/types.ts +142 -0
  187. package/src/core/tools/truncate.ts +265 -0
  188. package/src/core/tools/web-fetch.ts +2511 -0
  189. package/src/core/tools/web-search/auth.ts +199 -0
  190. package/src/core/tools/web-search/index.ts +583 -0
  191. package/src/core/tools/web-search/providers/anthropic.ts +198 -0
  192. package/src/core/tools/web-search/providers/exa.ts +196 -0
  193. package/src/core/tools/web-search/providers/perplexity.ts +195 -0
  194. package/src/core/tools/web-search/render.ts +372 -0
  195. package/src/core/tools/web-search/types.ts +180 -0
  196. package/src/core/tools/write.ts +63 -0
  197. package/src/core/ttsr.ts +211 -0
  198. package/src/core/utils.ts +187 -0
  199. package/src/discovery/agents-md.ts +75 -0
  200. package/src/discovery/builtin.ts +647 -0
  201. package/src/discovery/claude.ts +623 -0
  202. package/src/discovery/cline.ts +104 -0
  203. package/src/discovery/codex.ts +571 -0
  204. package/src/discovery/cursor.ts +266 -0
  205. package/src/discovery/gemini.ts +368 -0
  206. package/src/discovery/github.ts +120 -0
  207. package/src/discovery/helpers.test.ts +127 -0
  208. package/src/discovery/helpers.ts +249 -0
  209. package/src/discovery/index.ts +84 -0
  210. package/src/discovery/mcp-json.ts +127 -0
  211. package/src/discovery/vscode.ts +99 -0
  212. package/src/discovery/windsurf.ts +219 -0
  213. package/src/index.ts +192 -0
  214. package/src/main.ts +507 -0
  215. package/src/migrations.ts +156 -0
  216. package/src/modes/cleanup.ts +23 -0
  217. package/src/modes/index.ts +48 -0
  218. package/src/modes/interactive/components/armin.ts +382 -0
  219. package/src/modes/interactive/components/assistant-message.ts +86 -0
  220. package/src/modes/interactive/components/bash-execution.ts +199 -0
  221. package/src/modes/interactive/components/bordered-loader.ts +41 -0
  222. package/src/modes/interactive/components/branch-summary-message.ts +42 -0
  223. package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
  224. package/src/modes/interactive/components/custom-editor.ts +122 -0
  225. package/src/modes/interactive/components/diff.ts +147 -0
  226. package/src/modes/interactive/components/dynamic-border.ts +25 -0
  227. package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
  228. package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
  229. package/src/modes/interactive/components/extensions/index.ts +9 -0
  230. package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
  231. package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
  232. package/src/modes/interactive/components/extensions/types.ts +191 -0
  233. package/src/modes/interactive/components/hook-editor.ts +117 -0
  234. package/src/modes/interactive/components/hook-input.ts +64 -0
  235. package/src/modes/interactive/components/hook-message.ts +96 -0
  236. package/src/modes/interactive/components/hook-selector.ts +91 -0
  237. package/src/modes/interactive/components/model-selector.ts +560 -0
  238. package/src/modes/interactive/components/oauth-selector.ts +136 -0
  239. package/src/modes/interactive/components/plugin-settings.ts +481 -0
  240. package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
  241. package/src/modes/interactive/components/session-selector.ts +220 -0
  242. package/src/modes/interactive/components/settings-defs.ts +597 -0
  243. package/src/modes/interactive/components/settings-selector.ts +545 -0
  244. package/src/modes/interactive/components/show-images-selector.ts +45 -0
  245. package/src/modes/interactive/components/status-line/index.ts +4 -0
  246. package/src/modes/interactive/components/status-line/presets.ts +94 -0
  247. package/src/modes/interactive/components/status-line/segments.ts +350 -0
  248. package/src/modes/interactive/components/status-line/separators.ts +55 -0
  249. package/src/modes/interactive/components/status-line/types.ts +81 -0
  250. package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
  251. package/src/modes/interactive/components/status-line.ts +384 -0
  252. package/src/modes/interactive/components/theme-selector.ts +62 -0
  253. package/src/modes/interactive/components/thinking-selector.ts +64 -0
  254. package/src/modes/interactive/components/tool-execution.ts +946 -0
  255. package/src/modes/interactive/components/tree-selector.ts +877 -0
  256. package/src/modes/interactive/components/ttsr-notification.ts +82 -0
  257. package/src/modes/interactive/components/user-message-selector.ts +159 -0
  258. package/src/modes/interactive/components/user-message.ts +18 -0
  259. package/src/modes/interactive/components/visual-truncate.ts +50 -0
  260. package/src/modes/interactive/components/welcome.ts +228 -0
  261. package/src/modes/interactive/interactive-mode.ts +2669 -0
  262. package/src/modes/interactive/theme/dark.json +102 -0
  263. package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
  264. package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
  265. package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
  266. package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
  267. package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
  268. package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
  269. package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
  270. package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
  271. package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
  272. package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
  273. package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
  274. package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
  275. package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
  276. package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
  277. package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
  278. package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
  279. package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
  280. package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
  281. package/src/modes/interactive/theme/defaults/index.ts +67 -0
  282. package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
  283. package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
  284. package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
  285. package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
  286. package/src/modes/interactive/theme/defaults/light-github.json +114 -0
  287. package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
  288. package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
  289. package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
  290. package/src/modes/interactive/theme/defaults/light-one.json +105 -0
  291. package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
  292. package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
  293. package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
  294. package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
  295. package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
  296. package/src/modes/interactive/theme/light.json +99 -0
  297. package/src/modes/interactive/theme/theme-schema.json +424 -0
  298. package/src/modes/interactive/theme/theme.ts +2211 -0
  299. package/src/modes/print-mode.ts +163 -0
  300. package/src/modes/rpc/rpc-client.ts +527 -0
  301. package/src/modes/rpc/rpc-mode.ts +494 -0
  302. package/src/modes/rpc/rpc-types.ts +203 -0
  303. package/src/prompts/architect-plan.md +10 -0
  304. package/src/prompts/branch-summary-preamble.md +3 -0
  305. package/src/prompts/branch-summary.md +28 -0
  306. package/src/prompts/browser.md +71 -0
  307. package/src/prompts/compaction-summary.md +34 -0
  308. package/src/prompts/compaction-turn-prefix.md +16 -0
  309. package/src/prompts/compaction-update-summary.md +41 -0
  310. package/src/prompts/explore.md +82 -0
  311. package/src/prompts/implement-with-critic.md +11 -0
  312. package/src/prompts/implement.md +11 -0
  313. package/src/prompts/init.md +30 -0
  314. package/src/prompts/plan.md +54 -0
  315. package/src/prompts/reviewer.md +81 -0
  316. package/src/prompts/summarization-system.md +3 -0
  317. package/src/prompts/system-prompt.md +27 -0
  318. package/src/prompts/task.md +56 -0
  319. package/src/prompts/title-system.md +8 -0
  320. package/src/prompts/tools/ask.md +24 -0
  321. package/src/prompts/tools/bash.md +23 -0
  322. package/src/prompts/tools/edit.md +9 -0
  323. package/src/prompts/tools/find.md +6 -0
  324. package/src/prompts/tools/grep.md +12 -0
  325. package/src/prompts/tools/lsp.md +14 -0
  326. package/src/prompts/tools/output.md +23 -0
  327. package/src/prompts/tools/read.md +25 -0
  328. package/src/prompts/tools/web-fetch.md +8 -0
  329. package/src/prompts/tools/web-search.md +10 -0
  330. package/src/prompts/tools/write.md +10 -0
  331. package/src/utils/changelog.ts +99 -0
  332. package/src/utils/clipboard.ts +265 -0
  333. package/src/utils/fuzzy.ts +108 -0
  334. package/src/utils/mime.ts +30 -0
  335. package/src/utils/shell-snapshot.ts +218 -0
  336. package/src/utils/shell.ts +364 -0
  337. package/src/utils/tools-manager.ts +265 -0
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Bun macro that inlines HTML template with CSS/JS at compile time.
3
+ * This runs during `bun build` and embeds the result as a string.
4
+ */
5
+ export async function getTemplate(): Promise<string> {
6
+ const dir = new URL(".", import.meta.url).pathname;
7
+
8
+ // Read all files
9
+ const html = await Bun.file(`${dir}template.html`).text();
10
+ const css = await Bun.file(`${dir}template.css`).text();
11
+ const js = await Bun.file(`${dir}template.js`).text();
12
+
13
+ // Minify CSS
14
+ const minifiedCss = css
15
+ .replace(/\/\*[\s\S]*?\*\//g, "")
16
+ .replace(/\s+/g, " ")
17
+ .replace(/\s*([{}:;,])\s*/g, "$1")
18
+ .trim();
19
+
20
+ // Inline everything
21
+ return html
22
+ .replace("<template-css/>", `<style>${minifiedCss}</style>`)
23
+ .replace("<template-js/>", `<script>${js}</script>`);
24
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Auto-read file mentions from user prompts.
3
+ *
4
+ * When users reference files with @path syntax (e.g., "@src/foo.ts"),
5
+ * we automatically inject the file contents as a FileMentionMessage
6
+ * so the agent doesn't need to read them manually.
7
+ */
8
+
9
+ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
10
+ import type { FileMentionMessage } from "./messages";
11
+ import { createReadTool } from "./tools/read";
12
+
13
+ /** Regex to match @filepath patterns in text */
14
+ const FILE_MENTION_REGEX = /@((?:[^\s@]+\/)*[^\s@]+\.[a-zA-Z0-9]+)/g;
15
+
16
+ /** Extract all @filepath mentions from text */
17
+ export function extractFileMentions(text: string): string[] {
18
+ const matches = [...text.matchAll(FILE_MENTION_REGEX)];
19
+ return [...new Set(matches.map((m) => m[1]))];
20
+ }
21
+
22
+ /**
23
+ * Generate a FileMentionMessage containing the contents of mentioned files.
24
+ * Returns empty array if no files could be read.
25
+ */
26
+ export async function generateFileMentionMessages(filePaths: string[], cwd: string): Promise<AgentMessage[]> {
27
+ if (filePaths.length === 0) return [];
28
+
29
+ const readTool = createReadTool(cwd);
30
+ const files: FileMentionMessage["files"] = [];
31
+
32
+ for (const filePath of filePaths) {
33
+ try {
34
+ const result = await readTool.execute("auto-read", { path: filePath });
35
+ const textContent = result.content.find((c) => c.type === "text");
36
+ if (textContent && textContent.type === "text") {
37
+ const lineCount = textContent.text.split("\n").length;
38
+ files.push({ path: filePath, content: textContent.text, lineCount });
39
+ }
40
+ } catch {
41
+ // File doesn't exist or isn't readable - skip silently
42
+ }
43
+ }
44
+
45
+ if (files.length === 0) return [];
46
+
47
+ const message: FileMentionMessage = {
48
+ role: "fileMention",
49
+ files,
50
+ timestamp: Date.now(),
51
+ };
52
+
53
+ return [message];
54
+ }
@@ -0,0 +1,16 @@
1
+ // biome-ignore assist/source/organizeImports: biome is not smart
2
+ export {
3
+ discoverAndLoadHooks,
4
+ loadHooks,
5
+ type AppendEntryHandler,
6
+ type BranchHandler,
7
+ type LoadedHook,
8
+ type LoadHooksResult,
9
+ type NavigateTreeHandler,
10
+ type NewSessionHandler,
11
+ type SendMessageHandler,
12
+ } from "./loader";
13
+ export { execCommand, HookRunner, type HookErrorListener } from "./runner";
14
+ export { wrapToolsWithHooks, wrapToolWithHooks } from "./tool-wrapper";
15
+ export * from "./types";
16
+ export type { UsageStatistics, ReadonlySessionManager } from "../session-manager";
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Hook loader - loads TypeScript hook modules using native Bun import.
3
+ */
4
+
5
+ import * as os from "node:os";
6
+ import * as path from "node:path";
7
+ import * as typebox from "@sinclair/typebox";
8
+ import { hookCapability } from "../../capability/hook";
9
+ import type { Hook } from "../../discovery";
10
+ import { loadSync } from "../../discovery";
11
+ import * as piCodingAgent from "../../index";
12
+ import { logger } from "../logger";
13
+ import type { HookMessage } from "../messages";
14
+ import type { SessionManager } from "../session-manager";
15
+ import { execCommand } from "./runner";
16
+ import type { ExecOptions, HookAPI, HookFactory, HookMessageRenderer, RegisteredCommand } from "./types";
17
+
18
+ /**
19
+ * Generic handler function type.
20
+ */
21
+ type HandlerFn = (...args: unknown[]) => Promise<unknown>;
22
+
23
+ /**
24
+ * Send message handler type for pi.sendMessage().
25
+ */
26
+ export type SendMessageHandler = <T = unknown>(
27
+ message: Pick<HookMessage<T>, "customType" | "content" | "display" | "details">,
28
+ triggerTurn?: boolean,
29
+ ) => void;
30
+
31
+ /**
32
+ * Append entry handler type for pi.appendEntry().
33
+ */
34
+ export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;
35
+
36
+ /**
37
+ * New session handler type for ctx.newSession() in HookCommandContext.
38
+ */
39
+ export type NewSessionHandler = (options?: {
40
+ parentSession?: string;
41
+ setup?: (sessionManager: SessionManager) => Promise<void>;
42
+ }) => Promise<{ cancelled: boolean }>;
43
+
44
+ /**
45
+ * Branch handler type for ctx.branch() in HookCommandContext.
46
+ */
47
+ export type BranchHandler = (entryId: string) => Promise<{ cancelled: boolean }>;
48
+
49
+ /**
50
+ * Navigate tree handler type for ctx.navigateTree() in HookCommandContext.
51
+ */
52
+ export type NavigateTreeHandler = (
53
+ targetId: string,
54
+ options?: { summarize?: boolean },
55
+ ) => Promise<{ cancelled: boolean }>;
56
+
57
+ /**
58
+ * Registered handlers for a loaded hook.
59
+ */
60
+ export interface LoadedHook {
61
+ /** Original path from config */
62
+ path: string;
63
+ /** Resolved absolute path */
64
+ resolvedPath: string;
65
+ /** Map of event type to handler functions */
66
+ handlers: Map<string, HandlerFn[]>;
67
+ /** Map of customType to hook message renderer */
68
+ messageRenderers: Map<string, HookMessageRenderer>;
69
+ /** Map of command name to registered command */
70
+ commands: Map<string, RegisteredCommand>;
71
+ /** Set the send message handler for this hook's pi.sendMessage() */
72
+ setSendMessageHandler: (handler: SendMessageHandler) => void;
73
+ /** Set the append entry handler for this hook's pi.appendEntry() */
74
+ setAppendEntryHandler: (handler: AppendEntryHandler) => void;
75
+ }
76
+
77
+ /**
78
+ * Result of loading hooks.
79
+ */
80
+ export interface LoadHooksResult {
81
+ /** Successfully loaded hooks */
82
+ hooks: LoadedHook[];
83
+ /** Errors encountered during loading */
84
+ errors: Array<{ path: string; error: string }>;
85
+ }
86
+
87
+ const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
88
+
89
+ function normalizeUnicodeSpaces(str: string): string {
90
+ return str.replace(UNICODE_SPACES, " ");
91
+ }
92
+
93
+ function expandPath(p: string): string {
94
+ const normalized = normalizeUnicodeSpaces(p);
95
+ if (normalized.startsWith("~/")) {
96
+ return path.join(os.homedir(), normalized.slice(2));
97
+ }
98
+ if (normalized.startsWith("~")) {
99
+ return path.join(os.homedir(), normalized.slice(1));
100
+ }
101
+ return normalized;
102
+ }
103
+
104
+ /**
105
+ * Resolve hook path.
106
+ * - Absolute paths used as-is
107
+ * - Paths starting with ~ expanded to home directory
108
+ * - Relative paths resolved from cwd
109
+ */
110
+ function resolveHookPath(hookPath: string, cwd: string): string {
111
+ const expanded = expandPath(hookPath);
112
+
113
+ if (path.isAbsolute(expanded)) {
114
+ return expanded;
115
+ }
116
+
117
+ // Relative paths resolved from cwd
118
+ return path.resolve(cwd, expanded);
119
+ }
120
+
121
+ /**
122
+ * Create a HookAPI instance that collects handlers, renderers, and commands.
123
+ * Returns the API, maps, and functions to set handlers later.
124
+ */
125
+ function createHookAPI(
126
+ handlers: Map<string, HandlerFn[]>,
127
+ cwd: string,
128
+ ): {
129
+ api: HookAPI;
130
+ messageRenderers: Map<string, HookMessageRenderer>;
131
+ commands: Map<string, RegisteredCommand>;
132
+ setSendMessageHandler: (handler: SendMessageHandler) => void;
133
+ setAppendEntryHandler: (handler: AppendEntryHandler) => void;
134
+ } {
135
+ let sendMessageHandler: SendMessageHandler | null = null;
136
+ let appendEntryHandler: AppendEntryHandler | null = null;
137
+ const messageRenderers = new Map<string, HookMessageRenderer>();
138
+ const commands = new Map<string, RegisteredCommand>();
139
+
140
+ // Cast to HookAPI - the implementation is more general (string event names)
141
+ // but the interface has specific overloads for type safety in hooks
142
+ const api = {
143
+ on(event: string, handler: HandlerFn): void {
144
+ if (!handlers.has(event)) {
145
+ handlers.set(event, []);
146
+ }
147
+ handlers.get(event)!.push(handler);
148
+ },
149
+ sendMessage<T = unknown>(message: HookMessage<T>, triggerTurn?: boolean): void {
150
+ if (!sendMessageHandler) {
151
+ throw new Error("sendMessage handler not initialized");
152
+ }
153
+ sendMessageHandler(message, triggerTurn);
154
+ },
155
+ appendEntry<T = unknown>(customType: string, data?: T): void {
156
+ if (!appendEntryHandler) {
157
+ throw new Error("appendEntry handler not initialized");
158
+ }
159
+ appendEntryHandler(customType, data);
160
+ },
161
+ registerMessageRenderer<T = unknown>(customType: string, renderer: HookMessageRenderer<T>): void {
162
+ messageRenderers.set(customType, renderer as HookMessageRenderer);
163
+ },
164
+ registerCommand(name: string, options: { description?: string; handler: RegisteredCommand["handler"] }): void {
165
+ commands.set(name, { name, ...options });
166
+ },
167
+ exec(command: string, args: string[], options?: ExecOptions) {
168
+ return execCommand(command, args, options?.cwd ?? cwd, options);
169
+ },
170
+ logger,
171
+ typebox,
172
+ pi: piCodingAgent,
173
+ } as HookAPI;
174
+
175
+ return {
176
+ api,
177
+ messageRenderers,
178
+ commands,
179
+ setSendMessageHandler: (handler: SendMessageHandler) => {
180
+ sendMessageHandler = handler;
181
+ },
182
+ setAppendEntryHandler: (handler: AppendEntryHandler) => {
183
+ appendEntryHandler = handler;
184
+ },
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Load a single hook module using native Bun import.
190
+ */
191
+ async function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHook | null; error: string | null }> {
192
+ const resolvedPath = resolveHookPath(hookPath, cwd);
193
+
194
+ try {
195
+ // Import the module using native Bun import
196
+ const module = await import(resolvedPath);
197
+ const factory = module.default as HookFactory;
198
+
199
+ if (typeof factory !== "function") {
200
+ return { hook: null, error: "Hook must export a default function" };
201
+ }
202
+
203
+ // Create handlers map and API
204
+ const handlers = new Map<string, HandlerFn[]>();
205
+ const { api, messageRenderers, commands, setSendMessageHandler, setAppendEntryHandler } = createHookAPI(
206
+ handlers,
207
+ cwd,
208
+ );
209
+
210
+ // Call factory to register handlers
211
+ factory(api);
212
+
213
+ return {
214
+ hook: {
215
+ path: hookPath,
216
+ resolvedPath,
217
+ handlers,
218
+ messageRenderers,
219
+ commands,
220
+ setSendMessageHandler,
221
+ setAppendEntryHandler,
222
+ },
223
+ error: null,
224
+ };
225
+ } catch (err) {
226
+ const message = err instanceof Error ? err.message : String(err);
227
+ return { hook: null, error: `Failed to load hook: ${message}` };
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Load all hooks from configuration.
233
+ * @param paths - Array of hook file paths
234
+ * @param cwd - Current working directory for resolving relative paths
235
+ */
236
+ export async function loadHooks(paths: string[], cwd: string): Promise<LoadHooksResult> {
237
+ const hooks: LoadedHook[] = [];
238
+ const errors: Array<{ path: string; error: string }> = [];
239
+
240
+ for (const hookPath of paths) {
241
+ const { hook, error } = await loadHook(hookPath, cwd);
242
+
243
+ if (error) {
244
+ errors.push({ path: hookPath, error });
245
+ continue;
246
+ }
247
+
248
+ if (hook) {
249
+ hooks.push(hook);
250
+ }
251
+ }
252
+
253
+ return { hooks, errors };
254
+ }
255
+
256
+ /**
257
+ * Discover and load hooks from all registered providers.
258
+ * Uses the capability API to discover hook paths from:
259
+ * 1. OMP native configs (.omp/.pi hooks/)
260
+ * 2. Installed plugins
261
+ * 3. Other editor/IDE configurations
262
+ *
263
+ * Plus any explicitly configured paths from settings.
264
+ */
265
+ export async function discoverAndLoadHooks(configuredPaths: string[], cwd: string): Promise<LoadHooksResult> {
266
+ const allPaths: string[] = [];
267
+ const seen = new Set<string>();
268
+
269
+ // Helper to add paths without duplicates
270
+ const addPaths = (paths: string[]) => {
271
+ for (const p of paths) {
272
+ const resolved = path.resolve(p);
273
+ if (!seen.has(resolved)) {
274
+ seen.add(resolved);
275
+ allPaths.push(p);
276
+ }
277
+ }
278
+ };
279
+
280
+ // 1. Discover hooks via capability API
281
+ const discovered = loadSync<Hook>(hookCapability.id, { cwd });
282
+ addPaths(discovered.items.map((hook) => hook.path));
283
+
284
+ // 2. Explicitly configured paths (can override/add)
285
+ addPaths(configuredPaths.map((p) => resolveHookPath(p, cwd)));
286
+
287
+ return loadHooks(allPaths, cwd);
288
+ }