@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,157 @@
1
+ /**
2
+ * Workflow commands for orchestrating multi-agent workflows.
3
+ *
4
+ * Commands are embedded at build time via Bun's import with { type: "text" }.
5
+ */
6
+
7
+ import * as path from "node:path";
8
+ import { type SlashCommand, slashCommandCapability } from "../../../capability/slash-command";
9
+ import { loadSync } from "../../../discovery";
10
+
11
+ // Embed command markdown files at build time
12
+ import architectPlanMd from "../../../prompts/architect-plan.md" with { type: "text" };
13
+ import implementMd from "../../../prompts/implement.md" with { type: "text" };
14
+ import implementWithCriticMd from "../../../prompts/implement-with-critic.md" with { type: "text" };
15
+
16
+ const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
17
+ { name: "architect-plan.md", content: architectPlanMd },
18
+ { name: "implement-with-critic.md", content: implementWithCriticMd },
19
+ { name: "implement.md", content: implementMd },
20
+ ];
21
+
22
+ /** Workflow command definition */
23
+ export interface WorkflowCommand {
24
+ name: string;
25
+ description: string;
26
+ instructions: string;
27
+ source: "bundled" | "user" | "project";
28
+ filePath: string;
29
+ }
30
+
31
+ /**
32
+ * Parse YAML frontmatter from markdown content.
33
+ */
34
+ function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
35
+ const frontmatter: Record<string, string> = {};
36
+ const normalized = content.replace(/\r\n/g, "\n");
37
+
38
+ if (!normalized.startsWith("---")) {
39
+ return { frontmatter, body: normalized };
40
+ }
41
+
42
+ const endIndex = normalized.indexOf("\n---", 3);
43
+ if (endIndex === -1) {
44
+ return { frontmatter, body: normalized };
45
+ }
46
+
47
+ const frontmatterBlock = normalized.slice(4, endIndex);
48
+ const body = normalized.slice(endIndex + 4).trim();
49
+
50
+ for (const line of frontmatterBlock.split("\n")) {
51
+ const match = line.match(/^([\w-]+):\s*(.*)$/);
52
+ if (match) {
53
+ let value = match[2].trim();
54
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
55
+ value = value.slice(1, -1);
56
+ }
57
+ frontmatter[match[1]] = value;
58
+ }
59
+ }
60
+
61
+ return { frontmatter, body };
62
+ }
63
+
64
+ /** Cache for bundled commands */
65
+ let bundledCommandsCache: WorkflowCommand[] | null = null;
66
+
67
+ /**
68
+ * Load all bundled commands from embedded content.
69
+ */
70
+ export function loadBundledCommands(): WorkflowCommand[] {
71
+ if (bundledCommandsCache !== null) {
72
+ return bundledCommandsCache;
73
+ }
74
+
75
+ const commands: WorkflowCommand[] = [];
76
+
77
+ for (const { name, content } of EMBEDDED_COMMANDS) {
78
+ const { frontmatter, body } = parseFrontmatter(content);
79
+ const cmdName = name.replace(/\.md$/, "");
80
+
81
+ commands.push({
82
+ name: cmdName,
83
+ description: frontmatter.description || "",
84
+ instructions: body,
85
+ source: "bundled",
86
+ filePath: `embedded:${name}`,
87
+ });
88
+ }
89
+
90
+ bundledCommandsCache = commands;
91
+ return commands;
92
+ }
93
+
94
+ /**
95
+ * Discover all available commands.
96
+ *
97
+ * Precedence (highest wins): .omp > .pi > .claude (project before user), then bundled
98
+ */
99
+ export function discoverCommands(cwd: string): WorkflowCommand[] {
100
+ const resolvedCwd = path.resolve(cwd);
101
+
102
+ // Load slash commands from capability API
103
+ const result = loadSync<SlashCommand>(slashCommandCapability.id, { cwd: resolvedCwd });
104
+
105
+ const commands: WorkflowCommand[] = [];
106
+ const seen = new Set<string>();
107
+
108
+ // Convert SlashCommand to WorkflowCommand format
109
+ for (const cmd of result.items) {
110
+ if (seen.has(cmd.name)) continue;
111
+
112
+ const { frontmatter, body } = parseFrontmatter(cmd.content);
113
+
114
+ // Map capability levels to WorkflowCommand source
115
+ const source: "bundled" | "user" | "project" = cmd.level === "native" ? "bundled" : cmd.level;
116
+
117
+ commands.push({
118
+ name: cmd.name,
119
+ description: frontmatter.description || "",
120
+ instructions: body,
121
+ source,
122
+ filePath: cmd.path,
123
+ });
124
+ seen.add(cmd.name);
125
+ }
126
+
127
+ // Add bundled commands if not already present
128
+ for (const cmd of loadBundledCommands()) {
129
+ if (seen.has(cmd.name)) continue;
130
+ commands.push(cmd);
131
+ seen.add(cmd.name);
132
+ }
133
+
134
+ return commands;
135
+ }
136
+
137
+ /**
138
+ * Get a command by name.
139
+ */
140
+ export function getCommand(commands: WorkflowCommand[], name: string): WorkflowCommand | undefined {
141
+ return commands.find((c) => c.name === name);
142
+ }
143
+
144
+ /**
145
+ * Expand command instructions with task input.
146
+ * Replaces $@ with the provided input.
147
+ */
148
+ export function expandCommand(command: WorkflowCommand, input: string): string {
149
+ return command.instructions.replace(/\$@/g, input);
150
+ }
151
+
152
+ /**
153
+ * Clear the bundled commands cache (for testing).
154
+ */
155
+ export function clearBundledCommandsCache(): void {
156
+ bundledCommandsCache = null;
157
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Agent discovery from filesystem.
3
+ *
4
+ * Discovers agent definitions from:
5
+ * - ~/.omp/agent/agents/*.md (user-level, primary)
6
+ * - ~/.pi/agent/agents/*.md (user-level, legacy)
7
+ * - ~/.claude/agents/*.md (user-level, legacy)
8
+ * - .omp/agents/*.md (project-level, primary)
9
+ * - .pi/agents/*.md (project-level, legacy)
10
+ * - .claude/agents/*.md (project-level, legacy)
11
+ *
12
+ * Agent files use markdown with YAML frontmatter.
13
+ */
14
+
15
+ import * as fs from "node:fs";
16
+ import * as path from "node:path";
17
+ import { findAllNearestProjectConfigDirs, getConfigDirs } from "../../../config";
18
+ import { loadBundledAgents } from "./agents";
19
+ import type { AgentDefinition, AgentSource } from "./types";
20
+
21
+ /** Result of agent discovery */
22
+ export interface DiscoveryResult {
23
+ agents: AgentDefinition[];
24
+ projectAgentsDir: string | null;
25
+ }
26
+
27
+ /**
28
+ * Parse YAML frontmatter from markdown content.
29
+ */
30
+ function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
31
+ const frontmatter: Record<string, string> = {};
32
+ const normalized = content.replace(/\r\n/g, "\n");
33
+
34
+ if (!normalized.startsWith("---")) {
35
+ return { frontmatter, body: normalized };
36
+ }
37
+
38
+ const endIndex = normalized.indexOf("\n---", 3);
39
+ if (endIndex === -1) {
40
+ return { frontmatter, body: normalized };
41
+ }
42
+
43
+ const frontmatterBlock = normalized.slice(4, endIndex);
44
+ const body = normalized.slice(endIndex + 4).trim();
45
+
46
+ for (const line of frontmatterBlock.split("\n")) {
47
+ const match = line.match(/^([\w-]+):\s*(.*)$/);
48
+ if (match) {
49
+ let value = match[2].trim();
50
+ // Strip quotes
51
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
52
+ value = value.slice(1, -1);
53
+ }
54
+ frontmatter[match[1]] = value;
55
+ }
56
+ }
57
+
58
+ return { frontmatter, body };
59
+ }
60
+
61
+ /**
62
+ * Load agents from a directory.
63
+ */
64
+ function loadAgentsFromDir(dir: string, source: AgentSource): AgentDefinition[] {
65
+ const agents: AgentDefinition[] = [];
66
+
67
+ if (!fs.existsSync(dir)) {
68
+ return agents;
69
+ }
70
+
71
+ let entries: fs.Dirent[];
72
+ try {
73
+ entries = fs.readdirSync(dir, { withFileTypes: true });
74
+ } catch {
75
+ return agents;
76
+ }
77
+
78
+ for (const entry of entries) {
79
+ if (!entry.name.endsWith(".md")) continue;
80
+
81
+ const filePath = path.resolve(dir, entry.name);
82
+
83
+ // Handle both regular files and symlinks
84
+ try {
85
+ if (!fs.statSync(filePath).isFile()) continue;
86
+ } catch {
87
+ continue;
88
+ }
89
+
90
+ let content: string;
91
+ try {
92
+ content = fs.readFileSync(filePath, "utf-8");
93
+ } catch {
94
+ continue;
95
+ }
96
+
97
+ const { frontmatter, body } = parseFrontmatter(content);
98
+
99
+ // Require name and description
100
+ if (!frontmatter.name || !frontmatter.description) {
101
+ continue;
102
+ }
103
+
104
+ const tools = frontmatter.tools
105
+ ?.split(",")
106
+ .map((t) => t.trim())
107
+ .filter(Boolean);
108
+
109
+ // Parse spawns field
110
+ let spawns: string[] | "*" | undefined;
111
+ if (frontmatter.spawns !== undefined) {
112
+ const spawnsRaw = frontmatter.spawns.trim();
113
+ if (spawnsRaw === "*") {
114
+ spawns = "*";
115
+ } else if (spawnsRaw) {
116
+ spawns = spawnsRaw
117
+ .split(",")
118
+ .map((s) => s.trim())
119
+ .filter(Boolean);
120
+ if (spawns.length === 0) spawns = undefined;
121
+ }
122
+ }
123
+
124
+ // Backward compat: infer spawns: "*" when tools includes "task"
125
+ if (spawns === undefined && tools?.includes("task")) {
126
+ spawns = "*";
127
+ }
128
+
129
+ const recursive =
130
+ frontmatter.recursive === undefined
131
+ ? undefined
132
+ : frontmatter.recursive === "true" || frontmatter.recursive === "1";
133
+
134
+ agents.push({
135
+ name: frontmatter.name,
136
+ description: frontmatter.description,
137
+ tools: tools && tools.length > 0 ? tools : undefined,
138
+ spawns,
139
+ model: frontmatter.model,
140
+ recursive,
141
+ systemPrompt: body,
142
+ source,
143
+ filePath,
144
+ });
145
+ }
146
+
147
+ return agents;
148
+ }
149
+
150
+ /**
151
+ * Discover agents from filesystem and merge with bundled agents.
152
+ *
153
+ * Precedence (highest wins): .omp > .pi > .claude (project before user), then bundled
154
+ *
155
+ * @param cwd - Current working directory for project agent discovery
156
+ */
157
+ export function discoverAgents(cwd: string): DiscoveryResult {
158
+ const resolvedCwd = path.resolve(cwd);
159
+ const agentSources = Array.from(new Set(getConfigDirs("", { project: false }).map((entry) => entry.source)));
160
+
161
+ // Get user directories (priority order: .omp, .pi, .claude, ...)
162
+ const userDirs = getConfigDirs("agents", { project: false })
163
+ .filter((entry) => agentSources.includes(entry.source))
164
+ .map((entry) => ({
165
+ ...entry,
166
+ path: path.resolve(entry.path),
167
+ }));
168
+
169
+ // Get project directories by walking up from cwd (priority order)
170
+ const projectDirs = findAllNearestProjectConfigDirs("agents", resolvedCwd)
171
+ .filter((entry) => agentSources.includes(entry.source))
172
+ .map((entry) => ({
173
+ ...entry,
174
+ path: path.resolve(entry.path),
175
+ }));
176
+
177
+ const orderedSources = agentSources.filter(
178
+ (source) =>
179
+ userDirs.some((entry) => entry.source === source) || projectDirs.some((entry) => entry.source === source),
180
+ );
181
+
182
+ const orderedDirs: Array<{ dir: string; source: AgentSource }> = [];
183
+ for (const source of orderedSources) {
184
+ const project = projectDirs.find((entry) => entry.source === source);
185
+ if (project) orderedDirs.push({ dir: project.path, source: "project" });
186
+ const user = userDirs.find((entry) => entry.source === source);
187
+ if (user) orderedDirs.push({ dir: user.path, source: "user" });
188
+ }
189
+
190
+ const agents: AgentDefinition[] = [];
191
+ const seen = new Set<string>();
192
+
193
+ for (const { dir, source } of orderedDirs) {
194
+ for (const agent of loadAgentsFromDir(dir, source)) {
195
+ if (seen.has(agent.name)) continue;
196
+ agents.push(agent);
197
+ seen.add(agent.name);
198
+ }
199
+ }
200
+
201
+ for (const agent of loadBundledAgents()) {
202
+ if (seen.has(agent.name)) continue;
203
+ agents.push(agent);
204
+ seen.add(agent.name);
205
+ }
206
+
207
+ const projectAgentsDir = projectDirs.length > 0 ? projectDirs[0].path : null;
208
+
209
+ return { agents, projectAgentsDir };
210
+ }
211
+
212
+ /**
213
+ * Get an agent by name from discovered agents.
214
+ */
215
+ export function getAgent(agents: AgentDefinition[], name: string): AgentDefinition | undefined {
216
+ return agents.find((a) => a.name === name);
217
+ }