@prestyj/cli 4.3.34 → 4.3.55

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.
Files changed (99) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +248 -59
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +122 -3
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +2 -0
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/auto-update.d.ts +8 -0
  11. package/dist/core/auto-update.d.ts.map +1 -1
  12. package/dist/core/auto-update.js +20 -2
  13. package/dist/core/auto-update.js.map +1 -1
  14. package/dist/core/compaction/compactor.d.ts.map +1 -1
  15. package/dist/core/compaction/compactor.js +4 -1
  16. package/dist/core/compaction/compactor.js.map +1 -1
  17. package/dist/core/compaction/compactor.test.js +5 -5
  18. package/dist/core/compaction/compactor.test.js.map +1 -1
  19. package/dist/core/compaction/token-estimator.d.ts.map +1 -1
  20. package/dist/core/compaction/token-estimator.js +14 -1
  21. package/dist/core/compaction/token-estimator.js.map +1 -1
  22. package/dist/core/logger.d.ts +7 -2
  23. package/dist/core/logger.d.ts.map +1 -1
  24. package/dist/core/logger.js +48 -4
  25. package/dist/core/logger.js.map +1 -1
  26. package/dist/core/model-registry.d.ts.map +1 -1
  27. package/dist/core/model-registry.js +57 -25
  28. package/dist/core/model-registry.js.map +1 -1
  29. package/dist/core/prompt-commands.d.ts.map +1 -1
  30. package/dist/core/prompt-commands.js +224 -3
  31. package/dist/core/prompt-commands.js.map +1 -1
  32. package/dist/core/skills.d.ts.map +1 -1
  33. package/dist/core/skills.js +29 -14
  34. package/dist/core/skills.js.map +1 -1
  35. package/dist/modes/agent-home-mode.js +6 -6
  36. package/dist/modes/agent-home-mode.js.map +1 -1
  37. package/dist/system-prompt.d.ts +5 -1
  38. package/dist/system-prompt.d.ts.map +1 -1
  39. package/dist/system-prompt.js +74 -104
  40. package/dist/system-prompt.js.map +1 -1
  41. package/dist/tools/edit.d.ts +2 -2
  42. package/dist/tools/edit.d.ts.map +1 -1
  43. package/dist/tools/edit.js +16 -3
  44. package/dist/tools/edit.js.map +1 -1
  45. package/dist/tools/plan-mode.test.js +20 -4
  46. package/dist/tools/plan-mode.test.js.map +1 -1
  47. package/dist/tools/prompt-hints.d.ts +10 -0
  48. package/dist/tools/prompt-hints.d.ts.map +1 -0
  49. package/dist/tools/prompt-hints.js +45 -0
  50. package/dist/tools/prompt-hints.js.map +1 -0
  51. package/dist/tools/read.d.ts.map +1 -1
  52. package/dist/tools/read.js +32 -6
  53. package/dist/tools/read.js.map +1 -1
  54. package/dist/tools/read.test.js +23 -4
  55. package/dist/tools/read.test.js.map +1 -1
  56. package/dist/tools/write.test.js +13 -1
  57. package/dist/tools/write.test.js.map +1 -1
  58. package/dist/ui/App.d.ts +6 -1
  59. package/dist/ui/App.d.ts.map +1 -1
  60. package/dist/ui/App.js +95 -24
  61. package/dist/ui/App.js.map +1 -1
  62. package/dist/ui/components/EyesOverlay.d.ts +10 -0
  63. package/dist/ui/components/EyesOverlay.d.ts.map +1 -0
  64. package/dist/ui/components/EyesOverlay.js +220 -0
  65. package/dist/ui/components/EyesOverlay.js.map +1 -0
  66. package/dist/ui/components/Footer.d.ts.map +1 -1
  67. package/dist/ui/components/Footer.js +6 -5
  68. package/dist/ui/components/Footer.js.map +1 -1
  69. package/dist/ui/components/InputArea.d.ts +4 -1
  70. package/dist/ui/components/InputArea.d.ts.map +1 -1
  71. package/dist/ui/components/InputArea.js +4 -3
  72. package/dist/ui/components/InputArea.js.map +1 -1
  73. package/dist/ui/components/ModelSelector.d.ts.map +1 -1
  74. package/dist/ui/components/ModelSelector.js +1 -1
  75. package/dist/ui/components/ModelSelector.js.map +1 -1
  76. package/dist/ui/components/PlanOverlay.d.ts.map +1 -1
  77. package/dist/ui/components/PlanOverlay.js +7 -1
  78. package/dist/ui/components/PlanOverlay.js.map +1 -1
  79. package/dist/ui/components/SelectList.d.ts +3 -1
  80. package/dist/ui/components/SelectList.d.ts.map +1 -1
  81. package/dist/ui/components/SelectList.js +15 -2
  82. package/dist/ui/components/SelectList.js.map +1 -1
  83. package/dist/ui/components/SkillsOverlay.d.ts.map +1 -1
  84. package/dist/ui/components/SkillsOverlay.js +6 -59
  85. package/dist/ui/components/SkillsOverlay.js.map +1 -1
  86. package/dist/ui/components/SlashCommandMenu.d.ts.map +1 -1
  87. package/dist/ui/components/SlashCommandMenu.js +18 -8
  88. package/dist/ui/components/SlashCommandMenu.js.map +1 -1
  89. package/dist/ui/login.js +5 -5
  90. package/dist/ui/login.js.map +1 -1
  91. package/dist/ui/utils/token-to-ansi.d.ts.map +1 -1
  92. package/dist/ui/utils/token-to-ansi.js +9 -1
  93. package/dist/ui/utils/token-to-ansi.js.map +1 -1
  94. package/dist/utils/image.d.ts +18 -1
  95. package/dist/utils/image.d.ts.map +1 -1
  96. package/dist/utils/image.js +80 -31
  97. package/dist/utils/image.js.map +1 -1
  98. package/dist/utils/session-title.js +1 -1
  99. package/package.json +9 -7
