@prestyj/cli 4.3.15 → 4.3.54
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/README.md +4 -4
- package/dist/cli.js +529 -74
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +122 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +41 -23
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +14 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/auto-update.d.ts +24 -3
- package/dist/core/auto-update.d.ts.map +1 -1
- package/dist/core/auto-update.js +143 -55
- package/dist/core/auto-update.js.map +1 -1
- package/dist/core/auto-update.test.d.ts +2 -0
- package/dist/core/auto-update.test.d.ts.map +1 -0
- package/dist/core/auto-update.test.js +205 -0
- package/dist/core/auto-update.test.js.map +1 -0
- package/dist/core/compaction/compactor.d.ts +4 -0
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +17 -1
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +44 -11
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/token-estimator.d.ts.map +1 -1
- package/dist/core/compaction/token-estimator.js +14 -1
- package/dist/core/compaction/token-estimator.js.map +1 -1
- package/dist/core/compaction/token-estimator.test.js +1 -1
- package/dist/core/logger.d.ts +7 -2
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +48 -4
- package/dist/core/logger.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +56 -21
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/prompt-commands.d.ts.map +1 -1
- package/dist/core/prompt-commands.js +356 -1
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +3 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +29 -14
- package/dist/core/skills.js.map +1 -1
- package/dist/modes/agent-home-mode.d.ts +21 -0
- package/dist/modes/agent-home-mode.d.ts.map +1 -0
- package/dist/modes/agent-home-mode.js +553 -0
- package/dist/modes/agent-home-mode.js.map +1 -0
- package/dist/system-prompt.d.ts +5 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +118 -82
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/bash.js +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +4 -2
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +61 -33
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/edit.test.js +80 -20
- package/dist/tools/edit.test.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +12 -0
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/plan-mode.test.js +22 -6
- package/dist/tools/plan-mode.test.js.map +1 -1
- package/dist/tools/prompt-hints.d.ts +10 -0
- package/dist/tools/prompt-hints.d.ts.map +1 -0
- package/dist/tools/prompt-hints.js +45 -0
- package/dist/tools/prompt-hints.js.map +1 -0
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +32 -6
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/read.test.js +23 -4
- package/dist/tools/read.test.js.map +1 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +20 -3
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/write.test.js +13 -1
- package/dist/tools/write.test.js.map +1 -1
- package/dist/ui/App.d.ts +6 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +195 -70
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +0 -1
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/EyesOverlay.d.ts +10 -0
- package/dist/ui/components/EyesOverlay.d.ts.map +1 -0
- package/dist/ui/components/EyesOverlay.js +220 -0
- package/dist/ui/components/EyesOverlay.js.map +1 -0
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +7 -6
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/InputArea.d.ts +4 -1
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +6 -5
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/ModelSelector.d.ts.map +1 -1
- package/dist/ui/components/ModelSelector.js +2 -1
- package/dist/ui/components/ModelSelector.js.map +1 -1
- package/dist/ui/components/PlanOverlay.d.ts.map +1 -1
- package/dist/ui/components/PlanOverlay.js +7 -1
- package/dist/ui/components/PlanOverlay.js.map +1 -1
- package/dist/ui/components/SelectList.d.ts +3 -1
- package/dist/ui/components/SelectList.d.ts.map +1 -1
- package/dist/ui/components/SelectList.js +15 -2
- package/dist/ui/components/SelectList.js.map +1 -1
- package/dist/ui/components/SkillsOverlay.d.ts.map +1 -1
- package/dist/ui/components/SkillsOverlay.js +6 -59
- package/dist/ui/components/SkillsOverlay.js.map +1 -1
- package/dist/ui/components/SlashCommandMenu.d.ts.map +1 -1
- package/dist/ui/components/SlashCommandMenu.js +18 -8
- package/dist/ui/components/SlashCommandMenu.js.map +1 -1
- package/dist/ui/hooks/stall-retry-duplicate.test.d.ts +2 -0
- package/dist/ui/hooks/stall-retry-duplicate.test.d.ts.map +1 -0
- package/dist/ui/hooks/stall-retry-duplicate.test.js +94 -0
- package/dist/ui/hooks/stall-retry-duplicate.test.js.map +1 -0
- package/dist/ui/hooks/useAgentLoop.d.ts +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +19 -0
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/login.d.ts.map +1 -1
- package/dist/ui/login.js +6 -5
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/stores/create-store.d.ts +15 -0
- package/dist/ui/stores/create-store.d.ts.map +1 -0
- package/dist/ui/stores/create-store.js +30 -0
- package/dist/ui/stores/create-store.js.map +1 -0
- package/dist/ui/stores/index.d.ts +3 -0
- package/dist/ui/stores/index.d.ts.map +1 -0
- package/dist/ui/stores/index.js +3 -0
- package/dist/ui/stores/index.js.map +1 -0
- package/dist/ui/stores/taskbar-store.d.ts +19 -0
- package/dist/ui/stores/taskbar-store.d.ts.map +1 -0
- package/dist/ui/stores/taskbar-store.js +59 -0
- package/dist/ui/stores/taskbar-store.js.map +1 -0
- package/dist/ui/utils/token-to-ansi.d.ts.map +1 -1
- package/dist/ui/utils/token-to-ansi.js +9 -1
- package/dist/ui/utils/token-to-ansi.js.map +1 -1
- package/dist/utils/image.d.ts +18 -1
- package/dist/utils/image.d.ts.map +1 -1
- package/dist/utils/image.js +80 -31
- package/dist/utils/image.js.map +1 -1
- package/dist/utils/session-title.js +1 -1
- package/package.json +13 -9
package/dist/cli.js
CHANGED
|
@@ -13,25 +13,38 @@ process.on("unhandledRejection", (reason) => {
|
|
|
13
13
|
// Re-throw non-abort rejections so they still crash with a useful stack trace
|
|
14
14
|
throw reason;
|
|
15
15
|
});
|
|
16
|
-
// Drain performance entries to prevent
|
|
16
|
+
// Drain ALL performance entries to prevent unbounded memory growth.
|
|
17
|
+
// Node emits entries for marks, measures, resource timing (HTTP), DNS, net, etc.
|
|
18
|
+
// Without clearing, these accumulate across every LLM call and tool execution.
|
|
17
19
|
import { PerformanceObserver, performance } from "node:perf_hooks";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
{
|
|
21
|
+
const allTypes = PerformanceObserver.supportedEntryTypes.filter((t) => t !== "gc" && t !== "function");
|
|
22
|
+
new PerformanceObserver((list) => {
|
|
23
|
+
for (const entry of list.getEntries()) {
|
|
24
|
+
switch (entry.entryType) {
|
|
25
|
+
case "measure":
|
|
26
|
+
performance.clearMeasures(entry.name);
|
|
27
|
+
break;
|
|
28
|
+
case "mark":
|
|
29
|
+
performance.clearMarks(entry.name);
|
|
30
|
+
break;
|
|
31
|
+
case "resource":
|
|
32
|
+
performance.clearResourceTimings();
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}).observe({ entryTypes: allTypes });
|
|
37
|
+
}
|
|
26
38
|
import { parseArgs } from "node:util";
|
|
27
39
|
import fs from "node:fs";
|
|
28
40
|
import readline from "node:readline/promises";
|
|
29
|
-
import { execFile } from "node:child_process";
|
|
41
|
+
import { execFile, spawnSync } from "node:child_process";
|
|
30
42
|
import { createRequire } from "node:module";
|
|
31
43
|
import { renderApp } from "./ui/render.js";
|
|
32
44
|
import { runJsonMode } from "./modes/json-mode.js";
|
|
33
45
|
import { runRpcMode } from "./modes/rpc-mode.js";
|
|
34
46
|
import { runServeMode } from "./modes/serve-mode.js";
|
|
47
|
+
import { runAgentHomeMode } from "./modes/agent-home-mode.js";
|
|
35
48
|
import { renderLoginSelector } from "./ui/login.js";
|
|
36
49
|
import { renderSessionSelector } from "./ui/sessions.js";
|
|
37
50
|
import { formatUserError } from "./utils/error-handler.js";
|
|
@@ -41,10 +54,11 @@ import { ensureAppDirs, getAppPaths, loadSavedSettings } from "./config.js";
|
|
|
41
54
|
import { initLogger, log, closeLogger } from "./core/logger.js";
|
|
42
55
|
import { setStreamDiagnostic } from "@prestyj/agent";
|
|
43
56
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
57
|
+
import { isEyesActive, journalCount } from "@prestyj/ezcoder-eyes";
|
|
44
58
|
import { createTools } from "./tools/index.js";
|
|
45
59
|
import { shouldCompact, compact } from "./core/compaction/compactor.js";
|
|
46
60
|
import { setEstimatorModel } from "./core/compaction/token-estimator.js";
|
|
47
|
-
import { getContextWindow } from "./core/model-registry.js";
|
|
61
|
+
import { getContextWindow, getDefaultModel } from "./core/model-registry.js";
|
|
48
62
|
import { MCPClientManager, getMCPServers } from "./core/mcp/index.js";
|
|
49
63
|
import { discoverAgents } from "./core/agents.js";
|
|
50
64
|
import { discoverSkills } from "./core/skills.js";
|
|
@@ -116,6 +130,7 @@ function printHelp() {
|
|
|
116
130
|
const cmds = [
|
|
117
131
|
["login", "Log in to an AI provider (Anthropic, OpenAI)"],
|
|
118
132
|
["logout", "Log out and clear stored credentials"],
|
|
133
|
+
["doctor", "Diagnose and fix auth/config issues"],
|
|
119
134
|
["sessions", "Browse and resume previous sessions"],
|
|
120
135
|
["continue", "Resume the most recent session"],
|
|
121
136
|
["serve", "Start the HTTP/WebSocket API server"],
|
|
@@ -131,7 +146,7 @@ function printHelp() {
|
|
|
131
146
|
["-h, --help", "Show this help message"],
|
|
132
147
|
["-v, --version", "Show version number"],
|
|
133
148
|
["--provider <name>", "AI provider (anthropic, xiaomi, openai, glm, moonshot)"],
|
|
134
|
-
["--model <name>", "Model to use (e.g. claude-sonnet-4-6, gpt-
|
|
149
|
+
["--model <name>", "Model to use (e.g. claude-sonnet-4-6, gpt-5.5)"],
|
|
135
150
|
["--max-turns <n>", "Maximum agent turns per prompt"],
|
|
136
151
|
["--system-prompt <text>", "Override the system prompt"],
|
|
137
152
|
["--json", "JSON output mode (for sub-agents)"],
|
|
@@ -174,7 +189,7 @@ function main() {
|
|
|
174
189
|
// Silent auto-update check (throttled, non-blocking on failure)
|
|
175
190
|
const updateMessage = checkAndAutoUpdate(CLI_VERSION);
|
|
176
191
|
if (updateMessage) {
|
|
177
|
-
console.error(chalk.hex("#
|
|
192
|
+
console.error(chalk.bold.hex("#4ade80")(`✨ ${updateMessage}`));
|
|
178
193
|
}
|
|
179
194
|
// Intercept --help / -h before anything else so it works with subcommands
|
|
180
195
|
// (e.g. `ezcoder login --help` or `ezcoder --help`)
|
|
@@ -184,6 +199,24 @@ function main() {
|
|
|
184
199
|
}
|
|
185
200
|
// Handle subcommands before parseArgs
|
|
186
201
|
const subcommand = process.argv[2];
|
|
202
|
+
// Passthrough to @prestyj/ezcoder-eyes CLI. Agents call this from bash as
|
|
203
|
+
// `ezcoder eyes log rough "..."` etc. — `ezcoder` is guaranteed on PATH
|
|
204
|
+
// (user launched it), so this avoids depending on nested bin visibility in
|
|
205
|
+
// global npm/pnpm installs.
|
|
206
|
+
if (subcommand === "eyes") {
|
|
207
|
+
let cliPath;
|
|
208
|
+
try {
|
|
209
|
+
cliPath = _require.resolve("@prestyj/ezcoder-eyes/cli");
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
process.stderr.write("ezcoder-eyes package not installed\n");
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
const r = spawnSync(process.execPath, [cliPath, ...process.argv.slice(3)], {
|
|
216
|
+
stdio: "inherit",
|
|
217
|
+
});
|
|
218
|
+
process.exit(r.status ?? 0);
|
|
219
|
+
}
|
|
187
220
|
if (subcommand === "login") {
|
|
188
221
|
runLogin().catch((err) => {
|
|
189
222
|
log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
|
|
@@ -231,6 +264,13 @@ function main() {
|
|
|
231
264
|
});
|
|
232
265
|
return;
|
|
233
266
|
}
|
|
267
|
+
if (subcommand === "doctor") {
|
|
268
|
+
runDoctor().catch((err) => {
|
|
269
|
+
process.stderr.write(formatUserError(err) + "\n");
|
|
270
|
+
process.exit(1);
|
|
271
|
+
});
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
234
274
|
if (subcommand === "continue") {
|
|
235
275
|
// Remove "continue" so parseArgs handles remaining flags
|
|
236
276
|
process.argv.splice(2, 1);
|
|
@@ -261,7 +301,7 @@ function main() {
|
|
|
261
301
|
if (values.json) {
|
|
262
302
|
const message = positionals[0] ?? "";
|
|
263
303
|
const jsonProvider = (values.provider ?? "anthropic");
|
|
264
|
-
const jsonModel = values.model ?? "claude-opus-4-
|
|
304
|
+
const jsonModel = values.model ?? "claude-opus-4-7";
|
|
265
305
|
const maxTurns = values["max-turns"] ? parseInt(values["max-turns"], 10) : undefined;
|
|
266
306
|
const systemPrompt = values["system-prompt"];
|
|
267
307
|
const cwd = process.cwd();
|
|
@@ -281,7 +321,7 @@ function main() {
|
|
|
281
321
|
// RPC mode — headless JSON-over-stdio for IDE integrations
|
|
282
322
|
if (values.rpc) {
|
|
283
323
|
const rpcProvider = (values.provider ?? "anthropic");
|
|
284
|
-
const rpcModel = values.model ?? "claude-opus-4-
|
|
324
|
+
const rpcModel = values.model ?? "claude-opus-4-7";
|
|
285
325
|
const systemPrompt = values["system-prompt"];
|
|
286
326
|
const cwd = process.cwd();
|
|
287
327
|
runRpcMode({
|
|
@@ -301,14 +341,16 @@ function main() {
|
|
|
301
341
|
const provider = saved.provider ?? "anthropic";
|
|
302
342
|
function getHardcodedDefault(p) {
|
|
303
343
|
if (p === "openai")
|
|
304
|
-
return "gpt-5.
|
|
344
|
+
return "gpt-5.5";
|
|
305
345
|
if (p === "glm")
|
|
306
346
|
return "glm-5.1";
|
|
307
347
|
if (p === "moonshot")
|
|
308
|
-
return "kimi-k2.
|
|
348
|
+
return "kimi-k2.6";
|
|
309
349
|
if (p === "minimax")
|
|
310
350
|
return "MiniMax-M2.7";
|
|
311
|
-
|
|
351
|
+
if (p === "openrouter")
|
|
352
|
+
return "qwen/qwen3.6-plus";
|
|
353
|
+
return "claude-opus-4-7";
|
|
312
354
|
}
|
|
313
355
|
const model = saved.model ?? getHardcodedDefault(provider);
|
|
314
356
|
const thinkingLevel = saved.thinkingEnabled ? "medium" : undefined;
|
|
@@ -331,45 +373,42 @@ function main() {
|
|
|
331
373
|
}
|
|
332
374
|
// ── Ink TUI ───────────────────────────────────────────────
|
|
333
375
|
async function runInkTUI(opts) {
|
|
334
|
-
const {
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
//
|
|
376
|
+
const { cwd } = opts;
|
|
377
|
+
// Resolve auth first so we can pick an active provider the user has
|
|
378
|
+
// actually logged in with — we must never default to a provider they
|
|
379
|
+
// haven't authenticated against.
|
|
338
380
|
const paths = await ensureAppDirs();
|
|
339
|
-
initLogger(paths.logFile, {
|
|
340
|
-
version: CLI_VERSION,
|
|
341
|
-
provider,
|
|
342
|
-
model,
|
|
343
|
-
thinking: opts.thinkingLevel,
|
|
344
|
-
});
|
|
345
381
|
// Wire stream stall diagnostics into the debug log
|
|
346
382
|
setStreamDiagnostic((phase, data) => {
|
|
347
383
|
log("INFO", "stream", phase, data);
|
|
348
384
|
});
|
|
349
385
|
const authStorage = new AuthStorage(paths.authFile);
|
|
350
386
|
await authStorage.load();
|
|
351
|
-
const
|
|
352
|
-
//
|
|
353
|
-
const allProviders = ["anthropic", "xiaomi", "openai", "glm", "moonshot", "minimax"];
|
|
354
|
-
const loggedInProviders = [];
|
|
387
|
+
const { provider, model, loggedInProviders } = await resolveActiveProvider(authStorage, opts.provider, opts.model);
|
|
388
|
+
// Preload every logged-in provider's credentials for the model switcher.
|
|
355
389
|
const credentialsByProvider = {};
|
|
356
|
-
for (const p of
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
catch {
|
|
369
|
-
// Token refresh failed — still mark as logged in
|
|
370
|
-
}
|
|
390
|
+
for (const p of loggedInProviders) {
|
|
391
|
+
try {
|
|
392
|
+
const resolved = await authStorage.resolveCredentials(p);
|
|
393
|
+
credentialsByProvider[p] = {
|
|
394
|
+
accessToken: resolved.accessToken,
|
|
395
|
+
accountId: resolved.accountId,
|
|
396
|
+
baseUrl: resolved.baseUrl,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
// Token refresh failed — still leave them in loggedInProviders
|
|
371
401
|
}
|
|
372
402
|
}
|
|
403
|
+
// Set model for token estimation accuracy (after provider is finalized)
|
|
404
|
+
setEstimatorModel(model);
|
|
405
|
+
initLogger(paths.logFile, {
|
|
406
|
+
version: CLI_VERSION,
|
|
407
|
+
provider,
|
|
408
|
+
model,
|
|
409
|
+
thinking: opts.thinkingLevel,
|
|
410
|
+
});
|
|
411
|
+
const creds = await authStorage.resolveCredentials(provider);
|
|
373
412
|
// Ensure project-local .ezcoder directories exist
|
|
374
413
|
const localEzDir = path.join(cwd, ".ezcoder");
|
|
375
414
|
await fs.promises.mkdir(path.join(localEzDir, "skills"), { recursive: true });
|
|
@@ -475,6 +514,21 @@ async function runInkTUI(opts) {
|
|
|
475
514
|
sessionPath = session.path;
|
|
476
515
|
log("INFO", "session", `New session created`, { path: sessionPath });
|
|
477
516
|
}
|
|
517
|
+
// Eyes startup banner — surface open journal signals from past sessions so the
|
|
518
|
+
// user isn't relying on reading agent prose to know improvements are pending.
|
|
519
|
+
if (isEyesActive(cwd)) {
|
|
520
|
+
const openCount = journalCount({ status: "open" }, cwd);
|
|
521
|
+
if (openCount > 0) {
|
|
522
|
+
const s = openCount === 1 ? "" : "s";
|
|
523
|
+
if (!initialHistory)
|
|
524
|
+
initialHistory = [];
|
|
525
|
+
initialHistory.push({
|
|
526
|
+
kind: "info",
|
|
527
|
+
text: `👁 Eyes: ${openCount} open improvement signal${s} from recent sessions. Run /eyes-improve to triage.`,
|
|
528
|
+
id: "eyes-banner",
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
478
532
|
await renderApp({
|
|
479
533
|
provider,
|
|
480
534
|
model,
|
|
@@ -543,31 +597,27 @@ async function runLogin() {
|
|
|
543
597
|
if (provider === "glm" ||
|
|
544
598
|
provider === "moonshot" ||
|
|
545
599
|
provider === "xiaomi" ||
|
|
546
|
-
provider === "minimax"
|
|
600
|
+
provider === "minimax" ||
|
|
601
|
+
provider === "openrouter") {
|
|
547
602
|
const keyLabel = provider === "glm"
|
|
548
603
|
? "Z.AI"
|
|
549
604
|
: provider === "xiaomi"
|
|
550
605
|
? "Xiaomi MiMo"
|
|
551
606
|
: provider === "minimax"
|
|
552
607
|
? "MiniMax"
|
|
553
|
-
: "
|
|
608
|
+
: provider === "openrouter"
|
|
609
|
+
? "OpenRouter"
|
|
610
|
+
: "Moonshot";
|
|
554
611
|
const apiKey = await rl.question(chalk.hex("#60a5fa")(`Paste your ${keyLabel} API key: `));
|
|
555
612
|
if (!apiKey.trim()) {
|
|
556
613
|
console.log(chalk.hex("#ef4444")("No API key provided. Login cancelled."));
|
|
557
614
|
return;
|
|
558
615
|
}
|
|
559
|
-
let baseUrl;
|
|
560
|
-
if (provider === "xiaomi") {
|
|
561
|
-
const urlInput = await rl.question(chalk.hex("#60a5fa")(`Base URL ${chalk.hex("#6b7280")("(Enter for default, or paste token plan URL)")}: `));
|
|
562
|
-
if (urlInput.trim()) {
|
|
563
|
-
baseUrl = urlInput.trim();
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
616
|
creds = {
|
|
567
617
|
accessToken: apiKey.trim(),
|
|
568
618
|
refreshToken: "",
|
|
569
619
|
expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000 * 100, // ~100 years
|
|
570
|
-
...(
|
|
620
|
+
...(provider === "xiaomi" ? { baseUrl: "https://token-plan-sgp.xiaomimimo.com/v1" } : {}),
|
|
571
621
|
};
|
|
572
622
|
}
|
|
573
623
|
else {
|
|
@@ -583,6 +633,244 @@ async function runLogin() {
|
|
|
583
633
|
closeLogger();
|
|
584
634
|
}
|
|
585
635
|
}
|
|
636
|
+
// ── Doctor ─────────────────────────────────────────────────
|
|
637
|
+
async function runDoctor() {
|
|
638
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
639
|
+
const os = await import("node:os");
|
|
640
|
+
const fsP = await import("node:fs/promises");
|
|
641
|
+
const dim = chalk.hex("#6b7280");
|
|
642
|
+
const primary = chalk.hex("#60a5fa");
|
|
643
|
+
const accent = chalk.hex("#a78bfa");
|
|
644
|
+
const good = chalk.hex("#4ade80");
|
|
645
|
+
const warn = chalk.hex("#fbbf24");
|
|
646
|
+
const bad = chalk.hex("#ef4444");
|
|
647
|
+
// ── Banner ──────────────────────────────────────────────────
|
|
648
|
+
const LOGO = LOGO_LINES;
|
|
649
|
+
const GAP = " ";
|
|
650
|
+
console.log();
|
|
651
|
+
console.log(` ${gradientLine(LOGO[0])}${GAP}` +
|
|
652
|
+
primary.bold("EZ Coder") +
|
|
653
|
+
dim(` v${CLI_VERSION}`));
|
|
654
|
+
console.log(` ${gradientLine(LOGO[1])}${GAP}` + accent("Doctor"));
|
|
655
|
+
console.log(` ${gradientLine(LOGO[2])}${GAP}` + dim("Diagnose & Fix"));
|
|
656
|
+
console.log();
|
|
657
|
+
const home = os.homedir();
|
|
658
|
+
const ggDir = path.join(home, ".ezcoder");
|
|
659
|
+
const authFile = path.join(ggDir, "auth.json");
|
|
660
|
+
const lockFile = authFile + ".lock";
|
|
661
|
+
const myUid = process.getuid();
|
|
662
|
+
let fixed = 0;
|
|
663
|
+
// ── Environment ─────────────────────────────────────────────
|
|
664
|
+
console.log(accent(" Environment\n"));
|
|
665
|
+
console.log(dim(` Home: ${home}`));
|
|
666
|
+
console.log(dim(` $HOME: ${process.env.HOME ?? "(not set)"}`));
|
|
667
|
+
console.log(dim(` Node.js: ${process.version}`));
|
|
668
|
+
console.log(dim(` Platform: ${process.platform} ${process.arch}`));
|
|
669
|
+
console.log(dim(` UID: ${myUid} EUID: ${process.geteuid()}`));
|
|
670
|
+
if (process.env.HOME && process.env.HOME !== home) {
|
|
671
|
+
console.log(warn("\n ⚠ $HOME differs from os.homedir() — this can cause auth mismatches"));
|
|
672
|
+
}
|
|
673
|
+
if (myUid !== process.geteuid()) {
|
|
674
|
+
console.log(warn(" ⚠ uid ≠ euid — running with elevated privileges (sudo?)"));
|
|
675
|
+
console.log(dim(" Running ezcoder with sudo can cause ownership issues."));
|
|
676
|
+
console.log(dim(" Use without sudo, or fix after: sudo chown -R $(whoami) ~/.gg"));
|
|
677
|
+
}
|
|
678
|
+
console.log();
|
|
679
|
+
// ── Config Directory ────────────────────────────────────────
|
|
680
|
+
console.log(accent(" Config Directory\n"));
|
|
681
|
+
try {
|
|
682
|
+
const stat = await fsP.stat(ggDir);
|
|
683
|
+
const mode = stat.mode & 0o777;
|
|
684
|
+
console.log(dim(` Path: ${ggDir}`));
|
|
685
|
+
console.log(dim(` Mode: 0o${mode.toString(8)} UID: ${stat.uid}`));
|
|
686
|
+
// Fix ownership
|
|
687
|
+
if (stat.uid !== myUid) {
|
|
688
|
+
console.log(warn(` ⚠ Owned by uid ${stat.uid}, expected ${myUid}`));
|
|
689
|
+
try {
|
|
690
|
+
await fsP.chown(ggDir, myUid, process.getgid());
|
|
691
|
+
console.log(good(" ✓ Fixed directory ownership"));
|
|
692
|
+
fixed++;
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
console.log(bad(` ✗ Cannot fix — try: sudo chown -R $(whoami) ${ggDir}`));
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// Fix permissions (should be 0o700)
|
|
699
|
+
if (mode !== 0o700) {
|
|
700
|
+
try {
|
|
701
|
+
await fsP.chmod(ggDir, 0o700);
|
|
702
|
+
console.log(good(" ✓ Fixed directory permissions → 0o700"));
|
|
703
|
+
fixed++;
|
|
704
|
+
}
|
|
705
|
+
catch {
|
|
706
|
+
console.log(bad(` ✗ Cannot fix — try: chmod 700 ${ggDir}`));
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
catch {
|
|
711
|
+
console.log(warn(` ${ggDir} missing — creating...`));
|
|
712
|
+
try {
|
|
713
|
+
await fsP.mkdir(ggDir, { recursive: true, mode: 0o700 });
|
|
714
|
+
console.log(good(` ✓ Created ${ggDir}`));
|
|
715
|
+
fixed++;
|
|
716
|
+
}
|
|
717
|
+
catch (mkErr) {
|
|
718
|
+
console.log(bad(` ✗ Cannot create: ${mkErr instanceof Error ? mkErr.message : String(mkErr)}`));
|
|
719
|
+
console.log();
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
console.log();
|
|
724
|
+
// ── Lock File ───────────────────────────────────────────────
|
|
725
|
+
try {
|
|
726
|
+
const lockStat = await fsP.stat(lockFile);
|
|
727
|
+
const ageMs = Date.now() - lockStat.mtimeMs;
|
|
728
|
+
console.log(accent(" Lock File\n"));
|
|
729
|
+
console.log(warn(` ⚠ Stale lock found (age: ${Math.round(ageMs / 1000)}s)`));
|
|
730
|
+
await fsP.unlink(lockFile);
|
|
731
|
+
console.log(good(" ✓ Removed"));
|
|
732
|
+
fixed++;
|
|
733
|
+
console.log();
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
// No lock file — good, skip section entirely
|
|
737
|
+
}
|
|
738
|
+
// ── Auth File ───────────────────────────────────────────────
|
|
739
|
+
console.log(accent(" Auth File\n"));
|
|
740
|
+
let authData = null;
|
|
741
|
+
let authNeedsRewrite = false;
|
|
742
|
+
try {
|
|
743
|
+
const stat = await fsP.stat(authFile);
|
|
744
|
+
const mode = stat.mode & 0o777;
|
|
745
|
+
console.log(dim(` Path: ${authFile}`));
|
|
746
|
+
console.log(dim(` Size: ${stat.size} bytes Mode: 0o${mode.toString(8)} UID: ${stat.uid}`));
|
|
747
|
+
// Fix ownership
|
|
748
|
+
if (stat.uid !== myUid) {
|
|
749
|
+
console.log(warn(` ⚠ Owned by uid ${stat.uid}, expected ${myUid}`));
|
|
750
|
+
try {
|
|
751
|
+
await fsP.chown(authFile, myUid, process.getgid());
|
|
752
|
+
console.log(good(" ✓ Fixed file ownership"));
|
|
753
|
+
fixed++;
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
console.log(bad(` ✗ Cannot fix — try: sudo chown $(whoami) ${authFile}`));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// Fix permissions (should be 0o600)
|
|
760
|
+
if (mode !== 0o600) {
|
|
761
|
+
try {
|
|
762
|
+
await fsP.chmod(authFile, 0o600);
|
|
763
|
+
console.log(good(" ✓ Fixed file permissions → 0o600"));
|
|
764
|
+
fixed++;
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
console.log(bad(` ✗ Cannot fix — try: chmod 600 ${authFile}`));
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
// Try to read and parse
|
|
771
|
+
try {
|
|
772
|
+
const content = await fsP.readFile(authFile, "utf-8");
|
|
773
|
+
try {
|
|
774
|
+
authData = JSON.parse(content);
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
console.log(bad(" ✗ Invalid JSON — backing up and resetting"));
|
|
778
|
+
const backupName = `auth.json.corrupt.${Date.now()}`;
|
|
779
|
+
await fsP.copyFile(authFile, path.join(ggDir, backupName));
|
|
780
|
+
await fsP.writeFile(authFile, "{}", { encoding: "utf-8", mode: 0o600 });
|
|
781
|
+
console.log(good(` ✓ Corrupt file backed up as ${backupName}`));
|
|
782
|
+
console.log(dim(' Run "ezcoder login" to re-authenticate'));
|
|
783
|
+
authData = {};
|
|
784
|
+
fixed++;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch (readErr) {
|
|
788
|
+
const code = readErr.code;
|
|
789
|
+
if (code === "EACCES") {
|
|
790
|
+
console.log(bad(" ✗ Permission denied reading auth.json"));
|
|
791
|
+
console.log(dim(` Try: sudo chown $(whoami) ${authFile} && chmod 600 ${authFile}`));
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
console.log(bad(` ✗ Read error: ${readErr instanceof Error ? readErr.message : String(readErr)}`));
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
catch {
|
|
799
|
+
console.log(dim(` Path: ${authFile}`));
|
|
800
|
+
console.log(warn(' Not found — run "ezcoder login" to authenticate'));
|
|
801
|
+
}
|
|
802
|
+
console.log();
|
|
803
|
+
// ── Credentials ─────────────────────────────────────────────
|
|
804
|
+
if (authData && Object.keys(authData).length > 0) {
|
|
805
|
+
console.log(accent(" Credentials\n"));
|
|
806
|
+
for (const p of Object.keys(authData)) {
|
|
807
|
+
const cred = authData[p];
|
|
808
|
+
if (!cred || typeof cred !== "object") {
|
|
809
|
+
console.log(bad(` ✗ ${p}: invalid entry — removing`));
|
|
810
|
+
delete authData[p];
|
|
811
|
+
authNeedsRewrite = true;
|
|
812
|
+
fixed++;
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (!cred.accessToken || typeof cred.accessToken !== "string") {
|
|
816
|
+
console.log(bad(` ✗ ${p}: missing accessToken — removing`));
|
|
817
|
+
delete authData[p];
|
|
818
|
+
authNeedsRewrite = true;
|
|
819
|
+
fixed++;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
const token = String(cred.accessToken);
|
|
823
|
+
const masked = token.slice(0, 8) + "..." + token.slice(-4);
|
|
824
|
+
const expires = typeof cred.expiresAt === "number" ? new Date(cred.expiresAt).toISOString() : "unknown";
|
|
825
|
+
const expired = typeof cred.expiresAt === "number" && Date.now() > cred.expiresAt;
|
|
826
|
+
if (expired) {
|
|
827
|
+
console.log(warn(` ⚠ ${p}: ${masked} expired ${expires}`));
|
|
828
|
+
}
|
|
829
|
+
else {
|
|
830
|
+
console.log(good(` ✓ ${p}: ${masked} expires ${expires}`));
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (authNeedsRewrite) {
|
|
834
|
+
try {
|
|
835
|
+
await fsP.writeFile(authFile, JSON.stringify(authData, null, 2), {
|
|
836
|
+
encoding: "utf-8",
|
|
837
|
+
mode: 0o600,
|
|
838
|
+
});
|
|
839
|
+
console.log(good(" ✓ Cleaned up auth.json"));
|
|
840
|
+
}
|
|
841
|
+
catch {
|
|
842
|
+
console.log(bad(" ✗ Failed to write cleaned auth.json"));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
console.log();
|
|
846
|
+
}
|
|
847
|
+
// ── Temp Files ──────────────────────────────────────────────
|
|
848
|
+
try {
|
|
849
|
+
const entries = await fsP.readdir(ggDir);
|
|
850
|
+
const tmpFiles = entries.filter((e) => e.startsWith("auth.json.") && e.endsWith(".tmp"));
|
|
851
|
+
if (tmpFiles.length > 0) {
|
|
852
|
+
console.log(accent(" Temp Files\n"));
|
|
853
|
+
console.log(warn(` ⚠ ${tmpFiles.length} orphaned temp file(s) from interrupted writes`));
|
|
854
|
+
for (const tmp of tmpFiles) {
|
|
855
|
+
await fsP.unlink(path.join(ggDir, tmp)).catch(() => { });
|
|
856
|
+
}
|
|
857
|
+
console.log(good(` ✓ Removed ${tmpFiles.length} file(s)`));
|
|
858
|
+
fixed++;
|
|
859
|
+
console.log();
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
catch {
|
|
863
|
+
// Can't read directory — already flagged above
|
|
864
|
+
}
|
|
865
|
+
// ── Summary ─────────────────────────────────────────────────
|
|
866
|
+
if (fixed > 0) {
|
|
867
|
+
console.log(good(` ✓ Fixed ${fixed} issue${fixed > 1 ? "s" : ""}.`));
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
console.log(good(" ✓ Everything looks good."));
|
|
871
|
+
}
|
|
872
|
+
console.log();
|
|
873
|
+
}
|
|
586
874
|
// ── Logout ─────────────────────────────────────────────────
|
|
587
875
|
async function runLogout() {
|
|
588
876
|
const paths = await ensureAppDirs();
|
|
@@ -612,14 +900,14 @@ async function runSessions() {
|
|
|
612
900
|
const provider = saved2.provider ?? "anthropic";
|
|
613
901
|
function getDefault(p) {
|
|
614
902
|
if (p === "openai")
|
|
615
|
-
return "gpt-5.
|
|
903
|
+
return "gpt-5.5";
|
|
616
904
|
if (p === "glm")
|
|
617
905
|
return "glm-5.1";
|
|
618
906
|
if (p === "moonshot")
|
|
619
|
-
return "kimi-k2.
|
|
907
|
+
return "kimi-k2.6";
|
|
620
908
|
if (p === "minimax")
|
|
621
909
|
return "MiniMax-M2.7";
|
|
622
|
-
return "claude-opus-4-
|
|
910
|
+
return "claude-opus-4-7";
|
|
623
911
|
}
|
|
624
912
|
const model = saved2.model ?? getDefault(provider);
|
|
625
913
|
const thinkingLevel = saved2.thinkingEnabled ? "medium" : undefined;
|
|
@@ -800,21 +1088,12 @@ async function runServe() {
|
|
|
800
1088
|
process.exit(1);
|
|
801
1089
|
}
|
|
802
1090
|
const saved3 = loadSavedSettings();
|
|
803
|
-
const provider = serveValues.provider ?? saved3.provider ?? "anthropic";
|
|
804
|
-
function getDefault(p) {
|
|
805
|
-
if (p === "openai")
|
|
806
|
-
return "gpt-5.4";
|
|
807
|
-
if (p === "glm")
|
|
808
|
-
return "glm-5.1";
|
|
809
|
-
if (p === "moonshot")
|
|
810
|
-
return "kimi-k2.5";
|
|
811
|
-
if (p === "minimax")
|
|
812
|
-
return "MiniMax-M2.7";
|
|
813
|
-
return "claude-opus-4-6";
|
|
814
|
-
}
|
|
815
|
-
const model = serveValues.model ?? saved3.model ?? getDefault(provider);
|
|
816
1091
|
const thinkingLevel = saved3.thinkingEnabled ? "medium" : undefined;
|
|
817
1092
|
const paths = await ensureAppDirs();
|
|
1093
|
+
const authStorage = new AuthStorage(paths.authFile);
|
|
1094
|
+
await authStorage.load();
|
|
1095
|
+
const preferredProvider = serveValues.provider ?? saved3.provider ?? "anthropic";
|
|
1096
|
+
const { provider, model } = await resolveActiveProvider(authStorage, preferredProvider, serveValues.model ?? saved3.model);
|
|
818
1097
|
initLogger(paths.logFile, {
|
|
819
1098
|
version: CLI_VERSION,
|
|
820
1099
|
provider,
|
|
@@ -830,7 +1109,176 @@ async function runServe() {
|
|
|
830
1109
|
telegram: { botToken, userId },
|
|
831
1110
|
});
|
|
832
1111
|
}
|
|
1112
|
+
async function loadAgentHomeConfig() {
|
|
1113
|
+
try {
|
|
1114
|
+
const raw = await fs.promises.readFile(getAppPaths().agentHomeFile, "utf-8");
|
|
1115
|
+
const data = JSON.parse(raw);
|
|
1116
|
+
if (data.token)
|
|
1117
|
+
return data;
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
catch {
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
async function saveAgentHomeConfig(config) {
|
|
1125
|
+
const paths = await ensureAppDirs();
|
|
1126
|
+
await fs.promises.writeFile(paths.agentHomeFile, JSON.stringify(config, null, 2), {
|
|
1127
|
+
encoding: "utf-8",
|
|
1128
|
+
mode: 0o600,
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
async function runAgentHomeLogin() {
|
|
1132
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1133
|
+
const paths = await ensureAppDirs();
|
|
1134
|
+
initLogger(paths.logFile, { version: CLI_VERSION });
|
|
1135
|
+
log("INFO", "agent-home", "Agent Home login started");
|
|
1136
|
+
const existing = await loadAgentHomeConfig();
|
|
1137
|
+
// Banner
|
|
1138
|
+
const LOGO = [
|
|
1139
|
+
" \u2588\u2580\u2580\u2580 \u2580\u2580\u2580\u2588",
|
|
1140
|
+
" \u2588\u2580\u2580 \u2584\u2580 ",
|
|
1141
|
+
" \u2588\u2584\u2584\u2584 \u2588\u2584\u2584\u2584",
|
|
1142
|
+
];
|
|
1143
|
+
function gradientTextLocal(text) {
|
|
1144
|
+
let colorIdx = 0;
|
|
1145
|
+
return text
|
|
1146
|
+
.split("")
|
|
1147
|
+
.map((ch) => {
|
|
1148
|
+
if (ch === " ")
|
|
1149
|
+
return ch;
|
|
1150
|
+
const color = GRADIENT[colorIdx++ % GRADIENT.length];
|
|
1151
|
+
return chalk.hex(color)(ch);
|
|
1152
|
+
})
|
|
1153
|
+
.join("");
|
|
1154
|
+
}
|
|
1155
|
+
const GAP = " ";
|
|
1156
|
+
console.log();
|
|
1157
|
+
console.log(` ${gradientTextLocal(LOGO[0])}${GAP}` +
|
|
1158
|
+
chalk.hex("#60a5fa").bold("EZ Coder") +
|
|
1159
|
+
chalk.hex("#6b7280")(` v${CLI_VERSION}`) +
|
|
1160
|
+
chalk.hex("#6b7280")(" \u00b7 By ") +
|
|
1161
|
+
chalk.white.bold("Nolan Grout"));
|
|
1162
|
+
console.log(` ${gradientTextLocal(LOGO[1])}${GAP}` + chalk.hex("#a78bfa")("Agent Home Setup"));
|
|
1163
|
+
console.log(` ${gradientTextLocal(LOGO[2])}${GAP}` + chalk.hex("#6b7280")("Remote Control via iOS"));
|
|
1164
|
+
console.log();
|
|
1165
|
+
if (existing) {
|
|
1166
|
+
console.log(chalk.hex("#6b7280")(" Current config:\n") +
|
|
1167
|
+
chalk.hex("#6b7280")(` Token: ${existing.token.slice(0, 8)}...${existing.token.slice(-4)}\n`));
|
|
1168
|
+
}
|
|
1169
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1170
|
+
try {
|
|
1171
|
+
console.log(chalk.hex("#a78bfa")(" Auth Token\n") +
|
|
1172
|
+
chalk.hex("#6b7280")(" Open Agent Home iOS app \u2192 Settings \u2192 Generate SDK Token\n") +
|
|
1173
|
+
chalk.hex("#6b7280")(" Copy the token\n"));
|
|
1174
|
+
const tokenPrompt = existing
|
|
1175
|
+
? chalk.hex("#60a5fa")(" Auth token (enter to keep current): ")
|
|
1176
|
+
: chalk.hex("#60a5fa")(" Auth token: ");
|
|
1177
|
+
const tokenInput = await rl.question(tokenPrompt);
|
|
1178
|
+
const token = tokenInput.trim() || existing?.token;
|
|
1179
|
+
if (!token) {
|
|
1180
|
+
console.log(chalk.hex("#ef4444")("\n No token provided. Setup cancelled."));
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
// Save config
|
|
1184
|
+
await saveAgentHomeConfig({ token });
|
|
1185
|
+
log("INFO", "agent-home", `Agent Home setup complete`);
|
|
1186
|
+
console.log(chalk.hex("#4ade80")(`\n \u2713 Token saved`) +
|
|
1187
|
+
"\n" +
|
|
1188
|
+
chalk.hex("#4ade80")(` \u2713 Config saved to ${paths.agentHomeFile}`) +
|
|
1189
|
+
"\n\n" +
|
|
1190
|
+
chalk.hex("#60a5fa")(" To start:\n") +
|
|
1191
|
+
chalk.hex("#6b7280")(" cd your-project && ezcoder agent-home\n"));
|
|
1192
|
+
}
|
|
1193
|
+
finally {
|
|
1194
|
+
rl.close();
|
|
1195
|
+
closeLogger();
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
// ── Agent Home (Run) ────────────────────────────────────
|
|
1199
|
+
async function runAgentHome() {
|
|
1200
|
+
const { values: ahValues } = parseArgs({
|
|
1201
|
+
options: {
|
|
1202
|
+
token: { type: "string" },
|
|
1203
|
+
provider: { type: "string" },
|
|
1204
|
+
model: { type: "string" },
|
|
1205
|
+
},
|
|
1206
|
+
strict: true,
|
|
1207
|
+
});
|
|
1208
|
+
// Priority: CLI flags > saved config
|
|
1209
|
+
const saved = await loadAgentHomeConfig();
|
|
1210
|
+
const token = ahValues.token ?? saved?.token;
|
|
1211
|
+
if (!token) {
|
|
1212
|
+
console.error(chalk.hex("#ef4444")("Agent Home not configured.\n\n") +
|
|
1213
|
+
"Run " +
|
|
1214
|
+
chalk.hex("#60a5fa").bold("ezcoder agent-home-login") +
|
|
1215
|
+
" to set up your token.\n\n" +
|
|
1216
|
+
chalk.hex("#6b7280")("Or provide manually:\n") +
|
|
1217
|
+
chalk.hex("#6b7280")(" ezcoder agent-home --token TOKEN"));
|
|
1218
|
+
process.exit(1);
|
|
1219
|
+
}
|
|
1220
|
+
const saved4 = loadSavedSettings();
|
|
1221
|
+
const thinkingLevel = saved4.thinkingEnabled ? "medium" : undefined;
|
|
1222
|
+
const paths = await ensureAppDirs();
|
|
1223
|
+
const authStorage = new AuthStorage(paths.authFile);
|
|
1224
|
+
await authStorage.load();
|
|
1225
|
+
const preferredProvider = ahValues.provider ?? saved4.provider ?? "anthropic";
|
|
1226
|
+
const { provider, model } = await resolveActiveProvider(authStorage, preferredProvider, ahValues.model ?? saved4.model);
|
|
1227
|
+
initLogger(paths.logFile, {
|
|
1228
|
+
version: CLI_VERSION,
|
|
1229
|
+
provider,
|
|
1230
|
+
model,
|
|
1231
|
+
});
|
|
1232
|
+
setEstimatorModel(model);
|
|
1233
|
+
await runAgentHomeMode({
|
|
1234
|
+
provider,
|
|
1235
|
+
model,
|
|
1236
|
+
cwd: process.cwd(),
|
|
1237
|
+
version: CLI_VERSION,
|
|
1238
|
+
thinkingLevel,
|
|
1239
|
+
agentHome: { token },
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
833
1242
|
// ── Helpers ────────────────────────────────────────────────
|
|
1243
|
+
/**
|
|
1244
|
+
* Resolve the provider and model to start with. If the preferred provider isn't
|
|
1245
|
+
* one the user is logged into, fall back to the first provider they ARE
|
|
1246
|
+
* logged into (in `allProviders` order). Throws if nothing is logged in.
|
|
1247
|
+
*
|
|
1248
|
+
* This prevents the CLI from crashing with "Not logged in" on startup just
|
|
1249
|
+
* because settings.json remembers a provider the user later logged out of.
|
|
1250
|
+
*/
|
|
1251
|
+
async function resolveActiveProvider(authStorage, preferred, savedModel) {
|
|
1252
|
+
const allProviders = [
|
|
1253
|
+
"anthropic",
|
|
1254
|
+
"xiaomi",
|
|
1255
|
+
"openai",
|
|
1256
|
+
"glm",
|
|
1257
|
+
"moonshot",
|
|
1258
|
+
"minimax",
|
|
1259
|
+
"openrouter",
|
|
1260
|
+
];
|
|
1261
|
+
const loggedInProviders = [];
|
|
1262
|
+
for (const p of allProviders) {
|
|
1263
|
+
if (await authStorage.getCredentials(p))
|
|
1264
|
+
loggedInProviders.push(p);
|
|
1265
|
+
}
|
|
1266
|
+
if (loggedInProviders.length === 0) {
|
|
1267
|
+
throw new Error('Not logged in to any provider. Run "ezcoder login" to authenticate.');
|
|
1268
|
+
}
|
|
1269
|
+
if (loggedInProviders.includes(preferred)) {
|
|
1270
|
+
return {
|
|
1271
|
+
provider: preferred,
|
|
1272
|
+
model: savedModel ?? getDefaultModel(preferred).id,
|
|
1273
|
+
loggedInProviders,
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
// Preferred provider isn't authenticated — fall back to the first one
|
|
1277
|
+
// that is, and use that provider's default model (the saved model
|
|
1278
|
+
// belonged to a provider the user can no longer reach).
|
|
1279
|
+
const provider = loggedInProviders[0];
|
|
1280
|
+
return { provider, model: getDefaultModel(provider).id, loggedInProviders };
|
|
1281
|
+
}
|
|
834
1282
|
function displayName(provider) {
|
|
835
1283
|
if (provider === "anthropic")
|
|
836
1284
|
return "Anthropic";
|
|
@@ -842,6 +1290,8 @@ function displayName(provider) {
|
|
|
842
1290
|
return "Moonshot";
|
|
843
1291
|
if (provider === "minimax")
|
|
844
1292
|
return "MiniMax";
|
|
1293
|
+
if (provider === "openrouter")
|
|
1294
|
+
return "OpenRouter";
|
|
845
1295
|
return "OpenAI";
|
|
846
1296
|
}
|
|
847
1297
|
function extractText(content) {
|
|
@@ -860,8 +1310,13 @@ function messagesToHistoryItems(msgs) {
|
|
|
860
1310
|
for (const msg of msgs) {
|
|
861
1311
|
if (msg.role === "tool") {
|
|
862
1312
|
for (const tr of msg.content) {
|
|
1313
|
+
const text = typeof tr.content === "string"
|
|
1314
|
+
? tr.content
|
|
1315
|
+
: tr.content
|
|
1316
|
+
.map((b) => (b.type === "text" ? b.text : `[image ${b.mediaType}]`))
|
|
1317
|
+
.join("\n");
|
|
863
1318
|
toolResults.set(tr.toolCallId, {
|
|
864
|
-
content:
|
|
1319
|
+
content: text,
|
|
865
1320
|
isError: tr.isError ?? false,
|
|
866
1321
|
});
|
|
867
1322
|
}
|