@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
package/src/main.ts ADDED
@@ -0,0 +1,507 @@
1
+ /**
2
+ * Main entry point for the coding agent CLI.
3
+ *
4
+ * This file handles CLI argument parsing and translates them into
5
+ * createAgentSession() options. The SDK does the heavy lifting.
6
+ */
7
+
8
+ import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai";
9
+ import chalk from "chalk";
10
+ import { type Args, parseArgs, printHelp } from "./cli/args";
11
+ import { processFileArguments } from "./cli/file-processor";
12
+ import { listModels } from "./cli/list-models";
13
+ import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli";
14
+ import { selectSession } from "./cli/session-picker";
15
+ import { parseUpdateArgs, printUpdateHelp, runUpdateCommand } from "./cli/update-cli";
16
+ import { findConfigFile, getModelsPath, VERSION } from "./config";
17
+ import type { AgentSession } from "./core/agent-session";
18
+ import type { LoadedCustomTool } from "./core/custom-tools/index";
19
+ import { exportFromFile } from "./core/export-html/index";
20
+ import type { HookUIContext } from "./core/index";
21
+ import type { ModelRegistry } from "./core/model-registry";
22
+ import { parseModelPattern, resolveModelScope, type ScopedModel } from "./core/model-resolver";
23
+ import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage, discoverModels } from "./core/sdk";
24
+ import { SessionManager } from "./core/session-manager";
25
+ import { SettingsManager } from "./core/settings-manager";
26
+ import { resolvePromptInput } from "./core/system-prompt";
27
+ import { printTimings, time } from "./core/timings";
28
+ import { allTools } from "./core/tools/index";
29
+ import { runMigrations } from "./migrations";
30
+ import { InteractiveMode, installTerminalCrashHandlers, runPrintMode, runRpcMode } from "./modes/index";
31
+ import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme";
32
+ import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog";
33
+ import { ensureTool } from "./utils/tools-manager";
34
+
35
+ async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
36
+ try {
37
+ const response = await fetch("https://registry.npmjs.org/@oh-my-pi/pi-coding-agent/latest");
38
+ if (!response.ok) return undefined;
39
+
40
+ const data = (await response.json()) as { version?: string };
41
+ const latestVersion = data.version;
42
+
43
+ if (latestVersion && latestVersion !== currentVersion) {
44
+ return latestVersion;
45
+ }
46
+
47
+ return undefined;
48
+ } catch {
49
+ return undefined;
50
+ }
51
+ }
52
+
53
+ async function runInteractiveMode(
54
+ session: AgentSession,
55
+ version: string,
56
+ changelogMarkdown: string | undefined,
57
+ modelFallbackMessage: string | undefined,
58
+ modelsJsonError: string | undefined,
59
+ migratedProviders: string[],
60
+ versionCheckPromise: Promise<string | undefined>,
61
+ initialMessages: string[],
62
+ customTools: LoadedCustomTool[],
63
+ setToolUIContext: (uiContext: HookUIContext, hasUI: boolean) => void,
64
+ lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined,
65
+ initialMessage?: string,
66
+ initialImages?: ImageContent[],
67
+ fdPath: string | undefined = undefined,
68
+ ): Promise<void> {
69
+ const mode = new InteractiveMode(
70
+ session,
71
+ version,
72
+ changelogMarkdown,
73
+ customTools,
74
+ setToolUIContext,
75
+ lspServers,
76
+ fdPath,
77
+ );
78
+
79
+ await mode.init();
80
+
81
+ versionCheckPromise.then((newVersion) => {
82
+ if (newVersion) {
83
+ mode.showNewVersionNotification(newVersion);
84
+ }
85
+ });
86
+
87
+ mode.renderInitialMessages();
88
+
89
+ if (migratedProviders.length > 0) {
90
+ mode.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
91
+ }
92
+
93
+ if (modelsJsonError) {
94
+ mode.showError(`models.json error: ${modelsJsonError}`);
95
+ }
96
+
97
+ if (modelFallbackMessage) {
98
+ mode.showWarning(modelFallbackMessage);
99
+ }
100
+
101
+ if (initialMessage) {
102
+ try {
103
+ await session.prompt(initialMessage, { images: initialImages });
104
+ } catch (error: unknown) {
105
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
106
+ mode.showError(errorMessage);
107
+ }
108
+ }
109
+
110
+ for (const message of initialMessages) {
111
+ try {
112
+ await session.prompt(message);
113
+ } catch (error: unknown) {
114
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
115
+ mode.showError(errorMessage);
116
+ }
117
+ }
118
+
119
+ while (true) {
120
+ const { text, images } = await mode.getUserInput();
121
+ try {
122
+ await session.prompt(text, { images });
123
+ } catch (error: unknown) {
124
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
125
+ mode.showError(errorMessage);
126
+ }
127
+ }
128
+ }
129
+
130
+ async function prepareInitialMessage(parsed: Args): Promise<{
131
+ initialMessage?: string;
132
+ initialImages?: ImageContent[];
133
+ }> {
134
+ if (parsed.fileArgs.length === 0) {
135
+ return {};
136
+ }
137
+
138
+ const { text, images } = await processFileArguments(parsed.fileArgs);
139
+
140
+ let initialMessage: string;
141
+ if (parsed.messages.length > 0) {
142
+ initialMessage = text + parsed.messages[0];
143
+ parsed.messages.shift();
144
+ } else {
145
+ initialMessage = text;
146
+ }
147
+
148
+ return {
149
+ initialMessage,
150
+ initialImages: images.length > 0 ? images : undefined,
151
+ };
152
+ }
153
+
154
+ function getChangelogForDisplay(parsed: Args, settingsManager: SettingsManager): string | undefined {
155
+ if (parsed.continue || parsed.resume) {
156
+ return undefined;
157
+ }
158
+
159
+ const lastVersion = settingsManager.getLastChangelogVersion();
160
+ const changelogPath = getChangelogPath();
161
+ const entries = parseChangelog(changelogPath);
162
+
163
+ if (!lastVersion) {
164
+ if (entries.length > 0) {
165
+ settingsManager.setLastChangelogVersion(VERSION);
166
+ return entries.map((e) => e.content).join("\n\n");
167
+ }
168
+ } else {
169
+ const newEntries = getNewEntries(entries, lastVersion);
170
+ if (newEntries.length > 0) {
171
+ settingsManager.setLastChangelogVersion(VERSION);
172
+ return newEntries.map((e) => e.content).join("\n\n");
173
+ }
174
+ }
175
+
176
+ return undefined;
177
+ }
178
+
179
+ async function createSessionManager(parsed: Args, cwd: string): Promise<SessionManager | undefined> {
180
+ if (parsed.noSession) {
181
+ return SessionManager.inMemory();
182
+ }
183
+ if (parsed.session) {
184
+ return await SessionManager.open(parsed.session, parsed.sessionDir);
185
+ }
186
+ if (parsed.continue) {
187
+ return await SessionManager.continueRecent(cwd, parsed.sessionDir);
188
+ }
189
+ // --resume is handled separately (needs picker UI)
190
+ // If --session-dir provided without --continue/--resume, create new session there
191
+ if (parsed.sessionDir) {
192
+ return SessionManager.create(cwd, parsed.sessionDir);
193
+ }
194
+ // Default case (new session) returns undefined, SDK will create one
195
+ return undefined;
196
+ }
197
+
198
+ /** Discover SYSTEM.md file if no CLI system prompt was provided */
199
+ function discoverSystemPromptFile(): string | undefined {
200
+ // Check project-local first (.omp/SYSTEM.md, .pi/SYSTEM.md legacy)
201
+ const projectPath = findConfigFile("SYSTEM.md", { user: false });
202
+ if (projectPath) {
203
+ return projectPath;
204
+ }
205
+ // If not found, check SYSTEM.md file in the global directory.
206
+ const globalPath = findConfigFile("SYSTEM.md", { user: true });
207
+ if (globalPath) {
208
+ return globalPath;
209
+ }
210
+ return undefined;
211
+ }
212
+
213
+ async function buildSessionOptions(
214
+ parsed: Args,
215
+ scopedModels: ScopedModel[],
216
+ sessionManager: SessionManager | undefined,
217
+ modelRegistry: ModelRegistry,
218
+ ): Promise<CreateAgentSessionOptions> {
219
+ const options: CreateAgentSessionOptions = {};
220
+
221
+ // Auto-discover SYSTEM.md if no CLI system prompt provided
222
+ const systemPromptSource = parsed.systemPrompt ?? discoverSystemPromptFile();
223
+ const resolvedSystemPrompt = resolvePromptInput(systemPromptSource, "system prompt");
224
+ const resolvedAppendPrompt = resolvePromptInput(parsed.appendSystemPrompt, "append system prompt");
225
+
226
+ if (sessionManager) {
227
+ options.sessionManager = sessionManager;
228
+ }
229
+
230
+ // Model from CLI (--model) - uses same fuzzy matching as --models
231
+ if (parsed.model) {
232
+ const available = await modelRegistry.getAvailable();
233
+ const { model, warning } = parseModelPattern(parsed.model, available);
234
+ if (warning) {
235
+ console.warn(chalk.yellow(`Warning: ${warning}`));
236
+ }
237
+ if (!model) {
238
+ console.error(chalk.red(`Model "${parsed.model}" not found`));
239
+ process.exit(1);
240
+ }
241
+ options.model = model;
242
+ } else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
243
+ options.model = scopedModels[0].model;
244
+ }
245
+
246
+ // Thinking level
247
+ if (parsed.thinking) {
248
+ options.thinkingLevel = parsed.thinking;
249
+ } else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
250
+ options.thinkingLevel = scopedModels[0].thinkingLevel;
251
+ }
252
+
253
+ // Scoped models for Ctrl+P cycling
254
+ if (scopedModels.length > 0) {
255
+ options.scopedModels = scopedModels;
256
+ }
257
+
258
+ // API key from CLI - set in authStorage
259
+ // (handled by caller before createAgentSession)
260
+
261
+ // System prompt
262
+ if (resolvedSystemPrompt && resolvedAppendPrompt) {
263
+ options.systemPrompt = `${resolvedSystemPrompt}\n\n${resolvedAppendPrompt}`;
264
+ } else if (resolvedSystemPrompt) {
265
+ options.systemPrompt = resolvedSystemPrompt;
266
+ } else if (resolvedAppendPrompt) {
267
+ options.systemPrompt = (defaultPrompt) => `${defaultPrompt}\n\n${resolvedAppendPrompt}`;
268
+ }
269
+
270
+ // Tools
271
+ if (parsed.tools) {
272
+ options.tools = parsed.tools.map((name) => allTools[name]);
273
+ options.explicitTools = parsed.tools;
274
+ }
275
+
276
+ // Skills
277
+ if (parsed.noSkills) {
278
+ options.skills = [];
279
+ }
280
+
281
+ // Additional hook paths from CLI
282
+ if (parsed.hooks && parsed.hooks.length > 0) {
283
+ options.additionalHookPaths = parsed.hooks;
284
+ }
285
+
286
+ // Additional custom tool paths from CLI
287
+ if (parsed.customTools && parsed.customTools.length > 0) {
288
+ options.additionalCustomToolPaths = parsed.customTools;
289
+ }
290
+
291
+ return options;
292
+ }
293
+
294
+ export async function main(args: string[]) {
295
+ time("start");
296
+
297
+ // Initialize theme early with defaults (CLI commands need symbols)
298
+ // Will be re-initialized with user preferences later
299
+ initTheme();
300
+
301
+ // Handle plugin subcommand before regular parsing
302
+ const pluginCmd = parsePluginArgs(args);
303
+ if (pluginCmd) {
304
+ if (args.includes("--help") || args.includes("-h")) {
305
+ printPluginHelp();
306
+ return;
307
+ }
308
+ await runPluginCommand(pluginCmd);
309
+ return;
310
+ }
311
+
312
+ // Handle update subcommand
313
+ const updateCmd = parseUpdateArgs(args);
314
+ if (updateCmd) {
315
+ if (args.includes("--help") || args.includes("-h")) {
316
+ printUpdateHelp();
317
+ return;
318
+ }
319
+ await runUpdateCommand(updateCmd);
320
+ return;
321
+ }
322
+
323
+ // Run migrations
324
+ const { migratedAuthProviders: migratedProviders } = runMigrations();
325
+
326
+ // Create AuthStorage and ModelRegistry upfront
327
+ const authStorage = discoverAuthStorage();
328
+ const modelRegistry = discoverModels(authStorage);
329
+ time("discoverModels");
330
+
331
+ const parsed = parseArgs(args);
332
+ time("parseArgs");
333
+
334
+ if (parsed.version) {
335
+ console.log(VERSION);
336
+ return;
337
+ }
338
+
339
+ if (parsed.help) {
340
+ printHelp();
341
+ return;
342
+ }
343
+
344
+ if (parsed.listModels !== undefined) {
345
+ const searchPattern = typeof parsed.listModels === "string" ? parsed.listModels : undefined;
346
+ await listModels(modelRegistry, searchPattern);
347
+ return;
348
+ }
349
+
350
+ if (parsed.export) {
351
+ try {
352
+ const outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;
353
+ const result = await exportFromFile(parsed.export, outputPath);
354
+ console.log(`Exported to: ${result}`);
355
+ return;
356
+ } catch (error: unknown) {
357
+ const message = error instanceof Error ? error.message : "Failed to export session";
358
+ console.error(chalk.red(`Error: ${message}`));
359
+ process.exit(1);
360
+ }
361
+ }
362
+
363
+ if (parsed.mode === "rpc" && parsed.fileArgs.length > 0) {
364
+ console.error(chalk.red("Error: @file arguments are not supported in RPC mode"));
365
+ process.exit(1);
366
+ }
367
+
368
+ const cwd = process.cwd();
369
+ const { initialMessage, initialImages } = await prepareInitialMessage(parsed);
370
+ time("prepareInitialMessage");
371
+ const isInteractive = !parsed.print && parsed.mode === undefined;
372
+ const mode = parsed.mode || "text";
373
+
374
+ const settingsManager = SettingsManager.create(cwd);
375
+ time("SettingsManager.create");
376
+
377
+ // Initialize discovery system with settings for provider persistence
378
+ const { initializeWithSettings } = await import("./discovery");
379
+ initializeWithSettings(settingsManager);
380
+ time("initializeWithSettings");
381
+
382
+ // Apply model role overrides from CLI args or env vars (ephemeral, not persisted)
383
+ const smolModel = parsed.smol ?? process.env.OMP_SMOL_MODEL;
384
+ const slowModel = parsed.slow ?? process.env.OMP_SLOW_MODEL;
385
+ if (smolModel || slowModel) {
386
+ const roleOverrides: Record<string, string> = {};
387
+ if (smolModel) roleOverrides.smol = smolModel;
388
+ if (slowModel) roleOverrides.slow = slowModel;
389
+ settingsManager.applyOverrides({ modelRoles: roleOverrides });
390
+ }
391
+
392
+ initTheme(settingsManager.getTheme(), isInteractive, settingsManager.getSymbolPreset());
393
+ time("initTheme");
394
+
395
+ let scopedModels: ScopedModel[] = [];
396
+ const modelPatterns = parsed.models ?? settingsManager.getEnabledModels();
397
+ if (modelPatterns && modelPatterns.length > 0) {
398
+ scopedModels = await resolveModelScope(modelPatterns, modelRegistry);
399
+ time("resolveModelScope");
400
+ }
401
+
402
+ // Create session manager based on CLI flags
403
+ let sessionManager = await createSessionManager(parsed, cwd);
404
+ time("createSessionManager");
405
+
406
+ // Handle --resume: show session picker
407
+ if (parsed.resume) {
408
+ const sessions = SessionManager.list(cwd, parsed.sessionDir);
409
+ time("SessionManager.list");
410
+ if (sessions.length === 0) {
411
+ console.log(chalk.dim("No sessions found"));
412
+ return;
413
+ }
414
+ const selectedPath = await selectSession(sessions);
415
+ time("selectSession");
416
+ if (!selectedPath) {
417
+ console.log(chalk.dim("No session selected"));
418
+ return;
419
+ }
420
+ sessionManager = await SessionManager.open(selectedPath);
421
+ }
422
+
423
+ const sessionOptions = await buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry);
424
+ sessionOptions.authStorage = authStorage;
425
+ sessionOptions.modelRegistry = modelRegistry;
426
+ sessionOptions.hasUI = isInteractive;
427
+
428
+ // Handle CLI --api-key as runtime override (not persisted)
429
+ if (parsed.apiKey) {
430
+ if (!sessionOptions.model) {
431
+ console.error(chalk.red("--api-key requires a model to be specified via --provider/--model or -m/--models"));
432
+ process.exit(1);
433
+ }
434
+ authStorage.setRuntimeApiKey(sessionOptions.model.provider, parsed.apiKey);
435
+ }
436
+
437
+ time("buildSessionOptions");
438
+ const { session, customToolsResult, modelFallbackMessage, lspServers } = await createAgentSession(sessionOptions);
439
+ time("createAgentSession");
440
+
441
+ if (!isInteractive && !session.model) {
442
+ console.error(chalk.red("No models available."));
443
+ console.error(chalk.yellow("\nSet an API key environment variable:"));
444
+ console.error(" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.");
445
+ console.error(chalk.yellow(`\nOr create ${getModelsPath()}`));
446
+ process.exit(1);
447
+ }
448
+
449
+ // Clamp thinking level to model capabilities (for CLI override case)
450
+ if (session.model && parsed.thinking) {
451
+ let effectiveThinking = parsed.thinking;
452
+ if (!session.model.reasoning) {
453
+ effectiveThinking = "off";
454
+ } else if (effectiveThinking === "xhigh" && !supportsXhigh(session.model)) {
455
+ effectiveThinking = "high";
456
+ }
457
+ if (effectiveThinking !== session.thinkingLevel) {
458
+ session.setThinkingLevel(effectiveThinking);
459
+ }
460
+ }
461
+
462
+ if (mode === "rpc") {
463
+ await runRpcMode(session);
464
+ } else if (isInteractive) {
465
+ const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
466
+ const changelogMarkdown = getChangelogForDisplay(parsed, settingsManager);
467
+
468
+ if (scopedModels.length > 0) {
469
+ const modelList = scopedModels
470
+ .map((sm) => {
471
+ const thinkingStr = sm.thinkingLevel !== "off" ? `:${sm.thinkingLevel}` : "";
472
+ return `${sm.model.id}${thinkingStr}`;
473
+ })
474
+ .join(", ");
475
+ console.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray("(Ctrl+P to cycle)")}`));
476
+ }
477
+
478
+ const fdPath = await ensureTool("fd");
479
+ time("ensureTool(fd)");
480
+
481
+ installTerminalCrashHandlers();
482
+ printTimings();
483
+ await runInteractiveMode(
484
+ session,
485
+ VERSION,
486
+ changelogMarkdown,
487
+ modelFallbackMessage,
488
+ modelRegistry.getError(),
489
+ migratedProviders,
490
+ versionCheckPromise,
491
+ parsed.messages,
492
+ customToolsResult.tools,
493
+ customToolsResult.setUIContext,
494
+ lspServers,
495
+ initialMessage,
496
+ initialImages,
497
+ fdPath,
498
+ );
499
+ } else {
500
+ await runPrintMode(session, mode, parsed.messages, initialMessage, initialImages);
501
+ stopThemeWatcher();
502
+ if (process.stdout.writableLength > 0) {
503
+ await new Promise<void>((resolve) => process.stdout.once("drain", resolve));
504
+ }
505
+ process.exit(0);
506
+ }
507
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * One-time migrations that run on startup.
3
+ */
4
+
5
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { getAgentDir } from "./config";
8
+
9
+ /**
10
+ * Migrate PI_* environment variables to OMP_* equivalents.
11
+ * If PI_XX is set and OMP_XX is not, set OMP_XX to PI_XX's value.
12
+ * This provides backwards compatibility for users with existing PI_* env vars.
13
+ */
14
+ export function migrateEnvVars(): void {
15
+ for (const [key, value] of Object.entries(process.env)) {
16
+ if (key.startsWith("PI_") && value !== undefined) {
17
+ const ompKey = `OMP_${key.slice(3)}`; // PI_FOO -> OMP_FOO
18
+ if (process.env[ompKey] === undefined) {
19
+ process.env[ompKey] = value;
20
+ }
21
+ }
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Migrate legacy oauth.json and settings.json apiKeys to auth.json.
27
+ *
28
+ * @returns Array of provider names that were migrated
29
+ */
30
+ export function migrateAuthToAuthJson(): string[] {
31
+ const agentDir = getAgentDir();
32
+ const authPath = join(agentDir, "auth.json");
33
+ const oauthPath = join(agentDir, "oauth.json");
34
+ const settingsPath = join(agentDir, "settings.json");
35
+
36
+ // Skip if auth.json already exists
37
+ if (existsSync(authPath)) return [];
38
+
39
+ const migrated: Record<string, unknown> = {};
40
+ const providers: string[] = [];
41
+
42
+ // Migrate oauth.json
43
+ if (existsSync(oauthPath)) {
44
+ try {
45
+ const oauth = JSON.parse(readFileSync(oauthPath, "utf-8"));
46
+ for (const [provider, cred] of Object.entries(oauth)) {
47
+ migrated[provider] = { type: "oauth", ...(cred as object) };
48
+ providers.push(provider);
49
+ }
50
+ renameSync(oauthPath, `${oauthPath}.migrated`);
51
+ } catch {
52
+ // Skip on error
53
+ }
54
+ }
55
+
56
+ // Migrate settings.json apiKeys
57
+ if (existsSync(settingsPath)) {
58
+ try {
59
+ const content = readFileSync(settingsPath, "utf-8");
60
+ const settings = JSON.parse(content);
61
+ if (settings.apiKeys && typeof settings.apiKeys === "object") {
62
+ for (const [provider, key] of Object.entries(settings.apiKeys)) {
63
+ if (!migrated[provider] && typeof key === "string") {
64
+ migrated[provider] = { type: "api_key", key };
65
+ providers.push(provider);
66
+ }
67
+ }
68
+ delete settings.apiKeys;
69
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
70
+ }
71
+ } catch {
72
+ // Skip on error
73
+ }
74
+ }
75
+
76
+ if (Object.keys(migrated).length > 0) {
77
+ mkdirSync(dirname(authPath), { recursive: true });
78
+ writeFileSync(authPath, JSON.stringify(migrated, null, 2), { mode: 0o600 });
79
+ }
80
+
81
+ return providers;
82
+ }
83
+
84
+ /**
85
+ * Migrate sessions from ~/.omp/agent/*.jsonl to proper session directories.
86
+ *
87
+ * Bug in v0.30.0: Sessions were saved to ~/.omp/agent/ instead of
88
+ * ~/.omp/agent/sessions/<encoded-cwd>/. This migration moves them
89
+ * to the correct location based on the cwd in their session header.
90
+ *
91
+ * See: https://github.com/badlogic/pi-mono/issues/320
92
+ */
93
+ export function migrateSessionsFromAgentRoot(): void {
94
+ const agentDir = getAgentDir();
95
+
96
+ // Find all .jsonl files directly in agentDir (not in subdirectories)
97
+ let files: string[];
98
+ try {
99
+ files = readdirSync(agentDir)
100
+ .filter((f) => f.endsWith(".jsonl"))
101
+ .map((f) => join(agentDir, f));
102
+ } catch {
103
+ return;
104
+ }
105
+
106
+ if (files.length === 0) return;
107
+
108
+ for (const file of files) {
109
+ try {
110
+ // Read first line to get session header
111
+ const content = readFileSync(file, "utf8");
112
+ const firstLine = content.split("\n")[0];
113
+ if (!firstLine?.trim()) continue;
114
+
115
+ const header = JSON.parse(firstLine);
116
+ if (header.type !== "session" || !header.cwd) continue;
117
+
118
+ const cwd: string = header.cwd;
119
+
120
+ // Compute the correct session directory (same encoding as session-manager.ts)
121
+ const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
122
+ const correctDir = join(agentDir, "sessions", safePath);
123
+
124
+ // Create directory if needed
125
+ if (!existsSync(correctDir)) {
126
+ mkdirSync(correctDir, { recursive: true });
127
+ }
128
+
129
+ // Move the file
130
+ const fileName = file.split("/").pop() || file.split("\\").pop();
131
+ const newPath = join(correctDir, fileName!);
132
+
133
+ if (existsSync(newPath)) continue; // Skip if target exists
134
+
135
+ renameSync(file, newPath);
136
+ } catch {
137
+ // Skip files that can't be migrated
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Run all migrations. Called once on startup.
144
+ *
145
+ * @returns Object with migration results
146
+ */
147
+ export function runMigrations(): { migratedAuthProviders: string[] } {
148
+ // First: migrate env vars (before anything else reads them)
149
+ migrateEnvVars();
150
+
151
+ // Then: run data migrations
152
+ const migratedAuthProviders = migrateAuthToAuthJson();
153
+ migrateSessionsFromAgentRoot();
154
+
155
+ return { migratedAuthProviders };
156
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Async cleanup registry for graceful shutdown on signals.
3
+ */
4
+
5
+ /** Registry of async cleanup callbacks to run on shutdown/signals */
6
+ const asyncCleanupCallbacks: (() => Promise<void>)[] = [];
7
+
8
+ /**
9
+ * Register an async cleanup callback to be run on process signals (SIGINT, SIGTERM, SIGHUP).
10
+ * Returns an unsubscribe function.
11
+ */
12
+ export function registerAsyncCleanup(callback: () => Promise<void>): () => void {
13
+ asyncCleanupCallbacks.push(callback);
14
+ return () => {
15
+ const index = asyncCleanupCallbacks.indexOf(callback);
16
+ if (index >= 0) asyncCleanupCallbacks.splice(index, 1);
17
+ };
18
+ }
19
+
20
+ /** Run all registered async cleanup callbacks, settling all promises */
21
+ export async function runAsyncCleanup(): Promise<void> {
22
+ await Promise.allSettled(asyncCleanupCallbacks.map((cb) => cb()));
23
+ }