package/README.md CHANGED
@@ -75,7 +75,7 @@ Switch mid-conversation with `/model`. Not locked to anyone.
75
75
  | **Anthropic** | Claude Opus 4.7, Sonnet 4.6, Haiku 4.5 | OAuth |
76
76
  | **OpenAI** | GPT-4.1, o3, o4-mini | OAuth |
77
77
  | **Z.AI (GLM)** | GLM-5.1, GLM-4.7 | API key |
78
- | **Moonshot** | Kimi K2.5 | API key |
78
+ | **Moonshot** | Kimi K2.6 | API key |
79
79
 
80
80
  ---
81
81
 
@@ -85,7 +85,7 @@ Everything runs through slash commands inside the session. Not CLI flags.
85
85
 
86
86
  ```bash
87
87
  /model claude-opus-4-7 # Switch models on the fly
88
- /model kimi-k2.5
88
+ /model kimi-k2.6
89
89
  /compact # Compress context when it gets long
90
90
 
91
91
  # Built-in workflows
package/dist/cli.js CHANGED
@@ -38,12 +38,13 @@ import { PerformanceObserver, performance } from "node:perf_hooks";
38
38
  import { parseArgs } from "node:util";
39
39
  import fs from "node:fs";
40
40
  import readline from "node:readline/promises";
41
- import { execFile } from "node:child_process";
41
+ import { execFile, spawnSync } from "node:child_process";
42
42
  import { createRequire } from "node:module";
43
43
  import { renderApp } from "./ui/render.js";
44
44
  import { runJsonMode } from "./modes/json-mode.js";
45
45
  import { runRpcMode } from "./modes/rpc-mode.js";
46
46
  import { runServeMode } from "./modes/serve-mode.js";
47
+ import { runAgentHomeMode } from "./modes/agent-home-mode.js";
47
48
  import { renderLoginSelector } from "./ui/login.js";
48
49
  import { renderSessionSelector } from "./ui/sessions.js";
49
50
  import { formatUserError } from "./utils/error-handler.js";
@@ -53,10 +54,11 @@ import { ensureAppDirs, getAppPaths, loadSavedSettings } from "./config.js";
53
54
  import { initLogger, log, closeLogger } from "./core/logger.js";
54
55
  import { setStreamDiagnostic } from "@prestyj/agent";
55
56
  import { buildSystemPrompt } from "./system-prompt.js";
57
+ import { isEyesActive, journalCount } from "@prestyj/ezcoder-eyes";
56
58
  import { createTools } from "./tools/index.js";
57
59
  import { shouldCompact, compact } from "./core/compaction/compactor.js";
58
60
  import { setEstimatorModel } from "./core/compaction/token-estimator.js";
59
- import { getContextWindow } from "./core/model-registry.js";
61
+ import { getContextWindow, getDefaultModel } from "./core/model-registry.js";
60
62
  import { MCPClientManager, getMCPServers } from "./core/mcp/index.js";
61
63
  import { discoverAgents } from "./core/agents.js";
62
64
  import { discoverSkills } from "./core/skills.js";
@@ -144,7 +146,7 @@ function printHelp() {
144
146
  ["-h, --help", "Show this help message"],
145
147
  ["-v, --version", "Show version number"],
146
148
  ["--provider <name>", "AI provider (anthropic, xiaomi, openai, glm, moonshot)"],
147
- ["--model <name>", "Model to use (e.g. claude-sonnet-4-6, gpt-4.1)"],
149
+ ["--model <name>", "Model to use (e.g. claude-sonnet-4-6, gpt-5.5)"],
148
150
  ["--max-turns <n>", "Maximum agent turns per prompt"],
149
151
  ["--system-prompt <text>", "Override the system prompt"],
150
152
  ["--json", "JSON output mode (for sub-agents)"],
@@ -187,7 +189,7 @@ function main() {
187
189
  // Silent auto-update check (throttled, non-blocking on failure)
188
190
  const updateMessage = checkAndAutoUpdate(CLI_VERSION);
189
191
  if (updateMessage) {
190
- console.error(chalk.hex("#60a5fa")(updateMessage));
192
+ console.error(chalk.bold.hex("#4ade80")(`✨ ${updateMessage}`));
191
193
  }
192
194
  // Intercept --help / -h before anything else so it works with subcommands
193
195
  // (e.g. `ezcoder login --help` or `ezcoder --help`)
@@ -197,6 +199,24 @@ function main() {
197
199
  }
198
200
  // Handle subcommands before parseArgs
199
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
+ }
200
220
  if (subcommand === "login") {
201
221
  runLogin().catch((err) => {
202
222
  log("ERROR", "fatal", err instanceof Error ? err.message : String(err));
@@ -321,11 +341,11 @@ function main() {
321
341
  const provider = saved.provider ?? "anthropic";
322
342
  function getHardcodedDefault(p) {
323
343
  if (p === "openai")
324
- return "gpt-5.4";
344
+ return "gpt-5.5";
325
345
  if (p === "glm")
326
346
  return "glm-5.1";
327
347
  if (p === "moonshot")
328
- return "kimi-k2.5";
348
+ return "kimi-k2.6";
329
349
  if (p === "minimax")
330
350
  return "MiniMax-M2.7";
331
351
  if (p === "openrouter")
@@ -353,53 +373,42 @@ function main() {
353
373
  }
354
374
  // ── Ink TUI ───────────────────────────────────────────────
355
375
  async function runInkTUI(opts) {
356
- const { provider, model, cwd } = opts;
357
- // Set model for token estimation accuracy
358
- setEstimatorModel(model);
359
- // Resolve auth
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.
360
380
  const paths = await ensureAppDirs();
361
- initLogger(paths.logFile, {
362
- version: CLI_VERSION,
363
- provider,
364
- model,
365
- thinking: opts.thinkingLevel,
366
- });
367
381
  // Wire stream stall diagnostics into the debug log
368
382
  setStreamDiagnostic((phase, data) => {
369
383
  log("INFO", "stream", phase, data);
370
384
  });
371
385
  const authStorage = new AuthStorage(paths.authFile);
372
386
  await authStorage.load();
373
- const creds = await authStorage.resolveCredentials(provider);
374
- // Detect all logged-in providers and preload their credentials
375
- const allProviders = [
376
- "anthropic",
377
- "xiaomi",
378
- "openai",
379
- "glm",
380
- "moonshot",
381
- "minimax",
382
- "openrouter",
383
- ];
384
- 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.
385
389
  const credentialsByProvider = {};
386
- for (const p of allProviders) {
387
- const stored = await authStorage.getCredentials(p);
388
- if (stored) {
389
- loggedInProviders.push(p);
390
- try {
391
- const resolved = await authStorage.resolveCredentials(p);
392
- credentialsByProvider[p] = {
393
- accessToken: resolved.accessToken,
394
- accountId: resolved.accountId,
395
- baseUrl: resolved.baseUrl,
396
- };
397
- }
398
- catch {
399
- // Token refresh failed — still mark as logged in
400
- }
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
401
401
  }
402
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);
403
412
  // Ensure project-local .ezcoder directories exist
404
413
  const localEzDir = path.join(cwd, ".ezcoder");
405
414
  await fs.promises.mkdir(path.join(localEzDir, "skills"), { recursive: true });
@@ -505,6 +514,21 @@ async function runInkTUI(opts) {
505
514
  sessionPath = session.path;
506
515
  log("INFO", "session", `New session created`, { path: sessionPath });
507
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
+ }
508
532
  await renderApp({
509
533
  provider,
510
534
  model,
@@ -876,11 +900,11 @@ async function runSessions() {
876
900
  const provider = saved2.provider ?? "anthropic";
877
901
  function getDefault(p) {
878
902
  if (p === "openai")
879
- return "gpt-5.4";
903
+ return "gpt-5.5";
880
904
  if (p === "glm")
881
905
  return "glm-5.1";
882
906
  if (p === "moonshot")
883
- return "kimi-k2.5";
907
+ return "kimi-k2.6";
884
908
  if (p === "minimax")
885
909
  return "MiniMax-M2.7";
886
910
  return "claude-opus-4-7";
@@ -1064,21 +1088,12 @@ async function runServe() {
1064
1088
  process.exit(1);
1065
1089
  }
1066
1090
  const saved3 = loadSavedSettings();
1067
- const provider = serveValues.provider ?? saved3.provider ?? "anthropic";
1068
- function getDefault(p) {
1069
- if (p === "openai")
1070
- return "gpt-5.4";
1071
- if (p === "glm")
1072
- return "glm-5.1";
1073
- if (p === "moonshot")
1074
- return "kimi-k2.5";
1075
- if (p === "minimax")
1076
- return "MiniMax-M2.7";
1077
- return "claude-opus-4-7";
1078
- }
1079
- const model = serveValues.model ?? saved3.model ?? getDefault(provider);
1080
1091
  const thinkingLevel = saved3.thinkingEnabled ? "medium" : undefined;
1081
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);
1082
1097
  initLogger(paths.logFile, {
1083
1098
  version: CLI_VERSION,
1084
1099
  provider,
@@ -1094,7 +1109,176 @@ async function runServe() {
1094
1109
  telegram: { botToken, userId },
1095
1110
  });
1096
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
+ }
1097
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
+ }
1098
1282
  function displayName(provider) {
1099
1283
  if (provider === "anthropic")
1100
1284
  return "Anthropic";
@@ -1126,8 +1310,13 @@ function messagesToHistoryItems(msgs) {
1126
1310
  for (const msg of msgs) {
1127
1311
  if (msg.role === "tool") {
1128
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");
1129
1318
  toolResults.set(tr.toolCallId, {
1130
- content: tr.content,
1319
+ content: text,
1131
1320
  isError: tr.isError ?? false,
1132
1321
  });
1133
1322
  }