@mariozechner/pi-coding-agent 0.14.2 → 0.16.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 +18 -0
- package/README.md +415 -1098
- package/dist/cli/args.d.ts +30 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +179 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/file-processor.d.ts +11 -0
- package/dist/cli/file-processor.d.ts.map +1 -0
- package/dist/cli/file-processor.js +82 -0
- package/dist/cli/file-processor.js.map +1 -0
- package/dist/cli/session-picker.d.ts +7 -0
- package/dist/cli/session-picker.d.ts.map +1 -0
- package/dist/cli/session-picker.js +29 -0
- package/dist/cli/session-picker.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -18
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -9
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +287 -0
- package/dist/core/agent-session.d.ts.map +1 -0
- package/dist/core/agent-session.js +735 -0
- package/dist/core/agent-session.js.map +1 -0
- package/dist/core/bash-executor.d.ts +41 -0
- package/dist/core/bash-executor.d.ts.map +1 -0
- package/dist/core/bash-executor.js +132 -0
- package/dist/core/bash-executor.js.map +1 -0
- package/dist/{compaction.d.ts → core/compaction.d.ts} +5 -1
- package/dist/core/compaction.d.ts.map +1 -0
- package/dist/{compaction.js → core/compaction.js} +23 -1
- package/dist/core/compaction.js.map +1 -0
- package/dist/core/export-html.d.ts.map +1 -0
- package/dist/{export-html.js → core/export-html.js} +1 -1
- package/dist/{export-html.d.ts.map → core/export-html.js.map} +1 -1
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/model-config.d.ts.map +1 -0
- package/dist/{model-config.js → core/model-config.js} +1 -1
- package/dist/core/model-config.js.map +1 -0
- package/dist/core/model-resolver.d.ts +48 -0
- package/dist/core/model-resolver.d.ts.map +1 -0
- package/dist/core/model-resolver.js +244 -0
- package/dist/core/model-resolver.js.map +1 -0
- package/dist/core/oauth/anthropic.d.ts.map +1 -0
- package/dist/core/oauth/anthropic.js.map +1 -0
- package/dist/core/oauth/index.d.ts.map +1 -0
- package/dist/{oauth/index.d.ts.map → core/oauth/index.js.map} +1 -1
- package/dist/core/oauth/storage.d.ts.map +1 -0
- package/dist/{oauth → core/oauth}/storage.js +1 -1
- package/dist/core/oauth/storage.js.map +1 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/{session-manager.js → core/session-manager.js} +1 -1
- package/dist/core/session-manager.js.map +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -0
- package/dist/{settings-manager.js → core/settings-manager.js} +1 -1
- package/dist/core/settings-manager.js.map +1 -0
- package/dist/core/slash-commands.d.ts.map +1 -0
- package/dist/{slash-commands.js → core/slash-commands.js} +1 -1
- package/dist/core/slash-commands.js.map +1 -0
- package/dist/core/system-prompt.d.ts +17 -0
- package/dist/core/system-prompt.d.ts.map +1 -0
- package/dist/core/system-prompt.js +203 -0
- package/dist/core/system-prompt.js.map +1 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/{tools → core/tools}/bash.js +1 -1
- package/dist/core/tools/bash.js.map +1 -0
- package/dist/core/tools/edit.d.ts.map +1 -0
- package/dist/core/tools/edit.js.map +1 -0
- package/dist/core/tools/find.d.ts.map +1 -0
- package/dist/{tools → core/tools}/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/{tools → core/tools}/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js.map +1 -0
- package/dist/core/tools/ls.d.ts.map +1 -0
- package/dist/core/tools/ls.js.map +1 -0
- package/dist/core/tools/read.d.ts.map +1 -0
- package/dist/core/tools/read.js.map +1 -0
- package/dist/core/tools/truncate.d.ts.map +1 -0
- package/dist/core/tools/truncate.js.map +1 -0
- package/dist/core/tools/write.d.ts.map +1 -0
- package/dist/core/tools/write.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +176 -1082
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +9 -0
- package/dist/modes/index.d.ts.map +1 -0
- package/dist/modes/index.js +8 -0
- package/dist/modes/index.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/assistant-message.js.map +1 -0
- package/dist/{tui → modes/interactive/components}/bash-execution.d.ts +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
- package/dist/{tui → modes/interactive/components}/bash-execution.js +1 -1
- package/dist/modes/interactive/components/bash-execution.js.map +1 -0
- package/dist/modes/interactive/components/compaction.d.ts.map +1 -0
- package/dist/modes/interactive/components/compaction.js.map +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
- package/dist/modes/interactive/components/custom-editor.js.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -0
- package/dist/{tui → modes/interactive/components}/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -0
- package/dist/{tui → modes/interactive/components}/model-selector.d.ts +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
- package/dist/{tui → modes/interactive/components}/model-selector.js +3 -3
- package/dist/modes/interactive/components/model-selector.js.map +1 -0
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
- package/dist/{tui → modes/interactive/components}/oauth-selector.js +2 -2
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
- package/dist/modes/interactive/components/queue-mode-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/queue-mode-selector.js.map +1 -0
- package/dist/{tui → modes/interactive/components}/session-selector.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
- package/dist/{tui → modes/interactive/components}/session-selector.js +1 -1
- package/dist/modes/interactive/components/session-selector.js.map +1 -0
- package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
- package/dist/{tui/theme-selector.d.ts.map → modes/interactive/components/theme-selector.js.map} +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/user-message.js.map +1 -0
- package/dist/{tui/tui-renderer.d.ts → modes/interactive/interactive-mode.d.ts} +36 -38
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-mode.js +1217 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/dist/{theme → modes/interactive/theme}/theme.js +1 -1
- package/dist/modes/interactive/theme/theme.js.map +1 -0
- package/dist/modes/print-mode.d.ts +21 -0
- package/dist/modes/print-mode.d.ts.map +1 -0
- package/dist/modes/print-mode.js +53 -0
- package/dist/modes/print-mode.js.map +1 -0
- package/dist/modes/rpc/rpc-client.d.ts +182 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-client.js +362 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -0
- package/dist/modes/rpc/rpc-mode.d.ts +19 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-mode.js +204 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -0
- package/dist/modes/rpc/rpc-types.d.ts +254 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-types.js +8 -0
- package/dist/modes/rpc/rpc-types.js.map +1 -0
- package/dist/{changelog.d.ts → utils/changelog.d.ts} +1 -1
- package/dist/{changelog.js.map → utils/changelog.d.ts.map} +1 -1
- package/dist/{changelog.js → utils/changelog.js} +1 -1
- package/dist/utils/changelog.js.map +1 -0
- package/dist/utils/clipboard.d.ts.map +1 -0
- package/dist/utils/clipboard.js.map +1 -0
- package/dist/utils/fuzzy.d.ts.map +1 -0
- package/dist/utils/fuzzy.js.map +1 -0
- package/dist/utils/shell.d.ts.map +1 -0
- package/dist/{shell.js → utils/shell.js} +1 -1
- package/dist/utils/shell.js.map +1 -0
- package/dist/utils/tools-manager.d.ts.map +1 -0
- package/dist/{tools-manager.js → utils/tools-manager.js} +1 -1
- package/dist/utils/tools-manager.js.map +1 -0
- package/package.json +6 -6
- package/dist/changelog.d.ts.map +0 -1
- package/dist/clipboard.d.ts.map +0 -1
- package/dist/clipboard.js.map +0 -1
- package/dist/compaction.d.ts.map +0 -1
- package/dist/compaction.js.map +0 -1
- package/dist/export-html.js.map +0 -1
- package/dist/fuzzy.d.ts.map +0 -1
- package/dist/fuzzy.js.map +0 -1
- package/dist/messages.d.ts.map +0 -1
- package/dist/messages.js.map +0 -1
- package/dist/model-config.d.ts.map +0 -1
- package/dist/model-config.js.map +0 -1
- package/dist/oauth/anthropic.d.ts.map +0 -1
- package/dist/oauth/anthropic.js.map +0 -1
- package/dist/oauth/index.js.map +0 -1
- package/dist/oauth/storage.d.ts.map +0 -1
- package/dist/oauth/storage.js.map +0 -1
- package/dist/session-manager.d.ts.map +0 -1
- package/dist/session-manager.js.map +0 -1
- package/dist/settings-manager.d.ts.map +0 -1
- package/dist/settings-manager.js.map +0 -1
- package/dist/shell.d.ts.map +0 -1
- package/dist/shell.js.map +0 -1
- package/dist/slash-commands.d.ts.map +0 -1
- package/dist/slash-commands.js.map +0 -1
- package/dist/theme/theme.d.ts.map +0 -1
- package/dist/theme/theme.js.map +0 -1
- package/dist/tools/bash.d.ts.map +0 -1
- package/dist/tools/bash.js.map +0 -1
- package/dist/tools/edit.d.ts.map +0 -1
- package/dist/tools/edit.js.map +0 -1
- package/dist/tools/find.d.ts.map +0 -1
- package/dist/tools/find.js.map +0 -1
- package/dist/tools/grep.d.ts.map +0 -1
- package/dist/tools/grep.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/ls.d.ts.map +0 -1
- package/dist/tools/ls.js.map +0 -1
- package/dist/tools/read.d.ts.map +0 -1
- package/dist/tools/read.js.map +0 -1
- package/dist/tools/truncate.d.ts.map +0 -1
- package/dist/tools/truncate.js.map +0 -1
- package/dist/tools/write.d.ts.map +0 -1
- package/dist/tools/write.js.map +0 -1
- package/dist/tools-manager.d.ts.map +0 -1
- package/dist/tools-manager.js.map +0 -1
- package/dist/tui/assistant-message.d.ts.map +0 -1
- package/dist/tui/assistant-message.js.map +0 -1
- package/dist/tui/bash-execution.d.ts.map +0 -1
- package/dist/tui/bash-execution.js.map +0 -1
- package/dist/tui/compaction.d.ts.map +0 -1
- package/dist/tui/compaction.js.map +0 -1
- package/dist/tui/custom-editor.d.ts.map +0 -1
- package/dist/tui/custom-editor.js.map +0 -1
- package/dist/tui/dynamic-border.d.ts.map +0 -1
- package/dist/tui/dynamic-border.js.map +0 -1
- package/dist/tui/footer.d.ts.map +0 -1
- package/dist/tui/footer.js.map +0 -1
- package/dist/tui/model-selector.d.ts.map +0 -1
- package/dist/tui/model-selector.js.map +0 -1
- package/dist/tui/oauth-selector.d.ts.map +0 -1
- package/dist/tui/oauth-selector.js.map +0 -1
- package/dist/tui/queue-mode-selector.d.ts.map +0 -1
- package/dist/tui/queue-mode-selector.js.map +0 -1
- package/dist/tui/session-selector.d.ts.map +0 -1
- package/dist/tui/session-selector.js.map +0 -1
- package/dist/tui/theme-selector.js.map +0 -1
- package/dist/tui/thinking-selector.d.ts.map +0 -1
- package/dist/tui/thinking-selector.js.map +0 -1
- package/dist/tui/tool-execution.d.ts.map +0 -1
- package/dist/tui/tool-execution.js.map +0 -1
- package/dist/tui/tui-renderer.d.ts.map +0 -1
- package/dist/tui/tui-renderer.js +0 -1937
- package/dist/tui/tui-renderer.js.map +0 -1
- package/dist/tui/user-message-selector.d.ts.map +0 -1
- package/dist/tui/user-message-selector.js.map +0 -1
- package/dist/tui/user-message.d.ts.map +0 -1
- package/dist/tui/user-message.js.map +0 -1
- /package/dist/{export-html.d.ts → core/export-html.d.ts} +0 -0
- /package/dist/{messages.d.ts → core/messages.d.ts} +0 -0
- /package/dist/{messages.js → core/messages.js} +0 -0
- /package/dist/{model-config.d.ts → core/model-config.d.ts} +0 -0
- /package/dist/{oauth → core/oauth}/anthropic.d.ts +0 -0
- /package/dist/{oauth → core/oauth}/anthropic.js +0 -0
- /package/dist/{oauth → core/oauth}/index.d.ts +0 -0
- /package/dist/{oauth → core/oauth}/index.js +0 -0
- /package/dist/{oauth → core/oauth}/storage.d.ts +0 -0
- /package/dist/{session-manager.d.ts → core/session-manager.d.ts} +0 -0
- /package/dist/{settings-manager.d.ts → core/settings-manager.d.ts} +0 -0
- /package/dist/{slash-commands.d.ts → core/slash-commands.d.ts} +0 -0
- /package/dist/{tools → core/tools}/bash.d.ts +0 -0
- /package/dist/{tools → core/tools}/edit.d.ts +0 -0
- /package/dist/{tools → core/tools}/edit.js +0 -0
- /package/dist/{tools → core/tools}/find.d.ts +0 -0
- /package/dist/{tools → core/tools}/grep.d.ts +0 -0
- /package/dist/{tools → core/tools}/index.d.ts +0 -0
- /package/dist/{tools → core/tools}/index.js +0 -0
- /package/dist/{tools → core/tools}/ls.d.ts +0 -0
- /package/dist/{tools → core/tools}/ls.js +0 -0
- /package/dist/{tools → core/tools}/read.d.ts +0 -0
- /package/dist/{tools → core/tools}/read.js +0 -0
- /package/dist/{tools → core/tools}/truncate.d.ts +0 -0
- /package/dist/{tools → core/tools}/truncate.js +0 -0
- /package/dist/{tools → core/tools}/write.d.ts +0 -0
- /package/dist/{tools → core/tools}/write.js +0 -0
- /package/dist/{tui → modes/interactive/components}/assistant-message.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/assistant-message.js +0 -0
- /package/dist/{tui → modes/interactive/components}/compaction.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/compaction.js +0 -0
- /package/dist/{tui → modes/interactive/components}/custom-editor.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/custom-editor.js +0 -0
- /package/dist/{tui → modes/interactive/components}/dynamic-border.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/dynamic-border.js +0 -0
- /package/dist/{tui → modes/interactive/components}/footer.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/oauth-selector.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/queue-mode-selector.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/queue-mode-selector.js +0 -0
- /package/dist/{tui → modes/interactive/components}/theme-selector.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/theme-selector.js +0 -0
- /package/dist/{tui → modes/interactive/components}/thinking-selector.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/thinking-selector.js +0 -0
- /package/dist/{tui → modes/interactive/components}/tool-execution.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/tool-execution.js +0 -0
- /package/dist/{tui → modes/interactive/components}/user-message-selector.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/user-message-selector.js +0 -0
- /package/dist/{tui → modes/interactive/components}/user-message.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/user-message.js +0 -0
- /package/dist/{theme → modes/interactive/theme}/dark.json +0 -0
- /package/dist/{theme → modes/interactive/theme}/light.json +0 -0
- /package/dist/{theme → modes/interactive/theme}/theme-schema.json +0 -0
- /package/dist/{theme → modes/interactive/theme}/theme.d.ts +0 -0
- /package/dist/{clipboard.d.ts → utils/clipboard.d.ts} +0 -0
- /package/dist/{clipboard.js → utils/clipboard.js} +0 -0
- /package/dist/{fuzzy.d.ts → utils/fuzzy.d.ts} +0 -0
- /package/dist/{fuzzy.js → utils/fuzzy.js} +0 -0
- /package/dist/{shell.d.ts → utils/shell.d.ts} +0 -0
- /package/dist/{tools-manager.d.ts → utils/tools-manager.d.ts} +0 -0
package/dist/main.js
CHANGED
|
@@ -1,487 +1,27 @@
|
|
|
1
|
-
import { Agent, ProviderTransport } from "@mariozechner/pi-agent-core";
|
|
2
|
-
import { ProcessTerminal, TUI } from "@mariozechner/pi-tui";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import { spawn } from "child_process";
|
|
5
|
-
import { randomBytes } from "crypto";
|
|
6
|
-
import { createWriteStream, existsSync, readFileSync, statSync } from "fs";
|
|
7
|
-
import { homedir, tmpdir } from "os";
|
|
8
|
-
import { extname, join, resolve } from "path";
|
|
9
|
-
import stripAnsi from "strip-ansi";
|
|
10
|
-
import { getChangelogPath, getNewEntries, parseChangelog } from "./changelog.js";
|
|
11
|
-
import { calculateContextTokens, compact, shouldCompact } from "./compaction.js";
|
|
12
|
-
import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR, getAgentDir, getModelsPath, getReadmePath, VERSION, } from "./config.js";
|
|
13
|
-
import { exportFromFile } from "./export-html.js";
|
|
14
|
-
import { messageTransformer } from "./messages.js";
|
|
15
|
-
import { findModel, getApiKeyForModel, getAvailableModels } from "./model-config.js";
|
|
16
|
-
import { loadSessionFromEntries, SessionManager } from "./session-manager.js";
|
|
17
|
-
import { SettingsManager } from "./settings-manager.js";
|
|
18
|
-
import { getShellConfig } from "./shell.js";
|
|
19
|
-
import { expandSlashCommand, loadSlashCommands } from "./slash-commands.js";
|
|
20
|
-
import { initTheme } from "./theme/theme.js";
|
|
21
|
-
import { allTools, codingTools } from "./tools/index.js";
|
|
22
|
-
import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
|
|
23
|
-
import { ensureTool } from "./tools-manager.js";
|
|
24
|
-
import { SessionSelectorComponent } from "./tui/session-selector.js";
|
|
25
|
-
import { TuiRenderer } from "./tui/tui-renderer.js";
|
|
26
|
-
const defaultModelPerProvider = {
|
|
27
|
-
anthropic: "claude-sonnet-4-5",
|
|
28
|
-
openai: "gpt-5.1-codex",
|
|
29
|
-
google: "gemini-2.5-pro",
|
|
30
|
-
openrouter: "openai/gpt-5.1-codex",
|
|
31
|
-
xai: "grok-4-fast-non-reasoning",
|
|
32
|
-
groq: "openai/gpt-oss-120b",
|
|
33
|
-
cerebras: "zai-glm-4.6",
|
|
34
|
-
zai: "glm-4.6",
|
|
35
|
-
};
|
|
36
|
-
function parseArgs(args) {
|
|
37
|
-
const result = {
|
|
38
|
-
messages: [],
|
|
39
|
-
fileArgs: [],
|
|
40
|
-
};
|
|
41
|
-
for (let i = 0; i < args.length; i++) {
|
|
42
|
-
const arg = args[i];
|
|
43
|
-
if (arg === "--help" || arg === "-h") {
|
|
44
|
-
result.help = true;
|
|
45
|
-
}
|
|
46
|
-
else if (arg === "--mode" && i + 1 < args.length) {
|
|
47
|
-
const mode = args[++i];
|
|
48
|
-
if (mode === "text" || mode === "json" || mode === "rpc") {
|
|
49
|
-
result.mode = mode;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
else if (arg === "--continue" || arg === "-c") {
|
|
53
|
-
result.continue = true;
|
|
54
|
-
}
|
|
55
|
-
else if (arg === "--resume" || arg === "-r") {
|
|
56
|
-
result.resume = true;
|
|
57
|
-
}
|
|
58
|
-
else if (arg === "--provider" && i + 1 < args.length) {
|
|
59
|
-
result.provider = args[++i];
|
|
60
|
-
}
|
|
61
|
-
else if (arg === "--model" && i + 1 < args.length) {
|
|
62
|
-
result.model = args[++i];
|
|
63
|
-
}
|
|
64
|
-
else if (arg === "--api-key" && i + 1 < args.length) {
|
|
65
|
-
result.apiKey = args[++i];
|
|
66
|
-
}
|
|
67
|
-
else if (arg === "--system-prompt" && i + 1 < args.length) {
|
|
68
|
-
result.systemPrompt = args[++i];
|
|
69
|
-
}
|
|
70
|
-
else if (arg === "--append-system-prompt" && i + 1 < args.length) {
|
|
71
|
-
result.appendSystemPrompt = args[++i];
|
|
72
|
-
}
|
|
73
|
-
else if (arg === "--no-session") {
|
|
74
|
-
result.noSession = true;
|
|
75
|
-
}
|
|
76
|
-
else if (arg === "--session" && i + 1 < args.length) {
|
|
77
|
-
result.session = args[++i];
|
|
78
|
-
}
|
|
79
|
-
else if (arg === "--models" && i + 1 < args.length) {
|
|
80
|
-
result.models = args[++i].split(",").map((s) => s.trim());
|
|
81
|
-
}
|
|
82
|
-
else if (arg === "--tools" && i + 1 < args.length) {
|
|
83
|
-
const toolNames = args[++i].split(",").map((s) => s.trim());
|
|
84
|
-
const validTools = [];
|
|
85
|
-
for (const name of toolNames) {
|
|
86
|
-
if (name in allTools) {
|
|
87
|
-
validTools.push(name);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
console.error(chalk.yellow(`Warning: Unknown tool "${name}". Valid tools: ${Object.keys(allTools).join(", ")}`));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
result.tools = validTools;
|
|
94
|
-
}
|
|
95
|
-
else if (arg === "--thinking" && i + 1 < args.length) {
|
|
96
|
-
const level = args[++i];
|
|
97
|
-
if (level === "off" ||
|
|
98
|
-
level === "minimal" ||
|
|
99
|
-
level === "low" ||
|
|
100
|
-
level === "medium" ||
|
|
101
|
-
level === "high" ||
|
|
102
|
-
level === "xhigh") {
|
|
103
|
-
result.thinking = level;
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
console.error(chalk.yellow(`Warning: Invalid thinking level "${level}". Valid values: off, minimal, low, medium, high, xhigh`));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else if (arg === "--print" || arg === "-p") {
|
|
110
|
-
result.print = true;
|
|
111
|
-
}
|
|
112
|
-
else if (arg === "--export" && i + 1 < args.length) {
|
|
113
|
-
result.export = args[++i];
|
|
114
|
-
}
|
|
115
|
-
else if (arg.startsWith("@")) {
|
|
116
|
-
result.fileArgs.push(arg.slice(1)); // Remove @ prefix
|
|
117
|
-
}
|
|
118
|
-
else if (!arg.startsWith("-")) {
|
|
119
|
-
result.messages.push(arg);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return result;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Map of file extensions to MIME types for common image formats
|
|
126
|
-
*/
|
|
127
|
-
const IMAGE_MIME_TYPES = {
|
|
128
|
-
".jpg": "image/jpeg",
|
|
129
|
-
".jpeg": "image/jpeg",
|
|
130
|
-
".png": "image/png",
|
|
131
|
-
".gif": "image/gif",
|
|
132
|
-
".webp": "image/webp",
|
|
133
|
-
};
|
|
134
|
-
/**
|
|
135
|
-
* Check if a file is an image based on its extension
|
|
136
|
-
*/
|
|
137
|
-
function isImageFile(filePath) {
|
|
138
|
-
const ext = extname(filePath).toLowerCase();
|
|
139
|
-
return IMAGE_MIME_TYPES[ext] || null;
|
|
140
|
-
}
|
|
141
1
|
/**
|
|
142
|
-
*
|
|
2
|
+
* Main entry point for the coding agent
|
|
143
3
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
console.error(chalk.red(`Error: File not found: ${absolutePath}`));
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
// Check if file is empty
|
|
169
|
-
const stats = statSync(absolutePath);
|
|
170
|
-
if (stats.size === 0) {
|
|
171
|
-
// Skip empty files
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
const mimeType = isImageFile(absolutePath);
|
|
175
|
-
if (mimeType) {
|
|
176
|
-
// Handle image file
|
|
177
|
-
const content = readFileSync(absolutePath);
|
|
178
|
-
const base64Content = content.toString("base64");
|
|
179
|
-
const attachment = {
|
|
180
|
-
id: `file-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
181
|
-
type: "image",
|
|
182
|
-
fileName: absolutePath.split("/").pop() || absolutePath,
|
|
183
|
-
mimeType,
|
|
184
|
-
size: stats.size,
|
|
185
|
-
content: base64Content,
|
|
186
|
-
};
|
|
187
|
-
imageAttachments.push(attachment);
|
|
188
|
-
// Add text reference to image
|
|
189
|
-
textContent += `<file name="${absolutePath}"></file>\n`;
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
// Handle text file
|
|
193
|
-
try {
|
|
194
|
-
const content = readFileSync(absolutePath, "utf-8");
|
|
195
|
-
textContent += `<file name="${absolutePath}">\n${content}\n</file>\n`;
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
console.error(chalk.red(`Error: Could not read file ${absolutePath}: ${error.message}`));
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return { textContent, imageAttachments };
|
|
204
|
-
}
|
|
205
|
-
function printHelp() {
|
|
206
|
-
console.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools
|
|
207
|
-
|
|
208
|
-
${chalk.bold("Usage:")}
|
|
209
|
-
${APP_NAME} [options] [@files...] [messages...]
|
|
210
|
-
|
|
211
|
-
${chalk.bold("Options:")}
|
|
212
|
-
--provider <name> Provider name (default: google)
|
|
213
|
-
--model <id> Model ID (default: gemini-2.5-flash)
|
|
214
|
-
--api-key <key> API key (defaults to env vars)
|
|
215
|
-
--system-prompt <text> System prompt (default: coding assistant prompt)
|
|
216
|
-
--append-system-prompt <text> Append text or file contents to the system prompt
|
|
217
|
-
--mode <mode> Output mode: text (default), json, or rpc
|
|
218
|
-
--print, -p Non-interactive mode: process prompt and exit
|
|
219
|
-
--continue, -c Continue previous session
|
|
220
|
-
--resume, -r Select a session to resume
|
|
221
|
-
--session <path> Use specific session file
|
|
222
|
-
--no-session Don't save session (ephemeral)
|
|
223
|
-
--models <patterns> Comma-separated model patterns for quick cycling with Ctrl+P
|
|
224
|
-
--tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)
|
|
225
|
-
Available: read, bash, edit, write, grep, find, ls
|
|
226
|
-
--thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
|
|
227
|
-
--export <file> Export session file to HTML and exit
|
|
228
|
-
--help, -h Show this help
|
|
229
|
-
|
|
230
|
-
${chalk.bold("Examples:")}
|
|
231
|
-
# Interactive mode
|
|
232
|
-
${APP_NAME}
|
|
233
|
-
|
|
234
|
-
# Interactive mode with initial prompt
|
|
235
|
-
${APP_NAME} "List all .ts files in src/"
|
|
236
|
-
|
|
237
|
-
# Include files in initial message
|
|
238
|
-
${APP_NAME} @prompt.md @image.png "What color is the sky?"
|
|
239
|
-
|
|
240
|
-
# Non-interactive mode (process and exit)
|
|
241
|
-
${APP_NAME} -p "List all .ts files in src/"
|
|
242
|
-
|
|
243
|
-
# Multiple messages (interactive)
|
|
244
|
-
${APP_NAME} "Read package.json" "What dependencies do we have?"
|
|
245
|
-
|
|
246
|
-
# Continue previous session
|
|
247
|
-
${APP_NAME} --continue "What did we discuss?"
|
|
248
|
-
|
|
249
|
-
# Use different model
|
|
250
|
-
${APP_NAME} --provider openai --model gpt-4o-mini "Help me refactor this code"
|
|
251
|
-
|
|
252
|
-
# Limit model cycling to specific models
|
|
253
|
-
${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o
|
|
254
|
-
|
|
255
|
-
# Cycle models with fixed thinking levels
|
|
256
|
-
${APP_NAME} --models sonnet:high,haiku:low
|
|
257
|
-
|
|
258
|
-
# Start with a specific thinking level
|
|
259
|
-
${APP_NAME} --thinking high "Solve this complex problem"
|
|
260
|
-
|
|
261
|
-
# Read-only mode (no file modifications possible)
|
|
262
|
-
${APP_NAME} --tools read,grep,find,ls -p "Review the code in src/"
|
|
263
|
-
|
|
264
|
-
# Export a session file to HTML
|
|
265
|
-
${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl
|
|
266
|
-
${APP_NAME} --export session.jsonl output.html
|
|
267
|
-
|
|
268
|
-
${chalk.bold("Environment Variables:")}
|
|
269
|
-
ANTHROPIC_API_KEY - Anthropic Claude API key
|
|
270
|
-
ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)
|
|
271
|
-
OPENAI_API_KEY - OpenAI GPT API key
|
|
272
|
-
GEMINI_API_KEY - Google Gemini API key
|
|
273
|
-
GROQ_API_KEY - Groq API key
|
|
274
|
-
CEREBRAS_API_KEY - Cerebras API key
|
|
275
|
-
XAI_API_KEY - xAI Grok API key
|
|
276
|
-
OPENROUTER_API_KEY - OpenRouter API key
|
|
277
|
-
ZAI_API_KEY - ZAI API key
|
|
278
|
-
${ENV_AGENT_DIR.padEnd(23)} - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
|
|
279
|
-
|
|
280
|
-
${chalk.bold("Available Tools (default: read, bash, edit, write):")}
|
|
281
|
-
read - Read file contents
|
|
282
|
-
bash - Execute bash commands
|
|
283
|
-
edit - Edit files with find/replace
|
|
284
|
-
write - Write files (creates/overwrites)
|
|
285
|
-
grep - Search file contents (read-only, off by default)
|
|
286
|
-
find - Find files by glob pattern (read-only, off by default)
|
|
287
|
-
ls - List directory contents (read-only, off by default)
|
|
288
|
-
`);
|
|
289
|
-
}
|
|
290
|
-
// Tool descriptions for system prompt
|
|
291
|
-
const toolDescriptions = {
|
|
292
|
-
read: "Read file contents",
|
|
293
|
-
bash: "Execute bash commands (ls, grep, find, etc.)",
|
|
294
|
-
edit: "Make surgical edits to files (find exact text and replace)",
|
|
295
|
-
write: "Create or overwrite files",
|
|
296
|
-
grep: "Search file contents for patterns (respects .gitignore)",
|
|
297
|
-
find: "Find files by glob pattern (respects .gitignore)",
|
|
298
|
-
ls: "List directory contents",
|
|
299
|
-
};
|
|
300
|
-
function resolvePromptInput(input, description) {
|
|
301
|
-
if (!input) {
|
|
302
|
-
return undefined;
|
|
303
|
-
}
|
|
304
|
-
if (existsSync(input)) {
|
|
305
|
-
try {
|
|
306
|
-
return readFileSync(input, "utf-8");
|
|
307
|
-
}
|
|
308
|
-
catch (error) {
|
|
309
|
-
console.error(chalk.yellow(`Warning: Could not read ${description} file ${input}: ${error}`));
|
|
310
|
-
return input;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return input;
|
|
314
|
-
}
|
|
315
|
-
function buildSystemPrompt(customPrompt, selectedTools, appendSystemPrompt) {
|
|
316
|
-
const resolvedCustomPrompt = resolvePromptInput(customPrompt, "system prompt");
|
|
317
|
-
const resolvedAppendPrompt = resolvePromptInput(appendSystemPrompt, "append system prompt");
|
|
318
|
-
const now = new Date();
|
|
319
|
-
const dateTime = now.toLocaleString("en-US", {
|
|
320
|
-
weekday: "long",
|
|
321
|
-
year: "numeric",
|
|
322
|
-
month: "long",
|
|
323
|
-
day: "numeric",
|
|
324
|
-
hour: "2-digit",
|
|
325
|
-
minute: "2-digit",
|
|
326
|
-
second: "2-digit",
|
|
327
|
-
timeZoneName: "short",
|
|
328
|
-
});
|
|
329
|
-
const appendSection = resolvedAppendPrompt ? `\n\n${resolvedAppendPrompt}` : "";
|
|
330
|
-
if (resolvedCustomPrompt) {
|
|
331
|
-
let prompt = resolvedCustomPrompt;
|
|
332
|
-
if (appendSection) {
|
|
333
|
-
prompt += appendSection;
|
|
334
|
-
}
|
|
335
|
-
// Append project context files
|
|
336
|
-
const contextFiles = loadProjectContextFiles();
|
|
337
|
-
if (contextFiles.length > 0) {
|
|
338
|
-
prompt += "\n\n# Project Context\n\n";
|
|
339
|
-
prompt += "The following project context files have been loaded:\n\n";
|
|
340
|
-
for (const { path: filePath, content } of contextFiles) {
|
|
341
|
-
prompt += `## ${filePath}\n\n${content}\n\n`;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
// Add date/time and working directory last
|
|
345
|
-
prompt += `\nCurrent date and time: ${dateTime}`;
|
|
346
|
-
prompt += `\nCurrent working directory: ${process.cwd()}`;
|
|
347
|
-
return prompt;
|
|
348
|
-
}
|
|
349
|
-
// Get absolute path to README.md
|
|
350
|
-
const readmePath = getReadmePath();
|
|
351
|
-
// Build tools list based on selected tools
|
|
352
|
-
const tools = selectedTools || ["read", "bash", "edit", "write"];
|
|
353
|
-
const toolsList = tools.map((t) => `- ${t}: ${toolDescriptions[t]}`).join("\n");
|
|
354
|
-
// Build guidelines based on which tools are actually available
|
|
355
|
-
const guidelinesList = [];
|
|
356
|
-
const hasBash = tools.includes("bash");
|
|
357
|
-
const hasEdit = tools.includes("edit");
|
|
358
|
-
const hasWrite = tools.includes("write");
|
|
359
|
-
const hasGrep = tools.includes("grep");
|
|
360
|
-
const hasFind = tools.includes("find");
|
|
361
|
-
const hasLs = tools.includes("ls");
|
|
362
|
-
const hasRead = tools.includes("read");
|
|
363
|
-
// Read-only mode notice (no bash, edit, or write)
|
|
364
|
-
if (!hasBash && !hasEdit && !hasWrite) {
|
|
365
|
-
guidelinesList.push("You are in READ-ONLY mode - you cannot modify files or execute arbitrary commands");
|
|
366
|
-
}
|
|
367
|
-
// Bash without edit/write = read-only bash mode
|
|
368
|
-
if (hasBash && !hasEdit && !hasWrite) {
|
|
369
|
-
guidelinesList.push("Use bash ONLY for read-only operations (git log, gh issue view, curl, etc.) - do NOT modify any files");
|
|
370
|
-
}
|
|
371
|
-
// File exploration guidelines
|
|
372
|
-
if (hasBash && !hasGrep && !hasFind && !hasLs) {
|
|
373
|
-
guidelinesList.push("Use bash for file operations like ls, grep, find");
|
|
374
|
-
}
|
|
375
|
-
else if (hasBash && (hasGrep || hasFind || hasLs)) {
|
|
376
|
-
guidelinesList.push("Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)");
|
|
377
|
-
}
|
|
378
|
-
// Read before edit guideline
|
|
379
|
-
if (hasRead && hasEdit) {
|
|
380
|
-
guidelinesList.push("Use read to examine files before editing");
|
|
381
|
-
}
|
|
382
|
-
// Edit guideline
|
|
383
|
-
if (hasEdit) {
|
|
384
|
-
guidelinesList.push("Use edit for precise changes (old text must match exactly)");
|
|
385
|
-
}
|
|
386
|
-
// Write guideline
|
|
387
|
-
if (hasWrite) {
|
|
388
|
-
guidelinesList.push("Use write only for new files or complete rewrites");
|
|
389
|
-
}
|
|
390
|
-
// Output guideline (only when actually writing/executing)
|
|
391
|
-
if (hasEdit || hasWrite) {
|
|
392
|
-
guidelinesList.push("When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did");
|
|
393
|
-
}
|
|
394
|
-
// Always include these
|
|
395
|
-
guidelinesList.push("Be concise in your responses");
|
|
396
|
-
guidelinesList.push("Show file paths clearly when working with files");
|
|
397
|
-
const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
|
|
398
|
-
let prompt = `You are an expert coding assistant. You help users with coding tasks by reading files, executing commands, editing code, and writing new files.
|
|
399
|
-
|
|
400
|
-
Available tools:
|
|
401
|
-
${toolsList}
|
|
402
|
-
|
|
403
|
-
Guidelines:
|
|
404
|
-
${guidelines}
|
|
405
|
-
|
|
406
|
-
Documentation:
|
|
407
|
-
- Your own documentation (including custom model setup and theme creation) is at: ${readmePath}
|
|
408
|
-
- Read it when users ask about features, configuration, or setup, and especially if the user asks you to add a custom model or provider, or create a custom theme.`;
|
|
409
|
-
if (appendSection) {
|
|
410
|
-
prompt += appendSection;
|
|
411
|
-
}
|
|
412
|
-
// Append project context files
|
|
413
|
-
const contextFiles = loadProjectContextFiles();
|
|
414
|
-
if (contextFiles.length > 0) {
|
|
415
|
-
prompt += "\n\n# Project Context\n\n";
|
|
416
|
-
prompt += "The following project context files have been loaded:\n\n";
|
|
417
|
-
for (const { path: filePath, content } of contextFiles) {
|
|
418
|
-
prompt += `## ${filePath}\n\n${content}\n\n`;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
// Add date/time and working directory last
|
|
422
|
-
prompt += `\nCurrent date and time: ${dateTime}`;
|
|
423
|
-
prompt += `\nCurrent working directory: ${process.cwd()}`;
|
|
424
|
-
return prompt;
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Look for AGENTS.md or CLAUDE.md in a directory (prefers AGENTS.md)
|
|
428
|
-
*/
|
|
429
|
-
function loadContextFileFromDir(dir) {
|
|
430
|
-
const candidates = ["AGENTS.md", "CLAUDE.md"];
|
|
431
|
-
for (const filename of candidates) {
|
|
432
|
-
const filePath = join(dir, filename);
|
|
433
|
-
if (existsSync(filePath)) {
|
|
434
|
-
try {
|
|
435
|
-
return {
|
|
436
|
-
path: filePath,
|
|
437
|
-
content: readFileSync(filePath, "utf-8"),
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
catch (error) {
|
|
441
|
-
console.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Load all project context files in order:
|
|
449
|
-
* 1. Global: ~/{CONFIG_DIR_NAME}/agent/AGENTS.md or CLAUDE.md
|
|
450
|
-
* 2. Parent directories (top-most first) down to cwd
|
|
451
|
-
* Each returns {path, content} for separate messages
|
|
452
|
-
*/
|
|
453
|
-
function loadProjectContextFiles() {
|
|
454
|
-
const contextFiles = [];
|
|
455
|
-
// 1. Load global context from ~/{CONFIG_DIR_NAME}/agent/
|
|
456
|
-
const globalContextDir = getAgentDir();
|
|
457
|
-
const globalContext = loadContextFileFromDir(globalContextDir);
|
|
458
|
-
if (globalContext) {
|
|
459
|
-
contextFiles.push(globalContext);
|
|
460
|
-
}
|
|
461
|
-
// 2. Walk up from cwd to root, collecting all context files
|
|
462
|
-
const cwd = process.cwd();
|
|
463
|
-
const ancestorContextFiles = [];
|
|
464
|
-
let currentDir = cwd;
|
|
465
|
-
const root = resolve("/");
|
|
466
|
-
while (true) {
|
|
467
|
-
const contextFile = loadContextFileFromDir(currentDir);
|
|
468
|
-
if (contextFile) {
|
|
469
|
-
// Add to beginning so we get top-most parent first
|
|
470
|
-
ancestorContextFiles.unshift(contextFile);
|
|
471
|
-
}
|
|
472
|
-
// Stop if we've reached root
|
|
473
|
-
if (currentDir === root)
|
|
474
|
-
break;
|
|
475
|
-
// Move up one directory
|
|
476
|
-
const parentDir = resolve(currentDir, "..");
|
|
477
|
-
if (parentDir === currentDir)
|
|
478
|
-
break; // Safety check
|
|
479
|
-
currentDir = parentDir;
|
|
480
|
-
}
|
|
481
|
-
// Add ancestor files in order (top-most → cwd)
|
|
482
|
-
contextFiles.push(...ancestorContextFiles);
|
|
483
|
-
return contextFiles;
|
|
484
|
-
}
|
|
4
|
+
import { Agent, ProviderTransport } from "@mariozechner/pi-agent-core";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { parseArgs, printHelp } from "./cli/args.js";
|
|
7
|
+
import { processFileArguments } from "./cli/file-processor.js";
|
|
8
|
+
import { selectSession } from "./cli/session-picker.js";
|
|
9
|
+
import { getModelsPath, VERSION } from "./config.js";
|
|
10
|
+
import { AgentSession } from "./core/agent-session.js";
|
|
11
|
+
import { exportFromFile } from "./core/export-html.js";
|
|
12
|
+
import { messageTransformer } from "./core/messages.js";
|
|
13
|
+
import { findModel, getApiKeyForModel, getAvailableModels } from "./core/model-config.js";
|
|
14
|
+
import { resolveModelScope, restoreModelFromSession } from "./core/model-resolver.js";
|
|
15
|
+
import { SessionManager } from "./core/session-manager.js";
|
|
16
|
+
import { SettingsManager } from "./core/settings-manager.js";
|
|
17
|
+
import { loadSlashCommands } from "./core/slash-commands.js";
|
|
18
|
+
import { buildSystemPrompt, loadProjectContextFiles } from "./core/system-prompt.js";
|
|
19
|
+
import { allTools, codingTools } from "./core/tools/index.js";
|
|
20
|
+
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
|
|
21
|
+
import { initTheme } from "./modes/interactive/theme/theme.js";
|
|
22
|
+
import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog.js";
|
|
23
|
+
import { ensureTool } from "./utils/tools-manager.js";
|
|
24
|
+
/** Check npm registry for new version (non-blocking) */
|
|
485
25
|
async function checkForNewVersion(currentVersion) {
|
|
486
26
|
try {
|
|
487
27
|
const response = await fetch("https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest");
|
|
@@ -494,425 +34,80 @@ async function checkForNewVersion(currentVersion) {
|
|
|
494
34
|
}
|
|
495
35
|
return null;
|
|
496
36
|
}
|
|
497
|
-
catch
|
|
37
|
+
catch {
|
|
498
38
|
// Silently fail - don't disrupt the user experience
|
|
499
39
|
return null;
|
|
500
40
|
}
|
|
501
41
|
}
|
|
502
|
-
/**
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
* For each pattern, finds all matching models and picks the best version:
|
|
506
|
-
* 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)
|
|
507
|
-
* 2. If no alias, pick the latest dated version
|
|
508
|
-
*/
|
|
509
|
-
async function resolveModelScope(patterns) {
|
|
510
|
-
const { models: availableModels, error } = await getAvailableModels();
|
|
511
|
-
if (error) {
|
|
512
|
-
console.warn(chalk.yellow(`Warning: Error loading models: ${error}`));
|
|
513
|
-
return [];
|
|
514
|
-
}
|
|
515
|
-
const scopedModels = [];
|
|
516
|
-
for (const pattern of patterns) {
|
|
517
|
-
// Parse pattern:level format
|
|
518
|
-
const parts = pattern.split(":");
|
|
519
|
-
const modelPattern = parts[0];
|
|
520
|
-
let thinkingLevel = "off";
|
|
521
|
-
if (parts.length > 1) {
|
|
522
|
-
const level = parts[1];
|
|
523
|
-
if (level === "off" ||
|
|
524
|
-
level === "minimal" ||
|
|
525
|
-
level === "low" ||
|
|
526
|
-
level === "medium" ||
|
|
527
|
-
level === "high" ||
|
|
528
|
-
level === "xhigh") {
|
|
529
|
-
thinkingLevel = level;
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
console.warn(chalk.yellow(`Warning: Invalid thinking level "${level}" in pattern "${pattern}". Using "off" instead.`));
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
// Check for provider/modelId format (provider is everything before the first /)
|
|
536
|
-
const slashIndex = modelPattern.indexOf("/");
|
|
537
|
-
if (slashIndex !== -1) {
|
|
538
|
-
const provider = modelPattern.substring(0, slashIndex);
|
|
539
|
-
const modelId = modelPattern.substring(slashIndex + 1);
|
|
540
|
-
const providerMatch = availableModels.find((m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase());
|
|
541
|
-
if (providerMatch) {
|
|
542
|
-
if (!scopedModels.find((sm) => sm.model.id === providerMatch.id && sm.model.provider === providerMatch.provider)) {
|
|
543
|
-
scopedModels.push({ model: providerMatch, thinkingLevel });
|
|
544
|
-
}
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
// No exact provider/model match - fall through to other matching
|
|
548
|
-
}
|
|
549
|
-
// Check for exact ID match (case-insensitive)
|
|
550
|
-
const exactMatch = availableModels.find((m) => m.id.toLowerCase() === modelPattern.toLowerCase());
|
|
551
|
-
if (exactMatch) {
|
|
552
|
-
// Exact match found - use it directly
|
|
553
|
-
if (!scopedModels.find((sm) => sm.model.id === exactMatch.id && sm.model.provider === exactMatch.provider)) {
|
|
554
|
-
scopedModels.push({ model: exactMatch, thinkingLevel });
|
|
555
|
-
}
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
// No exact match - fall back to partial matching
|
|
559
|
-
const matches = availableModels.filter((m) => m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||
|
|
560
|
-
m.name?.toLowerCase().includes(modelPattern.toLowerCase()));
|
|
561
|
-
if (matches.length === 0) {
|
|
562
|
-
console.warn(chalk.yellow(`Warning: No models match pattern "${modelPattern}"`));
|
|
563
|
-
continue;
|
|
564
|
-
}
|
|
565
|
-
// Helper to check if a model ID looks like an alias (no date suffix)
|
|
566
|
-
// Dates are typically in format: -20241022 or -20250929
|
|
567
|
-
const isAlias = (id) => {
|
|
568
|
-
// Check if ID ends with -latest
|
|
569
|
-
if (id.endsWith("-latest"))
|
|
570
|
-
return true;
|
|
571
|
-
// Check if ID ends with a date pattern (-YYYYMMDD)
|
|
572
|
-
const datePattern = /-\d{8}$/;
|
|
573
|
-
return !datePattern.test(id);
|
|
574
|
-
};
|
|
575
|
-
// Separate into aliases and dated versions
|
|
576
|
-
const aliases = matches.filter((m) => isAlias(m.id));
|
|
577
|
-
const datedVersions = matches.filter((m) => !isAlias(m.id));
|
|
578
|
-
let bestMatch;
|
|
579
|
-
if (aliases.length > 0) {
|
|
580
|
-
// Prefer alias - if multiple aliases, pick the one that sorts highest
|
|
581
|
-
aliases.sort((a, b) => b.id.localeCompare(a.id));
|
|
582
|
-
bestMatch = aliases[0];
|
|
583
|
-
}
|
|
584
|
-
else {
|
|
585
|
-
// No alias found, pick latest dated version
|
|
586
|
-
datedVersions.sort((a, b) => b.id.localeCompare(a.id));
|
|
587
|
-
bestMatch = datedVersions[0];
|
|
588
|
-
}
|
|
589
|
-
// Avoid duplicates
|
|
590
|
-
if (!scopedModels.find((sm) => sm.model.id === bestMatch.id && sm.model.provider === bestMatch.provider)) {
|
|
591
|
-
scopedModels.push({ model: bestMatch, thinkingLevel });
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
return scopedModels;
|
|
595
|
-
}
|
|
596
|
-
async function selectSession(sessionManager) {
|
|
597
|
-
return new Promise((resolve) => {
|
|
598
|
-
const ui = new TUI(new ProcessTerminal());
|
|
599
|
-
let resolved = false;
|
|
600
|
-
const selector = new SessionSelectorComponent(sessionManager, (path) => {
|
|
601
|
-
if (!resolved) {
|
|
602
|
-
resolved = true;
|
|
603
|
-
ui.stop();
|
|
604
|
-
resolve(path);
|
|
605
|
-
}
|
|
606
|
-
}, () => {
|
|
607
|
-
if (!resolved) {
|
|
608
|
-
resolved = true;
|
|
609
|
-
ui.stop();
|
|
610
|
-
resolve(null);
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
ui.addChild(selector);
|
|
614
|
-
ui.setFocus(selector.getSessionList());
|
|
615
|
-
ui.start();
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
async function runInteractiveMode(agent, sessionManager, settingsManager, version, changelogMarkdown = null, collapseChangelog = false, modelFallbackMessage = null, versionCheckPromise, scopedModels = [], initialMessages = [], initialMessage, initialAttachments, fdPath = null) {
|
|
619
|
-
const renderer = new TuiRenderer(agent, sessionManager, settingsManager, version, changelogMarkdown, collapseChangelog, scopedModels, fdPath);
|
|
42
|
+
/** Run interactive mode with TUI */
|
|
43
|
+
async function runInteractiveMode(session, version, changelogMarkdown, modelFallbackMessage, versionCheckPromise, initialMessages, initialMessage, initialAttachments, fdPath = null) {
|
|
44
|
+
const mode = new InteractiveMode(session, version, changelogMarkdown, fdPath);
|
|
620
45
|
// Initialize TUI (subscribes to agent events internally)
|
|
621
|
-
await
|
|
46
|
+
await mode.init();
|
|
622
47
|
// Handle version check result when it completes (don't block)
|
|
623
48
|
versionCheckPromise.then((newVersion) => {
|
|
624
49
|
if (newVersion) {
|
|
625
|
-
|
|
50
|
+
mode.showNewVersionNotification(newVersion);
|
|
626
51
|
}
|
|
627
52
|
});
|
|
628
53
|
// Render any existing messages (from --continue mode)
|
|
629
|
-
|
|
54
|
+
mode.renderInitialMessages(session.state);
|
|
630
55
|
// Show model fallback warning at the end of the chat if applicable
|
|
631
56
|
if (modelFallbackMessage) {
|
|
632
|
-
|
|
57
|
+
mode.showWarning(modelFallbackMessage);
|
|
633
58
|
}
|
|
634
|
-
// Load file-based slash commands for expansion
|
|
635
|
-
const fileCommands = loadSlashCommands();
|
|
636
59
|
// Process initial message with attachments if provided (from @file args)
|
|
637
60
|
if (initialMessage) {
|
|
638
61
|
try {
|
|
639
|
-
await
|
|
62
|
+
await session.prompt(initialMessage, { attachments: initialAttachments });
|
|
640
63
|
}
|
|
641
64
|
catch (error) {
|
|
642
65
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
643
|
-
|
|
66
|
+
mode.showError(errorMessage);
|
|
644
67
|
}
|
|
645
68
|
}
|
|
646
69
|
// Process remaining initial messages if provided (from CLI args)
|
|
647
70
|
for (const message of initialMessages) {
|
|
648
71
|
try {
|
|
649
|
-
await
|
|
72
|
+
await session.prompt(message);
|
|
650
73
|
}
|
|
651
74
|
catch (error) {
|
|
652
75
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
653
|
-
|
|
76
|
+
mode.showError(errorMessage);
|
|
654
77
|
}
|
|
655
78
|
}
|
|
656
79
|
// Interactive loop
|
|
657
80
|
while (true) {
|
|
658
|
-
const userInput = await
|
|
659
|
-
// Process the message
|
|
81
|
+
const userInput = await mode.getUserInput();
|
|
82
|
+
// Process the message
|
|
660
83
|
try {
|
|
661
|
-
await
|
|
84
|
+
await session.prompt(userInput);
|
|
662
85
|
}
|
|
663
86
|
catch (error) {
|
|
664
|
-
// Display error in the TUI by adding an error message to the chat
|
|
665
87
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
666
|
-
|
|
88
|
+
mode.showError(errorMessage);
|
|
667
89
|
}
|
|
668
90
|
}
|
|
669
91
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
// Subscribe to all events and output as JSON
|
|
675
|
-
agent.subscribe((event) => {
|
|
676
|
-
// Output event as JSON (same format as session manager)
|
|
677
|
-
console.log(JSON.stringify(event));
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
// Send initial message with attachments if provided
|
|
681
|
-
if (initialMessage) {
|
|
682
|
-
await agent.prompt(expandSlashCommand(initialMessage, fileCommands), initialAttachments);
|
|
92
|
+
/** Prepare initial message from @file arguments */
|
|
93
|
+
function prepareInitialMessage(parsed) {
|
|
94
|
+
if (parsed.fileArgs.length === 0) {
|
|
95
|
+
return {};
|
|
683
96
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
97
|
+
const { textContent, imageAttachments } = processFileArguments(parsed.fileArgs);
|
|
98
|
+
// Combine file content with first plain text message (if any)
|
|
99
|
+
let initialMessage;
|
|
100
|
+
if (parsed.messages.length > 0) {
|
|
101
|
+
initialMessage = textContent + parsed.messages[0];
|
|
102
|
+
parsed.messages.shift(); // Remove first message as it's been combined
|
|
687
103
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const lastMessage = agent.state.messages[agent.state.messages.length - 1];
|
|
691
|
-
if (lastMessage.role === "assistant") {
|
|
692
|
-
const assistantMsg = lastMessage;
|
|
693
|
-
// Check for error/aborted and output error message
|
|
694
|
-
if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
|
|
695
|
-
console.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
|
|
696
|
-
process.exit(1);
|
|
697
|
-
}
|
|
698
|
-
for (const content of assistantMsg.content) {
|
|
699
|
-
if (content.type === "text") {
|
|
700
|
-
console.log(content.text);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
104
|
+
else {
|
|
105
|
+
initialMessage = textContent;
|
|
704
106
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
* Similar to tui-renderer's executeBashCommand but without streaming callbacks.
|
|
709
|
-
*/
|
|
710
|
-
async function executeRpcBashCommand(command) {
|
|
711
|
-
return new Promise((resolve, reject) => {
|
|
712
|
-
const { shell, args } = getShellConfig();
|
|
713
|
-
const child = spawn(shell, [...args, command], {
|
|
714
|
-
detached: true,
|
|
715
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
716
|
-
});
|
|
717
|
-
const chunks = [];
|
|
718
|
-
let chunksBytes = 0;
|
|
719
|
-
const maxChunksBytes = DEFAULT_MAX_BYTES * 2;
|
|
720
|
-
let tempFilePath;
|
|
721
|
-
let tempFileStream;
|
|
722
|
-
let totalBytes = 0;
|
|
723
|
-
const handleData = (data) => {
|
|
724
|
-
totalBytes += data.length;
|
|
725
|
-
// Start writing to temp file if exceeds threshold
|
|
726
|
-
if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
|
|
727
|
-
const id = randomBytes(8).toString("hex");
|
|
728
|
-
tempFilePath = join(tmpdir(), `pi-bash-${id}.log`);
|
|
729
|
-
tempFileStream = createWriteStream(tempFilePath);
|
|
730
|
-
for (const chunk of chunks) {
|
|
731
|
-
tempFileStream.write(chunk);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
if (tempFileStream) {
|
|
735
|
-
tempFileStream.write(data);
|
|
736
|
-
}
|
|
737
|
-
// Keep rolling buffer
|
|
738
|
-
chunks.push(data);
|
|
739
|
-
chunksBytes += data.length;
|
|
740
|
-
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
741
|
-
const removed = chunks.shift();
|
|
742
|
-
chunksBytes -= removed.length;
|
|
743
|
-
}
|
|
744
|
-
};
|
|
745
|
-
child.stdout?.on("data", handleData);
|
|
746
|
-
child.stderr?.on("data", handleData);
|
|
747
|
-
child.on("close", (code) => {
|
|
748
|
-
if (tempFileStream) {
|
|
749
|
-
tempFileStream.end();
|
|
750
|
-
}
|
|
751
|
-
// Combine buffered chunks
|
|
752
|
-
const fullBuffer = Buffer.concat(chunks);
|
|
753
|
-
const fullOutput = stripAnsi(fullBuffer.toString("utf-8")).replace(/\r/g, "");
|
|
754
|
-
const truncationResult = truncateTail(fullOutput);
|
|
755
|
-
resolve({
|
|
756
|
-
output: fullOutput,
|
|
757
|
-
exitCode: code,
|
|
758
|
-
truncationResult: truncationResult.truncated ? truncationResult : undefined,
|
|
759
|
-
fullOutputPath: tempFilePath,
|
|
760
|
-
});
|
|
761
|
-
});
|
|
762
|
-
child.on("error", (err) => {
|
|
763
|
-
if (tempFileStream) {
|
|
764
|
-
tempFileStream.end();
|
|
765
|
-
}
|
|
766
|
-
reject(err);
|
|
767
|
-
});
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
async function runRpcMode(agent, sessionManager, settingsManager) {
|
|
771
|
-
// Track if auto-compaction is in progress
|
|
772
|
-
let autoCompactionInProgress = false;
|
|
773
|
-
// Auto-compaction helper
|
|
774
|
-
const checkAutoCompaction = async () => {
|
|
775
|
-
if (autoCompactionInProgress)
|
|
776
|
-
return;
|
|
777
|
-
const settings = settingsManager.getCompactionSettings();
|
|
778
|
-
if (!settings.enabled)
|
|
779
|
-
return;
|
|
780
|
-
// Get last non-aborted assistant message
|
|
781
|
-
const messages = agent.state.messages;
|
|
782
|
-
let lastAssistant = null;
|
|
783
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
784
|
-
const msg = messages[i];
|
|
785
|
-
if (msg.role === "assistant") {
|
|
786
|
-
const assistantMsg = msg;
|
|
787
|
-
if (assistantMsg.stopReason !== "aborted") {
|
|
788
|
-
lastAssistant = assistantMsg;
|
|
789
|
-
break;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
if (!lastAssistant)
|
|
794
|
-
return;
|
|
795
|
-
const contextTokens = calculateContextTokens(lastAssistant.usage);
|
|
796
|
-
const contextWindow = agent.state.model.contextWindow;
|
|
797
|
-
if (!shouldCompact(contextTokens, contextWindow, settings))
|
|
798
|
-
return;
|
|
799
|
-
// Trigger auto-compaction
|
|
800
|
-
autoCompactionInProgress = true;
|
|
801
|
-
try {
|
|
802
|
-
const apiKey = await getApiKeyForModel(agent.state.model);
|
|
803
|
-
if (!apiKey) {
|
|
804
|
-
throw new Error(`No API key for ${agent.state.model.provider}`);
|
|
805
|
-
}
|
|
806
|
-
const entries = sessionManager.loadEntries();
|
|
807
|
-
const compactionEntry = await compact(entries, agent.state.model, settings, apiKey);
|
|
808
|
-
sessionManager.saveCompaction(compactionEntry);
|
|
809
|
-
const loaded = loadSessionFromEntries(sessionManager.loadEntries());
|
|
810
|
-
agent.replaceMessages(loaded.messages);
|
|
811
|
-
// Emit auto-compaction event
|
|
812
|
-
console.log(JSON.stringify({ ...compactionEntry, auto: true }));
|
|
813
|
-
}
|
|
814
|
-
catch (error) {
|
|
815
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
816
|
-
console.log(JSON.stringify({ type: "error", error: `Auto-compaction failed: ${message}` }));
|
|
817
|
-
}
|
|
818
|
-
finally {
|
|
819
|
-
autoCompactionInProgress = false;
|
|
820
|
-
}
|
|
107
|
+
return {
|
|
108
|
+
initialMessage,
|
|
109
|
+
initialAttachments: imageAttachments.length > 0 ? imageAttachments : undefined,
|
|
821
110
|
};
|
|
822
|
-
// Subscribe to all events and output as JSON (same pattern as tui-renderer)
|
|
823
|
-
agent.subscribe(async (event) => {
|
|
824
|
-
console.log(JSON.stringify(event));
|
|
825
|
-
// Save messages to session
|
|
826
|
-
if (event.type === "message_end") {
|
|
827
|
-
sessionManager.saveMessage(event.message);
|
|
828
|
-
// Yield to microtask queue to allow agent state to update
|
|
829
|
-
// (tui-renderer does this implicitly via await handleEvent)
|
|
830
|
-
await Promise.resolve();
|
|
831
|
-
// Check if we should initialize session now (after first user+assistant exchange)
|
|
832
|
-
if (sessionManager.shouldInitializeSession(agent.state.messages)) {
|
|
833
|
-
sessionManager.startSession(agent.state);
|
|
834
|
-
}
|
|
835
|
-
// Check for auto-compaction after assistant messages
|
|
836
|
-
if (event.message.role === "assistant") {
|
|
837
|
-
await checkAutoCompaction();
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
// Listen for JSON input on stdin
|
|
842
|
-
const readline = await import("readline");
|
|
843
|
-
const rl = readline.createInterface({
|
|
844
|
-
input: process.stdin,
|
|
845
|
-
output: process.stdout,
|
|
846
|
-
terminal: false,
|
|
847
|
-
});
|
|
848
|
-
rl.on("line", async (line) => {
|
|
849
|
-
try {
|
|
850
|
-
const input = JSON.parse(line);
|
|
851
|
-
// Handle different RPC commands
|
|
852
|
-
if (input.type === "prompt" && input.message) {
|
|
853
|
-
await agent.prompt(input.message, input.attachments);
|
|
854
|
-
}
|
|
855
|
-
else if (input.type === "abort") {
|
|
856
|
-
agent.abort();
|
|
857
|
-
}
|
|
858
|
-
else if (input.type === "compact") {
|
|
859
|
-
// Handle compaction request
|
|
860
|
-
try {
|
|
861
|
-
const apiKey = await getApiKeyForModel(agent.state.model);
|
|
862
|
-
if (!apiKey) {
|
|
863
|
-
throw new Error(`No API key for ${agent.state.model.provider}`);
|
|
864
|
-
}
|
|
865
|
-
const entries = sessionManager.loadEntries();
|
|
866
|
-
const settings = settingsManager.getCompactionSettings();
|
|
867
|
-
const compactionEntry = await compact(entries, agent.state.model, settings, apiKey, undefined, input.customInstructions);
|
|
868
|
-
// Save and reload
|
|
869
|
-
sessionManager.saveCompaction(compactionEntry);
|
|
870
|
-
const loaded = loadSessionFromEntries(sessionManager.loadEntries());
|
|
871
|
-
agent.replaceMessages(loaded.messages);
|
|
872
|
-
// Emit compaction event (compactionEntry already has type: "compaction")
|
|
873
|
-
console.log(JSON.stringify(compactionEntry));
|
|
874
|
-
}
|
|
875
|
-
catch (error) {
|
|
876
|
-
console.log(JSON.stringify({ type: "error", error: `Compaction failed: ${error.message}` }));
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
else if (input.type === "bash" && input.command) {
|
|
880
|
-
// Execute bash command and add to context
|
|
881
|
-
try {
|
|
882
|
-
const result = await executeRpcBashCommand(input.command);
|
|
883
|
-
// Create bash execution message
|
|
884
|
-
const bashMessage = {
|
|
885
|
-
role: "bashExecution",
|
|
886
|
-
command: input.command,
|
|
887
|
-
output: result.truncationResult?.content || result.output,
|
|
888
|
-
exitCode: result.exitCode,
|
|
889
|
-
cancelled: false,
|
|
890
|
-
truncated: result.truncationResult?.truncated || false,
|
|
891
|
-
fullOutputPath: result.fullOutputPath,
|
|
892
|
-
timestamp: Date.now(),
|
|
893
|
-
};
|
|
894
|
-
// Add to agent state and save to session
|
|
895
|
-
agent.appendMessage(bashMessage);
|
|
896
|
-
sessionManager.saveMessage(bashMessage);
|
|
897
|
-
// Initialize session if needed (same logic as message_end handler)
|
|
898
|
-
if (sessionManager.shouldInitializeSession(agent.state.messages)) {
|
|
899
|
-
sessionManager.startSession(agent.state);
|
|
900
|
-
}
|
|
901
|
-
// Emit bash_end event with the message
|
|
902
|
-
console.log(JSON.stringify({ type: "bash_end", message: bashMessage }));
|
|
903
|
-
}
|
|
904
|
-
catch (error) {
|
|
905
|
-
console.log(JSON.stringify({ type: "error", error: `Bash command failed: ${error.message}` }));
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
catch (error) {
|
|
910
|
-
// Output error as JSON
|
|
911
|
-
console.log(JSON.stringify({ type: "error", error: error.message }));
|
|
912
|
-
}
|
|
913
|
-
});
|
|
914
|
-
// Keep process alive
|
|
915
|
-
return new Promise(() => { });
|
|
916
111
|
}
|
|
917
112
|
export async function main(args) {
|
|
918
113
|
const parsed = parseArgs(args);
|
|
@@ -923,14 +118,14 @@ export async function main(args) {
|
|
|
923
118
|
// Handle --export flag: convert session file to HTML and exit
|
|
924
119
|
if (parsed.export) {
|
|
925
120
|
try {
|
|
926
|
-
// Use first message as output path if provided
|
|
927
121
|
const outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;
|
|
928
122
|
const result = exportFromFile(parsed.export, outputPath);
|
|
929
123
|
console.log(`Exported to: ${result}`);
|
|
930
124
|
return;
|
|
931
125
|
}
|
|
932
126
|
catch (error) {
|
|
933
|
-
|
|
127
|
+
const message = error instanceof Error ? error.message : "Failed to export session";
|
|
128
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
934
129
|
process.exit(1);
|
|
935
130
|
}
|
|
936
131
|
}
|
|
@@ -939,28 +134,14 @@ export async function main(args) {
|
|
|
939
134
|
console.error(chalk.red("Error: @file arguments are not supported in RPC mode"));
|
|
940
135
|
process.exit(1);
|
|
941
136
|
}
|
|
942
|
-
// Process @file arguments
|
|
943
|
-
|
|
944
|
-
let initialAttachments;
|
|
945
|
-
if (parsed.fileArgs.length > 0) {
|
|
946
|
-
const { textContent, imageAttachments } = processFileArguments(parsed.fileArgs);
|
|
947
|
-
// Combine file content with first plain text message (if any)
|
|
948
|
-
if (parsed.messages.length > 0) {
|
|
949
|
-
initialMessage = textContent + parsed.messages[0];
|
|
950
|
-
parsed.messages.shift(); // Remove first message as it's been combined
|
|
951
|
-
}
|
|
952
|
-
else {
|
|
953
|
-
initialMessage = textContent;
|
|
954
|
-
}
|
|
955
|
-
initialAttachments = imageAttachments.length > 0 ? imageAttachments : undefined;
|
|
956
|
-
}
|
|
137
|
+
// Process @file arguments
|
|
138
|
+
const { initialMessage, initialAttachments } = prepareInitialMessage(parsed);
|
|
957
139
|
// Initialize theme (before any TUI rendering)
|
|
958
140
|
const settingsManager = new SettingsManager();
|
|
959
141
|
const themeName = settingsManager.getTheme();
|
|
960
142
|
initTheme(themeName);
|
|
961
143
|
// Setup session manager
|
|
962
144
|
const sessionManager = new SessionManager(parsed.continue && !parsed.resume, parsed.session);
|
|
963
|
-
// Disable session saving if --no-session flag is set
|
|
964
145
|
if (parsed.noSession) {
|
|
965
146
|
sessionManager.disable();
|
|
966
147
|
}
|
|
@@ -971,95 +152,31 @@ export async function main(args) {
|
|
|
971
152
|
console.log(chalk.dim("No session selected"));
|
|
972
153
|
return;
|
|
973
154
|
}
|
|
974
|
-
// Set the selected session as the active session
|
|
975
155
|
sessionManager.setSessionFile(selectedSession);
|
|
976
156
|
}
|
|
977
|
-
// Resolve model scope early if provided
|
|
157
|
+
// Resolve model scope early if provided
|
|
978
158
|
let scopedModels = [];
|
|
979
159
|
if (parsed.models && parsed.models.length > 0) {
|
|
980
160
|
scopedModels = await resolveModelScope(parsed.models);
|
|
981
161
|
}
|
|
982
|
-
// Determine
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
//
|
|
987
|
-
|
|
988
|
-
// 6. null (allowed in interactive mode)
|
|
989
|
-
let initialModel = null;
|
|
162
|
+
// Determine mode and output behavior
|
|
163
|
+
const isInteractive = !parsed.print && parsed.mode === undefined;
|
|
164
|
+
const mode = parsed.mode || "text";
|
|
165
|
+
const shouldPrintMessages = isInteractive;
|
|
166
|
+
// Find initial model
|
|
167
|
+
let initialModel = await findInitialModelForSession(parsed, scopedModels, settingsManager);
|
|
990
168
|
let initialThinking = "off";
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
const { model, error } = findModel(parsed.provider, parsed.model);
|
|
994
|
-
if (error) {
|
|
995
|
-
console.error(chalk.red(error));
|
|
996
|
-
process.exit(1);
|
|
997
|
-
}
|
|
998
|
-
if (!model) {
|
|
999
|
-
console.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));
|
|
1000
|
-
process.exit(1);
|
|
1001
|
-
}
|
|
1002
|
-
initialModel = model;
|
|
1003
|
-
}
|
|
1004
|
-
else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
|
|
1005
|
-
// 2. Use first model from --models scope (skip if continuing/resuming session)
|
|
1006
|
-
initialModel = scopedModels[0].model;
|
|
169
|
+
// Get thinking level from scoped models if applicable
|
|
170
|
+
if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
|
|
1007
171
|
initialThinking = scopedModels[0].thinkingLevel;
|
|
1008
172
|
}
|
|
1009
|
-
else
|
|
1010
|
-
//
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
// 3. Try saved default from settings
|
|
1015
|
-
const defaultProvider = settingsManager.getDefaultProvider();
|
|
1016
|
-
const defaultModel = settingsManager.getDefaultModel();
|
|
1017
|
-
if (defaultProvider && defaultModel) {
|
|
1018
|
-
const { model, error } = findModel(defaultProvider, defaultModel);
|
|
1019
|
-
if (error) {
|
|
1020
|
-
console.error(chalk.red(error));
|
|
1021
|
-
process.exit(1);
|
|
1022
|
-
}
|
|
1023
|
-
initialModel = model;
|
|
1024
|
-
// Also load saved thinking level if we're using saved model
|
|
1025
|
-
const savedThinking = settingsManager.getDefaultThinkingLevel();
|
|
1026
|
-
if (savedThinking) {
|
|
1027
|
-
initialThinking = savedThinking;
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
if (!initialModel) {
|
|
1032
|
-
// 4. Try first available model with valid API key
|
|
1033
|
-
// Prefer default model for each provider if available
|
|
1034
|
-
const { models: availableModels, error } = await getAvailableModels();
|
|
1035
|
-
if (error) {
|
|
1036
|
-
console.error(chalk.red(error));
|
|
1037
|
-
process.exit(1);
|
|
1038
|
-
}
|
|
1039
|
-
if (availableModels.length > 0) {
|
|
1040
|
-
// Try to find a default model from known providers
|
|
1041
|
-
for (const provider of Object.keys(defaultModelPerProvider)) {
|
|
1042
|
-
const defaultModelId = defaultModelPerProvider[provider];
|
|
1043
|
-
const match = availableModels.find((m) => m.provider === provider && m.id === defaultModelId);
|
|
1044
|
-
if (match) {
|
|
1045
|
-
initialModel = match;
|
|
1046
|
-
break;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
// If no default found, use first available
|
|
1050
|
-
if (!initialModel) {
|
|
1051
|
-
initialModel = availableModels[0];
|
|
1052
|
-
}
|
|
173
|
+
else {
|
|
174
|
+
// Try saved thinking level
|
|
175
|
+
const savedThinking = settingsManager.getDefaultThinkingLevel();
|
|
176
|
+
if (savedThinking) {
|
|
177
|
+
initialThinking = savedThinking;
|
|
1053
178
|
}
|
|
1054
179
|
}
|
|
1055
|
-
// Determine mode early to know if we should print messages and fail early
|
|
1056
|
-
// Interactive mode: no --print flag and no --mode flag
|
|
1057
|
-
// Having initial messages doesn't make it non-interactive anymore
|
|
1058
|
-
const isInteractive = !parsed.print && parsed.mode === undefined;
|
|
1059
|
-
const mode = parsed.mode || "text";
|
|
1060
|
-
// Only print informational messages in interactive mode
|
|
1061
|
-
// Non-interactive modes (-p, --mode json, --mode rpc) should be silent except for output
|
|
1062
|
-
const shouldPrintMessages = isInteractive;
|
|
1063
180
|
// Non-interactive mode: fail early if no model available
|
|
1064
181
|
if (!isInteractive && !initialModel) {
|
|
1065
182
|
console.error(chalk.red("No models available."));
|
|
@@ -1076,71 +193,25 @@ export async function main(args) {
|
|
|
1076
193
|
process.exit(1);
|
|
1077
194
|
}
|
|
1078
195
|
}
|
|
196
|
+
// Build system prompt
|
|
1079
197
|
const systemPrompt = buildSystemPrompt(parsed.systemPrompt, parsed.tools, parsed.appendSystemPrompt);
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1082
|
-
if (parsed.continue || parsed.resume) {
|
|
1083
|
-
// Load and restore model (overrides initialModel if found and has API key)
|
|
198
|
+
// Handle session restoration
|
|
199
|
+
let modelFallbackMessage = null;
|
|
200
|
+
if (parsed.continue || parsed.resume || parsed.session) {
|
|
1084
201
|
const savedModel = sessionManager.loadModel();
|
|
1085
202
|
if (savedModel) {
|
|
1086
|
-
const
|
|
1087
|
-
if (
|
|
1088
|
-
|
|
1089
|
-
process.exit(1);
|
|
203
|
+
const result = await restoreModelFromSession(savedModel.provider, savedModel.modelId, initialModel, shouldPrintMessages);
|
|
204
|
+
if (result.model) {
|
|
205
|
+
initialModel = result.model;
|
|
1090
206
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
else {
|
|
1100
|
-
// Model not found or no API key - fall back to default selection
|
|
1101
|
-
const reason = !restoredModel ? "model no longer exists" : "no API key available";
|
|
1102
|
-
if (shouldPrintMessages) {
|
|
1103
|
-
console.error(chalk.yellow(`Warning: Could not restore model ${savedModel.provider}/${savedModel.modelId} (${reason}).`));
|
|
1104
|
-
}
|
|
1105
|
-
// Ensure we have a valid model - use the same fallback logic
|
|
1106
|
-
if (!initialModel) {
|
|
1107
|
-
const { models: availableModels, error: availableError } = await getAvailableModels();
|
|
1108
|
-
if (availableError) {
|
|
1109
|
-
console.error(chalk.red(availableError));
|
|
1110
|
-
process.exit(1);
|
|
1111
|
-
}
|
|
1112
|
-
if (availableModels.length > 0) {
|
|
1113
|
-
// Try to find a default model from known providers
|
|
1114
|
-
for (const provider of Object.keys(defaultModelPerProvider)) {
|
|
1115
|
-
const defaultModelId = defaultModelPerProvider[provider];
|
|
1116
|
-
const match = availableModels.find((m) => m.provider === provider && m.id === defaultModelId);
|
|
1117
|
-
if (match) {
|
|
1118
|
-
initialModel = match;
|
|
1119
|
-
break;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
// If no default found, use first available
|
|
1123
|
-
if (!initialModel) {
|
|
1124
|
-
initialModel = availableModels[0];
|
|
1125
|
-
}
|
|
1126
|
-
if (initialModel && shouldPrintMessages) {
|
|
1127
|
-
console.log(chalk.dim(`Falling back to: ${initialModel.provider}/${initialModel.id}`));
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
else {
|
|
1131
|
-
// No models available at all
|
|
1132
|
-
if (shouldPrintMessages) {
|
|
1133
|
-
console.error(chalk.red("\nNo models available."));
|
|
1134
|
-
console.error(chalk.yellow("Set an API key environment variable:"));
|
|
1135
|
-
console.error(" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.");
|
|
1136
|
-
console.error(chalk.yellow(`\nOr create ${getModelsPath()}`));
|
|
1137
|
-
}
|
|
1138
|
-
process.exit(1);
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
else if (shouldPrintMessages) {
|
|
1142
|
-
console.log(chalk.dim(`Falling back to: ${initialModel.provider}/${initialModel.id}`));
|
|
1143
|
-
}
|
|
207
|
+
modelFallbackMessage = result.fallbackMessage;
|
|
208
|
+
}
|
|
209
|
+
// Load and restore thinking level
|
|
210
|
+
const thinkingLevel = sessionManager.loadThinkingLevel();
|
|
211
|
+
if (thinkingLevel) {
|
|
212
|
+
initialThinking = thinkingLevel;
|
|
213
|
+
if (shouldPrintMessages) {
|
|
214
|
+
console.log(chalk.dim(`Restored thinking level: ${thinkingLevel}`));
|
|
1144
215
|
}
|
|
1145
216
|
}
|
|
1146
217
|
}
|
|
@@ -1150,28 +221,25 @@ export async function main(args) {
|
|
|
1150
221
|
}
|
|
1151
222
|
// Determine which tools to use
|
|
1152
223
|
const selectedTools = parsed.tools ? parsed.tools.map((name) => allTools[name]) : codingTools;
|
|
1153
|
-
// Create agent
|
|
224
|
+
// Create agent
|
|
1154
225
|
const agent = new Agent({
|
|
1155
226
|
initialState: {
|
|
1156
227
|
systemPrompt,
|
|
1157
|
-
model: initialModel, // Can be null
|
|
228
|
+
model: initialModel, // Can be null in interactive mode
|
|
1158
229
|
thinkingLevel: initialThinking,
|
|
1159
230
|
tools: selectedTools,
|
|
1160
231
|
},
|
|
1161
232
|
messageTransformer,
|
|
1162
233
|
queueMode: settingsManager.getQueueMode(),
|
|
1163
234
|
transport: new ProviderTransport({
|
|
1164
|
-
// Dynamic API key lookup based on current model's provider
|
|
1165
235
|
getApiKey: async () => {
|
|
1166
236
|
const currentModel = agent.state.model;
|
|
1167
237
|
if (!currentModel) {
|
|
1168
238
|
throw new Error("No model selected");
|
|
1169
239
|
}
|
|
1170
|
-
// Try CLI override first
|
|
1171
240
|
if (parsed.apiKey) {
|
|
1172
241
|
return parsed.apiKey;
|
|
1173
242
|
}
|
|
1174
|
-
// Use model-specific key lookup
|
|
1175
243
|
const key = await getApiKeyForModel(currentModel);
|
|
1176
244
|
if (!key) {
|
|
1177
245
|
throw new Error(`No API key found for provider "${currentModel.provider}". Please set the appropriate environment variable or update ${getModelsPath()}`);
|
|
@@ -1180,44 +248,18 @@ export async function main(args) {
|
|
|
1180
248
|
},
|
|
1181
249
|
}),
|
|
1182
250
|
});
|
|
1183
|
-
// If initial thinking was requested but model doesn't support it,
|
|
251
|
+
// If initial thinking was requested but model doesn't support it, reset to off
|
|
1184
252
|
if (initialThinking !== "off" && initialModel && !initialModel.reasoning) {
|
|
1185
253
|
agent.setThinkingLevel("off");
|
|
1186
254
|
}
|
|
1187
|
-
//
|
|
1188
|
-
|
|
1189
|
-
// Load previous messages if continuing or resuming
|
|
1190
|
-
if (parsed.continue || parsed.resume) {
|
|
255
|
+
// Load previous messages if continuing, resuming, or using --session
|
|
256
|
+
if (parsed.continue || parsed.resume || parsed.session) {
|
|
1191
257
|
const messages = sessionManager.loadMessages();
|
|
1192
258
|
if (messages.length > 0) {
|
|
1193
259
|
agent.replaceMessages(messages);
|
|
1194
260
|
}
|
|
1195
|
-
// Load and restore thinking level
|
|
1196
|
-
const thinkingLevel = sessionManager.loadThinkingLevel();
|
|
1197
|
-
if (thinkingLevel) {
|
|
1198
|
-
agent.setThinkingLevel(thinkingLevel);
|
|
1199
|
-
if (shouldPrintMessages) {
|
|
1200
|
-
console.log(chalk.dim(`Restored thinking level: ${thinkingLevel}`));
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
// Check if we had to fall back from saved model
|
|
1204
|
-
const savedModel = sessionManager.loadModel();
|
|
1205
|
-
if (savedModel && initialModel) {
|
|
1206
|
-
const savedMatches = initialModel.provider === savedModel.provider && initialModel.id === savedModel.modelId;
|
|
1207
|
-
if (!savedMatches) {
|
|
1208
|
-
const { model: restoredModel, error } = findModel(savedModel.provider, savedModel.modelId);
|
|
1209
|
-
if (error) {
|
|
1210
|
-
// Config error - already shown above, just use generic message
|
|
1211
|
-
modelFallbackMessage = `Could not restore model ${savedModel.provider}/${savedModel.modelId}. Using ${initialModel.provider}/${initialModel.id}.`;
|
|
1212
|
-
}
|
|
1213
|
-
else {
|
|
1214
|
-
const reason = !restoredModel ? "model no longer exists" : "no API key available";
|
|
1215
|
-
modelFallbackMessage = `Could not restore model ${savedModel.provider}/${savedModel.modelId} (${reason}). Using ${initialModel.provider}/${initialModel.id}.`;
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
261
|
}
|
|
1220
|
-
// Log loaded context files
|
|
262
|
+
// Log loaded context files
|
|
1221
263
|
if (shouldPrintMessages && !parsed.continue && !parsed.resume) {
|
|
1222
264
|
const contextFiles = loadProjectContextFiles();
|
|
1223
265
|
if (contextFiles.length > 0) {
|
|
@@ -1227,39 +269,25 @@ export async function main(args) {
|
|
|
1227
269
|
}
|
|
1228
270
|
}
|
|
1229
271
|
}
|
|
272
|
+
// Load file commands for slash command expansion
|
|
273
|
+
const fileCommands = loadSlashCommands();
|
|
274
|
+
// Create session
|
|
275
|
+
const session = new AgentSession({
|
|
276
|
+
agent,
|
|
277
|
+
sessionManager,
|
|
278
|
+
settingsManager,
|
|
279
|
+
scopedModels,
|
|
280
|
+
fileCommands,
|
|
281
|
+
});
|
|
1230
282
|
// Route to appropriate mode
|
|
1231
283
|
if (mode === "rpc") {
|
|
1232
|
-
|
|
1233
|
-
await runRpcMode(agent, sessionManager, settingsManager);
|
|
284
|
+
await runRpcMode(session);
|
|
1234
285
|
}
|
|
1235
286
|
else if (isInteractive) {
|
|
1236
|
-
// Check for new version in the background
|
|
287
|
+
// Check for new version in the background
|
|
1237
288
|
const versionCheckPromise = checkForNewVersion(VERSION).catch(() => null);
|
|
1238
|
-
// Check if we should show changelog
|
|
1239
|
-
|
|
1240
|
-
if (!parsed.continue && !parsed.resume) {
|
|
1241
|
-
const lastVersion = settingsManager.getLastChangelogVersion();
|
|
1242
|
-
// Check if we need to show changelog
|
|
1243
|
-
if (!lastVersion) {
|
|
1244
|
-
// First run - show all entries
|
|
1245
|
-
const changelogPath = getChangelogPath();
|
|
1246
|
-
const entries = parseChangelog(changelogPath);
|
|
1247
|
-
if (entries.length > 0) {
|
|
1248
|
-
changelogMarkdown = entries.map((e) => e.content).join("\n\n");
|
|
1249
|
-
settingsManager.setLastChangelogVersion(VERSION);
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
else {
|
|
1253
|
-
// Parse current and last versions
|
|
1254
|
-
const changelogPath = getChangelogPath();
|
|
1255
|
-
const entries = parseChangelog(changelogPath);
|
|
1256
|
-
const newEntries = getNewEntries(entries, lastVersion);
|
|
1257
|
-
if (newEntries.length > 0) {
|
|
1258
|
-
changelogMarkdown = newEntries.map((e) => e.content).join("\n\n");
|
|
1259
|
-
settingsManager.setLastChangelogVersion(VERSION);
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
289
|
+
// Check if we should show changelog
|
|
290
|
+
const changelogMarkdown = getChangelogForDisplay(parsed, settingsManager);
|
|
1263
291
|
// Show model scope if provided
|
|
1264
292
|
if (scopedModels.length > 0) {
|
|
1265
293
|
const modelList = scopedModels
|
|
@@ -1272,13 +300,79 @@ export async function main(args) {
|
|
|
1272
300
|
}
|
|
1273
301
|
// Ensure fd tool is available for file autocomplete
|
|
1274
302
|
const fdPath = await ensureTool("fd");
|
|
1275
|
-
|
|
1276
|
-
const collapseChangelog = settingsManager.getCollapseChangelog();
|
|
1277
|
-
await runInteractiveMode(agent, sessionManager, settingsManager, VERSION, changelogMarkdown, collapseChangelog, modelFallbackMessage, versionCheckPromise, scopedModels, parsed.messages, initialMessage, initialAttachments, fdPath);
|
|
303
|
+
await runInteractiveMode(session, VERSION, changelogMarkdown, modelFallbackMessage, versionCheckPromise, parsed.messages, initialMessage, initialAttachments, fdPath);
|
|
1278
304
|
}
|
|
1279
305
|
else {
|
|
1280
306
|
// Non-interactive mode (--print flag or --mode flag)
|
|
1281
|
-
await
|
|
307
|
+
await runPrintMode(session, mode, parsed.messages, initialMessage, initialAttachments);
|
|
1282
308
|
}
|
|
1283
309
|
}
|
|
310
|
+
/** Find initial model based on CLI args, scoped models, settings, or available models */
|
|
311
|
+
async function findInitialModelForSession(parsed, scopedModels, settingsManager) {
|
|
312
|
+
// 1. CLI args take priority
|
|
313
|
+
if (parsed.provider && parsed.model) {
|
|
314
|
+
const { model, error } = findModel(parsed.provider, parsed.model);
|
|
315
|
+
if (error) {
|
|
316
|
+
console.error(chalk.red(error));
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
if (!model) {
|
|
320
|
+
console.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
return model;
|
|
324
|
+
}
|
|
325
|
+
// 2. Use first model from scoped models (skip if continuing/resuming)
|
|
326
|
+
if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
|
|
327
|
+
return scopedModels[0].model;
|
|
328
|
+
}
|
|
329
|
+
// 3. Try saved default from settings
|
|
330
|
+
const defaultProvider = settingsManager.getDefaultProvider();
|
|
331
|
+
const defaultModelId = settingsManager.getDefaultModel();
|
|
332
|
+
if (defaultProvider && defaultModelId) {
|
|
333
|
+
const { model, error } = findModel(defaultProvider, defaultModelId);
|
|
334
|
+
if (error) {
|
|
335
|
+
console.error(chalk.red(error));
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
if (model) {
|
|
339
|
+
return model;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// 4. Try first available model with valid API key
|
|
343
|
+
const { models: availableModels, error } = await getAvailableModels();
|
|
344
|
+
if (error) {
|
|
345
|
+
console.error(chalk.red(error));
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
if (availableModels.length > 0) {
|
|
349
|
+
return availableModels[0];
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
/** Get changelog markdown to display (only for new sessions with updates) */
|
|
354
|
+
function getChangelogForDisplay(parsed, settingsManager) {
|
|
355
|
+
if (parsed.continue || parsed.resume) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
const lastVersion = settingsManager.getLastChangelogVersion();
|
|
359
|
+
const changelogPath = getChangelogPath();
|
|
360
|
+
const entries = parseChangelog(changelogPath);
|
|
361
|
+
if (!lastVersion) {
|
|
362
|
+
// First run - show all entries
|
|
363
|
+
if (entries.length > 0) {
|
|
364
|
+
settingsManager.setLastChangelogVersion(VERSION);
|
|
365
|
+
return entries.map((e) => e.content).join("\n\n");
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
// Check for new entries since last version
|
|
370
|
+
const newEntries = getNewEntries(entries, lastVersion);
|
|
371
|
+
if (newEntries.length > 0) {
|
|
372
|
+
settingsManager.setLastChangelogVersion(VERSION);
|
|
373
|
+
return newEntries.map((e) => e.content).join("\n\n");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
1284
378
|
//# sourceMappingURL=main.js.map
|