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