@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.
- package/CHANGELOG.md +1228 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +637 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +81 -0
- package/src/cli/args.ts +246 -0
- package/src/cli/file-processor.ts +72 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +650 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli.ts +10 -0
- package/src/commands/init.md +20 -0
- package/src/config.ts +159 -0
- package/src/core/agent-session.ts +1900 -0
- package/src/core/auth-storage.ts +236 -0
- package/src/core/bash-executor.ts +196 -0
- package/src/core/compaction/branch-summarization.ts +343 -0
- package/src/core/compaction/compaction.ts +742 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +154 -0
- package/src/core/custom-tools/index.ts +21 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +169 -0
- package/src/core/custom-tools/wrapper.ts +28 -0
- package/src/core/exec.ts +129 -0
- package/src/core/export-html/index.ts +211 -0
- package/src/core/export-html/template.css +781 -0
- package/src/core/export-html/template.html +54 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +312 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +99 -0
- package/src/core/hooks/types.ts +773 -0
- package/src/core/index.ts +52 -0
- package/src/core/mcp/client.ts +158 -0
- package/src/core/mcp/config.ts +154 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +68 -0
- package/src/core/mcp/manager.ts +181 -0
- package/src/core/mcp/tool-bridge.ts +148 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +220 -0
- package/src/core/messages.ts +189 -0
- package/src/core/model-registry.ts +317 -0
- package/src/core/model-resolver.ts +393 -0
- package/src/core/plugins/doctor.ts +59 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +338 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +32 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +760 -0
- package/src/core/session-manager.ts +1128 -0
- package/src/core/settings-manager.ts +443 -0
- package/src/core/skills.ts +437 -0
- package/src/core/slash-commands.ts +248 -0
- package/src/core/system-prompt.ts +439 -0
- package/src/core/timings.ts +25 -0
- package/src/core/tools/ask.ts +211 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +250 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +475 -0
- package/src/core/tools/edit.ts +208 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +64 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/logger.ts +56 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +196 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +337 -0
- package/src/core/tools/exa/types.ts +168 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +261 -0
- package/src/core/tools/grep.ts +555 -0
- package/src/core/tools/index.ts +202 -0
- package/src/core/tools/ls.ts +140 -0
- package/src/core/tools/lsp/client.ts +605 -0
- package/src/core/tools/lsp/config.ts +147 -0
- package/src/core/tools/lsp/edits.ts +101 -0
- package/src/core/tools/lsp/index.ts +804 -0
- package/src/core/tools/lsp/render.ts +447 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +463 -0
- package/src/core/tools/lsp/utils.ts +486 -0
- package/src/core/tools/notebook.ts +229 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +240 -0
- package/src/core/tools/renderers.ts +540 -0
- package/src/core/tools/task/agents.ts +153 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/bundled-agents/browser.md +71 -0
- package/src/core/tools/task/bundled-agents/explore.md +82 -0
- package/src/core/tools/task/bundled-agents/plan.md +54 -0
- package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
- package/src/core/tools/task/bundled-agents/task.md +53 -0
- package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
- package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
- package/src/core/tools/task/bundled-commands/implement.md +11 -0
- package/src/core/tools/task/commands.ts +213 -0
- package/src/core/tools/task/discovery.ts +208 -0
- package/src/core/tools/task/executor.ts +367 -0
- package/src/core/tools/task/index.ts +388 -0
- package/src/core/tools/task/model-resolver.ts +115 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +232 -0
- package/src/core/tools/task/types.ts +99 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2370 -0
- package/src/core/tools/web-search/auth.ts +193 -0
- package/src/core/tools/web-search/index.ts +537 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +302 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +182 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +99 -0
- package/src/index.ts +176 -0
- package/src/main.ts +464 -0
- package/src/migrations.ts +135 -0
- package/src/modes/index.ts +43 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +196 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/footer.ts +381 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +247 -0
- package/src/modes/interactive/components/oauth-selector.ts +120 -0
- package/src/modes/interactive/components/plugin-settings.ts +479 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +204 -0
- package/src/modes/interactive/components/settings-selector.ts +453 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +675 -0
- package/src/modes/interactive/components/tree-selector.ts +866 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +183 -0
- package/src/modes/interactive/interactive-mode.ts +2516 -0
- package/src/modes/interactive/theme/dark.json +101 -0
- package/src/modes/interactive/theme/light.json +98 -0
- package/src/modes/interactive/theme/theme-schema.json +308 -0
- package/src/modes/interactive/theme/theme.ts +998 -0
- package/src/modes/print-mode.ts +128 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +483 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell.ts +276 -0
- 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";
|