@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,274 @@
1
+ /**
2
+ * Update CLI command handler.
3
+ *
4
+ * Handles `omp update` to check for and install updates.
5
+ * Uses bun if available, otherwise downloads binary from GitHub releases.
6
+ */
7
+
8
+ import { execSync, spawnSync } from "node:child_process";
9
+ import { createWriteStream, existsSync, renameSync, unlinkSync } from "node:fs";
10
+ import { dirname } from "node:path";
11
+ import { Readable } from "node:stream";
12
+ import { pipeline } from "node:stream/promises";
13
+ import chalk from "chalk";
14
+ import { APP_NAME, VERSION } from "../config";
15
+ import { theme } from "../modes/interactive/theme/theme";
16
+
17
+ /**
18
+ * Detect if we're running as a Bun compiled binary.
19
+ */
20
+ const isBunBinary =
21
+ import.meta.url.includes("$bunfs") || import.meta.url.includes("~BUN") || import.meta.url.includes("%7EBUN");
22
+
23
+ const REPO = "can1357/oh-my-pi";
24
+ const PACKAGE = "@oh-my-pi/pi-coding-agent";
25
+
26
+ interface ReleaseInfo {
27
+ tag: string;
28
+ version: string;
29
+ assets: Array<{ name: string; url: string }>;
30
+ }
31
+
32
+ /**
33
+ * Parse update subcommand arguments.
34
+ * Returns undefined if not an update command.
35
+ */
36
+ export function parseUpdateArgs(args: string[]): { force: boolean; check: boolean } | undefined {
37
+ if (args.length === 0 || args[0] !== "update") {
38
+ return undefined;
39
+ }
40
+
41
+ return {
42
+ force: args.includes("--force") || args.includes("-f"),
43
+ check: args.includes("--check") || args.includes("-c"),
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Check if bun is available in PATH.
49
+ */
50
+ function hasBun(): boolean {
51
+ try {
52
+ const result = spawnSync("bun", ["--version"], { encoding: "utf-8", stdio: "pipe" });
53
+ return result.status === 0;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Get the latest release info from GitHub.
61
+ */
62
+ async function getLatestRelease(): Promise<ReleaseInfo> {
63
+ const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`);
64
+ if (!response.ok) {
65
+ throw new Error(`Failed to fetch release info: ${response.statusText}`);
66
+ }
67
+
68
+ const data = (await response.json()) as {
69
+ tag_name: string;
70
+ assets: Array<{ name: string; browser_download_url: string }>;
71
+ };
72
+
73
+ return {
74
+ tag: data.tag_name,
75
+ version: data.tag_name.replace(/^v/, ""),
76
+ assets: data.assets.map((a) => ({ name: a.name, url: a.browser_download_url })),
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Compare semver versions. Returns:
82
+ * - negative if a < b
83
+ * - 0 if a == b
84
+ * - positive if a > b
85
+ */
86
+ function compareVersions(a: string, b: string): number {
87
+ const pa = a.split(".").map(Number);
88
+ const pb = b.split(".").map(Number);
89
+
90
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
91
+ const na = pa[i] || 0;
92
+ const nb = pb[i] || 0;
93
+ if (na !== nb) return na - nb;
94
+ }
95
+ return 0;
96
+ }
97
+
98
+ /**
99
+ * Get the appropriate binary name for this platform.
100
+ */
101
+ function getBinaryName(): string {
102
+ const platform = process.platform;
103
+ const arch = process.arch;
104
+
105
+ let os: string;
106
+ switch (platform) {
107
+ case "linux":
108
+ os = "linux";
109
+ break;
110
+ case "darwin":
111
+ os = "darwin";
112
+ break;
113
+ case "win32":
114
+ os = "windows";
115
+ break;
116
+ default:
117
+ throw new Error(`Unsupported platform: ${platform}`);
118
+ }
119
+
120
+ let archName: string;
121
+ switch (arch) {
122
+ case "x64":
123
+ archName = "x64";
124
+ break;
125
+ case "arm64":
126
+ archName = "arm64";
127
+ break;
128
+ default:
129
+ throw new Error(`Unsupported architecture: ${arch}`);
130
+ }
131
+
132
+ if (os === "windows") {
133
+ return `${APP_NAME}-${os}-${archName}.exe`;
134
+ }
135
+ return `${APP_NAME}-${os}-${archName}`;
136
+ }
137
+
138
+ /**
139
+ * Update via bun package manager.
140
+ */
141
+ async function updateViaBun(): Promise<void> {
142
+ console.log(chalk.dim("Updating via bun..."));
143
+
144
+ try {
145
+ execSync(`bun update -g ${PACKAGE}`, { stdio: "inherit" });
146
+ console.log(chalk.green(`\n${theme.status.success} Update complete`));
147
+ } catch {
148
+ throw new Error("bun update failed");
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Update by downloading binary from GitHub releases.
154
+ */
155
+ async function updateViaBinary(release: ReleaseInfo): Promise<void> {
156
+ const binaryName = getBinaryName();
157
+ const asset = release.assets.find((a) => a.name === binaryName);
158
+
159
+ if (!asset) {
160
+ throw new Error(`No binary found for ${binaryName}`);
161
+ }
162
+
163
+ const execPath = process.execPath;
164
+ const _execDir = dirname(execPath);
165
+ const tempPath = `${execPath}.new`;
166
+ const backupPath = `${execPath}.bak`;
167
+
168
+ console.log(chalk.dim(`Downloading ${binaryName}...`));
169
+
170
+ // Download to temp file
171
+ const response = await fetch(asset.url, { redirect: "follow" });
172
+ if (!response.ok || !response.body) {
173
+ throw new Error(`Download failed: ${response.statusText}`);
174
+ }
175
+
176
+ const fileStream = createWriteStream(tempPath, { mode: 0o755 });
177
+ const nodeStream = Readable.fromWeb(response.body as import("stream/web").ReadableStream);
178
+ await pipeline(nodeStream, fileStream);
179
+
180
+ // Replace current binary
181
+ console.log(chalk.dim("Installing update..."));
182
+
183
+ try {
184
+ // Backup current binary
185
+ if (existsSync(backupPath)) {
186
+ unlinkSync(backupPath);
187
+ }
188
+ renameSync(execPath, backupPath);
189
+
190
+ // Move new binary into place
191
+ renameSync(tempPath, execPath);
192
+
193
+ // Clean up backup
194
+ unlinkSync(backupPath);
195
+
196
+ console.log(chalk.green(`\n${theme.status.success} Updated to ${release.version}`));
197
+ console.log(chalk.dim(`Restart ${APP_NAME} to use the new version`));
198
+ } catch (err) {
199
+ // Restore from backup if possible
200
+ if (existsSync(backupPath) && !existsSync(execPath)) {
201
+ renameSync(backupPath, execPath);
202
+ }
203
+ if (existsSync(tempPath)) {
204
+ unlinkSync(tempPath);
205
+ }
206
+ throw err;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Run the update command.
212
+ */
213
+ export async function runUpdateCommand(opts: { force: boolean; check: boolean }): Promise<void> {
214
+ console.log(chalk.dim(`Current version: ${VERSION}`));
215
+
216
+ // Check for updates
217
+ let release: ReleaseInfo;
218
+ try {
219
+ release = await getLatestRelease();
220
+ } catch (err) {
221
+ console.error(chalk.red(`Failed to check for updates: ${err}`));
222
+ process.exit(1);
223
+ }
224
+
225
+ const comparison = compareVersions(release.version, VERSION);
226
+
227
+ if (comparison <= 0 && !opts.force) {
228
+ console.log(chalk.green(`${theme.status.success} Already up to date`));
229
+ return;
230
+ }
231
+
232
+ if (comparison > 0) {
233
+ console.log(chalk.cyan(`New version available: ${release.version}`));
234
+ } else {
235
+ console.log(chalk.yellow(`Forcing reinstall of ${release.version}`));
236
+ }
237
+
238
+ if (opts.check) {
239
+ // Just check, don't install
240
+ return;
241
+ }
242
+
243
+ // Choose update method
244
+ try {
245
+ if (!isBunBinary && hasBun()) {
246
+ await updateViaBun();
247
+ } else {
248
+ await updateViaBinary(release);
249
+ }
250
+ } catch (err) {
251
+ console.error(chalk.red(`Update failed: ${err}`));
252
+ process.exit(1);
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Print update command help.
258
+ */
259
+ export function printUpdateHelp(): void {
260
+ console.log(`${chalk.bold(`${APP_NAME} update`)} - Check for and install updates
261
+
262
+ ${chalk.bold("Usage:")}
263
+ ${APP_NAME} update [options]
264
+
265
+ ${chalk.bold("Options:")}
266
+ -c, --check Check for updates without installing
267
+ -f, --force Force reinstall even if up to date
268
+
269
+ ${chalk.bold("Examples:")}
270
+ ${APP_NAME} update Update to latest version
271
+ ${APP_NAME} update --check Check if updates are available
272
+ ${APP_NAME} update --force Force reinstall
273
+ `);
274
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI entry point for the refactored coding agent.
4
+ * Uses main.ts with AgentSession and new mode modules.
5
+ *
6
+ * Test with: npx tsx src/cli-new.ts [args...]
7
+ */
8
+ import { main } from "./main";
9
+
10
+ main(process.argv.slice(2));
package/src/config.ts ADDED
@@ -0,0 +1,391 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+
5
+ // Embed package.json at build time for config
6
+ import packageJson from "../package.json" with { type: "json" };
7
+
8
+ // =============================================================================
9
+ // App Config (from embedded package.json)
10
+ // =============================================================================
11
+
12
+ export const APP_NAME: string = (packageJson as { ompConfig?: { name?: string } }).ompConfig?.name || "omp";
13
+ export const CONFIG_DIR_NAME: string =
14
+ (packageJson as { ompConfig?: { configDir?: string } }).ompConfig?.configDir || ".omp";
15
+ export const VERSION: string = (packageJson as { version: string }).version;
16
+
17
+ const priorityList = [
18
+ { dir: ".omp", globalAgentDir: ".omp/agent" },
19
+ { dir: ".pi", globalAgentDir: ".pi/agent" },
20
+ { dir: ".claude" },
21
+ { dir: ".codex" },
22
+ { dir: ".gemini" },
23
+ ];
24
+
25
+ // e.g., OMP_CODING_AGENT_DIR
26
+ export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
27
+
28
+ // =============================================================================
29
+ // Package Directory (for optional external docs/examples)
30
+ // =============================================================================
31
+
32
+ /**
33
+ * Get the base directory for resolving optional package assets (docs, examples).
34
+ * Walk up from import.meta.dir until we find package.json, or fall back to cwd.
35
+ */
36
+ export function getPackageDir(): string {
37
+ let dir = import.meta.dir;
38
+ while (dir !== dirname(dir)) {
39
+ if (existsSync(join(dir, "package.json"))) {
40
+ return dir;
41
+ }
42
+ dir = dirname(dir);
43
+ }
44
+ // Fallback to cwd (docs/examples won't be found, but that's fine)
45
+ return process.cwd();
46
+ }
47
+
48
+ /** Get path to README.md (optional, may not exist in binary) */
49
+ export function getReadmePath(): string {
50
+ return resolve(join(getPackageDir(), "README.md"));
51
+ }
52
+
53
+ /** Get path to docs directory (optional, may not exist in binary) */
54
+ export function getDocsPath(): string {
55
+ return resolve(join(getPackageDir(), "docs"));
56
+ }
57
+
58
+ /** Get path to examples directory (optional, may not exist in binary) */
59
+ export function getExamplesPath(): string {
60
+ return resolve(join(getPackageDir(), "examples"));
61
+ }
62
+
63
+ /** Get path to CHANGELOG.md (optional, may not exist in binary) */
64
+ export function getChangelogPath(): string {
65
+ return resolve(join(getPackageDir(), "CHANGELOG.md"));
66
+ }
67
+
68
+ // =============================================================================
69
+ // User Config Paths (~/.omp/agent/*)
70
+ // =============================================================================
71
+
72
+ /** Get the agent config directory (e.g., ~/.omp/agent/) */
73
+ export function getAgentDir(): string {
74
+ return process.env[ENV_AGENT_DIR] || join(homedir(), CONFIG_DIR_NAME, "agent");
75
+ }
76
+
77
+ /** Get path to user's custom themes directory */
78
+ export function getCustomThemesDir(): string {
79
+ return join(getAgentDir(), "themes");
80
+ }
81
+
82
+ /** Get path to models.json */
83
+ export function getModelsPath(): string {
84
+ return join(getAgentDir(), "models.json");
85
+ }
86
+
87
+ /** Get path to auth.json */
88
+ export function getAuthPath(): string {
89
+ return join(getAgentDir(), "auth.json");
90
+ }
91
+
92
+ /** Get path to settings.json */
93
+ export function getSettingsPath(): string {
94
+ return join(getAgentDir(), "settings.json");
95
+ }
96
+
97
+ /** Get path to tools directory */
98
+ export function getToolsDir(): string {
99
+ return join(getAgentDir(), "tools");
100
+ }
101
+
102
+ /** Get path to slash commands directory */
103
+ export function getCommandsDir(): string {
104
+ return join(getAgentDir(), "commands");
105
+ }
106
+
107
+ /** Get path to sessions directory */
108
+ export function getSessionsDir(): string {
109
+ return join(getAgentDir(), "sessions");
110
+ }
111
+
112
+ /** Get path to debug log file */
113
+ export function getDebugLogPath(): string {
114
+ return join(getAgentDir(), `${APP_NAME}-debug.log`);
115
+ }
116
+
117
+ // =============================================================================
118
+ // Multi-Config Directory Helpers
119
+ // =============================================================================
120
+
121
+ /**
122
+ * Config directory bases in priority order (highest first).
123
+ * User-level: ~/.omp/agent, ~/.pi/agent, ~/.claude, ~/.codex, ~/.gemini
124
+ * Project-level: .omp, .pi, .claude, .codex, .gemini
125
+ */
126
+ const USER_CONFIG_BASES = priorityList.map(({ dir, globalAgentDir }) => ({
127
+ base: () => join(homedir(), globalAgentDir ?? dir),
128
+ name: dir,
129
+ }));
130
+
131
+ const PROJECT_CONFIG_BASES = priorityList.map(({ dir }) => ({
132
+ base: dir,
133
+ name: dir,
134
+ }));
135
+
136
+ export interface ConfigDirEntry {
137
+ path: string;
138
+ source: string; // e.g., ".omp", ".pi", ".claude"
139
+ level: "user" | "project";
140
+ }
141
+
142
+ export interface GetConfigDirsOptions {
143
+ /** Include user-level directories (~/.omp/agent/...). Default: true */
144
+ user?: boolean;
145
+ /** Include project-level directories (.omp/...). Default: true */
146
+ project?: boolean;
147
+ /** Current working directory for project paths. Default: process.cwd() */
148
+ cwd?: string;
149
+ /** Only return directories that exist. Default: false */
150
+ existingOnly?: boolean;
151
+ }
152
+
153
+ /**
154
+ * Get all config directories for a subpath, ordered by priority (highest first).
155
+ *
156
+ * @param subpath - Subpath within config dirs (e.g., "commands", "hooks", "agents")
157
+ * @param options - Options for filtering
158
+ * @returns Array of directory entries, highest priority first
159
+ *
160
+ * @example
161
+ * // Get all command directories
162
+ * getConfigDirs("commands")
163
+ * // → [{ path: "~/.omp/agent/commands", source: ".omp", level: "user" }, ...]
164
+ *
165
+ * @example
166
+ * // Get only existing project skill directories
167
+ * getConfigDirs("skills", { user: false, existingOnly: true })
168
+ */
169
+ export function getConfigDirs(subpath: string, options: GetConfigDirsOptions = {}): ConfigDirEntry[] {
170
+ const { user = true, project = true, cwd = process.cwd(), existingOnly = false } = options;
171
+ const results: ConfigDirEntry[] = [];
172
+
173
+ // User-level directories (highest priority)
174
+ if (user) {
175
+ for (const { base, name } of USER_CONFIG_BASES) {
176
+ const path = join(base(), subpath);
177
+ if (!existingOnly || existsSync(path)) {
178
+ results.push({ path, source: name, level: "user" });
179
+ }
180
+ }
181
+ }
182
+
183
+ // Project-level directories
184
+ if (project) {
185
+ for (const { base, name } of PROJECT_CONFIG_BASES) {
186
+ const path = resolve(cwd, base, subpath);
187
+ if (!existingOnly || existsSync(path)) {
188
+ results.push({ path, source: name, level: "project" });
189
+ }
190
+ }
191
+ }
192
+
193
+ return results;
194
+ }
195
+
196
+ /**
197
+ * Get all config directory paths for a subpath (convenience wrapper).
198
+ * Returns just the paths, highest priority first.
199
+ */
200
+ export function getConfigDirPaths(subpath: string, options: GetConfigDirsOptions = {}): string[] {
201
+ return getConfigDirs(subpath, options).map((e) => e.path);
202
+ }
203
+
204
+ export interface ConfigFileResult<T> {
205
+ path: string;
206
+ source: string;
207
+ level: "user" | "project";
208
+ content: T;
209
+ }
210
+
211
+ /**
212
+ * Read the first existing config file from priority-ordered locations.
213
+ *
214
+ * @param subpath - Subpath within config dirs (e.g., "settings.json", "models.json")
215
+ * @param options - Options for filtering (same as getConfigDirs)
216
+ * @returns The parsed content and metadata, or undefined if not found
217
+ *
218
+ * @example
219
+ * const result = readConfigFile<Settings>("settings.json", { project: false });
220
+ * if (result) {
221
+ * console.log(`Loaded from ${result.path}`);
222
+ * console.log(result.content);
223
+ * }
224
+ */
225
+ export function readConfigFile<T = unknown>(
226
+ subpath: string,
227
+ options: GetConfigDirsOptions = {},
228
+ ): ConfigFileResult<T> | undefined {
229
+ const dirs = getConfigDirs("", { ...options, existingOnly: false });
230
+
231
+ for (const { path: base, source, level } of dirs) {
232
+ const filePath = join(base, subpath);
233
+ if (existsSync(filePath)) {
234
+ try {
235
+ const content = readFileSync(filePath, "utf-8");
236
+ return {
237
+ path: filePath,
238
+ source,
239
+ level,
240
+ content: JSON.parse(content) as T,
241
+ };
242
+ } catch {
243
+ // Continue to next file on parse error
244
+ }
245
+ }
246
+ }
247
+
248
+ return undefined;
249
+ }
250
+
251
+ /**
252
+ * Get all existing config files for a subpath (for merging scenarios).
253
+ * Returns in priority order (highest first).
254
+ */
255
+ export function readAllConfigFiles<T = unknown>(
256
+ subpath: string,
257
+ options: GetConfigDirsOptions = {},
258
+ ): ConfigFileResult<T>[] {
259
+ const dirs = getConfigDirs("", { ...options, existingOnly: false });
260
+ const results: ConfigFileResult<T>[] = [];
261
+
262
+ for (const { path: base, source, level } of dirs) {
263
+ const filePath = join(base, subpath);
264
+ if (existsSync(filePath)) {
265
+ try {
266
+ const content = readFileSync(filePath, "utf-8");
267
+ results.push({
268
+ path: filePath,
269
+ source,
270
+ level,
271
+ content: JSON.parse(content) as T,
272
+ });
273
+ } catch {
274
+ // Skip files that fail to parse
275
+ }
276
+ }
277
+ }
278
+
279
+ return results;
280
+ }
281
+
282
+ /**
283
+ * Find the first existing config file (for non-JSON files like SYSTEM.md).
284
+ * Returns just the path, or undefined if not found.
285
+ */
286
+ export function findConfigFile(subpath: string, options: GetConfigDirsOptions = {}): string | undefined {
287
+ const dirs = getConfigDirs("", { ...options, existingOnly: false });
288
+
289
+ for (const { path: base } of dirs) {
290
+ const filePath = join(base, subpath);
291
+ if (existsSync(filePath)) {
292
+ return filePath;
293
+ }
294
+ }
295
+
296
+ return undefined;
297
+ }
298
+
299
+ /**
300
+ * Find the first existing config file with metadata.
301
+ */
302
+ export function findConfigFileWithMeta(
303
+ subpath: string,
304
+ options: GetConfigDirsOptions = {},
305
+ ): Omit<ConfigFileResult<never>, "content"> | undefined {
306
+ const dirs = getConfigDirs("", { ...options, existingOnly: false });
307
+
308
+ for (const { path: base, source, level } of dirs) {
309
+ const filePath = join(base, subpath);
310
+ if (existsSync(filePath)) {
311
+ return { path: filePath, source, level };
312
+ }
313
+ }
314
+
315
+ return undefined;
316
+ }
317
+
318
+ // =============================================================================
319
+ // Walk-Up Config Discovery (for monorepo scenarios)
320
+ // =============================================================================
321
+
322
+ function isDirectory(p: string): boolean {
323
+ try {
324
+ return statSync(p).isDirectory();
325
+ } catch {
326
+ return false;
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Find nearest config directory by walking up from cwd.
332
+ * Checks all config bases (.omp, .pi, .claude) at each level.
333
+ *
334
+ * @param subpath - Subpath within config dirs (e.g., "commands", "agents")
335
+ * @param cwd - Starting directory
336
+ * @returns First existing directory found, or undefined
337
+ */
338
+ export function findNearestProjectConfigDir(subpath: string, cwd: string = process.cwd()): ConfigDirEntry | undefined {
339
+ let currentDir = cwd;
340
+
341
+ while (true) {
342
+ // Check all config bases at this level, in priority order
343
+ for (const { base, name } of PROJECT_CONFIG_BASES) {
344
+ const candidate = join(currentDir, base, subpath);
345
+ if (isDirectory(candidate)) {
346
+ return { path: candidate, source: name, level: "project" };
347
+ }
348
+ }
349
+
350
+ // Move up one directory
351
+ const parentDir = dirname(currentDir);
352
+ if (parentDir === currentDir) break; // Reached root
353
+ currentDir = parentDir;
354
+ }
355
+
356
+ return undefined;
357
+ }
358
+
359
+ /**
360
+ * Find all nearest config directories by walking up from cwd.
361
+ * Returns one entry per config base (.omp, .pi, .claude) - the nearest one found.
362
+ * Results are in priority order (highest first).
363
+ */
364
+ export function findAllNearestProjectConfigDirs(subpath: string, cwd: string = process.cwd()): ConfigDirEntry[] {
365
+ const results: ConfigDirEntry[] = [];
366
+ const foundBases = new Set<string>();
367
+
368
+ let currentDir = cwd;
369
+
370
+ while (foundBases.size < PROJECT_CONFIG_BASES.length) {
371
+ for (const { base, name } of PROJECT_CONFIG_BASES) {
372
+ if (foundBases.has(name)) continue;
373
+
374
+ const candidate = join(currentDir, base, subpath);
375
+ if (isDirectory(candidate)) {
376
+ results.push({ path: candidate, source: name, level: "project" });
377
+ foundBases.add(name);
378
+ }
379
+ }
380
+
381
+ const parentDir = dirname(currentDir);
382
+ if (parentDir === currentDir) break;
383
+ currentDir = parentDir;
384
+ }
385
+
386
+ // Sort by priority order
387
+ const order = PROJECT_CONFIG_BASES.map((b) => b.name);
388
+ results.sort((a, b) => order.indexOf(a.source) - order.indexOf(b.source));
389
+
390
+ return results;
391
+ }