@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,259 @@
1
+ /**
2
+ * CLI argument parsing and help display
3
+ */
4
+
5
+ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
6
+ import chalk from "chalk";
7
+ import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR } from "../config";
8
+ import { allTools, type ToolName } from "../core/tools/index";
9
+
10
+ export type Mode = "text" | "json" | "rpc";
11
+
12
+ export interface Args {
13
+ provider?: string;
14
+ model?: string;
15
+ smol?: string;
16
+ slow?: string;
17
+ apiKey?: string;
18
+ systemPrompt?: string;
19
+ appendSystemPrompt?: string;
20
+ thinking?: ThinkingLevel;
21
+ continue?: boolean;
22
+ resume?: boolean;
23
+ help?: boolean;
24
+ version?: boolean;
25
+ mode?: Mode;
26
+ noSession?: boolean;
27
+ session?: string;
28
+ sessionDir?: string;
29
+ models?: string[];
30
+ tools?: ToolName[];
31
+ hooks?: string[];
32
+ customTools?: string[];
33
+ print?: boolean;
34
+ export?: string;
35
+ noSkills?: boolean;
36
+ skills?: string[];
37
+ listModels?: string | true;
38
+ messages: string[];
39
+ fileArgs: string[];
40
+ }
41
+
42
+ const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
43
+
44
+ export function isValidThinkingLevel(level: string): level is ThinkingLevel {
45
+ return VALID_THINKING_LEVELS.includes(level as ThinkingLevel);
46
+ }
47
+
48
+ export function parseArgs(args: string[]): Args {
49
+ const result: Args = {
50
+ messages: [],
51
+ fileArgs: [],
52
+ };
53
+
54
+ for (let i = 0; i < args.length; i++) {
55
+ const arg = args[i];
56
+
57
+ if (arg === "--help" || arg === "-h") {
58
+ result.help = true;
59
+ } else if (arg === "--version" || arg === "-v") {
60
+ result.version = true;
61
+ } else if (arg === "--mode" && i + 1 < args.length) {
62
+ const mode = args[++i];
63
+ if (mode === "text" || mode === "json" || mode === "rpc") {
64
+ result.mode = mode;
65
+ }
66
+ } else if (arg === "--continue" || arg === "-c") {
67
+ result.continue = true;
68
+ } else if (arg === "--resume" || arg === "-r") {
69
+ result.resume = true;
70
+ } else if (arg === "--provider" && i + 1 < args.length) {
71
+ result.provider = args[++i];
72
+ } else if (arg === "--model" && i + 1 < args.length) {
73
+ result.model = args[++i];
74
+ } else if (arg === "--smol" && i + 1 < args.length) {
75
+ result.smol = args[++i];
76
+ } else if (arg === "--slow" && i + 1 < args.length) {
77
+ result.slow = args[++i];
78
+ } else if (arg === "--api-key" && i + 1 < args.length) {
79
+ result.apiKey = args[++i];
80
+ } else if (arg === "--system-prompt" && i + 1 < args.length) {
81
+ result.systemPrompt = args[++i];
82
+ } else if (arg === "--append-system-prompt" && i + 1 < args.length) {
83
+ result.appendSystemPrompt = args[++i];
84
+ } else if (arg === "--no-session") {
85
+ result.noSession = true;
86
+ } else if (arg === "--session" && i + 1 < args.length) {
87
+ result.session = args[++i];
88
+ } else if (arg === "--session-dir" && i + 1 < args.length) {
89
+ result.sessionDir = args[++i];
90
+ } else if (arg === "--models" && i + 1 < args.length) {
91
+ result.models = args[++i].split(",").map((s) => s.trim());
92
+ } else if (arg === "--tools" && i + 1 < args.length) {
93
+ const toolNames = args[++i].split(",").map((s) => s.trim());
94
+ const validTools: ToolName[] = [];
95
+ for (const name of toolNames) {
96
+ if (name in allTools) {
97
+ validTools.push(name as ToolName);
98
+ } else {
99
+ console.error(
100
+ chalk.yellow(`Warning: Unknown tool "${name}". Valid tools: ${Object.keys(allTools).join(", ")}`),
101
+ );
102
+ }
103
+ }
104
+ result.tools = validTools;
105
+ } else if (arg === "--thinking" && i + 1 < args.length) {
106
+ const level = args[++i];
107
+ if (isValidThinkingLevel(level)) {
108
+ result.thinking = level;
109
+ } else {
110
+ console.error(
111
+ chalk.yellow(
112
+ `Warning: Invalid thinking level "${level}". Valid values: ${VALID_THINKING_LEVELS.join(", ")}`,
113
+ ),
114
+ );
115
+ }
116
+ } else if (arg === "--print" || arg === "-p") {
117
+ result.print = true;
118
+ } else if (arg === "--export" && i + 1 < args.length) {
119
+ result.export = args[++i];
120
+ } else if (arg === "--hook" && i + 1 < args.length) {
121
+ result.hooks = result.hooks ?? [];
122
+ result.hooks.push(args[++i]);
123
+ } else if (arg === "--tool" && i + 1 < args.length) {
124
+ result.customTools = result.customTools ?? [];
125
+ result.customTools.push(args[++i]);
126
+ } else if (arg === "--no-skills") {
127
+ result.noSkills = true;
128
+ } else if (arg === "--skills" && i + 1 < args.length) {
129
+ // Comma-separated glob patterns for skill filtering
130
+ result.skills = args[++i].split(",").map((s) => s.trim());
131
+ } else if (arg === "--list-models") {
132
+ // Check if next arg is a search pattern (not a flag or file arg)
133
+ if (i + 1 < args.length && !args[i + 1].startsWith("-") && !args[i + 1].startsWith("@")) {
134
+ result.listModels = args[++i];
135
+ } else {
136
+ result.listModels = true;
137
+ }
138
+ } else if (arg.startsWith("@")) {
139
+ result.fileArgs.push(arg.slice(1)); // Remove @ prefix
140
+ } else if (!arg.startsWith("-")) {
141
+ result.messages.push(arg);
142
+ }
143
+ }
144
+
145
+ return result;
146
+ }
147
+
148
+ export function printHelp(): void {
149
+ console.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools
150
+
151
+ ${chalk.bold("Usage:")}
152
+ ${APP_NAME} [options] [@files...] [messages...]
153
+
154
+ ${chalk.bold("Options:")}
155
+ --model <pattern> Model to use (fuzzy match: "opus", "gpt-5.2", or "p-openai/gpt-5.2")
156
+ --smol <id> Smol/fast model for lightweight tasks (or OMP_SMOL_MODEL env)
157
+ --slow <id> Slow/reasoning model for thorough analysis (or OMP_SLOW_MODEL env)
158
+ --api-key <key> API key (defaults to env vars)
159
+ --system-prompt <text> System prompt (default: coding assistant prompt)
160
+ --append-system-prompt <text> Append text or file contents to the system prompt
161
+ --mode <mode> Output mode: text (default), json, or rpc
162
+ --print, -p Non-interactive mode: process prompt and exit
163
+ --continue, -c Continue previous session
164
+ --resume, -r Select a session to resume
165
+ --session <path> Use specific session file
166
+ --session-dir <dir> Directory for session storage and lookup
167
+ --no-session Don't save session (ephemeral)
168
+ --models <patterns> Comma-separated model patterns for Ctrl+P cycling
169
+ Supports globs (anthropic/*, *sonnet*) and fuzzy matching
170
+ --tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)
171
+ Available: read, bash, edit, write, grep, find, ls
172
+ --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
173
+ --hook <path> Load a hook file (can be used multiple times)
174
+ --tool <path> Load a custom tool file (can be used multiple times)
175
+ --no-skills Disable skills discovery and loading
176
+ --skills <patterns> Comma-separated glob patterns to filter skills (e.g., git-*,docker)
177
+ --export <file> Export session file to HTML and exit
178
+ --list-models [search] List available models (with optional fuzzy search)
179
+ --help, -h Show this help
180
+ --version, -v Show version number
181
+
182
+ ${chalk.bold("Examples:")}
183
+ # Interactive mode
184
+ ${APP_NAME}
185
+
186
+ # Interactive mode with initial prompt
187
+ ${APP_NAME} "List all .ts files in src/"
188
+
189
+ # Include files in initial message
190
+ ${APP_NAME} @prompt.md @image.png "What color is the sky?"
191
+
192
+ # Non-interactive mode (process and exit)
193
+ ${APP_NAME} -p "List all .ts files in src/"
194
+
195
+ # Multiple messages (interactive)
196
+ ${APP_NAME} "Read package.json" "What dependencies do we have?"
197
+
198
+ # Continue previous session
199
+ ${APP_NAME} --continue "What did we discuss?"
200
+
201
+ # Use different model (fuzzy matching)
202
+ ${APP_NAME} --model opus "Help me refactor this code"
203
+
204
+ # Limit model cycling to specific models
205
+ ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o
206
+
207
+ # Limit to a specific provider with glob pattern
208
+ ${APP_NAME} --models "github-copilot/*"
209
+
210
+ # Cycle models with fixed thinking levels
211
+ ${APP_NAME} --models sonnet:high,haiku:low
212
+
213
+ # Start with a specific thinking level
214
+ ${APP_NAME} --thinking high "Solve this complex problem"
215
+
216
+ # Read-only mode (no file modifications possible)
217
+ ${APP_NAME} --tools read,grep,find,ls -p "Review the code in src/"
218
+
219
+ # Export a session file to HTML
220
+ ${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl
221
+ ${APP_NAME} --export session.jsonl output.html
222
+
223
+ ${chalk.bold("Environment Variables:")}
224
+ ${chalk.dim("# Model providers")}
225
+ ANTHROPIC_API_KEY - Anthropic Claude API key
226
+ ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)
227
+ OPENAI_API_KEY - OpenAI GPT API key
228
+ GEMINI_API_KEY - Google Gemini API key
229
+ GROQ_API_KEY - Groq API key
230
+ CEREBRAS_API_KEY - Cerebras API key
231
+ XAI_API_KEY - xAI Grok API key
232
+ OPENROUTER_API_KEY - OpenRouter API key
233
+ MISTRAL_API_KEY - Mistral API key
234
+ ZAI_API_KEY - ZAI API key
235
+ GITHUB_TOKEN - GitHub Copilot models (or GH_TOKEN, COPILOT_GITHUB_TOKEN)
236
+
237
+ ${chalk.dim("# Web search providers")}
238
+ EXA_API_KEY - Exa search API key
239
+ PERPLEXITY_API_KEY - Perplexity search API key
240
+
241
+ ${chalk.dim("# Configuration")}
242
+ ${ENV_AGENT_DIR.padEnd(23)} - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
243
+
244
+ ${chalk.bold("Available Tools (all enabled by default):")}
245
+ read - Read file contents
246
+ bash - Execute bash commands
247
+ edit - Edit files with find/replace
248
+ write - Write files (creates/overwrites)
249
+ grep - Search file contents
250
+ find - Find files by glob pattern
251
+ ls - List directory contents
252
+ lsp - Language server protocol (code intelligence)
253
+ notebook - Edit Jupyter notebooks
254
+ task - Launch sub-agents for parallel tasks
255
+ web_fetch - Fetch and process web pages
256
+ web_search - Search the web
257
+ ask - Ask user questions (interactive mode only)
258
+ `);
259
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Process @file CLI arguments into text content and image attachments
3
+ */
4
+
5
+ import { access, readFile, stat } from "node:fs/promises";
6
+ import { resolve } from "node:path";
7
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
8
+ import chalk from "chalk";
9
+ import sharp from "sharp";
10
+ import { resolveReadPath } from "../core/tools/path-utils";
11
+ import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
12
+
13
+ export interface ProcessedFiles {
14
+ text: string;
15
+ images: ImageContent[];
16
+ }
17
+
18
+ const RESIZE_TRIGGER_MAX_DIMENSION = 2048;
19
+ const MAX_RESIZE_WIDTH = 1920;
20
+ const MAX_RESIZE_HEIGHT = 1080;
21
+ const JPEG_CONVERT_THRESHOLD_BYTES = 2 * 1024 * 1024;
22
+ const JPEG_QUALITY = 85;
23
+
24
+ async function processImageAttachment(buffer: Buffer, mimeType: string): Promise<{ buffer: Buffer; mimeType: string }> {
25
+ const metadata = await sharp(buffer, { failOnError: false }).metadata();
26
+ const width = metadata.width ?? 0;
27
+ const height = metadata.height ?? 0;
28
+ const maxDim = Math.max(width, height);
29
+ const shouldResize = width > 0 && height > 0 && maxDim > RESIZE_TRIGGER_MAX_DIMENSION;
30
+ const shouldConvertToJpeg = buffer.length > JPEG_CONVERT_THRESHOLD_BYTES;
31
+
32
+ if (!shouldResize && !shouldConvertToJpeg) {
33
+ return { buffer, mimeType };
34
+ }
35
+
36
+ let pipeline = sharp(buffer, { failOnError: false });
37
+ if (shouldResize) {
38
+ pipeline = pipeline.resize({
39
+ width: MAX_RESIZE_WIDTH,
40
+ height: MAX_RESIZE_HEIGHT,
41
+ fit: "inside",
42
+ withoutEnlargement: true,
43
+ });
44
+ }
45
+
46
+ if (shouldConvertToJpeg) {
47
+ pipeline = pipeline.jpeg({ quality: JPEG_QUALITY });
48
+ return { buffer: await pipeline.toBuffer(), mimeType: "image/jpeg" };
49
+ }
50
+
51
+ if (mimeType === "image/png") {
52
+ pipeline = pipeline.png();
53
+ } else if (mimeType === "image/webp") {
54
+ pipeline = pipeline.webp();
55
+ } else if (mimeType === "image/gif") {
56
+ pipeline = pipeline.gif();
57
+ } else {
58
+ pipeline = pipeline.jpeg({ quality: JPEG_QUALITY });
59
+ return { buffer: await pipeline.toBuffer(), mimeType: "image/jpeg" };
60
+ }
61
+
62
+ return { buffer: await pipeline.toBuffer(), mimeType };
63
+ }
64
+
65
+ /** Process @file arguments into text content and image attachments */
66
+ export async function processFileArguments(fileArgs: string[]): Promise<ProcessedFiles> {
67
+ let text = "";
68
+ const images: ImageContent[] = [];
69
+
70
+ for (const fileArg of fileArgs) {
71
+ // Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)
72
+ const absolutePath = resolve(resolveReadPath(fileArg, process.cwd()));
73
+
74
+ // Check if file exists
75
+ try {
76
+ await access(absolutePath);
77
+ } catch {
78
+ console.error(chalk.red(`Error: File not found: ${absolutePath}`));
79
+ process.exit(1);
80
+ }
81
+
82
+ // Check if file is empty
83
+ const stats = await stat(absolutePath);
84
+ if (stats.size === 0) {
85
+ // Skip empty files
86
+ continue;
87
+ }
88
+
89
+ const mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);
90
+
91
+ if (mimeType) {
92
+ // Handle image file
93
+ const content = await readFile(absolutePath);
94
+ const processed = await processImageAttachment(content, mimeType);
95
+ const base64Content = processed.buffer.toString("base64");
96
+
97
+ const attachment: ImageContent = {
98
+ type: "image",
99
+ mimeType: processed.mimeType,
100
+ data: base64Content,
101
+ };
102
+
103
+ images.push(attachment);
104
+
105
+ // Add text reference to image
106
+ text += `<file name="${absolutePath}"></file>\n`;
107
+ } else {
108
+ // Handle text file
109
+ try {
110
+ const content = await readFile(absolutePath, "utf-8");
111
+ text += `<file name="${absolutePath}">\n${content}\n</file>\n`;
112
+ } catch (error: unknown) {
113
+ const message = error instanceof Error ? error.message : String(error);
114
+ console.error(chalk.red(`Error: Could not read file ${absolutePath}: ${message}`));
115
+ process.exit(1);
116
+ }
117
+ }
118
+ }
119
+
120
+ return { text, images };
121
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * List available models with optional fuzzy search
3
+ */
4
+
5
+ import type { Api, Model } from "@oh-my-pi/pi-ai";
6
+ import type { ModelRegistry } from "../core/model-registry";
7
+ import { fuzzyFilter } from "../utils/fuzzy";
8
+
9
+ /**
10
+ * Format a number as human-readable (e.g., 200000 -> "200K", 1000000 -> "1M")
11
+ */
12
+ function formatTokenCount(count: number): string {
13
+ if (count >= 1_000_000) {
14
+ const millions = count / 1_000_000;
15
+ return millions % 1 === 0 ? `${millions}M` : `${millions.toFixed(1)}M`;
16
+ }
17
+ if (count >= 1_000) {
18
+ const thousands = count / 1_000;
19
+ return thousands % 1 === 0 ? `${thousands}K` : `${thousands.toFixed(1)}K`;
20
+ }
21
+ return count.toString();
22
+ }
23
+
24
+ /**
25
+ * List available models, optionally filtered by search pattern
26
+ */
27
+ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: string): Promise<void> {
28
+ const models = await modelRegistry.getAvailable();
29
+
30
+ if (models.length === 0) {
31
+ console.log("No models available. Set API keys in environment variables.");
32
+ return;
33
+ }
34
+
35
+ // Apply fuzzy filter if search pattern provided
36
+ let filteredModels: Model<Api>[] = models;
37
+ if (searchPattern) {
38
+ filteredModels = fuzzyFilter(models, searchPattern, (m) => `${m.provider} ${m.id}`);
39
+ }
40
+
41
+ if (filteredModels.length === 0) {
42
+ console.log(`No models matching "${searchPattern}"`);
43
+ return;
44
+ }
45
+
46
+ // Sort by provider, then by model id
47
+ filteredModels.sort((a, b) => {
48
+ const providerCmp = a.provider.localeCompare(b.provider);
49
+ if (providerCmp !== 0) return providerCmp;
50
+ return a.id.localeCompare(b.id);
51
+ });
52
+
53
+ // Calculate column widths
54
+ const rows = filteredModels.map((m) => ({
55
+ provider: m.provider,
56
+ model: m.id,
57
+ context: formatTokenCount(m.contextWindow),
58
+ maxOut: formatTokenCount(m.maxTokens),
59
+ thinking: m.reasoning ? "yes" : "no",
60
+ images: m.input.includes("image") ? "yes" : "no",
61
+ }));
62
+
63
+ const headers = {
64
+ provider: "provider",
65
+ model: "model",
66
+ context: "context",
67
+ maxOut: "max-out",
68
+ thinking: "thinking",
69
+ images: "images",
70
+ };
71
+
72
+ const widths = {
73
+ provider: Math.max(headers.provider.length, ...rows.map((r) => r.provider.length)),
74
+ model: Math.max(headers.model.length, ...rows.map((r) => r.model.length)),
75
+ context: Math.max(headers.context.length, ...rows.map((r) => r.context.length)),
76
+ maxOut: Math.max(headers.maxOut.length, ...rows.map((r) => r.maxOut.length)),
77
+ thinking: Math.max(headers.thinking.length, ...rows.map((r) => r.thinking.length)),
78
+ images: Math.max(headers.images.length, ...rows.map((r) => r.images.length)),
79
+ };
80
+
81
+ // Print header
82
+ const headerLine = [
83
+ headers.provider.padEnd(widths.provider),
84
+ headers.model.padEnd(widths.model),
85
+ headers.context.padEnd(widths.context),
86
+ headers.maxOut.padEnd(widths.maxOut),
87
+ headers.thinking.padEnd(widths.thinking),
88
+ headers.images.padEnd(widths.images),
89
+ ].join(" ");
90
+ console.log(headerLine);
91
+
92
+ // Print rows
93
+ for (const row of rows) {
94
+ const line = [
95
+ row.provider.padEnd(widths.provider),
96
+ row.model.padEnd(widths.model),
97
+ row.context.padEnd(widths.context),
98
+ row.maxOut.padEnd(widths.maxOut),
99
+ row.thinking.padEnd(widths.thinking),
100
+ row.images.padEnd(widths.images),
101
+ ].join(" ");
102
+ console.log(line);
103
+ }
104
+ }