@oh-my-pi/pi-coding-agent 1.337.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 (224) hide show
  1. package/CHANGELOG.md +1228 -0
  2. package/README.md +1041 -0
  3. package/docs/compaction.md +403 -0
  4. package/docs/custom-tools.md +541 -0
  5. package/docs/extension-loading.md +1004 -0
  6. package/docs/hooks.md +867 -0
  7. package/docs/rpc.md +1040 -0
  8. package/docs/sdk.md +994 -0
  9. package/docs/session-tree-plan.md +441 -0
  10. package/docs/session.md +240 -0
  11. package/docs/skills.md +290 -0
  12. package/docs/theme.md +637 -0
  13. package/docs/tree.md +197 -0
  14. package/docs/tui.md +341 -0
  15. package/examples/README.md +21 -0
  16. package/examples/custom-tools/README.md +124 -0
  17. package/examples/custom-tools/hello/index.ts +20 -0
  18. package/examples/custom-tools/question/index.ts +84 -0
  19. package/examples/custom-tools/subagent/README.md +172 -0
  20. package/examples/custom-tools/subagent/agents/planner.md +37 -0
  21. package/examples/custom-tools/subagent/agents/reviewer.md +35 -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 +81 -0
  57. package/src/cli/args.ts +246 -0
  58. package/src/cli/file-processor.ts +72 -0
  59. package/src/cli/list-models.ts +104 -0
  60. package/src/cli/plugin-cli.ts +650 -0
  61. package/src/cli/session-picker.ts +41 -0
  62. package/src/cli.ts +10 -0
  63. package/src/commands/init.md +20 -0
  64. package/src/config.ts +159 -0
  65. package/src/core/agent-session.ts +1900 -0
  66. package/src/core/auth-storage.ts +236 -0
  67. package/src/core/bash-executor.ts +196 -0
  68. package/src/core/compaction/branch-summarization.ts +343 -0
  69. package/src/core/compaction/compaction.ts +742 -0
  70. package/src/core/compaction/index.ts +7 -0
  71. package/src/core/compaction/utils.ts +154 -0
  72. package/src/core/custom-tools/index.ts +21 -0
  73. package/src/core/custom-tools/loader.ts +248 -0
  74. package/src/core/custom-tools/types.ts +169 -0
  75. package/src/core/custom-tools/wrapper.ts +28 -0
  76. package/src/core/exec.ts +129 -0
  77. package/src/core/export-html/index.ts +211 -0
  78. package/src/core/export-html/template.css +781 -0
  79. package/src/core/export-html/template.html +54 -0
  80. package/src/core/export-html/template.js +1185 -0
  81. package/src/core/export-html/vendor/highlight.min.js +1213 -0
  82. package/src/core/export-html/vendor/marked.min.js +6 -0
  83. package/src/core/hooks/index.ts +16 -0
  84. package/src/core/hooks/loader.ts +312 -0
  85. package/src/core/hooks/runner.ts +434 -0
  86. package/src/core/hooks/tool-wrapper.ts +99 -0
  87. package/src/core/hooks/types.ts +773 -0
  88. package/src/core/index.ts +52 -0
  89. package/src/core/mcp/client.ts +158 -0
  90. package/src/core/mcp/config.ts +154 -0
  91. package/src/core/mcp/index.ts +45 -0
  92. package/src/core/mcp/loader.ts +68 -0
  93. package/src/core/mcp/manager.ts +181 -0
  94. package/src/core/mcp/tool-bridge.ts +148 -0
  95. package/src/core/mcp/transports/http.ts +316 -0
  96. package/src/core/mcp/transports/index.ts +6 -0
  97. package/src/core/mcp/transports/stdio.ts +252 -0
  98. package/src/core/mcp/types.ts +220 -0
  99. package/src/core/messages.ts +189 -0
  100. package/src/core/model-registry.ts +317 -0
  101. package/src/core/model-resolver.ts +393 -0
  102. package/src/core/plugins/doctor.ts +59 -0
  103. package/src/core/plugins/index.ts +38 -0
  104. package/src/core/plugins/installer.ts +189 -0
  105. package/src/core/plugins/loader.ts +338 -0
  106. package/src/core/plugins/manager.ts +672 -0
  107. package/src/core/plugins/parser.ts +105 -0
  108. package/src/core/plugins/paths.ts +32 -0
  109. package/src/core/plugins/types.ts +190 -0
  110. package/src/core/sdk.ts +760 -0
  111. package/src/core/session-manager.ts +1128 -0
  112. package/src/core/settings-manager.ts +443 -0
  113. package/src/core/skills.ts +437 -0
  114. package/src/core/slash-commands.ts +248 -0
  115. package/src/core/system-prompt.ts +439 -0
  116. package/src/core/timings.ts +25 -0
  117. package/src/core/tools/ask.ts +211 -0
  118. package/src/core/tools/bash-interceptor.ts +120 -0
  119. package/src/core/tools/bash.ts +250 -0
  120. package/src/core/tools/context.ts +32 -0
  121. package/src/core/tools/edit-diff.ts +475 -0
  122. package/src/core/tools/edit.ts +208 -0
  123. package/src/core/tools/exa/company.ts +59 -0
  124. package/src/core/tools/exa/index.ts +64 -0
  125. package/src/core/tools/exa/linkedin.ts +59 -0
  126. package/src/core/tools/exa/logger.ts +56 -0
  127. package/src/core/tools/exa/mcp-client.ts +368 -0
  128. package/src/core/tools/exa/render.ts +196 -0
  129. package/src/core/tools/exa/researcher.ts +90 -0
  130. package/src/core/tools/exa/search.ts +337 -0
  131. package/src/core/tools/exa/types.ts +168 -0
  132. package/src/core/tools/exa/websets.ts +248 -0
  133. package/src/core/tools/find.ts +261 -0
  134. package/src/core/tools/grep.ts +555 -0
  135. package/src/core/tools/index.ts +202 -0
  136. package/src/core/tools/ls.ts +140 -0
  137. package/src/core/tools/lsp/client.ts +605 -0
  138. package/src/core/tools/lsp/config.ts +147 -0
  139. package/src/core/tools/lsp/edits.ts +101 -0
  140. package/src/core/tools/lsp/index.ts +804 -0
  141. package/src/core/tools/lsp/render.ts +447 -0
  142. package/src/core/tools/lsp/rust-analyzer.ts +145 -0
  143. package/src/core/tools/lsp/types.ts +463 -0
  144. package/src/core/tools/lsp/utils.ts +486 -0
  145. package/src/core/tools/notebook.ts +229 -0
  146. package/src/core/tools/path-utils.ts +61 -0
  147. package/src/core/tools/read.ts +240 -0
  148. package/src/core/tools/renderers.ts +540 -0
  149. package/src/core/tools/task/agents.ts +153 -0
  150. package/src/core/tools/task/artifacts.ts +114 -0
  151. package/src/core/tools/task/bundled-agents/browser.md +71 -0
  152. package/src/core/tools/task/bundled-agents/explore.md +82 -0
  153. package/src/core/tools/task/bundled-agents/plan.md +54 -0
  154. package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
  155. package/src/core/tools/task/bundled-agents/task.md +53 -0
  156. package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
  157. package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
  158. package/src/core/tools/task/bundled-commands/implement.md +11 -0
  159. package/src/core/tools/task/commands.ts +213 -0
  160. package/src/core/tools/task/discovery.ts +208 -0
  161. package/src/core/tools/task/executor.ts +367 -0
  162. package/src/core/tools/task/index.ts +388 -0
  163. package/src/core/tools/task/model-resolver.ts +115 -0
  164. package/src/core/tools/task/parallel.ts +38 -0
  165. package/src/core/tools/task/render.ts +232 -0
  166. package/src/core/tools/task/types.ts +99 -0
  167. package/src/core/tools/truncate.ts +265 -0
  168. package/src/core/tools/web-fetch.ts +2370 -0
  169. package/src/core/tools/web-search/auth.ts +193 -0
  170. package/src/core/tools/web-search/index.ts +537 -0
  171. package/src/core/tools/web-search/providers/anthropic.ts +198 -0
  172. package/src/core/tools/web-search/providers/exa.ts +302 -0
  173. package/src/core/tools/web-search/providers/perplexity.ts +195 -0
  174. package/src/core/tools/web-search/render.ts +182 -0
  175. package/src/core/tools/web-search/types.ts +180 -0
  176. package/src/core/tools/write.ts +99 -0
  177. package/src/index.ts +176 -0
  178. package/src/main.ts +464 -0
  179. package/src/migrations.ts +135 -0
  180. package/src/modes/index.ts +43 -0
  181. package/src/modes/interactive/components/armin.ts +382 -0
  182. package/src/modes/interactive/components/assistant-message.ts +86 -0
  183. package/src/modes/interactive/components/bash-execution.ts +196 -0
  184. package/src/modes/interactive/components/bordered-loader.ts +41 -0
  185. package/src/modes/interactive/components/branch-summary-message.ts +42 -0
  186. package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
  187. package/src/modes/interactive/components/custom-editor.ts +122 -0
  188. package/src/modes/interactive/components/diff.ts +147 -0
  189. package/src/modes/interactive/components/dynamic-border.ts +25 -0
  190. package/src/modes/interactive/components/footer.ts +381 -0
  191. package/src/modes/interactive/components/hook-editor.ts +117 -0
  192. package/src/modes/interactive/components/hook-input.ts +64 -0
  193. package/src/modes/interactive/components/hook-message.ts +96 -0
  194. package/src/modes/interactive/components/hook-selector.ts +91 -0
  195. package/src/modes/interactive/components/model-selector.ts +247 -0
  196. package/src/modes/interactive/components/oauth-selector.ts +120 -0
  197. package/src/modes/interactive/components/plugin-settings.ts +479 -0
  198. package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
  199. package/src/modes/interactive/components/session-selector.ts +204 -0
  200. package/src/modes/interactive/components/settings-selector.ts +453 -0
  201. package/src/modes/interactive/components/show-images-selector.ts +45 -0
  202. package/src/modes/interactive/components/theme-selector.ts +62 -0
  203. package/src/modes/interactive/components/thinking-selector.ts +64 -0
  204. package/src/modes/interactive/components/tool-execution.ts +675 -0
  205. package/src/modes/interactive/components/tree-selector.ts +866 -0
  206. package/src/modes/interactive/components/user-message-selector.ts +159 -0
  207. package/src/modes/interactive/components/user-message.ts +18 -0
  208. package/src/modes/interactive/components/visual-truncate.ts +50 -0
  209. package/src/modes/interactive/components/welcome.ts +183 -0
  210. package/src/modes/interactive/interactive-mode.ts +2516 -0
  211. package/src/modes/interactive/theme/dark.json +101 -0
  212. package/src/modes/interactive/theme/light.json +98 -0
  213. package/src/modes/interactive/theme/theme-schema.json +308 -0
  214. package/src/modes/interactive/theme/theme.ts +998 -0
  215. package/src/modes/print-mode.ts +128 -0
  216. package/src/modes/rpc/rpc-client.ts +527 -0
  217. package/src/modes/rpc/rpc-mode.ts +483 -0
  218. package/src/modes/rpc/rpc-types.ts +203 -0
  219. package/src/utils/changelog.ts +99 -0
  220. package/src/utils/clipboard.ts +265 -0
  221. package/src/utils/fuzzy.ts +108 -0
  222. package/src/utils/mime.ts +30 -0
  223. package/src/utils/shell.ts +276 -0
  224. package/src/utils/tools-manager.ts +274 -0
