@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,193 @@
1
+ /**
2
+ * Ask Tool - Interactive user prompting during execution
3
+ *
4
+ * Use this tool when you need to ask the user questions during execution.
5
+ * This allows you to:
6
+ * 1. Gather user preferences or requirements
7
+ * 2. Clarify ambiguous instructions
8
+ * 3. Get decisions on implementation choices as you work
9
+ * 4. Offer choices to the user about what direction to take
10
+ *
11
+ * Usage notes:
12
+ * - Users will always be able to select "Other" to provide custom text input
13
+ * - Use multi: true to allow multiple answers to be selected for a question
14
+ * - If you recommend a specific option, make that the first option in the list
15
+ * and add "(Recommended)" at the end of the label
16
+ */
17
+
18
+ import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
19
+ import { Type } from "@sinclair/typebox";
20
+ import { theme } from "../../modes/interactive/theme/theme";
21
+ import askDescription from "../../prompts/tools/ask.md" with { type: "text" };
22
+
23
+ // =============================================================================
24
+ // Types
25
+ // =============================================================================
26
+
27
+ const OptionItem = Type.Object({
28
+ label: Type.String({ description: "Display label for this option" }),
29
+ });
30
+
31
+ const askSchema = Type.Object({
32
+ question: Type.String({ description: "The question to ask the user" }),
33
+ options: Type.Array(OptionItem, {
34
+ description: "Available options for the user to choose from.",
35
+ minItems: 1,
36
+ }),
37
+ multi: Type.Optional(
38
+ Type.Boolean({
39
+ description: "Allow multiple options to be selected (default: false)",
40
+ default: false,
41
+ }),
42
+ ),
43
+ });
44
+
45
+ export interface AskToolDetails {
46
+ question: string;
47
+ options: string[];
48
+ multi: boolean;
49
+ selectedOptions: string[];
50
+ customInput?: string;
51
+ }
52
+
53
+ // =============================================================================
54
+ // Constants
55
+ // =============================================================================
56
+
57
+ const OTHER_OPTION = "Other (type your own)";
58
+ function getDoneOptionLabel(): string {
59
+ return `${theme.status.success} Done selecting`;
60
+ }
61
+
62
+ // =============================================================================
63
+ // Tool Implementation
64
+ // =============================================================================
65
+
66
+ export function createAskTool(_cwd: string): AgentTool<typeof askSchema, AskToolDetails> {
67
+ return {
68
+ name: "ask",
69
+ label: "Ask",
70
+ description: askDescription,
71
+ parameters: askSchema,
72
+
73
+ async execute(
74
+ _toolCallId: string,
75
+ params: { question: string; options: Array<{ label: string }>; multi?: boolean },
76
+ _signal?: AbortSignal,
77
+ _onUpdate?: AgentToolUpdateCallback<AskToolDetails>,
78
+ context?: AgentToolContext,
79
+ ) {
80
+ const { question, options, multi = false } = params;
81
+ const optionLabels = options.map((o) => o.label);
82
+ const doneLabel = getDoneOptionLabel();
83
+
84
+ // Headless fallback - return error if no UI available
85
+ if (!context?.hasUI || !context.ui) {
86
+ return {
87
+ content: [
88
+ {
89
+ type: "text" as const,
90
+ text: "Error: User prompt requires interactive mode",
91
+ },
92
+ ],
93
+ details: {
94
+ question,
95
+ options: optionLabels,
96
+ multi,
97
+ selectedOptions: [],
98
+ },
99
+ };
100
+ }
101
+
102
+ const { ui } = context;
103
+ let selectedOptions: string[] = [];
104
+ let customInput: string | undefined;
105
+
106
+ if (multi) {
107
+ // Multi-select: show checkboxes in the label to indicate selection state
108
+ const selected = new Set<string>();
109
+
110
+ while (true) {
111
+ // Build options with checkbox indicators
112
+ const opts: string[] = [];
113
+
114
+ // Add "Done" option if any selected
115
+ if (selected.size > 0) {
116
+ opts.push(doneLabel);
117
+ }
118
+
119
+ // Add all options with checkbox prefix
120
+ for (const opt of optionLabels) {
121
+ const checkbox = selected.has(opt) ? theme.checkbox.checked : theme.checkbox.unchecked;
122
+ opts.push(`${checkbox} ${opt}`);
123
+ }
124
+
125
+ // Add "Other" option
126
+ opts.push(OTHER_OPTION);
127
+
128
+ const prefix = selected.size > 0 ? `(${selected.size} selected) ` : "";
129
+ const choice = await ui.select(`${prefix}${question}`, opts);
130
+
131
+ if (choice === undefined || choice === doneLabel) break;
132
+
133
+ if (choice === OTHER_OPTION) {
134
+ const input = await ui.input("Enter your response:");
135
+ if (input) customInput = input;
136
+ break;
137
+ }
138
+
139
+ // Toggle selection - extract the actual option name
140
+ const checkedPrefix = `${theme.checkbox.checked} `;
141
+ const uncheckedPrefix = `${theme.checkbox.unchecked} `;
142
+ let opt: string | undefined;
143
+ if (choice.startsWith(checkedPrefix)) {
144
+ opt = choice.slice(checkedPrefix.length);
145
+ } else if (choice.startsWith(uncheckedPrefix)) {
146
+ opt = choice.slice(uncheckedPrefix.length);
147
+ }
148
+ if (opt) {
149
+ if (selected.has(opt)) {
150
+ selected.delete(opt);
151
+ } else {
152
+ selected.add(opt);
153
+ }
154
+ }
155
+ }
156
+ selectedOptions = Array.from(selected);
157
+ } else {
158
+ // Single select with "Other" option
159
+ const choice = await ui.select(question, [...optionLabels, OTHER_OPTION]);
160
+ if (choice === OTHER_OPTION) {
161
+ const input = await ui.input("Enter your response:");
162
+ if (input) customInput = input;
163
+ } else if (choice) {
164
+ selectedOptions = [choice];
165
+ }
166
+ }
167
+
168
+ const details: AskToolDetails = {
169
+ question,
170
+ options: optionLabels,
171
+ multi,
172
+ selectedOptions,
173
+ customInput,
174
+ };
175
+
176
+ let responseText: string;
177
+ if (customInput) {
178
+ responseText = `User provided custom input: ${customInput}`;
179
+ } else if (selectedOptions.length > 0) {
180
+ responseText = multi
181
+ ? `User selected: ${selectedOptions.join(", ")}`
182
+ : `User selected: ${selectedOptions[0]}`;
183
+ } else {
184
+ responseText = "User cancelled the selection";
185
+ }
186
+
187
+ return { content: [{ type: "text" as const, text: responseText }], details };
188
+ },
189
+ };
190
+ }
191
+
192
+ /** Default ask tool using process.cwd() - for backwards compatibility (no UI) */
193
+ export const askTool = createAskTool(process.cwd());
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Bash intent interceptor - redirects common shell patterns to proper tools.
3
+ *
4
+ * When an LLM calls bash with patterns like `grep`, `cat`, `find`, etc.,
5
+ * this interceptor provides helpful error messages directing them to use
6
+ * the specialized tools instead.
7
+ */
8
+
9
+ export interface InterceptionResult {
10
+ /** If true, the bash command should be blocked */
11
+ block: boolean;
12
+ /** Error message to return instead of executing */
13
+ message?: string;
14
+ /** Suggested tool to use instead */
15
+ suggestedTool?: string;
16
+ }
17
+
18
+ /**
19
+ * Patterns that should NEVER use bash when specialized tools exist.
20
+ * Each pattern maps to a helpful error message.
21
+ */
22
+ const forbiddenPatterns: Array<{
23
+ pattern: RegExp;
24
+ tool: string;
25
+ message: string;
26
+ }> = [
27
+ // File reading
28
+ {
29
+ pattern: /^\s*(cat|head|tail|less|more)\s+/,
30
+ tool: "read",
31
+ message: "Use the `read` tool instead of cat/head/tail. It provides better context and handles binary files.",
32
+ },
33
+ // Content search (grep variants)
34
+ {
35
+ pattern: /^\s*(grep|rg|ripgrep|ag|ack)\s+/,
36
+ tool: "grep",
37
+ message: "Use the `grep` tool instead of grep/rg. It respects .gitignore and provides structured output.",
38
+ },
39
+ // File finding
40
+ {
41
+ pattern: /^\s*(find|fd|locate)\s+.*(-name|-iname|-type|--type|-glob)/,
42
+ tool: "find",
43
+ message: "Use the `find` tool instead of find/fd. It respects .gitignore and is faster for glob patterns.",
44
+ },
45
+ // In-place file editing
46
+ {
47
+ pattern: /^\s*sed\s+(-i|--in-place)/,
48
+ tool: "edit",
49
+ message: "Use the `edit` tool instead of sed -i. It provides diff preview and fuzzy matching.",
50
+ },
51
+ {
52
+ pattern: /^\s*perl\s+.*-[pn]?i/,
53
+ tool: "edit",
54
+ message: "Use the `edit` tool instead of perl -i. It provides diff preview and fuzzy matching.",
55
+ },
56
+ {
57
+ pattern: /^\s*awk\s+.*-i\s+inplace/,
58
+ tool: "edit",
59
+ message: "Use the `edit` tool instead of awk -i inplace. It provides diff preview and fuzzy matching.",
60
+ },
61
+ // File creation via redirection (but allow legitimate uses like piping)
62
+ {
63
+ pattern: /^\s*(echo|printf|cat\s*<<)\s+.*[^|]>\s*\S/,
64
+ tool: "write",
65
+ message: "Use the `write` tool instead of echo/cat redirection. It handles encoding and provides confirmation.",
66
+ },
67
+ ];
68
+
69
+ /**
70
+ * Check if a bash command should be intercepted.
71
+ *
72
+ * @param command The bash command to check
73
+ * @param availableTools Set of tool names that are available
74
+ * @returns InterceptionResult indicating if the command should be blocked
75
+ */
76
+ export function checkBashInterception(command: string, availableTools: Set<string>): InterceptionResult {
77
+ // Normalize command for pattern matching
78
+ const normalizedCommand = command.trim();
79
+
80
+ for (const { pattern, tool, message } of forbiddenPatterns) {
81
+ // Only block if the suggested tool is actually available
82
+ if (!availableTools.has(tool)) {
83
+ continue;
84
+ }
85
+
86
+ if (pattern.test(normalizedCommand)) {
87
+ return {
88
+ block: true,
89
+ message: `❌ Blocked: ${message}\n\nOriginal command: ${command}`,
90
+ suggestedTool: tool,
91
+ };
92
+ }
93
+ }
94
+
95
+ return { block: false };
96
+ }
97
+
98
+ /**
99
+ * Check if a command is a simple directory listing that should use `ls` tool.
100
+ * Only applies to bare `ls` without complex flags.
101
+ */
102
+ export function checkSimpleLsInterception(command: string, availableTools: Set<string>): InterceptionResult {
103
+ if (!availableTools.has("ls")) {
104
+ return { block: false };
105
+ }
106
+
107
+ // Match simple ls commands (ls, ls -la, ls /path, etc.)
108
+ // Don't intercept complex pipes or commands
109
+ const simpleLsPattern = /^\s*ls(\s+(-[a-zA-Z]+\s*)*)?(\s+[^|;&]+)?\s*$/;
110
+
111
+ if (simpleLsPattern.test(command.trim())) {
112
+ return {
113
+ block: true,
114
+ message: `Use the \`ls\` tool instead of bash ls. It provides structured output.\n\nOriginal command: ${command}`,
115
+ suggestedTool: "ls",
116
+ };
117
+ }
118
+
119
+ return { block: false };
120
+ }
@@ -0,0 +1,91 @@
1
+ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
+ import { Type } from "@sinclair/typebox";
3
+ import bashDescription from "../../prompts/tools/bash.md" with { type: "text" };
4
+ import { executeBash } from "../bash-executor";
5
+ import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateTail } from "./truncate";
6
+
7
+ const bashSchema = Type.Object({
8
+ command: Type.String({ description: "Bash command to execute" }),
9
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
10
+ });
11
+
12
+ export interface BashToolDetails {
13
+ truncation?: TruncationResult;
14
+ fullOutputPath?: string;
15
+ }
16
+
17
+ export function createBashTool(cwd: string): AgentTool<typeof bashSchema> {
18
+ return {
19
+ name: "bash",
20
+ label: "Bash",
21
+ description: bashDescription,
22
+ parameters: bashSchema,
23
+ execute: async (
24
+ _toolCallId: string,
25
+ { command, timeout }: { command: string; timeout?: number },
26
+ signal?: AbortSignal,
27
+ onUpdate?,
28
+ ) => {
29
+ // Track output for streaming updates
30
+ let currentOutput = "";
31
+
32
+ const result = await executeBash(command, {
33
+ cwd,
34
+ timeout: timeout ? timeout * 1000 : undefined, // Convert to milliseconds
35
+ signal,
36
+ onChunk: (chunk) => {
37
+ currentOutput += chunk;
38
+ if (onUpdate) {
39
+ const truncation = truncateTail(currentOutput);
40
+ onUpdate({
41
+ content: [{ type: "text", text: truncation.content || "" }],
42
+ details: {
43
+ truncation: truncation.truncated ? truncation : undefined,
44
+ },
45
+ });
46
+ }
47
+ },
48
+ });
49
+
50
+ // Handle errors
51
+ if (result.cancelled) {
52
+ throw new Error(result.output || "Command aborted");
53
+ }
54
+
55
+ // Apply tail truncation for final output
56
+ const truncation = truncateTail(result.output);
57
+ let outputText = truncation.content || "(no output)";
58
+
59
+ let details: BashToolDetails | undefined;
60
+
61
+ if (truncation.truncated) {
62
+ details = {
63
+ truncation,
64
+ fullOutputPath: result.fullOutputPath,
65
+ };
66
+
67
+ const startLine = truncation.totalLines - truncation.outputLines + 1;
68
+ const endLine = truncation.totalLines;
69
+
70
+ if (truncation.lastLinePartial) {
71
+ const lastLineSize = formatSize(Buffer.byteLength(result.output.split("\n").pop() || "", "utf-8"));
72
+ outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${result.fullOutputPath}]`;
73
+ } else if (truncation.truncatedBy === "lines") {
74
+ outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${result.fullOutputPath}]`;
75
+ } else {
76
+ outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${result.fullOutputPath}]`;
77
+ }
78
+ }
79
+
80
+ if (result.exitCode !== 0 && result.exitCode !== undefined) {
81
+ outputText += `\n\nCommand exited with code ${result.exitCode}`;
82
+ throw new Error(outputText);
83
+ }
84
+
85
+ return { content: [{ type: "text", text: outputText }], details };
86
+ },
87
+ };
88
+ }
89
+
90
+ /** Default bash tool using process.cwd() - for backwards compatibility */
91
+ export const bashTool = createBashTool(process.cwd());
@@ -0,0 +1,32 @@
1
+ import type { AgentToolContext } from "@oh-my-pi/pi-agent-core";
2
+ import type { CustomToolContext } from "../custom-tools/types";
3
+ import type { HookUIContext } from "../hooks/types";
4
+
5
+ declare module "@oh-my-pi/pi-agent-core" {
6
+ interface AgentToolContext extends CustomToolContext {
7
+ ui?: HookUIContext;
8
+ hasUI?: boolean;
9
+ }
10
+ }
11
+
12
+ export interface ToolContextStore {
13
+ getContext(): AgentToolContext;
14
+ setUIContext(uiContext: HookUIContext, hasUI: boolean): void;
15
+ }
16
+
17
+ export function createToolContextStore(getBaseContext: () => CustomToolContext): ToolContextStore {
18
+ let uiContext: HookUIContext | undefined;
19
+ let hasUI = false;
20
+
21
+ return {
22
+ getContext: () => ({
23
+ ...getBaseContext(),
24
+ ui: uiContext,
25
+ hasUI,
26
+ }),
27
+ setUIContext: (context, uiAvailable) => {
28
+ uiContext = context;
29
+ hasUI = uiAvailable;
30
+ },
31
+ };
32
+ }