@prestyj/cli 4.2.15 → 4.2.44
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 +457 -225
- 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 -73
- 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 +9 -9
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
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,52 +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
55
|
const _require = createRequire(import.meta.url);
|
|
28
56
|
const CLI_VERSION = _require("../package.json").version;
|
|
29
|
-
const USAGE = `
|
|
30
|
-
Usage: ezcoder [command] [options] [message...]
|
|
31
|
-
|
|
32
|
-
Commands:
|
|
33
|
-
login Log in to a provider via OAuth
|
|
34
|
-
logout Log out (clear stored credentials)
|
|
35
|
-
continue Resume the most recent session for this directory
|
|
36
|
-
|
|
37
|
-
Options:
|
|
38
|
-
-p, --provider <name> LLM provider (anthropic, openai, glm, moonshot) [default: anthropic]
|
|
39
|
-
-m, --model <name> Model name [default: claude-opus-4-6]
|
|
40
|
-
--base-url <url> Custom API base URL
|
|
41
|
-
--system-prompt <text> Override system prompt
|
|
42
|
-
--thinking <level> Thinking level (low, medium, high, max)
|
|
43
|
-
--max-turns <n> Maximum agent loop turns [default: 40]
|
|
44
|
-
-s, --session <path> Resume a specific session file
|
|
45
|
-
--print Print mode: one-shot, output to stdout, then exit
|
|
46
|
-
--json JSON mode: one-shot, output NDJSON events to stdout
|
|
47
|
-
-v, --version Show version number
|
|
48
|
-
-h, --help Show this help message
|
|
49
|
-
|
|
50
|
-
Print mode:
|
|
51
|
-
echo "hello" | ezcoder --print
|
|
52
|
-
ezcoder --print "explain this code"
|
|
53
|
-
|
|
54
|
-
Authentication:
|
|
55
|
-
ezcoder login Log in (select provider interactively)
|
|
56
|
-
ezcoder logout Log out from all providers
|
|
57
|
-
`.trim();
|
|
58
|
-
function getHardcodedDefault(p) {
|
|
59
|
-
if (p === "openai")
|
|
60
|
-
return "gpt-5.3-codex";
|
|
61
|
-
if (p === "glm")
|
|
62
|
-
return "glm-5";
|
|
63
|
-
if (p === "moonshot")
|
|
64
|
-
return "kimi-k2.5";
|
|
65
|
-
return "claude-opus-4-6";
|
|
66
|
-
}
|
|
67
57
|
function main() {
|
|
68
58
|
// Silent auto-update check (throttled, non-blocking on failure)
|
|
69
59
|
const updateMessage = checkAndAutoUpdate(CLI_VERSION);
|
|
@@ -90,23 +80,48 @@ function main() {
|
|
|
90
80
|
});
|
|
91
81
|
return;
|
|
92
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
|
+
}
|
|
93
112
|
if (subcommand === "continue") {
|
|
94
113
|
// Remove "continue" so parseArgs handles remaining flags
|
|
95
114
|
process.argv.splice(2, 1);
|
|
96
115
|
}
|
|
97
116
|
const { values, positionals } = parseArgs({
|
|
98
117
|
options: {
|
|
99
|
-
provider: { type: "string", short: "p" },
|
|
100
|
-
model: { type: "string", short: "m" },
|
|
101
|
-
"base-url": { type: "string" },
|
|
102
|
-
"system-prompt": { type: "string" },
|
|
103
|
-
thinking: { type: "string" },
|
|
104
|
-
"max-turns": { type: "string" },
|
|
105
|
-
session: { type: "string", short: "s" },
|
|
106
|
-
print: { type: "boolean" },
|
|
107
|
-
json: { type: "boolean" },
|
|
108
118
|
version: { type: "boolean", short: "v" },
|
|
109
|
-
|
|
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" },
|
|
110
125
|
},
|
|
111
126
|
allowPositionals: true,
|
|
112
127
|
strict: true,
|
|
@@ -115,9 +130,43 @@ function main() {
|
|
|
115
130
|
console.log(CLI_VERSION);
|
|
116
131
|
process.exit(0);
|
|
117
132
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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;
|
|
121
170
|
}
|
|
122
171
|
// Load saved settings for model/provider persistence
|
|
123
172
|
let savedProvider;
|
|
@@ -138,74 +187,27 @@ function main() {
|
|
|
138
187
|
catch {
|
|
139
188
|
// No settings file or invalid JSON — use defaults
|
|
140
189
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Print mode
|
|
151
|
-
if (values.print) {
|
|
152
|
-
const message = positionals.join(" ").trim() || readStdinSync();
|
|
153
|
-
if (!message) {
|
|
154
|
-
console.error("Error: --print requires a message (positional args or stdin)");
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
runPrintMode({
|
|
158
|
-
message,
|
|
159
|
-
provider,
|
|
160
|
-
model,
|
|
161
|
-
baseUrl: values["base-url"],
|
|
162
|
-
systemPrompt: values["system-prompt"],
|
|
163
|
-
cwd: process.cwd(),
|
|
164
|
-
thinkingLevel,
|
|
165
|
-
}).catch((err) => {
|
|
166
|
-
log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
|
|
167
|
-
closeLogger();
|
|
168
|
-
process.stderr.write(formatUserError(err) + "\n");
|
|
169
|
-
process.exit(1);
|
|
170
|
-
});
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
// JSON mode
|
|
174
|
-
if (values.json) {
|
|
175
|
-
const message = positionals.join(" ").trim() || readStdinSync();
|
|
176
|
-
if (!message) {
|
|
177
|
-
console.error("Error: --json requires a message (positional args or stdin)");
|
|
178
|
-
process.exit(1);
|
|
179
|
-
}
|
|
180
|
-
runJsonMode({
|
|
181
|
-
message,
|
|
182
|
-
provider,
|
|
183
|
-
model,
|
|
184
|
-
baseUrl: values["base-url"],
|
|
185
|
-
systemPrompt: values["system-prompt"],
|
|
186
|
-
cwd: process.cwd(),
|
|
187
|
-
thinkingLevel,
|
|
188
|
-
maxTurns,
|
|
189
|
-
}).catch((err) => {
|
|
190
|
-
log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
|
|
191
|
-
closeLogger();
|
|
192
|
-
process.stderr.write(formatUserError(err) + "\n");
|
|
193
|
-
process.exit(1);
|
|
194
|
-
});
|
|
195
|
-
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";
|
|
196
199
|
}
|
|
200
|
+
const model = savedModel ?? getHardcodedDefault(provider);
|
|
201
|
+
const thinkingLevel = savedThinkingEnabled ? "medium" : undefined;
|
|
197
202
|
// Interactive mode (Ink TUI)
|
|
198
203
|
const cwd = process.cwd();
|
|
199
204
|
const continueRecent = subcommand === "continue";
|
|
200
205
|
runInkTUI({
|
|
201
206
|
provider,
|
|
202
207
|
model,
|
|
203
|
-
baseUrl: values["base-url"],
|
|
204
208
|
cwd,
|
|
205
209
|
thinkingLevel,
|
|
206
|
-
systemPrompt: values["system-prompt"],
|
|
207
210
|
continueRecent,
|
|
208
|
-
sessionPath: values.session,
|
|
209
211
|
theme: savedTheme,
|
|
210
212
|
}).catch((err) => {
|
|
211
213
|
log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
|
|
@@ -216,42 +218,20 @@ function main() {
|
|
|
216
218
|
}
|
|
217
219
|
// ── Ink TUI ───────────────────────────────────────────────
|
|
218
220
|
async function runInkTUI(opts) {
|
|
219
|
-
const { cwd } = opts;
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
const { provider, model, cwd } = opts;
|
|
222
|
+
// Set model for token estimation accuracy
|
|
223
|
+
setEstimatorModel(model);
|
|
222
224
|
// Resolve auth
|
|
223
225
|
const paths = await ensureAppDirs();
|
|
224
|
-
const authStorage = new AuthStorage(paths.authFile);
|
|
225
|
-
await authStorage.load();
|
|
226
|
-
let creds;
|
|
227
|
-
try {
|
|
228
|
-
creds = await authStorage.resolveCredentials(provider);
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
// Not logged in — let user pick a provider and log in
|
|
232
|
-
const picked = await renderLoginSelector();
|
|
233
|
-
if (!picked) {
|
|
234
|
-
console.log(chalk.hex("#6b7280")("Login cancelled."));
|
|
235
|
-
process.exit(0);
|
|
236
|
-
}
|
|
237
|
-
provider = picked;
|
|
238
|
-
// Update model to match the picked provider's default
|
|
239
|
-
model = getHardcodedDefault(provider);
|
|
240
|
-
// Check if already logged into the picked provider
|
|
241
|
-
try {
|
|
242
|
-
creds = await authStorage.resolveCredentials(provider);
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
console.log(chalk.hex("#60a5fa")(`\nLogging in to ${displayName(provider)}...\n`));
|
|
246
|
-
creds = await runInlineLogin(authStorage, provider);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
226
|
initLogger(paths.logFile, {
|
|
250
227
|
version: CLI_VERSION,
|
|
251
228
|
provider,
|
|
252
229
|
model,
|
|
253
230
|
thinking: opts.thinkingLevel,
|
|
254
231
|
});
|
|
232
|
+
const authStorage = new AuthStorage(paths.authFile);
|
|
233
|
+
await authStorage.load();
|
|
234
|
+
const creds = await authStorage.resolveCredentials(provider);
|
|
255
235
|
// Detect all logged-in providers and preload their credentials
|
|
256
236
|
const allProviders = ["anthropic", "openai", "glm", "moonshot"];
|
|
257
237
|
const loggedInProviders = [];
|
|
@@ -272,24 +252,45 @@ async function runInkTUI(opts) {
|
|
|
272
252
|
}
|
|
273
253
|
}
|
|
274
254
|
}
|
|
275
|
-
//
|
|
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
|
|
276
261
|
const agents = await discoverAgents({
|
|
277
262
|
globalAgentsDir: paths.agentsDir,
|
|
278
263
|
projectDir: cwd,
|
|
279
264
|
});
|
|
265
|
+
const skills = await discoverSkills({
|
|
266
|
+
globalSkillsDir: paths.skillsDir,
|
|
267
|
+
projectDir: cwd,
|
|
268
|
+
});
|
|
280
269
|
// Build system prompt & tools (with sub-agent support)
|
|
281
|
-
const systemPrompt =
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
284
289
|
const mcpManager = new MCPClientManager();
|
|
285
290
|
try {
|
|
286
291
|
const providerApiKey = provider === "glm" ? credentialsByProvider["glm"]?.accessToken : undefined;
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
const lazyConfigs = allConfigs.filter((c) => c.lazy);
|
|
290
|
-
const mcpTools = await mcpManager.connectAll(eagerConfigs);
|
|
291
|
-
const lazyTools = mcpManager.registerLazy(lazyConfigs);
|
|
292
|
-
tools.push(...mcpTools, ...lazyTools);
|
|
292
|
+
const mcpTools = await mcpManager.connectAll(getMCPServers(provider, providerApiKey));
|
|
293
|
+
tools.push(...mcpTools);
|
|
293
294
|
}
|
|
294
295
|
catch (err) {
|
|
295
296
|
log("WARN", "mcp", `MCP initialization failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -305,31 +306,50 @@ async function runInkTUI(opts) {
|
|
|
305
306
|
const sessionManager = new SessionManager(paths.sessionsDir);
|
|
306
307
|
let sessionPath;
|
|
307
308
|
let initialHistory;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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,
|
|
320
333
|
});
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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),
|
|
326
340
|
});
|
|
327
341
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
342
|
+
initialHistory = messagesToHistoryItems(loadedMessages);
|
|
343
|
+
initialHistory.push({
|
|
344
|
+
kind: "info",
|
|
345
|
+
text: `↻ Restored session (${loadedMessages.length} messages)`,
|
|
346
|
+
id: `restore-info`,
|
|
347
|
+
});
|
|
331
348
|
}
|
|
332
349
|
}
|
|
350
|
+
catch {
|
|
351
|
+
// Session file corrupt or missing — start fresh
|
|
352
|
+
}
|
|
333
353
|
}
|
|
334
354
|
// Create a new session file if we didn't reuse one
|
|
335
355
|
if (!sessionPath) {
|
|
@@ -347,7 +367,6 @@ async function runInkTUI(opts) {
|
|
|
347
367
|
maxTokens: 16384,
|
|
348
368
|
thinking: opts.thinkingLevel,
|
|
349
369
|
apiKey: creds.accessToken,
|
|
350
|
-
baseUrl: opts.baseUrl,
|
|
351
370
|
accountId: creds.accountId,
|
|
352
371
|
cwd,
|
|
353
372
|
theme: opts.theme,
|
|
@@ -360,18 +379,23 @@ async function runInkTUI(opts) {
|
|
|
360
379
|
settingsFile: paths.settingsFile,
|
|
361
380
|
mcpManager,
|
|
362
381
|
authStorage,
|
|
382
|
+
planModeRef,
|
|
383
|
+
onEnterPlanRef,
|
|
384
|
+
onExitPlanRef,
|
|
385
|
+
skills,
|
|
363
386
|
});
|
|
364
387
|
closeLogger();
|
|
365
388
|
}
|
|
366
389
|
// ── Login ──────────────────────────────────────────────────
|
|
367
390
|
async function runLogin() {
|
|
391
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
368
392
|
const paths = await ensureAppDirs();
|
|
369
393
|
initLogger(paths.logFile, { version: CLI_VERSION });
|
|
370
394
|
log("INFO", "auth", "Login flow started");
|
|
371
395
|
const authStorage = new AuthStorage();
|
|
372
396
|
await authStorage.load();
|
|
373
397
|
// Phase 1: Ink-based provider selector
|
|
374
|
-
const provider = await renderLoginSelector();
|
|
398
|
+
const provider = await renderLoginSelector(CLI_VERSION);
|
|
375
399
|
if (!provider) {
|
|
376
400
|
console.log(chalk.hex("#6b7280")("Login cancelled."));
|
|
377
401
|
return;
|
|
@@ -424,52 +448,6 @@ async function runLogin() {
|
|
|
424
448
|
closeLogger();
|
|
425
449
|
}
|
|
426
450
|
}
|
|
427
|
-
// ── Inline Login (auto-triggered when not logged in) ──────
|
|
428
|
-
async function runInlineLogin(authStorage, provider) {
|
|
429
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
430
|
-
try {
|
|
431
|
-
const callbacks = {
|
|
432
|
-
onOpenUrl: (url) => {
|
|
433
|
-
console.log(chalk.hex("#60a5fa").bold("Opening browser..."));
|
|
434
|
-
openBrowser(url);
|
|
435
|
-
console.log(chalk.hex("#6b7280")("\nIf the browser didn't open, visit:\n") +
|
|
436
|
-
chalk.hex("#6b7280")(url) +
|
|
437
|
-
"\n");
|
|
438
|
-
},
|
|
439
|
-
onPromptCode: async (message) => {
|
|
440
|
-
return rl.question(message + " ");
|
|
441
|
-
},
|
|
442
|
-
onStatus: (message) => {
|
|
443
|
-
console.log(chalk.hex("#6b7280")(message));
|
|
444
|
-
},
|
|
445
|
-
};
|
|
446
|
-
let creds;
|
|
447
|
-
if (provider === "glm" || provider === "moonshot") {
|
|
448
|
-
const keyLabel = provider === "glm" ? "Z.AI" : "Moonshot";
|
|
449
|
-
const apiKey = await rl.question(chalk.hex("#60a5fa")(`Paste your ${keyLabel} API key: `));
|
|
450
|
-
if (!apiKey.trim()) {
|
|
451
|
-
console.error(chalk.hex("#ef4444")("No API key provided."));
|
|
452
|
-
process.exit(1);
|
|
453
|
-
}
|
|
454
|
-
creds = {
|
|
455
|
-
accessToken: apiKey.trim(),
|
|
456
|
-
refreshToken: "",
|
|
457
|
-
expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000 * 100,
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
creds =
|
|
462
|
-
provider === "anthropic" ? await loginAnthropic(callbacks) : await loginOpenAI(callbacks);
|
|
463
|
-
}
|
|
464
|
-
await authStorage.setCredentials(provider, creds);
|
|
465
|
-
log("INFO", "auth", `Inline login succeeded for ${displayName(provider)}`);
|
|
466
|
-
console.log(chalk.hex("#4ade80")(`✓ Logged in to ${displayName(provider)}!\n`));
|
|
467
|
-
return creds;
|
|
468
|
-
}
|
|
469
|
-
finally {
|
|
470
|
-
rl.close();
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
451
|
// ── Logout ─────────────────────────────────────────────────
|
|
474
452
|
async function runLogout() {
|
|
475
453
|
const paths = await ensureAppDirs();
|
|
@@ -482,17 +460,271 @@ async function runLogout() {
|
|
|
482
460
|
closeLogger();
|
|
483
461
|
console.log(chalk.green("Logged out successfully."));
|
|
484
462
|
}
|
|
485
|
-
// ──
|
|
486
|
-
function
|
|
487
|
-
|
|
488
|
-
|
|
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";
|
|
481
|
+
try {
|
|
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;
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
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";
|
|
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
|
+
});
|
|
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
|
+
" \u2584\u2580\u2580\u2580 \u2584\u2580\u2580\u2580",
|
|
545
|
+
" \u2588 \u2580\u2588 \u2588 \u2580\u2588",
|
|
546
|
+
" \u2580\u2584\u2584\u2580 \u2580\u2584\u2584\u2580",
|
|
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;
|
|
489
687
|
try {
|
|
490
|
-
|
|
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;
|
|
491
695
|
}
|
|
492
696
|
catch {
|
|
493
|
-
|
|
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";
|
|
494
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
|
+
});
|
|
495
726
|
}
|
|
727
|
+
// ── Helpers ────────────────────────────────────────────────
|
|
496
728
|
function displayName(provider) {
|
|
497
729
|
if (provider === "anthropic")
|
|
498
730
|
return "Anthropic";
|