@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,191 @@
1
+ /**
2
+ * Types for the Extension Control Center dashboard.
3
+ */
4
+
5
+ import type { SourceMeta } from "../../../../capability/types";
6
+
7
+ /**
8
+ * Extension kinds matching capability types.
9
+ */
10
+ export type ExtensionKind =
11
+ | "skill"
12
+ | "rule"
13
+ | "tool"
14
+ | "mcp"
15
+ | "prompt"
16
+ | "instruction"
17
+ | "context-file"
18
+ | "hook"
19
+ | "slash-command";
20
+
21
+ /**
22
+ * Extension state (active, disabled, or shadowed).
23
+ */
24
+ export type ExtensionState = "active" | "disabled" | "shadowed";
25
+
26
+ /**
27
+ * Reason why an extension is disabled.
28
+ */
29
+ export type DisabledReason = "provider-disabled" | "item-disabled" | "shadowed";
30
+
31
+ /**
32
+ * Unified extension representation for the dashboard.
33
+ * Normalizes all capability types into a common shape.
34
+ */
35
+ export interface Extension {
36
+ /** Unique ID: `${kind}:${name}` */
37
+ id: string;
38
+ /** Extension kind */
39
+ kind: ExtensionKind;
40
+ /** Extension name */
41
+ name: string;
42
+ /** Display name (may differ from name) */
43
+ displayName: string;
44
+ /** Description if available */
45
+ description?: string;
46
+ /** Trigger pattern (slash command, glob, regex) */
47
+ trigger?: string;
48
+ /** Absolute path to source file */
49
+ path: string;
50
+ /** Source metadata */
51
+ source: {
52
+ provider: string;
53
+ providerName: string;
54
+ level: "user" | "project" | "native";
55
+ };
56
+ /** Current state */
57
+ state: ExtensionState;
58
+ /** Reason for disabled state */
59
+ disabledReason?: DisabledReason;
60
+ /** If shadowed, what shadows it */
61
+ shadowedBy?: string;
62
+ /** Raw item data for inspector */
63
+ raw: unknown;
64
+ }
65
+
66
+ /**
67
+ * Tree node types for sidebar hierarchy.
68
+ */
69
+ export type TreeNodeType = "provider" | "kind" | "item";
70
+
71
+ /**
72
+ * Sidebar tree node.
73
+ */
74
+ export interface TreeNode {
75
+ /** Unique ID */
76
+ id: string;
77
+ /** Display label */
78
+ label: string;
79
+ /** Node type (provider can be toggled, kind groups items) */
80
+ type: TreeNodeType;
81
+ /** Whether this node/provider is enabled */
82
+ enabled: boolean;
83
+ /** Whether collapsed */
84
+ collapsed: boolean;
85
+ /** Child nodes */
86
+ children: TreeNode[];
87
+ /** Extension count (for display) */
88
+ count?: number;
89
+ }
90
+
91
+ /**
92
+ * Flattened tree item for navigation.
93
+ */
94
+ export interface FlatTreeItem {
95
+ node: TreeNode;
96
+ depth: number;
97
+ index: number;
98
+ }
99
+
100
+ /**
101
+ * Focus region in the tabbed dashboard.
102
+ */
103
+ export type FocusRegion = "tabs" | "list";
104
+
105
+ /**
106
+ * Provider tab representation.
107
+ */
108
+ export interface ProviderTab {
109
+ /** Provider ID (or "all" for the ALL tab) */
110
+ id: string;
111
+ /** Display label */
112
+ label: string;
113
+ /** Whether provider is enabled (always true for "all") */
114
+ enabled: boolean;
115
+ /** Extension count for this provider */
116
+ count: number;
117
+ }
118
+
119
+ /**
120
+ * Tabbed dashboard state.
121
+ */
122
+ export interface DashboardState {
123
+ /** Provider tabs */
124
+ tabs: ProviderTab[];
125
+ /** Active tab index */
126
+ activeTabIndex: number;
127
+
128
+ /** All extensions (unfiltered) */
129
+ extensions: Extension[];
130
+ /** Extensions filtered by active tab */
131
+ tabFiltered: Extension[];
132
+ /** Extensions filtered by search (applied after tab filter) */
133
+ searchFiltered: Extension[];
134
+ /** Current search query */
135
+ searchQuery: string;
136
+
137
+ /** Selected index in main list */
138
+ listIndex: number;
139
+ /** Scroll offset for main list */
140
+ scrollOffset: number;
141
+
142
+ /** Currently selected extension for inspector */
143
+ selected: Extension | null;
144
+ }
145
+
146
+ /**
147
+ * @deprecated Use FocusRegion instead
148
+ */
149
+ export type FocusPane = "sidebar" | "main" | "inspector";
150
+
151
+ /**
152
+ * Callbacks from dashboard to parent.
153
+ */
154
+ export interface DashboardCallbacks {
155
+ /** Called when provider is toggled */
156
+ onProviderToggle: (providerId: string, enabled: boolean) => void;
157
+ /** Called when extension item is toggled */
158
+ onExtensionToggle: (extensionId: string, enabled: boolean) => void;
159
+ /** Called when dashboard is closed */
160
+ onClose: () => void;
161
+ }
162
+
163
+ /**
164
+ * Create extension ID from kind and name.
165
+ */
166
+ export function makeExtensionId(kind: ExtensionKind, name: string): string {
167
+ return `${kind}:${name}`;
168
+ }
169
+
170
+ /**
171
+ * Parse extension ID into kind and name.
172
+ */
173
+ export function parseExtensionId(id: string): { kind: ExtensionKind; name: string } | null {
174
+ const colonIdx = id.indexOf(":");
175
+ if (colonIdx === -1) return null;
176
+ return {
177
+ kind: id.slice(0, colonIdx) as ExtensionKind,
178
+ name: id.slice(colonIdx + 1),
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Map SourceMeta to extension source shape.
184
+ */
185
+ export function sourceFromMeta(meta: SourceMeta): Extension["source"] {
186
+ return {
187
+ provider: meta.provider,
188
+ providerName: meta.providerName,
189
+ level: meta.level,
190
+ };
191
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Multi-line editor component for hooks.
3
+ * Supports Ctrl+G for external editor.
4
+ */
5
+
6
+ import * as fs from "node:fs";
7
+ import * as os from "node:os";
8
+ import * as path from "node:path";
9
+ import { Container, Editor, isCtrlG, isEscape, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
10
+ import { getEditorTheme, theme } from "../theme/theme";
11
+ import { DynamicBorder } from "./dynamic-border";
12
+
13
+ export class HookEditorComponent extends Container {
14
+ private editor: Editor;
15
+ private onSubmitCallback: (value: string) => void;
16
+ private onCancelCallback: () => void;
17
+ private tui: TUI;
18
+
19
+ constructor(
20
+ tui: TUI,
21
+ title: string,
22
+ prefill: string | undefined,
23
+ onSubmit: (value: string) => void,
24
+ onCancel: () => void,
25
+ ) {
26
+ super();
27
+
28
+ this.tui = tui;
29
+ this.onSubmitCallback = onSubmit;
30
+ this.onCancelCallback = onCancel;
31
+
32
+ // Add top border
33
+ this.addChild(new DynamicBorder());
34
+ this.addChild(new Spacer(1));
35
+
36
+ // Add title
37
+ this.addChild(new Text(theme.fg("accent", title), 1, 0));
38
+ this.addChild(new Spacer(1));
39
+
40
+ // Create editor
41
+ this.editor = new Editor(getEditorTheme());
42
+ if (prefill) {
43
+ this.editor.setText(prefill);
44
+ }
45
+ this.addChild(this.editor);
46
+
47
+ this.addChild(new Spacer(1));
48
+
49
+ // Add hint
50
+ const hasExternalEditor = !!(process.env.VISUAL || process.env.EDITOR);
51
+ const hint = hasExternalEditor
52
+ ? "ctrl+enter submit esc cancel ctrl+g external editor"
53
+ : "ctrl+enter submit esc cancel";
54
+ this.addChild(new Text(theme.fg("dim", hint), 1, 0));
55
+
56
+ this.addChild(new Spacer(1));
57
+
58
+ // Add bottom border
59
+ this.addChild(new DynamicBorder());
60
+ }
61
+
62
+ handleInput(keyData: string): void {
63
+ // Ctrl+Enter to submit
64
+ if (keyData === "\x1b[13;5u" || keyData === "\x1b[27;5;13~") {
65
+ this.onSubmitCallback(this.editor.getText());
66
+ return;
67
+ }
68
+
69
+ // Escape to cancel
70
+ if (isEscape(keyData)) {
71
+ this.onCancelCallback();
72
+ return;
73
+ }
74
+
75
+ // Ctrl+G for external editor
76
+ if (isCtrlG(keyData)) {
77
+ this.openExternalEditor();
78
+ return;
79
+ }
80
+
81
+ // Forward to editor
82
+ this.editor.handleInput(keyData);
83
+ }
84
+
85
+ private openExternalEditor(): void {
86
+ const editorCmd = process.env.VISUAL || process.env.EDITOR;
87
+ if (!editorCmd) {
88
+ return;
89
+ }
90
+
91
+ const currentText = this.editor.getText();
92
+ const tmpFile = path.join(os.tmpdir(), `omp-hook-editor-${Date.now()}.md`);
93
+
94
+ try {
95
+ fs.writeFileSync(tmpFile, currentText, "utf-8");
96
+ this.tui.stop();
97
+
98
+ const [editor, ...editorArgs] = editorCmd.split(" ");
99
+ const result = Bun.spawnSync([editor, ...editorArgs, tmpFile], {
100
+ stdio: ["inherit", "inherit", "inherit"],
101
+ });
102
+
103
+ if (result.exitCode === 0) {
104
+ const newContent = fs.readFileSync(tmpFile, "utf-8").replace(/\n$/, "");
105
+ this.editor.setText(newContent);
106
+ }
107
+ } finally {
108
+ try {
109
+ fs.unlinkSync(tmpFile);
110
+ } catch {
111
+ // Ignore cleanup errors
112
+ }
113
+ this.tui.start();
114
+ this.tui.requestRender();
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Simple text input component for hooks.
3
+ */
4
+
5
+ import { Container, Input, isEnter, isEscape, Spacer, Text } from "@oh-my-pi/pi-tui";
6
+ import { theme } from "../theme/theme";
7
+ import { DynamicBorder } from "./dynamic-border";
8
+
9
+ export class HookInputComponent extends Container {
10
+ private input: Input;
11
+ private onSubmitCallback: (value: string) => void;
12
+ private onCancelCallback: () => void;
13
+
14
+ constructor(
15
+ title: string,
16
+ _placeholder: string | undefined,
17
+ onSubmit: (value: string) => void,
18
+ onCancel: () => void,
19
+ ) {
20
+ super();
21
+
22
+ this.onSubmitCallback = onSubmit;
23
+ this.onCancelCallback = onCancel;
24
+
25
+ // Add top border
26
+ this.addChild(new DynamicBorder());
27
+ this.addChild(new Spacer(1));
28
+
29
+ // Add title
30
+ this.addChild(new Text(theme.fg("accent", title), 1, 0));
31
+ this.addChild(new Spacer(1));
32
+
33
+ // Create input
34
+ this.input = new Input();
35
+ this.addChild(this.input);
36
+
37
+ this.addChild(new Spacer(1));
38
+
39
+ // Add hint
40
+ this.addChild(new Text(theme.fg("dim", "enter submit esc cancel"), 1, 0));
41
+
42
+ this.addChild(new Spacer(1));
43
+
44
+ // Add bottom border
45
+ this.addChild(new DynamicBorder());
46
+ }
47
+
48
+ handleInput(keyData: string): void {
49
+ // Enter
50
+ if (isEnter(keyData) || keyData === "\n") {
51
+ this.onSubmitCallback(this.input.getValue());
52
+ return;
53
+ }
54
+
55
+ // Escape to cancel
56
+ if (isEscape(keyData)) {
57
+ this.onCancelCallback();
58
+ return;
59
+ }
60
+
61
+ // Forward to input
62
+ this.input.handleInput(keyData);
63
+ }
64
+ }
@@ -0,0 +1,96 @@
1
+ import type { TextContent } from "@oh-my-pi/pi-ai";
2
+ import type { Component } from "@oh-my-pi/pi-tui";
3
+ import { Box, Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
4
+ import type { HookMessageRenderer } from "../../../core/hooks/types";
5
+ import type { HookMessage } from "../../../core/messages";
6
+ import { getMarkdownTheme, theme } from "../theme/theme";
7
+
8
+ /**
9
+ * Component that renders a custom message entry from hooks.
10
+ * Uses distinct styling to differentiate from user messages.
11
+ */
12
+ export class HookMessageComponent extends Container {
13
+ private message: HookMessage<unknown>;
14
+ private customRenderer?: HookMessageRenderer;
15
+ private box: Box;
16
+ private customComponent?: Component;
17
+ private _expanded = false;
18
+
19
+ constructor(message: HookMessage<unknown>, customRenderer?: HookMessageRenderer) {
20
+ super();
21
+ this.message = message;
22
+ this.customRenderer = customRenderer;
23
+
24
+ this.addChild(new Spacer(1));
25
+
26
+ // Create box with purple background (used for default rendering)
27
+ this.box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
28
+
29
+ this.rebuild();
30
+ }
31
+
32
+ setExpanded(expanded: boolean): void {
33
+ if (this._expanded !== expanded) {
34
+ this._expanded = expanded;
35
+ this.rebuild();
36
+ }
37
+ }
38
+
39
+ private rebuild(): void {
40
+ // Remove previous content component
41
+ if (this.customComponent) {
42
+ this.removeChild(this.customComponent);
43
+ this.customComponent = undefined;
44
+ }
45
+ this.removeChild(this.box);
46
+
47
+ // Try custom renderer first - it handles its own styling
48
+ if (this.customRenderer) {
49
+ try {
50
+ const component = this.customRenderer(this.message, { expanded: this._expanded }, theme);
51
+ if (component) {
52
+ // Custom renderer provides its own styled component
53
+ this.customComponent = component;
54
+ this.addChild(component);
55
+ return;
56
+ }
57
+ } catch {
58
+ // Fall through to default rendering
59
+ }
60
+ }
61
+
62
+ // Default rendering uses our box
63
+ this.addChild(this.box);
64
+ this.box.clear();
65
+
66
+ // Default rendering: label + content
67
+ const label = theme.fg("customMessageLabel", theme.bold(`[${this.message.customType}]`));
68
+ this.box.addChild(new Text(label, 0, 0));
69
+ this.box.addChild(new Spacer(1));
70
+
71
+ // Extract text content
72
+ let text: string;
73
+ if (typeof this.message.content === "string") {
74
+ text = this.message.content;
75
+ } else {
76
+ text = this.message.content
77
+ .filter((c): c is TextContent => c.type === "text")
78
+ .map((c) => c.text)
79
+ .join("\n");
80
+ }
81
+
82
+ // Limit lines when collapsed
83
+ if (!this._expanded) {
84
+ const lines = text.split("\n");
85
+ if (lines.length > 5) {
86
+ text = `${lines.slice(0, 5).join("\n")}\n${theme.format.ellipsis}`;
87
+ }
88
+ }
89
+
90
+ this.box.addChild(
91
+ new Markdown(text, 0, 0, getMarkdownTheme(), {
92
+ color: (text: string) => theme.fg("customMessageText", text),
93
+ }),
94
+ );
95
+ }
96
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Generic selector component for hooks.
3
+ * Displays a list of string options with keyboard navigation.
4
+ */
5
+
6
+ import { Container, isArrowDown, isArrowUp, isEnter, isEscape, Spacer, Text } from "@oh-my-pi/pi-tui";
7
+ import { theme } from "../theme/theme";
8
+ import { DynamicBorder } from "./dynamic-border";
9
+
10
+ export class HookSelectorComponent extends Container {
11
+ private options: string[];
12
+ private selectedIndex = 0;
13
+ private listContainer: Container;
14
+ private onSelectCallback: (option: string) => void;
15
+ private onCancelCallback: () => void;
16
+
17
+ constructor(title: string, options: string[], onSelect: (option: string) => void, onCancel: () => void) {
18
+ super();
19
+
20
+ this.options = options;
21
+ this.onSelectCallback = onSelect;
22
+ this.onCancelCallback = onCancel;
23
+
24
+ // Add top border
25
+ this.addChild(new DynamicBorder());
26
+ this.addChild(new Spacer(1));
27
+
28
+ // Add title
29
+ this.addChild(new Text(theme.fg("accent", title), 1, 0));
30
+ this.addChild(new Spacer(1));
31
+
32
+ // Create list container
33
+ this.listContainer = new Container();
34
+ this.addChild(this.listContainer);
35
+
36
+ this.addChild(new Spacer(1));
37
+
38
+ // Add hint
39
+ this.addChild(new Text(theme.fg("dim", "↑↓ navigate enter select esc cancel"), 1, 0));
40
+
41
+ this.addChild(new Spacer(1));
42
+
43
+ // Add bottom border
44
+ this.addChild(new DynamicBorder());
45
+
46
+ // Initial render
47
+ this.updateList();
48
+ }
49
+
50
+ private updateList(): void {
51
+ this.listContainer.clear();
52
+
53
+ for (let i = 0; i < this.options.length; i++) {
54
+ const option = this.options[i];
55
+ const isSelected = i === this.selectedIndex;
56
+
57
+ let text = "";
58
+ if (isSelected) {
59
+ text = theme.fg("accent", `${theme.nav.cursor} `) + theme.fg("accent", option);
60
+ } else {
61
+ text = ` ${theme.fg("text", option)}`;
62
+ }
63
+
64
+ this.listContainer.addChild(new Text(text, 1, 0));
65
+ }
66
+ }
67
+
68
+ handleInput(keyData: string): void {
69
+ // Up arrow or k
70
+ if (isArrowUp(keyData) || keyData === "k") {
71
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
72
+ this.updateList();
73
+ }
74
+ // Down arrow or j
75
+ else if (isArrowDown(keyData) || keyData === "j") {
76
+ this.selectedIndex = Math.min(this.options.length - 1, this.selectedIndex + 1);
77
+ this.updateList();
78
+ }
79
+ // Enter
80
+ else if (isEnter(keyData) || keyData === "\n") {
81
+ const selected = this.options[this.selectedIndex];
82
+ if (selected) {
83
+ this.onSelectCallback(selected);
84
+ }
85
+ }
86
+ // Escape
87
+ else if (isEscape(keyData)) {
88
+ this.onCancelCallback();
89
+ }
90
+ }
91
+ }