@@ -0,0 +1,154 @@
1
+ # SDK Examples
2
+
3
+ Programmatic usage of pi-coding-agent via `createAgentSession()`.
4
+
5
+ ## Examples
6
+
7
+ | File | Description |
8
+ | -------------------------- | ---------------------------------------------- |
9
+ | `01-minimal.ts` | Simplest usage with all defaults |
10
+ | `02-custom-model.ts` | Select model and thinking level |
11
+ | `03-custom-prompt.ts` | Replace or modify system prompt |
12
+ | `04-skills.ts` | Discover, filter, or replace skills |
13
+ | `05-tools.ts` | Built-in tools, custom tools |
14
+ | `06-hooks.ts` | Logging, blocking, result modification |
15
+ | `07-context-files.ts` | AGENTS.md context files |
16
+ | `08-slash-commands.ts` | File-based slash commands |
17
+ | `09-api-keys-and-oauth.ts` | API key resolution, OAuth config |
18
+ | `10-settings.ts` | Override compaction, retry, terminal settings |
19
+ | `11-sessions.ts` | In-memory, persistent, continue, list sessions |
20
+ | `12-full-control.ts` | Replace everything, no discovery |
21
+
22
+ ## Running
23
+
24
+ ```bash
25
+ cd packages/coding-agent
26
+ npx tsx examples/sdk/01-minimal.ts
27
+ ```
28
+
29
+ ## Quick Reference
30
+
31
+ ```typescript
32
+ import { getModel } from "@oh-my-pi/pi-ai";
33
+ import {
34
+ AuthStorage,
35
+ createAgentSession,
36
+ discoverAuthStorage,
37
+ discoverModels,
38
+ discoverSkills,
39
+ discoverHooks,
40
+ discoverCustomTools,
41
+ discoverContextFiles,
42
+ discoverSlashCommands,
43
+ loadSettings,
44
+ buildSystemPrompt,
45
+ ModelRegistry,
46
+ SessionManager,
47
+ codingTools,
48
+ readOnlyTools,
49
+ readTool,
50
+ bashTool,
51
+ editTool,
52
+ writeTool,
53
+ } from "@oh-my-pi/pi-coding-agent";
54
+
55
+ // Auth and models setup
56
+ const authStorage = discoverAuthStorage();
57
+ const modelRegistry = discoverModels(authStorage);
58
+
59
+ // Minimal
60
+ const { session } = await createAgentSession({ authStorage, modelRegistry });
61
+
62
+ // Custom model
63
+ const model = getModel("anthropic", "claude-opus-4-5");
64
+ const { session } = await createAgentSession({ model, thinkingLevel: "high", authStorage, modelRegistry });
65
+
66
+ // Modify prompt
67
+ const { session } = await createAgentSession({
68
+ systemPrompt: (defaultPrompt) => defaultPrompt + "\n\nBe concise.",
69
+ authStorage,
70
+ modelRegistry,
71
+ });
72
+
73
+ // Read-only
74
+ const { session } = await createAgentSession({ tools: readOnlyTools, authStorage, modelRegistry });
75
+
76
+ // In-memory
77
+ const { session } = await createAgentSession({
78
+ sessionManager: SessionManager.inMemory(),
79
+ authStorage,
80
+ modelRegistry,
81
+ });
82
+
83
+ // Full control
84
+ const customAuth = new AuthStorage("/my/app/auth.json");
85
+ customAuth.setRuntimeApiKey("anthropic", process.env.MY_KEY!);
86
+ const customRegistry = new ModelRegistry(customAuth);
87
+
88
+ const { session } = await createAgentSession({
89
+ model,
90
+ authStorage: customAuth,
91
+ modelRegistry: customRegistry,
92
+ systemPrompt: "You are helpful.",
93
+ tools: [readTool, bashTool],
94
+ customTools: [{ tool: myTool }],
95
+ hooks: [{ factory: myHook }],
96
+ skills: [],
97
+ contextFiles: [],
98
+ slashCommands: [],
99
+ sessionManager: SessionManager.inMemory(),
100
+ });
101
+
102
+ // Run prompts
103
+ session.subscribe((event) => {
104
+ if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
105
+ process.stdout.write(event.assistantMessageEvent.delta);
106
+ }
107
+ });
108
+ await session.prompt("Hello");
109
+ ```
110
+
111
+ ## Options
112
+
113
+ | Option | Default | Description |
114
+ | --------------------------- | ----------------------------- | --------------------------------- |
115
+ | `authStorage` | `discoverAuthStorage()` | Credential storage |
116
+ | `modelRegistry` | `discoverModels(authStorage)` | Model registry |
117
+ | `cwd` | `process.cwd()` | Working directory |
118
+ | `agentDir` | `~/.pi/agent` | Config directory |
119
+ | `model` | From settings/first available | Model to use |
120
+ | `thinkingLevel` | From settings/"off" | off, low, medium, high |
121
+ | `systemPrompt` | Discovered | String or `(default) => modified` |
122
+ | `tools` | `codingTools` | Built-in tools |
123
+ | `customTools` | Discovered | Replaces discovery |
124
+ | `additionalCustomToolPaths` | `[]` | Merge with discovery |
125
+ | `hooks` | Discovered | Replaces discovery |
126
+ | `additionalHookPaths` | `[]` | Merge with discovery |
127
+ | `skills` | Discovered | Skills for prompt |
128
+ | `contextFiles` | Discovered | AGENTS.md files |
129
+ | `slashCommands` | Discovered | File commands |
130
+ | `sessionManager` | `SessionManager.create(cwd)` | Persistence |
131
+ | `settingsManager` | From agentDir | Settings overrides |
132
+
133
+ ## Events
134
+
135
+ ```typescript
136
+ session.subscribe((event) => {
137
+ switch (event.type) {
138
+ case "message_update":
139
+ if (event.assistantMessageEvent.type === "text_delta") {
140
+ process.stdout.write(event.assistantMessageEvent.delta);
141
+ }
142
+ break;
143
+ case "tool_execution_start":
144
+ console.log(`Tool: ${event.toolName}`);
145
+ break;
146
+ case "tool_execution_end":
147
+ console.log(`Result: ${event.result}`);
148
+ break;
149
+ case "agent_end":
150
+ console.log("Done");
151
+ break;
152
+ }
153
+ });
154
+ ```
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@oh-my-pi/pi-coding-agent",
3
+ "version": "1.337.0",
4
+ "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
+ "type": "module",
6
+ "piConfig": {
7
+ "name": "pi",
8
+ "configDir": ".pi"
9
+ },
10
+ "bin": {
11
+ "pi": "src/cli.ts"
12
+ },
13
+ "main": "./src/index.ts",
14
+ "types": "./src/index.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./src/index.ts",
18
+ "import": "./src/index.ts"
19
+ },
20
+ "./hooks": {
21
+ "types": "./src/core/hooks/index.ts",
22
+ "import": "./src/core/hooks/index.ts"
23
+ }
24
+ },
25
+ "files": [
26
+ "src",
27
+ "docs",
28
+ "examples",
29
+ "CHANGELOG.md"
30
+ ],
31
+ "scripts": {
32
+ "check": "tsgo --noEmit",
33
+ "clean": "rm -rf dist",
34
+ "build": "tsgo -p tsconfig.build.json && chmod +x dist/cli.js && npm run copy-assets",
35
+ "build:binary": "npm run build && bun build --compile ./dist/cli.js --outfile dist/pi && npm run copy-binary-assets",
36
+ "copy-assets": "mkdir -p dist/modes/interactive/theme && cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && mkdir -p dist/core/export-html/vendor && cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
37
+ "copy-binary-assets": "cp package.json dist/ && cp README.md dist/ && cp CHANGELOG.md dist/ && mkdir -p dist/theme && cp src/modes/interactive/theme/*.json dist/theme/ && mkdir -p dist/export-html/vendor && cp src/core/export-html/template.html dist/export-html/ && cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && cp -r docs dist/ && cp -r examples dist/",
38
+ "test": "vitest --run",
39
+ "prepublishOnly": "npm run clean && npm run build"
40
+ },
41
+ "dependencies": {
42
+ "@oh-my-pi/pi-agent-core": "workspace:*",
43
+ "@oh-my-pi/pi-ai": "workspace:*",
44
+ "@oh-my-pi/pi-tui": "workspace:*",
45
+ "@sinclair/typebox": "^0.34.46",
46
+ "ajv": "^8.17.1",
47
+ "chalk": "^5.5.0",
48
+ "node-html-parser": "^6.1.13",
49
+ "cli-highlight": "^2.1.11",
50
+ "diff": "^8.0.2",
51
+ "file-type": "^21.1.1",
52
+ "glob": "^11.0.3",
53
+ "highlight.js": "^11.11.1",
54
+ "marked": "^15.0.12",
55
+ "minimatch": "^10.1.1",
56
+ "strip-ansi": "^7.1.2"
57
+ },
58
+ "devDependencies": {
59
+ "@types/diff": "^7.0.2",
60
+ "@types/node": "^24.3.0",
61
+ "vitest": "^3.2.4"
62
+ },
63
+ "keywords": [
64
+ "coding-agent",
65
+ "ai",
66
+ "llm",
67
+ "cli",
68
+ "tui",
69
+ "agent"
70
+ ],
71
+ "author": "Mario Zechner",
72
+ "license": "MIT",
73
+ "repository": {
74
+ "type": "git",
75
+ "url": "git+https://github.com/can1357/oh-my-pi.git",
76
+ "directory": "packages/coding-agent"
77
+ },
78
+ "engines": {
79
+ "bun": ">=1.0.0"
80
+ }
81
+ }
@@ -0,0 +1,246 @@
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.js";
8
+ import { allTools, type ToolName } from "../core/tools/index.js";
9
+
10
+ export type Mode = "text" | "json" | "rpc";
11
+
12
+ export interface Args {
13
+ provider?: string;
14
+ model?: string;
15
+ apiKey?: string;
16
+ systemPrompt?: string;
17
+ appendSystemPrompt?: string;
18
+ thinking?: ThinkingLevel;
19
+ continue?: boolean;
20
+ resume?: boolean;
21
+ help?: boolean;
22
+ version?: boolean;
23
+ mode?: Mode;
24
+ noSession?: boolean;
25
+ session?: string;
26
+ sessionDir?: string;
27
+ models?: string[];
28
+ tools?: ToolName[];
29
+ hooks?: string[];
30
+ customTools?: string[];
31
+ print?: boolean;
32
+ export?: string;
33
+ noSkills?: boolean;
34
+ skills?: string[];
35
+ listModels?: string | true;
36
+ messages: string[];
37
+ fileArgs: string[];
38
+ }
39
+
40
+ const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
41
+
42
+ export function isValidThinkingLevel(level: string): level is ThinkingLevel {
43
+ return VALID_THINKING_LEVELS.includes(level as ThinkingLevel);
44
+ }
45
+
46
+ export function parseArgs(args: string[]): Args {
47
+ const result: Args = {
48
+ messages: [],
49
+ fileArgs: [],
50
+ };
51
+
52
+ for (let i = 0; i < args.length; i++) {
53
+ const arg = args[i];
54
+
55
+ if (arg === "--help" || arg === "-h") {
56
+ result.help = true;
57
+ } else if (arg === "--version" || arg === "-v") {
58
+ result.version = true;
59
+ } else if (arg === "--mode" && i + 1 < args.length) {
60
+ const mode = args[++i];
61
+ if (mode === "text" || mode === "json" || mode === "rpc") {
62
+ result.mode = mode;
63
+ }
64
+ } else if (arg === "--continue" || arg === "-c") {
65
+ result.continue = true;
66
+ } else if (arg === "--resume" || arg === "-r") {
67
+ result.resume = true;
68
+ } else if (arg === "--provider" && i + 1 < args.length) {
69
+ result.provider = args[++i];
70
+ } else if (arg === "--model" && i + 1 < args.length) {
71
+ result.model = args[++i];
72
+ } else if (arg === "--api-key" && i + 1 < args.length) {
73
+ result.apiKey = args[++i];
74
+ } else if (arg === "--system-prompt" && i + 1 < args.length) {
75
+ result.systemPrompt = args[++i];
76
+ } else if (arg === "--append-system-prompt" && i + 1 < args.length) {
77
+ result.appendSystemPrompt = args[++i];
78
+ } else if (arg === "--no-session") {
79
+ result.noSession = true;
80
+ } else if (arg === "--session" && i + 1 < args.length) {
81
+ result.session = args[++i];
82
+ } else if (arg === "--session-dir" && i + 1 < args.length) {
83
+ result.sessionDir = args[++i];
84
+ } else if (arg === "--models" && i + 1 < args.length) {
85
+ result.models = args[++i].split(",").map((s) => s.trim());
86
+ } else if (arg === "--tools" && i + 1 < args.length) {
87
+ const toolNames = args[++i].split(",").map((s) => s.trim());
88
+ const validTools: ToolName[] = [];
89
+ for (const name of toolNames) {
90
+ if (name in allTools) {
91
+ validTools.push(name as ToolName);
92
+ } else {
93
+ console.error(
94
+ chalk.yellow(`Warning: Unknown tool "${name}". Valid tools: ${Object.keys(allTools).join(", ")}`),
95
+ );
96
+ }
97
+ }
98
+ result.tools = validTools;
99
+ } else if (arg === "--thinking" && i + 1 < args.length) {
100
+ const level = args[++i];
101
+ if (isValidThinkingLevel(level)) {
102
+ result.thinking = level;
103
+ } else {
104
+ console.error(
105
+ chalk.yellow(
106
+ `Warning: Invalid thinking level "${level}". Valid values: ${VALID_THINKING_LEVELS.join(", ")}`,
107
+ ),
108
+ );
109
+ }
110
+ } else if (arg === "--print" || arg === "-p") {
111
+ result.print = true;
112
+ } else if (arg === "--export" && i + 1 < args.length) {
113
+ result.export = args[++i];
114
+ } else if (arg === "--hook" && i + 1 < args.length) {
115
+ result.hooks = result.hooks ?? [];
116
+ result.hooks.push(args[++i]);
117
+ } else if (arg === "--tool" && i + 1 < args.length) {
118
+ result.customTools = result.customTools ?? [];
119
+ result.customTools.push(args[++i]);
120
+ } else if (arg === "--no-skills") {
121
+ result.noSkills = true;
122
+ } else if (arg === "--skills" && i + 1 < args.length) {
123
+ // Comma-separated glob patterns for skill filtering
124
+ result.skills = args[++i].split(",").map((s) => s.trim());
125
+ } else if (arg === "--list-models") {
126
+ // Check if next arg is a search pattern (not a flag or file arg)
127
+ if (i + 1 < args.length && !args[i + 1].startsWith("-") && !args[i + 1].startsWith("@")) {
128
+ result.listModels = args[++i];
129
+ } else {
130
+ result.listModels = true;
131
+ }
132
+ } else if (arg.startsWith("@")) {
133
+ result.fileArgs.push(arg.slice(1)); // Remove @ prefix
134
+ } else if (!arg.startsWith("-")) {
135
+ result.messages.push(arg);
136
+ }
137
+ }
138
+
139
+ return result;
140
+ }
141
+
142
+ export function printHelp(): void {
143
+ console.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools
144
+
145
+ ${chalk.bold("Usage:")}
146
+ ${APP_NAME} [options] [@files...] [messages...]
147
+
148
+ ${chalk.bold("Options:")}
149
+ --provider <name> Provider name (default: google)
150
+ --model <id> Model ID (default: gemini-2.5-flash)
151
+ --api-key <key> API key (defaults to env vars)
152
+ --system-prompt <text> System prompt (default: coding assistant prompt)
153
+ --append-system-prompt <text> Append text or file contents to the system prompt
154
+ --mode <mode> Output mode: text (default), json, or rpc
155
+ --print, -p Non-interactive mode: process prompt and exit
156
+ --continue, -c Continue previous session
157
+ --resume, -r Select a session to resume
158
+ --session <path> Use specific session file
159
+ --session-dir <dir> Directory for session storage and lookup
160
+ --no-session Don't save session (ephemeral)
161
+ --models <patterns> Comma-separated model patterns for Ctrl+P cycling
162
+ Supports globs (anthropic/*, *sonnet*) and fuzzy matching
163
+ --tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)
164
+ Available: read, bash, edit, write, grep, find, ls
165
+ --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
166
+ --hook <path> Load a hook file (can be used multiple times)
167
+ --tool <path> Load a custom tool file (can be used multiple times)
168
+ --no-skills Disable skills discovery and loading
169
+ --skills <patterns> Comma-separated glob patterns to filter skills (e.g., git-*,docker)
170
+ --export <file> Export session file to HTML and exit
171
+ --list-models [search] List available models (with optional fuzzy search)
172
+ --help, -h Show this help
173
+ --version, -v Show version number
174
+
175
+ ${chalk.bold("Examples:")}
176
+ # Interactive mode
177
+ ${APP_NAME}
178
+
179
+ # Interactive mode with initial prompt
180
+ ${APP_NAME} "List all .ts files in src/"
181
+
182
+ # Include files in initial message
183
+ ${APP_NAME} @prompt.md @image.png "What color is the sky?"
184
+
185
+ # Non-interactive mode (process and exit)
186
+ ${APP_NAME} -p "List all .ts files in src/"
187
+
188
+ # Multiple messages (interactive)
189
+ ${APP_NAME} "Read package.json" "What dependencies do we have?"
190
+
191
+ # Continue previous session
192
+ ${APP_NAME} --continue "What did we discuss?"
193
+
194
+ # Use different model
195
+ ${APP_NAME} --provider openai --model gpt-4o-mini "Help me refactor this code"
196
+
197
+ # Limit model cycling to specific models
198
+ ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o
199
+
200
+ # Limit to a specific provider with glob pattern
201
+ ${APP_NAME} --models "github-copilot/*"
202
+
203
+ # Cycle models with fixed thinking levels
204
+ ${APP_NAME} --models sonnet:high,haiku:low
205
+
206
+ # Start with a specific thinking level
207
+ ${APP_NAME} --thinking high "Solve this complex problem"
208
+
209
+ # Read-only mode (no file modifications possible)
210
+ ${APP_NAME} --tools read,grep,find,ls -p "Review the code in src/"
211
+
212
+ # Export a session file to HTML
213
+ ${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl
214
+ ${APP_NAME} --export session.jsonl output.html
215
+
216
+ ${chalk.bold("Environment Variables:")}
217
+ ${chalk.dim("# Model providers")}
218
+ ANTHROPIC_API_KEY - Anthropic Claude API key
219
+ ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)
220
+ OPENAI_API_KEY - OpenAI GPT API key
221
+ GEMINI_API_KEY - Google Gemini API key
222
+ GROQ_API_KEY - Groq API key
223
+ CEREBRAS_API_KEY - Cerebras API key
224
+ XAI_API_KEY - xAI Grok API key
225
+ OPENROUTER_API_KEY - OpenRouter API key
226
+ MISTRAL_API_KEY - Mistral API key
227
+ ZAI_API_KEY - ZAI API key
228
+ GITHUB_TOKEN - GitHub Copilot models (or GH_TOKEN, COPILOT_GITHUB_TOKEN)
229
+
230
+ ${chalk.dim("# Web search providers")}
231
+ EXA_API_KEY - Exa search API key
232
+ PERPLEXITY_API_KEY - Perplexity search API key
233
+
234
+ ${chalk.dim("# Configuration")}
235
+ ${ENV_AGENT_DIR.padEnd(23)} - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
236
+
237
+ ${chalk.bold("Available Tools (default: read, bash, edit, write):")}
238
+ read - Read file contents
239
+ bash - Execute bash commands
240
+ edit - Edit files with find/replace
241
+ write - Write files (creates/overwrites)
242
+ grep - Search file contents (read-only, off by default)
243
+ find - Find files by glob pattern (read-only, off by default)
244
+ ls - List directory contents (read-only, off by default)
245
+ `);
246
+ }
@@ -0,0 +1,72 @@
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 type { ImageContent } from "@oh-my-pi/pi-ai";
7
+ import chalk from "chalk";
8
+ import { resolve } from "path";
9
+ import { resolveReadPath } from "../core/tools/path-utils.js";
10
+ import { detectSupportedImageMimeTypeFromFile } from "../utils/mime.js";
11
+
12
+ export interface ProcessedFiles {
13
+ text: string;
14
+ images: ImageContent[];
15
+ }
16
+
17
+ /** Process @file arguments into text content and image attachments */
18
+ export async function processFileArguments(fileArgs: string[]): Promise<ProcessedFiles> {
19
+ let text = "";
20
+ const images: ImageContent[] = [];
21
+
22
+ for (const fileArg of fileArgs) {
23
+ // Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)
24
+ const absolutePath = resolve(resolveReadPath(fileArg, process.cwd()));
25
+
26
+ // Check if file exists
27
+ try {
28
+ await access(absolutePath);
29
+ } catch {
30
+ console.error(chalk.red(`Error: File not found: ${absolutePath}`));
31
+ process.exit(1);
32
+ }
33
+
34
+ // Check if file is empty
35
+ const stats = await stat(absolutePath);
36
+ if (stats.size === 0) {
37
+ // Skip empty files
38
+ continue;
39
+ }
40
+
41
+ const mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);
42
+
43
+ if (mimeType) {
44
+ // Handle image file
45
+ const content = await readFile(absolutePath);
46
+ const base64Content = content.toString("base64");
47
+
48
+ const attachment: ImageContent = {
49
+ type: "image",
50
+ mimeType,
51
+ data: base64Content,
52
+ };
53
+
54
+ images.push(attachment);
55
+
56
+ // Add text reference to image
57
+ text += `<file name="${absolutePath}"></file>\n`;
58
+ } else {
59
+ // Handle text file
60
+ try {
61
+ const content = await readFile(absolutePath, "utf-8");
62
+ text += `<file name="${absolutePath}">\n${content}\n</file>\n`;
63
+ } catch (error: unknown) {
64
+ const message = error instanceof Error ? error.message : String(error);
65
+ console.error(chalk.red(`Error: Could not read file ${absolutePath}: ${message}`));
66
+ process.exit(1);
67
+ }
68
+ }
69
+ }
70
+
71
+ return { text, images };
72
+ }
@@ -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.js";
7
+ import { fuzzyFilter } from "../utils/fuzzy.js";
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
+ }