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