@prestyj/cli 4.2.16 → 4.2.45
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/LICENSE +1 -1
- package/README.md +15 -7
- package/dist/cli.js +458 -238
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +66 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +22 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +184 -23
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agents.d.ts +0 -2
- package/dist/core/agents.d.ts.map +1 -1
- package/dist/core/agents.js +1 -5
- package/dist/core/agents.js.map +1 -1
- package/dist/core/auth-storage.d.ts +2 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +66 -17
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/auto-update.js +1 -1
- package/dist/core/auto-update.js.map +1 -1
- package/dist/core/compaction/compactor.d.ts +36 -5
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +222 -63
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +377 -32
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/token-estimator.d.ts +5 -0
- package/dist/core/compaction/token-estimator.d.ts.map +1 -1
- package/dist/core/compaction/token-estimator.js +41 -2
- package/dist/core/compaction/token-estimator.js.map +1 -1
- package/dist/core/compaction/token-estimator.test.js +43 -21
- package/dist/core/compaction/token-estimator.test.js.map +1 -1
- package/dist/core/event-bus.d.ts +6 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +7 -0
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/file-lock.d.ts +6 -0
- package/dist/core/file-lock.d.ts.map +1 -0
- package/dist/core/file-lock.js +76 -0
- package/dist/core/file-lock.js.map +1 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/mcp/client.d.ts +0 -26
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +34 -141
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/defaults.d.ts.map +1 -1
- package/dist/core/mcp/defaults.js +4 -8
- package/dist/core/mcp/defaults.js.map +1 -1
- package/dist/core/mcp/types.d.ts +0 -2
- package/dist/core/mcp/types.d.ts.map +1 -1
- package/dist/core/model-registry.d.ts +2 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +16 -5
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/oauth/anthropic.d.ts.map +1 -1
- package/dist/core/oauth/anthropic.js +3 -3
- package/dist/core/oauth/anthropic.js.map +1 -1
- package/dist/core/prompt-commands.d.ts.map +1 -1
- package/dist/core/prompt-commands.js +21 -0
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/session-manager.d.ts +34 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +160 -54
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -2
- package/dist/core/skills.d.ts +2 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +8 -8
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts +4 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +22 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/telegram.d.ts +94 -0
- package/dist/core/telegram.d.ts.map +1 -0
- package/dist/core/telegram.js +227 -0
- package/dist/core/telegram.js.map +1 -0
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +36 -5
- package/dist/interactive.js.map +1 -1
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/json-mode.d.ts.map +1 -1
- package/dist/modes/json-mode.js +2 -1
- package/dist/modes/json-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +2 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc-mode.d.ts +28 -0
- package/dist/modes/rpc-mode.d.ts.map +1 -0
- package/dist/modes/rpc-mode.js +146 -0
- package/dist/modes/rpc-mode.js.map +1 -0
- package/dist/modes/serve-mode.d.ts +21 -0
- package/dist/modes/serve-mode.d.ts.map +1 -0
- package/dist/modes/serve-mode.js +650 -0
- package/dist/modes/serve-mode.js.map +1 -0
- package/dist/system-prompt.d.ts +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +45 -10
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/bash.d.ts +4 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +58 -5
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +4 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +9 -5
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/enter-plan.d.ts +8 -0
- package/dist/tools/enter-plan.d.ts.map +1 -0
- package/dist/tools/enter-plan.js +28 -0
- package/dist/tools/enter-plan.js.map +1 -0
- package/dist/tools/exit-plan.d.ts +8 -0
- package/dist/tools/exit-plan.d.ts.map +1 -0
- package/dist/tools/exit-plan.js +35 -0
- package/dist/tools/exit-plan.js.map +1 -0
- package/dist/tools/grep.d.ts +2 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +7 -8
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/index.d.ts +17 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +26 -7
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/ls.d.ts +2 -1
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +4 -4
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/operations.d.ts +39 -0
- package/dist/tools/operations.d.ts.map +1 -0
- package/dist/tools/operations.js +27 -0
- package/dist/tools/operations.js.map +1 -0
- package/dist/tools/path-utils.d.ts +5 -0
- package/dist/tools/path-utils.d.ts.map +1 -1
- package/dist/tools/path-utils.js +18 -0
- package/dist/tools/path-utils.js.map +1 -1
- package/dist/tools/plan-mode.test.d.ts +2 -0
- package/dist/tools/plan-mode.test.d.ts.map +1 -0
- package/dist/tools/plan-mode.test.js +214 -0
- package/dist/tools/plan-mode.test.js.map +1 -0
- package/dist/tools/read.d.ts +2 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +6 -5
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/skill.d.ts +10 -0
- package/dist/tools/skill.d.ts.map +1 -0
- package/dist/tools/skill.js +36 -0
- package/dist/tools/skill.js.map +1 -0
- package/dist/tools/subagent.d.ts +3 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +46 -4
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/tasks.d.ts +2 -2
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +47 -0
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/write.d.ts +4 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +17 -6
- package/dist/tools/write.js.map +1 -1
- package/dist/ui/App.d.ts +35 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +367 -91
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts +2 -1
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +25 -4
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/AnimationContext.d.ts +22 -0
- package/dist/ui/components/AnimationContext.d.ts.map +1 -0
- package/dist/ui/components/AnimationContext.js +35 -0
- package/dist/ui/components/AnimationContext.js.map +1 -0
- package/dist/ui/components/AssistantMessage.d.ts.map +1 -1
- package/dist/ui/components/AssistantMessage.js +6 -1
- package/dist/ui/components/AssistantMessage.js.map +1 -1
- package/dist/ui/components/Banner.d.ts.map +1 -1
- package/dist/ui/components/Banner.js +4 -7
- package/dist/ui/components/Banner.js.map +1 -1
- package/dist/ui/components/CompactionNotice.d.ts.map +1 -1
- package/dist/ui/components/CompactionNotice.js +4 -8
- package/dist/ui/components/CompactionNotice.js.map +1 -1
- package/dist/ui/components/Footer.d.ts +2 -1
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +8 -15
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/InputArea.d.ts +3 -1
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +23 -25
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/PlanApproval.d.ts +8 -0
- package/dist/ui/components/PlanApproval.d.ts.map +1 -0
- package/dist/ui/components/PlanApproval.js +44 -0
- package/dist/ui/components/PlanApproval.js.map +1 -0
- package/dist/ui/components/PlanBanner.d.ts +6 -0
- package/dist/ui/components/PlanBanner.d.ts.map +1 -0
- package/dist/ui/components/PlanBanner.js +14 -0
- package/dist/ui/components/PlanBanner.js.map +1 -0
- package/dist/ui/components/PlanOverlay.d.ts +11 -0
- package/dist/ui/components/PlanOverlay.d.ts.map +1 -0
- package/dist/ui/components/PlanOverlay.js +263 -0
- package/dist/ui/components/PlanOverlay.js.map +1 -0
- package/dist/ui/components/ServerToolExecution.d.ts +1 -0
- package/dist/ui/components/ServerToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ServerToolExecution.js +12 -3
- package/dist/ui/components/ServerToolExecution.js.map +1 -1
- package/dist/ui/components/SkillsOverlay.d.ts +7 -0
- package/dist/ui/components/SkillsOverlay.d.ts.map +1 -0
- package/dist/ui/components/SkillsOverlay.js +158 -0
- package/dist/ui/components/SkillsOverlay.js.map +1 -0
- package/dist/ui/components/Spinner.d.ts.map +1 -1
- package/dist/ui/components/Spinner.js +5 -9
- package/dist/ui/components/Spinner.js.map +1 -1
- package/dist/ui/components/StreamingArea.d.ts +2 -1
- package/dist/ui/components/StreamingArea.d.ts.map +1 -1
- package/dist/ui/components/StreamingArea.js +11 -4
- package/dist/ui/components/StreamingArea.js.map +1 -1
- package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
- package/dist/ui/components/SubAgentPanel.js +27 -29
- package/dist/ui/components/SubAgentPanel.js.map +1 -1
- package/dist/ui/components/TaskOverlay.d.ts.map +1 -1
- package/dist/ui/components/TaskOverlay.js +7 -3
- package/dist/ui/components/TaskOverlay.js.map +1 -1
- package/dist/ui/components/ThinkingBlock.d.ts.map +1 -1
- package/dist/ui/components/ThinkingBlock.js +4 -1
- package/dist/ui/components/ThinkingBlock.js.map +1 -1
- package/dist/ui/components/ThinkingIndicator.d.ts.map +1 -1
- package/dist/ui/components/ThinkingIndicator.js +8 -33
- package/dist/ui/components/ThinkingIndicator.js.map +1 -1
- package/dist/ui/components/ToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ToolExecution.js +95 -41
- package/dist/ui/components/ToolExecution.js.map +1 -1
- package/dist/ui/components/ToolGroupExecution.d.ts +7 -0
- package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -0
- package/dist/ui/components/ToolGroupExecution.js +115 -0
- package/dist/ui/components/ToolGroupExecution.js.map +1 -0
- package/dist/ui/components/UserMessage.js +1 -1
- package/dist/ui/components/UserMessage.js.map +1 -1
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.d.ts.map +1 -1
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/index.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +8 -0
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +320 -220
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/hooks/useTerminalSize.d.ts +21 -9
- package/dist/ui/hooks/useTerminalSize.d.ts.map +1 -1
- package/dist/ui/hooks/useTerminalSize.js +33 -16
- package/dist/ui/hooks/useTerminalSize.js.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.js +8 -16
- package/dist/ui/hooks/useTerminalTitle.js.map +1 -1
- package/dist/ui/live-item-flush.d.ts +11 -1
- package/dist/ui/live-item-flush.d.ts.map +1 -1
- package/dist/ui/live-item-flush.js +81 -2
- package/dist/ui/live-item-flush.js.map +1 -1
- package/dist/ui/login.d.ts +1 -1
- package/dist/ui/login.d.ts.map +1 -1
- package/dist/ui/login.js +30 -6
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/render.d.ts +11 -0
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +24 -10
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/sessions.d.ts +2 -0
- package/dist/ui/sessions.d.ts.map +1 -0
- package/dist/ui/sessions.js +208 -0
- package/dist/ui/sessions.js.map +1 -0
- package/dist/ui/theme/dark.json +3 -1
- package/dist/ui/theme/light.json +3 -1
- package/dist/ui/theme/theme.d.ts +2 -0
- package/dist/ui/theme/theme.d.ts.map +1 -1
- package/dist/utils/error-handler.d.ts.map +1 -1
- package/dist/utils/error-handler.js +17 -5
- package/dist/utils/error-handler.js.map +1 -1
- package/dist/utils/image.d.ts.map +1 -1
- package/dist/utils/image.js +60 -5
- package/dist/utils/image.js.map +1 -1
- package/package.json +8 -8
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// Catch stray abort-related promise rejections that escape the normal error
|
|
3
|
+
// handling chain (e.g. race conditions during Ctrl+C). Without this, Node.js
|
|
4
|
+
// v25+ crashes the process on any unhandled rejection.
|
|
5
|
+
process.on("unhandledRejection", (reason) => {
|
|
6
|
+
if (reason instanceof Error) {
|
|
7
|
+
const msg = reason.message.toLowerCase();
|
|
8
|
+
if (reason.name === "AbortError" || msg.includes("aborted") || msg.includes("abort")) {
|
|
9
|
+
// Silently swallow abort rejections — these are expected during cancellation
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
// Re-throw non-abort rejections so they still crash with a useful stack trace
|
|
14
|
+
throw reason;
|
|
15
|
+
});
|
|
2
16
|
// Drain performance entries to prevent buffer overflow warning from dependencies
|
|
3
|
-
import { PerformanceObserver } from "node:perf_hooks";
|
|
4
|
-
new PerformanceObserver(() => {
|
|
17
|
+
import { PerformanceObserver, performance } from "node:perf_hooks";
|
|
18
|
+
new PerformanceObserver((list) => {
|
|
19
|
+
for (const entry of list.getEntries()) {
|
|
20
|
+
if (entry.entryType === "measure")
|
|
21
|
+
performance.clearMeasures(entry.name);
|
|
22
|
+
else if (entry.entryType === "mark")
|
|
23
|
+
performance.clearMarks(entry.name);
|
|
24
|
+
}
|
|
25
|
+
}).observe({ entryTypes: ["measure", "mark"] });
|
|
5
26
|
import { parseArgs } from "node:util";
|
|
6
27
|
import fs from "node:fs";
|
|
7
28
|
import readline from "node:readline/promises";
|
|
8
|
-
import { execFile
|
|
29
|
+
import { execFile } from "node:child_process";
|
|
9
30
|
import { createRequire } from "node:module";
|
|
10
|
-
import { runPrintMode } from "./modes/print-mode.js";
|
|
11
|
-
import { runJsonMode } from "./modes/json-mode.js";
|
|
12
31
|
import { renderApp } from "./ui/render.js";
|
|
32
|
+
import { runJsonMode } from "./modes/json-mode.js";
|
|
33
|
+
import { runRpcMode } from "./modes/rpc-mode.js";
|
|
34
|
+
import { runServeMode } from "./modes/serve-mode.js";
|
|
13
35
|
import { renderLoginSelector } from "./ui/login.js";
|
|
36
|
+
import { renderSessionSelector } from "./ui/sessions.js";
|
|
14
37
|
import { formatUserError } from "./utils/error-handler.js";
|
|
15
38
|
import { AuthStorage } from "./core/auth-storage.js";
|
|
16
39
|
import { SessionManager } from "./core/session-manager.js";
|
|
@@ -18,64 +41,19 @@ import { ensureAppDirs, getAppPaths } from "./config.js";
|
|
|
18
41
|
import { initLogger, log, closeLogger } from "./core/logger.js";
|
|
19
42
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
20
43
|
import { createTools } from "./tools/index.js";
|
|
44
|
+
import { shouldCompact, compact } from "./core/compaction/compactor.js";
|
|
45
|
+
import { setEstimatorModel } from "./core/compaction/token-estimator.js";
|
|
46
|
+
import { getContextWindow } from "./core/model-registry.js";
|
|
21
47
|
import { MCPClientManager, getMCPServers } from "./core/mcp/index.js";
|
|
22
48
|
import { discoverAgents } from "./core/agents.js";
|
|
49
|
+
import { discoverSkills } from "./core/skills.js";
|
|
50
|
+
import path from "node:path";
|
|
23
51
|
import { loginAnthropic } from "./core/oauth/anthropic.js";
|
|
24
52
|
import { loginOpenAI } from "./core/oauth/openai.js";
|
|
25
53
|
import chalk from "chalk";
|
|
26
54
|
import { checkAndAutoUpdate } from "./core/auto-update.js";
|
|
27
|
-
// Re-exec with V8 flags for memory management during long sessions
|
|
28
|
-
if (!process.env["EZCODER_V8_FLAGS_SET"]) {
|
|
29
|
-
const needFlags = [];
|
|
30
|
-
if (!process.execArgv.some((a) => a.includes("max-old-space-size")))
|
|
31
|
-
needFlags.push("--max-old-space-size=8192");
|
|
32
|
-
if (!process.execArgv.includes("--expose-gc"))
|
|
33
|
-
needFlags.push("--expose-gc");
|
|
34
|
-
if (needFlags.length > 0) {
|
|
35
|
-
const result = spawnSync(process.execPath, [...needFlags, ...process.execArgv, ...process.argv.slice(1)], { stdio: "inherit", env: { ...process.env, EZCODER_V8_FLAGS_SET: "1" } });
|
|
36
|
-
process.exit(result.status ?? (result.signal ? 128 : 1));
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
55
|
const _require = createRequire(import.meta.url);
|
|
40
56
|
const CLI_VERSION = _require("../package.json").version;
|
|
41
|
-
const USAGE = `
|
|
42
|
-
Usage: ezcoder [command] [options] [message...]
|
|
43
|
-
|
|
44
|
-
Commands:
|
|
45
|
-
login Log in to a provider via OAuth
|
|
46
|
-
logout Log out (clear stored credentials)
|
|
47
|
-
continue Resume the most recent session for this directory
|
|
48
|
-
|
|
49
|
-
Options:
|
|
50
|
-
-p, --provider <name> LLM provider (anthropic, openai, glm, moonshot) [default: anthropic]
|
|
51
|
-
-m, --model <name> Model name [default: claude-opus-4-6]
|
|
52
|
-
--base-url <url> Custom API base URL
|
|
53
|
-
--system-prompt <text> Override system prompt
|
|
54
|
-
--thinking <level> Thinking level (low, medium, high, max)
|
|
55
|
-
--max-turns <n> Maximum agent loop turns [default: 40]
|
|
56
|
-
-s, --session <path> Resume a specific session file
|
|
57
|
-
--print Print mode: one-shot, output to stdout, then exit
|
|
58
|
-
--json JSON mode: one-shot, output NDJSON events to stdout
|
|
59
|
-
-v, --version Show version number
|
|
60
|
-
-h, --help Show this help message
|
|
61
|
-
|
|
62
|
-
Print mode:
|
|
63
|
-
echo "hello" | ezcoder --print
|
|
64
|
-
ezcoder --print "explain this code"
|
|
65
|
-
|
|
66
|
-
Authentication:
|
|
67
|
-
ezcoder login Log in (select provider interactively)
|
|
68
|
-
ezcoder logout Log out from all providers
|
|
69
|
-
`.trim();
|
|
70
|
-
function getHardcodedDefault(p) {
|
|
71
|
-
if (p === "openai")
|
|
72
|
-
return "gpt-5.3-codex";
|
|
73
|
-
if (p === "glm")
|
|
74
|
-
return "glm-5";
|
|
75
|
-
if (p === "moonshot")
|
|
76
|
-
return "kimi-k2.5";
|
|
77
|
-
return "claude-opus-4-6";
|
|
78
|
-
}
|
|
79
57
|
function main() {
|
|
80
58
|
// Silent auto-update check (throttled, non-blocking on failure)
|
|
81
59
|
const updateMessage = checkAndAutoUpdate(CLI_VERSION);
|
|
@@ -102,23 +80,48 @@ function main() {
|
|
|
102
80
|
});
|
|
103
81
|
return;
|
|
104
82
|
}
|
|
83
|
+
if (subcommand === "sessions") {
|
|
84
|
+
process.argv.splice(2, 1);
|
|
85
|
+
runSessions().catch((err) => {
|
|
86
|
+
log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
|
|
87
|
+
closeLogger();
|
|
88
|
+
process.stderr.write(formatUserError(err) + "\n");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (subcommand === "telegram") {
|
|
94
|
+
runTelegramSetup().catch((err) => {
|
|
95
|
+
log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
|
|
96
|
+
closeLogger();
|
|
97
|
+
process.stderr.write(formatUserError(err) + "\n");
|
|
98
|
+
process.exit(1);
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (subcommand === "serve") {
|
|
103
|
+
process.argv.splice(2, 1);
|
|
104
|
+
runServe().catch((err) => {
|
|
105
|
+
log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
|
|
106
|
+
closeLogger();
|
|
107
|
+
process.stderr.write(formatUserError(err) + "\n");
|
|
108
|
+
process.exit(1);
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
105
112
|
if (subcommand === "continue") {
|
|
106
113
|
// Remove "continue" so parseArgs handles remaining flags
|
|
107
114
|
process.argv.splice(2, 1);
|
|
108
115
|
}
|
|
109
116
|
const { values, positionals } = parseArgs({
|
|
110
117
|
options: {
|
|
111
|
-
provider: { type: "string", short: "p" },
|
|
112
|
-
model: { type: "string", short: "m" },
|
|
113
|
-
"base-url": { type: "string" },
|
|
114
|
-
"system-prompt": { type: "string" },
|
|
115
|
-
thinking: { type: "string" },
|
|
116
|
-
"max-turns": { type: "string" },
|
|
117
|
-
session: { type: "string", short: "s" },
|
|
118
|
-
print: { type: "boolean" },
|
|
119
|
-
json: { type: "boolean" },
|
|
120
118
|
version: { type: "boolean", short: "v" },
|
|
121
|
-
|
|
119
|
+
json: { type: "boolean" },
|
|
120
|
+
rpc: { type: "boolean" },
|
|
121
|
+
provider: { type: "string" },
|
|
122
|
+
model: { type: "string" },
|
|
123
|
+
"max-turns": { type: "string" },
|
|
124
|
+
"system-prompt": { type: "string" },
|
|
122
125
|
},
|
|
123
126
|
allowPositionals: true,
|
|
124
127
|
strict: true,
|
|
@@ -127,9 +130,43 @@ function main() {
|
|
|
127
130
|
console.log(CLI_VERSION);
|
|
128
131
|
process.exit(0);
|
|
129
132
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
// JSON mode — used by sub-agents
|
|
134
|
+
if (values.json) {
|
|
135
|
+
const message = positionals[0] ?? "";
|
|
136
|
+
const jsonProvider = (values.provider ?? "anthropic");
|
|
137
|
+
const jsonModel = values.model ?? "claude-opus-4-6";
|
|
138
|
+
const maxTurns = values["max-turns"] ? parseInt(values["max-turns"], 10) : undefined;
|
|
139
|
+
const systemPrompt = values["system-prompt"];
|
|
140
|
+
const cwd = process.cwd();
|
|
141
|
+
runJsonMode({
|
|
142
|
+
message,
|
|
143
|
+
provider: jsonProvider,
|
|
144
|
+
model: jsonModel,
|
|
145
|
+
cwd,
|
|
146
|
+
systemPrompt,
|
|
147
|
+
maxTurns,
|
|
148
|
+
}).catch((err) => {
|
|
149
|
+
process.stderr.write(formatUserError(err) + "\n");
|
|
150
|
+
process.exit(1);
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// RPC mode — headless JSON-over-stdio for IDE integrations
|
|
155
|
+
if (values.rpc) {
|
|
156
|
+
const rpcProvider = (values.provider ?? "anthropic");
|
|
157
|
+
const rpcModel = values.model ?? "claude-opus-4-6";
|
|
158
|
+
const systemPrompt = values["system-prompt"];
|
|
159
|
+
const cwd = process.cwd();
|
|
160
|
+
runRpcMode({
|
|
161
|
+
provider: rpcProvider,
|
|
162
|
+
model: rpcModel,
|
|
163
|
+
cwd,
|
|
164
|
+
systemPrompt,
|
|
165
|
+
}).catch((err) => {
|
|
166
|
+
process.stderr.write(formatUserError(err) + "\n");
|
|
167
|
+
process.exit(1);
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
133
170
|
}
|
|
134
171
|
// Load saved settings for model/provider persistence
|
|
135
172
|
let savedProvider;
|
|
@@ -150,74 +187,27 @@ function main() {
|
|
|
150
187
|
catch {
|
|
151
188
|
// No settings file or invalid JSON — use defaults
|
|
152
189
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// Print mode
|
|
163
|
-
if (values.print) {
|
|
164
|
-
const message = positionals.join(" ").trim() || readStdinSync();
|
|
165
|
-
if (!message) {
|
|
166
|
-
console.error("Error: --print requires a message (positional args or stdin)");
|
|
167
|
-
process.exit(1);
|
|
168
|
-
}
|
|
169
|
-
runPrintMode({
|
|
170
|
-
message,
|
|
171
|
-
provider,
|
|
172
|
-
model,
|
|
173
|
-
baseUrl: values["base-url"],
|
|
174
|
-
systemPrompt: values["system-prompt"],
|
|
175
|
-
cwd: process.cwd(),
|
|
176
|
-
thinkingLevel,
|
|
177
|
-
}).catch((err) => {
|
|
178
|
-
log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
|
|
179
|
-
closeLogger();
|
|
180
|
-
process.stderr.write(formatUserError(err) + "\n");
|
|
181
|
-
process.exit(1);
|
|
182
|
-
});
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
// JSON mode
|
|
186
|
-
if (values.json) {
|
|
187
|
-
const message = positionals.join(" ").trim() || readStdinSync();
|
|
188
|
-
if (!message) {
|
|
189
|
-
console.error("Error: --json requires a message (positional args or stdin)");
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
192
|
-
runJsonMode({
|
|
193
|
-
message,
|
|
194
|
-
provider,
|
|
195
|
-
model,
|
|
196
|
-
baseUrl: values["base-url"],
|
|
197
|
-
systemPrompt: values["system-prompt"],
|
|
198
|
-
cwd: process.cwd(),
|
|
199
|
-
thinkingLevel,
|
|
200
|
-
maxTurns,
|
|
201
|
-
}).catch((err) => {
|
|
202
|
-
log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
|
|
203
|
-
closeLogger();
|
|
204
|
-
process.stderr.write(formatUserError(err) + "\n");
|
|
205
|
-
process.exit(1);
|
|
206
|
-
});
|
|
207
|
-
return;
|
|
190
|
+
const provider = savedProvider ?? "anthropic";
|
|
191
|
+
function getHardcodedDefault(p) {
|
|
192
|
+
if (p === "openai")
|
|
193
|
+
return "gpt-5.3-codex";
|
|
194
|
+
if (p === "glm")
|
|
195
|
+
return "glm-5";
|
|
196
|
+
if (p === "moonshot")
|
|
197
|
+
return "kimi-k2.5";
|
|
198
|
+
return "claude-opus-4-6";
|
|
208
199
|
}
|
|
200
|
+
const model = savedModel ?? getHardcodedDefault(provider);
|
|
201
|
+
const thinkingLevel = savedThinkingEnabled ? "medium" : undefined;
|
|
209
202
|
// Interactive mode (Ink TUI)
|
|
210
203
|
const cwd = process.cwd();
|
|
211
204
|
const continueRecent = subcommand === "continue";
|
|
212
205
|
runInkTUI({
|
|
213
206
|
provider,
|
|
214
207
|
model,
|
|
215
|
-
baseUrl: values["base-url"],
|
|
216
208
|
cwd,
|
|
217
209
|
thinkingLevel,
|
|
218
|
-
systemPrompt: values["system-prompt"],
|
|
219
210
|
continueRecent,
|
|
220
|
-
sessionPath: values.session,
|
|
221
211
|
theme: savedTheme,
|
|
222
212
|
}).catch((err) => {
|
|
223
213
|
log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
|
|
@@ -228,42 +218,20 @@ function main() {
|
|
|
228
218
|
}
|
|
229
219
|
// ── Ink TUI ───────────────────────────────────────────────
|
|
230
220
|
async function runInkTUI(opts) {
|
|
231
|
-
const { cwd } = opts;
|
|
232
|
-
|
|
233
|
-
|
|
221
|
+
const { provider, model, cwd } = opts;
|
|
222
|
+
// Set model for token estimation accuracy
|
|
223
|
+
setEstimatorModel(model);
|
|
234
224
|
// Resolve auth
|
|
235
225
|
const paths = await ensureAppDirs();
|
|
236
|
-
const authStorage = new AuthStorage(paths.authFile);
|
|
237
|
-
await authStorage.load();
|
|
238
|
-
let creds;
|
|
239
|
-
try {
|
|
240
|
-
creds = await authStorage.resolveCredentials(provider);
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
243
|
-
// Not logged in — let user pick a provider and log in
|
|
244
|
-
const picked = await renderLoginSelector();
|
|
245
|
-
if (!picked) {
|
|
246
|
-
console.log(chalk.hex("#6b7280")("Login cancelled."));
|
|
247
|
-
process.exit(0);
|
|
248
|
-
}
|
|
249
|
-
provider = picked;
|
|
250
|
-
// Update model to match the picked provider's default
|
|
251
|
-
model = getHardcodedDefault(provider);
|
|
252
|
-
// Check if already logged into the picked provider
|
|
253
|
-
try {
|
|
254
|
-
creds = await authStorage.resolveCredentials(provider);
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
console.log(chalk.hex("#60a5fa")(`\nLogging in to ${displayName(provider)}...\n`));
|
|
258
|
-
creds = await runInlineLogin(authStorage, provider);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
226
|
initLogger(paths.logFile, {
|
|
262
227
|
version: CLI_VERSION,
|
|
263
228
|
provider,
|
|
264
229
|
model,
|
|
265
230
|
thinking: opts.thinkingLevel,
|
|
266
231
|
});
|
|
232
|
+
const authStorage = new AuthStorage(paths.authFile);
|
|
233
|
+
await authStorage.load();
|
|
234
|
+
const creds = await authStorage.resolveCredentials(provider);
|
|
267
235
|
// Detect all logged-in providers and preload their credentials
|
|
268
236
|
const allProviders = ["anthropic", "openai", "glm", "moonshot"];
|
|
269
237
|
const loggedInProviders = [];
|
|
@@ -284,24 +252,45 @@ async function runInkTUI(opts) {
|
|
|
284
252
|
}
|
|
285
253
|
}
|
|
286
254
|
}
|
|
287
|
-
//
|
|
255
|
+
// Ensure project-local .ezcoder directories exist
|
|
256
|
+
const localGGDir = path.join(cwd, ".ezcoder");
|
|
257
|
+
await fs.promises.mkdir(path.join(localGGDir, "skills"), { recursive: true });
|
|
258
|
+
await fs.promises.mkdir(path.join(localGGDir, "commands"), { recursive: true });
|
|
259
|
+
await fs.promises.mkdir(path.join(localGGDir, "agents"), { recursive: true });
|
|
260
|
+
// Discover agents and skills
|
|
288
261
|
const agents = await discoverAgents({
|
|
289
262
|
globalAgentsDir: paths.agentsDir,
|
|
290
263
|
projectDir: cwd,
|
|
291
264
|
});
|
|
265
|
+
const skills = await discoverSkills({
|
|
266
|
+
globalSkillsDir: paths.skillsDir,
|
|
267
|
+
projectDir: cwd,
|
|
268
|
+
});
|
|
292
269
|
// Build system prompt & tools (with sub-agent support)
|
|
293
|
-
const systemPrompt =
|
|
294
|
-
|
|
295
|
-
|
|
270
|
+
const systemPrompt = await buildSystemPrompt(cwd, skills);
|
|
271
|
+
// Plan mode refs — shared between tools and UI
|
|
272
|
+
const planModeRef = { current: false };
|
|
273
|
+
const onEnterPlanRef = {
|
|
274
|
+
current: () => { },
|
|
275
|
+
};
|
|
276
|
+
const onExitPlanRef = {
|
|
277
|
+
current: () => Promise.resolve("cancelled"),
|
|
278
|
+
};
|
|
279
|
+
const { tools, processManager } = createTools(cwd, {
|
|
280
|
+
agents,
|
|
281
|
+
skills,
|
|
282
|
+
provider,
|
|
283
|
+
model,
|
|
284
|
+
planModeRef,
|
|
285
|
+
onEnterPlan: (reason) => onEnterPlanRef.current(reason),
|
|
286
|
+
onExitPlan: (planPath) => onExitPlanRef.current(planPath),
|
|
287
|
+
});
|
|
288
|
+
// Connect MCP servers
|
|
296
289
|
const mcpManager = new MCPClientManager();
|
|
297
290
|
try {
|
|
298
291
|
const providerApiKey = provider === "glm" ? credentialsByProvider["glm"]?.accessToken : undefined;
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
const lazyConfigs = allConfigs.filter((c) => c.lazy);
|
|
302
|
-
const mcpTools = await mcpManager.connectAll(eagerConfigs);
|
|
303
|
-
const lazyTools = mcpManager.registerLazy(lazyConfigs);
|
|
304
|
-
tools.push(...mcpTools, ...lazyTools);
|
|
292
|
+
const mcpTools = await mcpManager.connectAll(getMCPServers(provider, providerApiKey));
|
|
293
|
+
tools.push(...mcpTools);
|
|
305
294
|
}
|
|
306
295
|
catch (err) {
|
|
307
296
|
log("WARN", "mcp", `MCP initialization failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -317,30 +306,49 @@ async function runInkTUI(opts) {
|
|
|
317
306
|
const sessionManager = new SessionManager(paths.sessionsDir);
|
|
318
307
|
let sessionPath;
|
|
319
308
|
let initialHistory;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
309
|
+
// Determine which session to resume (explicit path or most recent)
|
|
310
|
+
const resumePath = opts.resumeSessionPath ??
|
|
311
|
+
(opts.continueRecent ? await sessionManager.getMostRecent(cwd) : null);
|
|
312
|
+
if (resumePath) {
|
|
313
|
+
try {
|
|
314
|
+
const loaded = await sessionManager.load(resumePath);
|
|
315
|
+
const loadedMessages = sessionManager.getMessages(loaded.entries);
|
|
316
|
+
if (loadedMessages.length > 0) {
|
|
317
|
+
messages.push(...loadedMessages);
|
|
318
|
+
sessionPath = resumePath;
|
|
319
|
+
log("INFO", "session", `Restored session`, {
|
|
320
|
+
path: resumePath,
|
|
321
|
+
messageCount: String(loadedMessages.length),
|
|
322
|
+
});
|
|
323
|
+
// Auto-compact on load if the restored session exceeds the context window.
|
|
324
|
+
// Without this, huge sessions (1M+ tokens) get loaded into memory and OOM.
|
|
325
|
+
const contextWindow = getContextWindow(model);
|
|
326
|
+
if (shouldCompact(messages, contextWindow, 0.8)) {
|
|
327
|
+
log("INFO", "session", `Restored session exceeds context — auto-compacting`);
|
|
328
|
+
const compacted = await compact(messages, {
|
|
329
|
+
provider,
|
|
330
|
+
model,
|
|
331
|
+
apiKey: creds.accessToken,
|
|
332
|
+
contextWindow,
|
|
332
333
|
});
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
334
|
+
// Replace messages array contents with compacted messages
|
|
335
|
+
messages.length = 0;
|
|
336
|
+
messages.push(...compacted.messages);
|
|
337
|
+
log("INFO", "session", `Auto-compaction complete`, {
|
|
338
|
+
before: String(compacted.result.originalCount),
|
|
339
|
+
after: String(compacted.result.newCount),
|
|
338
340
|
});
|
|
339
341
|
}
|
|
342
|
+
initialHistory = messagesToHistoryItems(loadedMessages);
|
|
343
|
+
initialHistory.push({
|
|
344
|
+
kind: "info",
|
|
345
|
+
text: `↻ Restored session (${loadedMessages.length} messages)`,
|
|
346
|
+
id: `restore-info`,
|
|
347
|
+
});
|
|
340
348
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// Session file corrupt or missing — start fresh
|
|
344
352
|
}
|
|
345
353
|
}
|
|
346
354
|
// Create a new session file if we didn't reuse one
|
|
@@ -359,7 +367,6 @@ async function runInkTUI(opts) {
|
|
|
359
367
|
maxTokens: 16384,
|
|
360
368
|
thinking: opts.thinkingLevel,
|
|
361
369
|
apiKey: creds.accessToken,
|
|
362
|
-
baseUrl: opts.baseUrl,
|
|
363
370
|
accountId: creds.accountId,
|
|
364
371
|
cwd,
|
|
365
372
|
theme: opts.theme,
|
|
@@ -372,18 +379,23 @@ async function runInkTUI(opts) {
|
|
|
372
379
|
settingsFile: paths.settingsFile,
|
|
373
380
|
mcpManager,
|
|
374
381
|
authStorage,
|
|
382
|
+
planModeRef,
|
|
383
|
+
onEnterPlanRef,
|
|
384
|
+
onExitPlanRef,
|
|
385
|
+
skills,
|
|
375
386
|
});
|
|
376
387
|
closeLogger();
|
|
377
388
|
}
|
|
378
389
|
// ── Login ──────────────────────────────────────────────────
|
|
379
390
|
async function runLogin() {
|
|
391
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
380
392
|
const paths = await ensureAppDirs();
|
|
381
393
|
initLogger(paths.logFile, { version: CLI_VERSION });
|
|
382
394
|
log("INFO", "auth", "Login flow started");
|
|
383
395
|
const authStorage = new AuthStorage();
|
|
384
396
|
await authStorage.load();
|
|
385
397
|
// Phase 1: Ink-based provider selector
|
|
386
|
-
const provider = await renderLoginSelector();
|
|
398
|
+
const provider = await renderLoginSelector(CLI_VERSION);
|
|
387
399
|
if (!provider) {
|
|
388
400
|
console.log(chalk.hex("#6b7280")("Login cancelled."));
|
|
389
401
|
return;
|
|
@@ -436,52 +448,6 @@ async function runLogin() {
|
|
|
436
448
|
closeLogger();
|
|
437
449
|
}
|
|
438
450
|
}
|
|
439
|
-
// ── Inline Login (auto-triggered when not logged in) ──────
|
|
440
|
-
async function runInlineLogin(authStorage, provider) {
|
|
441
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
442
|
-
try {
|
|
443
|
-
const callbacks = {
|
|
444
|
-
onOpenUrl: (url) => {
|
|
445
|
-
console.log(chalk.hex("#60a5fa").bold("Opening browser..."));
|
|
446
|
-
openBrowser(url);
|
|
447
|
-
console.log(chalk.hex("#6b7280")("\nIf the browser didn't open, visit:\n") +
|
|
448
|
-
chalk.hex("#6b7280")(url) +
|
|
449
|
-
"\n");
|
|
450
|
-
},
|
|
451
|
-
onPromptCode: async (message) => {
|
|
452
|
-
return rl.question(message + " ");
|
|
453
|
-
},
|
|
454
|
-
onStatus: (message) => {
|
|
455
|
-
console.log(chalk.hex("#6b7280")(message));
|
|
456
|
-
},
|
|
457
|
-
};
|
|
458
|
-
let creds;
|
|
459
|
-
if (provider === "glm" || provider === "moonshot") {
|
|
460
|
-
const keyLabel = provider === "glm" ? "Z.AI" : "Moonshot";
|
|
461
|
-
const apiKey = await rl.question(chalk.hex("#60a5fa")(`Paste your ${keyLabel} API key: `));
|
|
462
|
-
if (!apiKey.trim()) {
|
|
463
|
-
console.error(chalk.hex("#ef4444")("No API key provided."));
|
|
464
|
-
process.exit(1);
|
|
465
|
-
}
|
|
466
|
-
creds = {
|
|
467
|
-
accessToken: apiKey.trim(),
|
|
468
|
-
refreshToken: "",
|
|
469
|
-
expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000 * 100,
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
creds =
|
|
474
|
-
provider === "anthropic" ? await loginAnthropic(callbacks) : await loginOpenAI(callbacks);
|
|
475
|
-
}
|
|
476
|
-
await authStorage.setCredentials(provider, creds);
|
|
477
|
-
log("INFO", "auth", `Inline login succeeded for ${displayName(provider)}`);
|
|
478
|
-
console.log(chalk.hex("#4ade80")(`✓ Logged in to ${displayName(provider)}!\n`));
|
|
479
|
-
return creds;
|
|
480
|
-
}
|
|
481
|
-
finally {
|
|
482
|
-
rl.close();
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
451
|
// ── Logout ─────────────────────────────────────────────────
|
|
486
452
|
async function runLogout() {
|
|
487
453
|
const paths = await ensureAppDirs();
|
|
@@ -494,17 +460,271 @@ async function runLogout() {
|
|
|
494
460
|
closeLogger();
|
|
495
461
|
console.log(chalk.green("Logged out successfully."));
|
|
496
462
|
}
|
|
497
|
-
// ──
|
|
498
|
-
function
|
|
499
|
-
|
|
500
|
-
|
|
463
|
+
// ── Sessions ──────────────────────────────────────────────
|
|
464
|
+
async function runSessions() {
|
|
465
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
466
|
+
const paths = await ensureAppDirs();
|
|
467
|
+
initLogger(paths.logFile, { version: CLI_VERSION });
|
|
468
|
+
log("INFO", "session", "Sessions selector started");
|
|
469
|
+
const cwd = process.cwd();
|
|
470
|
+
const selectedPath = await renderSessionSelector(paths.sessionsDir, cwd, CLI_VERSION);
|
|
471
|
+
if (!selectedPath) {
|
|
472
|
+
console.log(chalk.hex("#6b7280")("No session selected."));
|
|
473
|
+
closeLogger();
|
|
474
|
+
process.exit(0);
|
|
475
|
+
}
|
|
476
|
+
// Load saved settings for provider/model/theme
|
|
477
|
+
let savedProvider;
|
|
478
|
+
let savedModel;
|
|
479
|
+
let savedThinkingEnabled = false;
|
|
480
|
+
let savedTheme = "auto";
|
|
501
481
|
try {
|
|
502
|
-
|
|
482
|
+
const raw = JSON.parse(fs.readFileSync(paths.settingsFile, "utf-8"));
|
|
483
|
+
if (raw.defaultProvider)
|
|
484
|
+
savedProvider = raw.defaultProvider;
|
|
485
|
+
if (raw.defaultModel)
|
|
486
|
+
savedModel = raw.defaultModel;
|
|
487
|
+
if (raw.thinkingEnabled === true)
|
|
488
|
+
savedThinkingEnabled = true;
|
|
489
|
+
if (raw.theme === "dark" || raw.theme === "light" || raw.theme === "auto")
|
|
490
|
+
savedTheme = raw.theme;
|
|
503
491
|
}
|
|
504
492
|
catch {
|
|
505
|
-
|
|
493
|
+
// No settings file — use defaults
|
|
494
|
+
}
|
|
495
|
+
const provider = savedProvider ?? "anthropic";
|
|
496
|
+
function getDefault(p) {
|
|
497
|
+
if (p === "openai")
|
|
498
|
+
return "gpt-5.3-codex";
|
|
499
|
+
if (p === "glm")
|
|
500
|
+
return "glm-5";
|
|
501
|
+
if (p === "moonshot")
|
|
502
|
+
return "kimi-k2.5";
|
|
503
|
+
return "claude-opus-4-6";
|
|
506
504
|
}
|
|
505
|
+
const model = savedModel ?? getDefault(provider);
|
|
506
|
+
const thinkingLevel = savedThinkingEnabled ? "medium" : undefined;
|
|
507
|
+
closeLogger();
|
|
508
|
+
await runInkTUI({
|
|
509
|
+
provider,
|
|
510
|
+
model,
|
|
511
|
+
cwd,
|
|
512
|
+
thinkingLevel,
|
|
513
|
+
resumeSessionPath: selectedPath,
|
|
514
|
+
theme: savedTheme,
|
|
515
|
+
});
|
|
507
516
|
}
|
|
517
|
+
async function loadTelegramConfig() {
|
|
518
|
+
try {
|
|
519
|
+
const raw = await fs.promises.readFile(getAppPaths().telegramFile, "utf-8");
|
|
520
|
+
const data = JSON.parse(raw);
|
|
521
|
+
if (data.botToken && data.userId)
|
|
522
|
+
return data;
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
async function saveTelegramConfig(config) {
|
|
530
|
+
const paths = await ensureAppDirs();
|
|
531
|
+
await fs.promises.writeFile(paths.telegramFile, JSON.stringify(config, null, 2), {
|
|
532
|
+
encoding: "utf-8",
|
|
533
|
+
mode: 0o600,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
async function runTelegramSetup() {
|
|
537
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
538
|
+
const paths = await ensureAppDirs();
|
|
539
|
+
initLogger(paths.logFile, { version: CLI_VERSION });
|
|
540
|
+
log("INFO", "telegram", "Telegram setup started");
|
|
541
|
+
const existing = await loadTelegramConfig();
|
|
542
|
+
// Banner (matches Banner.tsx)
|
|
543
|
+
const LOGO = [
|
|
544
|
+
" \u2588\u2580\u2580\u2580 \u2580\u2580\u2580\u2588",
|
|
545
|
+
" \u2588\u2580\u2580 \u2584\u2580 ",
|
|
546
|
+
" \u2588\u2584\u2584\u2584 \u2588\u2584\u2584\u2584",
|
|
547
|
+
];
|
|
548
|
+
const GRADIENT = [
|
|
549
|
+
"#60a5fa",
|
|
550
|
+
"#6da1f9",
|
|
551
|
+
"#7a9df7",
|
|
552
|
+
"#8799f5",
|
|
553
|
+
"#9495f3",
|
|
554
|
+
"#a18ff1",
|
|
555
|
+
"#a78bfa",
|
|
556
|
+
"#a18ff1",
|
|
557
|
+
"#9495f3",
|
|
558
|
+
"#8799f5",
|
|
559
|
+
"#7a9df7",
|
|
560
|
+
"#6da1f9",
|
|
561
|
+
];
|
|
562
|
+
function gradientText(text) {
|
|
563
|
+
let colorIdx = 0;
|
|
564
|
+
return text
|
|
565
|
+
.split("")
|
|
566
|
+
.map((ch) => {
|
|
567
|
+
if (ch === " ")
|
|
568
|
+
return ch;
|
|
569
|
+
const color = GRADIENT[colorIdx++ % GRADIENT.length];
|
|
570
|
+
return chalk.hex(color)(ch);
|
|
571
|
+
})
|
|
572
|
+
.join("");
|
|
573
|
+
}
|
|
574
|
+
const GAP = " ";
|
|
575
|
+
console.log();
|
|
576
|
+
console.log(` ${gradientText(LOGO[0])}${GAP}` +
|
|
577
|
+
chalk.hex("#60a5fa").bold("EZ Coder") +
|
|
578
|
+
chalk.hex("#6b7280")(` v${CLI_VERSION}`) +
|
|
579
|
+
chalk.hex("#6b7280")(" · By ") +
|
|
580
|
+
chalk.white.bold("Nolan Grout"));
|
|
581
|
+
console.log(` ${gradientText(LOGO[1])}${GAP}` + chalk.hex("#a78bfa")("Telegram Setup"));
|
|
582
|
+
console.log(` ${gradientText(LOGO[2])}${GAP}` + chalk.hex("#6b7280")("Remote Control"));
|
|
583
|
+
console.log();
|
|
584
|
+
if (existing) {
|
|
585
|
+
console.log(chalk.hex("#6b7280")(" Current config:\n") +
|
|
586
|
+
chalk.hex("#6b7280")(` Bot token: ${existing.botToken.slice(0, 10)}...${existing.botToken.slice(-4)}\n`) +
|
|
587
|
+
chalk.hex("#6b7280")(` User ID: ${existing.userId}\n`));
|
|
588
|
+
}
|
|
589
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
590
|
+
try {
|
|
591
|
+
// Step 1: Bot token
|
|
592
|
+
console.log(chalk.hex("#a78bfa")(" Step 1: Bot Token\n") +
|
|
593
|
+
chalk.hex("#6b7280")(" 1. Open BotFather: ") +
|
|
594
|
+
chalk.hex("#60a5fa").underline("https://t.me/BotFather") +
|
|
595
|
+
"\n" +
|
|
596
|
+
chalk.hex("#6b7280")(" 2. Send /newbot and follow the prompts\n") +
|
|
597
|
+
chalk.hex("#6b7280")(" 3. Copy the bot token\n"));
|
|
598
|
+
const tokenPrompt = existing
|
|
599
|
+
? chalk.hex("#60a5fa")(" Paste bot token (enter to keep current): ")
|
|
600
|
+
: chalk.hex("#60a5fa")(" Paste bot token: ");
|
|
601
|
+
const tokenInput = await rl.question(tokenPrompt);
|
|
602
|
+
const botToken = tokenInput.trim() || existing?.botToken;
|
|
603
|
+
if (!botToken) {
|
|
604
|
+
console.log(chalk.hex("#ef4444")("\n No bot token provided. Setup cancelled."));
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
// Validate token format (roughly: digits:alphanumeric)
|
|
608
|
+
if (!/^\d+:[A-Za-z0-9_-]+$/.test(botToken)) {
|
|
609
|
+
console.log(chalk.hex("#ef4444")("\n Invalid token format. Expected: 123456789:ABCdef..."));
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
// Step 2: User ID
|
|
613
|
+
console.log(chalk.hex("#a78bfa")("\n Step 2: User ID\n") +
|
|
614
|
+
chalk.hex("#6b7280")(" 1. Open userinfobot: ") +
|
|
615
|
+
chalk.hex("#60a5fa").underline("https://t.me/userinfobot") +
|
|
616
|
+
"\n" +
|
|
617
|
+
chalk.hex("#6b7280")(" 2. Send any message — it replies with your numeric ID\n") +
|
|
618
|
+
chalk.hex("#6b7280")(" Only this user ID can control the bot.\n"));
|
|
619
|
+
const userPrompt = existing
|
|
620
|
+
? chalk.hex("#60a5fa")(` Your Telegram user ID (enter to keep ${existing.userId}): `)
|
|
621
|
+
: chalk.hex("#60a5fa")(" Your Telegram user ID: ");
|
|
622
|
+
const userInput = await rl.question(userPrompt);
|
|
623
|
+
const userId = userInput.trim() ? parseInt(userInput.trim(), 10) : existing?.userId;
|
|
624
|
+
if (!userId || isNaN(userId)) {
|
|
625
|
+
console.log(chalk.hex("#ef4444")("\n Invalid user ID. Must be a number."));
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
// Step 3: Verify bot token by calling getMe
|
|
629
|
+
console.log(chalk.hex("#6b7280")("\n Verifying bot token..."));
|
|
630
|
+
const verifyRes = await fetch(`https://api.telegram.org/bot${botToken}/getMe`, {
|
|
631
|
+
method: "POST",
|
|
632
|
+
});
|
|
633
|
+
const verifyData = (await verifyRes.json());
|
|
634
|
+
if (!verifyData.ok || !verifyData.result) {
|
|
635
|
+
console.log(chalk.hex("#ef4444")("\n Invalid bot token — Telegram rejected it. Check and try again."));
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const botName = verifyData.result.first_name;
|
|
639
|
+
const botUsername = verifyData.result.username;
|
|
640
|
+
// Save config
|
|
641
|
+
await saveTelegramConfig({ botToken, userId });
|
|
642
|
+
log("INFO", "telegram", `Telegram setup complete: @${botUsername} (user ${userId})`);
|
|
643
|
+
console.log(chalk.hex("#4ade80")(`\n ✓ Connected to @${botUsername} (${botName})\n`) +
|
|
644
|
+
chalk.hex("#4ade80")(` ✓ Authorized user ID: ${userId}\n`) +
|
|
645
|
+
chalk.hex("#4ade80")(` ✓ Config saved to ${paths.telegramFile}\n\n`) +
|
|
646
|
+
chalk.hex("#a78bfa")(" For group chats:\n") +
|
|
647
|
+
chalk.hex("#6b7280")(" 1. Message @BotFather → /setprivacy → select your bot → Disable\n") +
|
|
648
|
+
chalk.hex("#6b7280")(" 2. Add the bot to your group\n") +
|
|
649
|
+
chalk.hex("#6b7280")(" 3. Send /link in the group to connect it to a project\n\n") +
|
|
650
|
+
chalk.hex("#60a5fa")(" To start:\n") +
|
|
651
|
+
chalk.hex("#6b7280")(" cd your-project && ezcoder serve\n"));
|
|
652
|
+
}
|
|
653
|
+
finally {
|
|
654
|
+
rl.close();
|
|
655
|
+
closeLogger();
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// ── Serve (Telegram) ─────────────────────────────────────
|
|
659
|
+
async function runServe() {
|
|
660
|
+
const { values: serveValues } = parseArgs({
|
|
661
|
+
options: {
|
|
662
|
+
"bot-token": { type: "string" },
|
|
663
|
+
"user-id": { type: "string" },
|
|
664
|
+
provider: { type: "string" },
|
|
665
|
+
model: { type: "string" },
|
|
666
|
+
},
|
|
667
|
+
strict: true,
|
|
668
|
+
});
|
|
669
|
+
// Priority: CLI flags > env vars > saved config
|
|
670
|
+
const saved = await loadTelegramConfig();
|
|
671
|
+
const botToken = serveValues["bot-token"] ?? process.env.GG_TELEGRAM_BOT_TOKEN ?? saved?.botToken;
|
|
672
|
+
const userIdStr = serveValues["user-id"] ?? process.env.GG_TELEGRAM_USER_ID;
|
|
673
|
+
const userId = userIdStr ? parseInt(userIdStr, 10) : saved?.userId;
|
|
674
|
+
if (!botToken || !userId || isNaN(userId)) {
|
|
675
|
+
console.error(chalk.hex("#ef4444")("Telegram not configured.\n\n") +
|
|
676
|
+
"Run " +
|
|
677
|
+
chalk.hex("#60a5fa").bold("ezcoder telegram") +
|
|
678
|
+
" to set up your bot token and user ID.\n\n" +
|
|
679
|
+
chalk.hex("#6b7280")("Or provide manually:\n") +
|
|
680
|
+
chalk.hex("#6b7280")(" ezcoder serve --bot-token TOKEN --user-id ID"));
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
683
|
+
// Load saved settings
|
|
684
|
+
let savedProvider;
|
|
685
|
+
let savedModel;
|
|
686
|
+
let savedThinkingEnabled = false;
|
|
687
|
+
try {
|
|
688
|
+
const raw = JSON.parse(fs.readFileSync(getAppPaths().settingsFile, "utf-8"));
|
|
689
|
+
if (raw.defaultProvider)
|
|
690
|
+
savedProvider = raw.defaultProvider;
|
|
691
|
+
if (raw.defaultModel)
|
|
692
|
+
savedModel = raw.defaultModel;
|
|
693
|
+
if (raw.thinkingEnabled === true)
|
|
694
|
+
savedThinkingEnabled = true;
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
// No settings file
|
|
698
|
+
}
|
|
699
|
+
const provider = serveValues.provider ?? savedProvider ?? "anthropic";
|
|
700
|
+
function getDefault(p) {
|
|
701
|
+
if (p === "openai")
|
|
702
|
+
return "gpt-5.3-codex";
|
|
703
|
+
if (p === "glm")
|
|
704
|
+
return "glm-5";
|
|
705
|
+
if (p === "moonshot")
|
|
706
|
+
return "kimi-k2.5";
|
|
707
|
+
return "claude-opus-4-6";
|
|
708
|
+
}
|
|
709
|
+
const model = serveValues.model ?? savedModel ?? getDefault(provider);
|
|
710
|
+
const thinkingLevel = savedThinkingEnabled ? "medium" : undefined;
|
|
711
|
+
const paths = await ensureAppDirs();
|
|
712
|
+
initLogger(paths.logFile, {
|
|
713
|
+
version: CLI_VERSION,
|
|
714
|
+
provider,
|
|
715
|
+
model,
|
|
716
|
+
});
|
|
717
|
+
setEstimatorModel(model);
|
|
718
|
+
await runServeMode({
|
|
719
|
+
provider,
|
|
720
|
+
model,
|
|
721
|
+
cwd: process.cwd(),
|
|
722
|
+
version: CLI_VERSION,
|
|
723
|
+
thinkingLevel,
|
|
724
|
+
telegram: { botToken, userId },
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
// ── Helpers ────────────────────────────────────────────────
|
|
508
728
|
function displayName(provider) {
|
|
509
729
|
if (provider === "anthropic")
|
|
510
730
|
return "Anthropic";
|