@prestyj/cli 4.2.77 → 4.3.34

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 (233) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +335 -85
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config.d.ts +10 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +33 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +42 -26
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/auth-storage.d.ts.map +1 -1
  12. package/dist/core/auth-storage.js +17 -2
  13. package/dist/core/auth-storage.js.map +1 -1
  14. package/dist/core/auto-update.d.ts +16 -3
  15. package/dist/core/auto-update.d.ts.map +1 -1
  16. package/dist/core/auto-update.js +126 -56
  17. package/dist/core/auto-update.js.map +1 -1
  18. package/dist/core/auto-update.test.d.ts +2 -0
  19. package/dist/core/auto-update.test.d.ts.map +1 -0
  20. package/dist/core/auto-update.test.js +205 -0
  21. package/dist/core/auto-update.test.js.map +1 -0
  22. package/dist/core/compaction/compactor.d.ts +18 -1
  23. package/dist/core/compaction/compactor.d.ts.map +1 -1
  24. package/dist/core/compaction/compactor.js +48 -7
  25. package/dist/core/compaction/compactor.js.map +1 -1
  26. package/dist/core/compaction/compactor.test.js +39 -6
  27. package/dist/core/compaction/compactor.test.js.map +1 -1
  28. package/dist/core/compaction/token-estimator.d.ts.map +1 -1
  29. package/dist/core/compaction/token-estimator.js +4 -0
  30. package/dist/core/compaction/token-estimator.js.map +1 -1
  31. package/dist/core/compaction/token-estimator.test.js +1 -1
  32. package/dist/core/index.d.ts +1 -1
  33. package/dist/core/index.d.ts.map +1 -1
  34. package/dist/core/index.js +1 -1
  35. package/dist/core/index.js.map +1 -1
  36. package/dist/core/mcp/client.d.ts.map +1 -1
  37. package/dist/core/mcp/client.js +15 -2
  38. package/dist/core/mcp/client.js.map +1 -1
  39. package/dist/core/model-registry.d.ts.map +1 -1
  40. package/dist/core/model-registry.js +74 -5
  41. package/dist/core/model-registry.js.map +1 -1
  42. package/dist/core/oauth/types.d.ts +1 -0
  43. package/dist/core/oauth/types.d.ts.map +1 -1
  44. package/dist/core/prompt-commands.d.ts.map +1 -1
  45. package/dist/core/prompt-commands.js +134 -0
  46. package/dist/core/prompt-commands.js.map +1 -1
  47. package/dist/core/settings-manager.d.ts +5 -0
  48. package/dist/core/settings-manager.d.ts.map +1 -1
  49. package/dist/core/settings-manager.js +14 -2
  50. package/dist/core/settings-manager.js.map +1 -1
  51. package/dist/interactive.js +1 -1
  52. package/dist/interactive.js.map +1 -1
  53. package/dist/modes/agent-home-mode.d.ts +21 -0
  54. package/dist/modes/agent-home-mode.d.ts.map +1 -0
  55. package/dist/modes/agent-home-mode.js +553 -0
  56. package/dist/modes/agent-home-mode.js.map +1 -0
  57. package/dist/system-prompt.d.ts.map +1 -1
  58. package/dist/system-prompt.js +30 -14
  59. package/dist/system-prompt.js.map +1 -1
  60. package/dist/tools/bash.d.ts.map +1 -1
  61. package/dist/tools/bash.js +9 -2
  62. package/dist/tools/bash.js.map +1 -1
  63. package/dist/tools/edit.d.ts +4 -2
  64. package/dist/tools/edit.d.ts.map +1 -1
  65. package/dist/tools/edit.js +48 -33
  66. package/dist/tools/edit.js.map +1 -1
  67. package/dist/tools/edit.test.js +80 -20
  68. package/dist/tools/edit.test.js.map +1 -1
  69. package/dist/tools/grep.d.ts.map +1 -1
  70. package/dist/tools/grep.js +12 -0
  71. package/dist/tools/grep.js.map +1 -1
  72. package/dist/tools/index.d.ts +1 -0
  73. package/dist/tools/index.d.ts.map +1 -1
  74. package/dist/tools/index.js +6 -0
  75. package/dist/tools/index.js.map +1 -1
  76. package/dist/tools/overflow.js +1 -1
  77. package/dist/tools/overflow.js.map +1 -1
  78. package/dist/tools/plan-mode.test.js +2 -2
  79. package/dist/tools/plan-mode.test.js.map +1 -1
  80. package/dist/tools/read.d.ts.map +1 -1
  81. package/dist/tools/read.js +14 -1
  82. package/dist/tools/read.js.map +1 -1
  83. package/dist/tools/subagent.d.ts.map +1 -1
  84. package/dist/tools/subagent.js +20 -3
  85. package/dist/tools/subagent.js.map +1 -1
  86. package/dist/tools/web-search.d.ts +9 -0
  87. package/dist/tools/web-search.d.ts.map +1 -0
  88. package/dist/tools/web-search.js +241 -0
  89. package/dist/tools/web-search.js.map +1 -0
  90. package/dist/tools/write.js +2 -2
  91. package/dist/tools/write.js.map +1 -1
  92. package/dist/tools/write.test.js +7 -9
  93. package/dist/tools/write.test.js.map +1 -1
  94. package/dist/ui/App.d.ts +4 -0
  95. package/dist/ui/App.d.ts.map +1 -1
  96. package/dist/ui/App.js +324 -161
  97. package/dist/ui/App.js.map +1 -1
  98. package/dist/ui/activity-phrases.d.ts.map +1 -1
  99. package/dist/ui/activity-phrases.js +74 -129
  100. package/dist/ui/activity-phrases.js.map +1 -1
  101. package/dist/ui/components/ActivityIndicator.d.ts +3 -1
  102. package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
  103. package/dist/ui/components/ActivityIndicator.js +5 -2
  104. package/dist/ui/components/ActivityIndicator.js.map +1 -1
  105. package/dist/ui/components/AssistantMessage.d.ts +1 -0
  106. package/dist/ui/components/AssistantMessage.d.ts.map +1 -1
  107. package/dist/ui/components/AssistantMessage.js +2 -2
  108. package/dist/ui/components/AssistantMessage.js.map +1 -1
  109. package/dist/ui/components/DiffFrame.d.ts +10 -0
  110. package/dist/ui/components/DiffFrame.d.ts.map +1 -0
  111. package/dist/ui/components/DiffFrame.js +13 -0
  112. package/dist/ui/components/DiffFrame.js.map +1 -0
  113. package/dist/ui/components/Footer.d.ts +2 -3
  114. package/dist/ui/components/Footer.d.ts.map +1 -1
  115. package/dist/ui/components/Footer.js +6 -5
  116. package/dist/ui/components/Footer.js.map +1 -1
  117. package/dist/ui/components/InputArea.d.ts.map +1 -1
  118. package/dist/ui/components/InputArea.js +297 -8
  119. package/dist/ui/components/InputArea.js.map +1 -1
  120. package/dist/ui/components/Markdown.d.ts +1 -1
  121. package/dist/ui/components/Markdown.d.ts.map +1 -1
  122. package/dist/ui/components/Markdown.js +25 -213
  123. package/dist/ui/components/Markdown.js.map +1 -1
  124. package/dist/ui/components/ModelSelector.d.ts.map +1 -1
  125. package/dist/ui/components/ModelSelector.js +10 -2
  126. package/dist/ui/components/ModelSelector.js.map +1 -1
  127. package/dist/ui/components/Ratchet.d.ts.map +1 -1
  128. package/dist/ui/components/Ratchet.js +10 -3
  129. package/dist/ui/components/Ratchet.js.map +1 -1
  130. package/dist/ui/components/StreamingArea.d.ts.map +1 -1
  131. package/dist/ui/components/StreamingArea.js +2 -2
  132. package/dist/ui/components/StreamingArea.js.map +1 -1
  133. package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
  134. package/dist/ui/components/SubAgentPanel.js +5 -1
  135. package/dist/ui/components/SubAgentPanel.js.map +1 -1
  136. package/dist/ui/components/ThemeSelector.d.ts +9 -0
  137. package/dist/ui/components/ThemeSelector.d.ts.map +1 -0
  138. package/dist/ui/components/ThemeSelector.js +25 -0
  139. package/dist/ui/components/ThemeSelector.js.map +1 -0
  140. package/dist/ui/components/ToolExecution.d.ts +2 -0
  141. package/dist/ui/components/ToolExecution.d.ts.map +1 -1
  142. package/dist/ui/components/ToolExecution.js +95 -30
  143. package/dist/ui/components/ToolExecution.js.map +1 -1
  144. package/dist/ui/components/ToolGroupExecution.d.ts +3 -2
  145. package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -1
  146. package/dist/ui/components/ToolGroupExecution.js +68 -51
  147. package/dist/ui/components/ToolGroupExecution.js.map +1 -1
  148. package/dist/ui/hooks/stall-retry-duplicate.test.d.ts +2 -0
  149. package/dist/ui/hooks/stall-retry-duplicate.test.d.ts.map +1 -0
  150. package/dist/ui/hooks/stall-retry-duplicate.test.js +94 -0
  151. package/dist/ui/hooks/stall-retry-duplicate.test.js.map +1 -0
  152. package/dist/ui/hooks/useAgentLoop.d.ts +10 -3
  153. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  154. package/dist/ui/hooks/useAgentLoop.js +131 -16
  155. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  156. package/dist/ui/hooks/useDoublePress.d.ts +8 -0
  157. package/dist/ui/hooks/useDoublePress.d.ts.map +1 -0
  158. package/dist/ui/hooks/useDoublePress.js +44 -0
  159. package/dist/ui/hooks/useDoublePress.js.map +1 -0
  160. package/dist/ui/hooks/useTerminalProgress.d.ts +1 -1
  161. package/dist/ui/hooks/useTerminalProgress.d.ts.map +1 -1
  162. package/dist/ui/hooks/useTerminalProgress.js +18 -15
  163. package/dist/ui/hooks/useTerminalProgress.js.map +1 -1
  164. package/dist/ui/hooks/useTerminalTitle.d.ts +3 -6
  165. package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -1
  166. package/dist/ui/hooks/useTerminalTitle.js +10 -19
  167. package/dist/ui/hooks/useTerminalTitle.js.map +1 -1
  168. package/dist/ui/live-item-flush.d.ts +4 -0
  169. package/dist/ui/live-item-flush.d.ts.map +1 -1
  170. package/dist/ui/live-item-flush.js +36 -0
  171. package/dist/ui/live-item-flush.js.map +1 -1
  172. package/dist/ui/login.d.ts.map +1 -1
  173. package/dist/ui/login.js +3 -0
  174. package/dist/ui/login.js.map +1 -1
  175. package/dist/ui/render.d.ts +3 -1
  176. package/dist/ui/render.d.ts.map +1 -1
  177. package/dist/ui/render.js +9 -3
  178. package/dist/ui/render.js.map +1 -1
  179. package/dist/ui/stores/create-store.d.ts +15 -0
  180. package/dist/ui/stores/create-store.d.ts.map +1 -0
  181. package/dist/ui/stores/create-store.js +30 -0
  182. package/dist/ui/stores/create-store.js.map +1 -0
  183. package/dist/ui/stores/index.d.ts +3 -0
  184. package/dist/ui/stores/index.d.ts.map +1 -0
  185. package/dist/ui/stores/index.js +3 -0
  186. package/dist/ui/stores/index.js.map +1 -0
  187. package/dist/ui/stores/taskbar-store.d.ts +19 -0
  188. package/dist/ui/stores/taskbar-store.d.ts.map +1 -0
  189. package/dist/ui/stores/taskbar-store.js +59 -0
  190. package/dist/ui/stores/taskbar-store.js.map +1 -0
  191. package/dist/ui/theme/dark-ansi.json +48 -0
  192. package/dist/ui/theme/dark-daltonized.json +48 -0
  193. package/dist/ui/theme/dark.json +22 -1
  194. package/dist/ui/theme/detect-theme.d.ts +12 -8
  195. package/dist/ui/theme/detect-theme.d.ts.map +1 -1
  196. package/dist/ui/theme/detect-theme.js +58 -39
  197. package/dist/ui/theme/detect-theme.js.map +1 -1
  198. package/dist/ui/theme/light-ansi.json +48 -0
  199. package/dist/ui/theme/light-daltonized.json +48 -0
  200. package/dist/ui/theme/light.json +44 -23
  201. package/dist/ui/theme/theme.d.ts +27 -1
  202. package/dist/ui/theme/theme.d.ts.map +1 -1
  203. package/dist/ui/theme/theme.js +24 -1
  204. package/dist/ui/theme/theme.js.map +1 -1
  205. package/dist/ui/utils/highlight.d.ts +1 -1
  206. package/dist/ui/utils/highlight.d.ts.map +1 -1
  207. package/dist/ui/utils/highlight.js +24 -4
  208. package/dist/ui/utils/highlight.js.map +1 -1
  209. package/dist/ui/utils/hyperlink.d.ts +11 -0
  210. package/dist/ui/utils/hyperlink.d.ts.map +1 -0
  211. package/dist/ui/utils/hyperlink.js +27 -0
  212. package/dist/ui/utils/hyperlink.js.map +1 -0
  213. package/dist/ui/utils/markdown-cache.d.ts +5 -6
  214. package/dist/ui/utils/markdown-cache.d.ts.map +1 -1
  215. package/dist/ui/utils/markdown-cache.js +5 -5
  216. package/dist/ui/utils/markdown-cache.js.map +1 -1
  217. package/dist/ui/utils/signal.d.ts +21 -0
  218. package/dist/ui/utils/signal.d.ts.map +1 -0
  219. package/dist/ui/utils/signal.js +30 -0
  220. package/dist/ui/utils/signal.js.map +1 -0
  221. package/dist/ui/utils/supports-hyperlinks.d.ts +6 -0
  222. package/dist/ui/utils/supports-hyperlinks.d.ts.map +1 -0
  223. package/dist/ui/utils/supports-hyperlinks.js +52 -0
  224. package/dist/ui/utils/supports-hyperlinks.js.map +1 -0
  225. package/dist/ui/utils/token-to-ansi.d.ts +9 -0
  226. package/dist/ui/utils/token-to-ansi.d.ts.map +1 -0
  227. package/dist/ui/utils/token-to-ansi.js +305 -0
  228. package/dist/ui/utils/token-to-ansi.js.map +1 -0
  229. package/dist/utils/session-title.d.ts +19 -0
  230. package/dist/utils/session-title.d.ts.map +1 -0
  231. package/dist/utils/session-title.js +82 -0
  232. package/dist/utils/session-title.js.map +1 -0
  233. package/package.json +7 -5
package/README.md CHANGED
@@ -72,7 +72,7 @@ Switch mid-conversation with `/model`. Not locked to anyone.
72
72
 
73
73
  | Provider | Models | Auth |
74
74
  |---|---|---|
75
- | **Anthropic** | Claude Opus 4.6, Sonnet 4.6, Haiku 4.5 | OAuth |
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
78
  | **Moonshot** | Kimi K2.5 | API key |
@@ -84,7 +84,7 @@ Switch mid-conversation with `/model`. Not locked to anyone.
84
84
  Everything runs through slash commands inside the session. Not CLI flags.
85
85
 
86
86
  ```bash
87
- /model claude-opus-4-6 # Switch models on the fly
87
+ /model claude-opus-4-7 # Switch models on the fly
88
88
  /model kimi-k2.5
89
89
  /compact # Compress context when it gets long
90
90
 
package/dist/cli.js CHANGED
@@ -13,16 +13,28 @@ 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 buffer overflow warning from dependencies
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
- 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"] });
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";
@@ -37,8 +49,9 @@ import { renderSessionSelector } from "./ui/sessions.js";
37
49
  import { formatUserError } from "./utils/error-handler.js";
38
50
  import { AuthStorage } from "./core/auth-storage.js";
39
51
  import { SessionManager } from "./core/session-manager.js";
40
- import { ensureAppDirs, getAppPaths } from "./config.js";
52
+ import { ensureAppDirs, getAppPaths, loadSavedSettings } from "./config.js";
41
53
  import { initLogger, log, closeLogger } from "./core/logger.js";
54
+ import { setStreamDiagnostic } from "@prestyj/agent";
42
55
  import { buildSystemPrompt } from "./system-prompt.js";
43
56
  import { createTools } from "./tools/index.js";
44
57
  import { shouldCompact, compact } from "./core/compaction/compactor.js";
@@ -115,6 +128,7 @@ function printHelp() {
115
128
  const cmds = [
116
129
  ["login", "Log in to an AI provider (Anthropic, OpenAI)"],
117
130
  ["logout", "Log out and clear stored credentials"],
131
+ ["doctor", "Diagnose and fix auth/config issues"],
118
132
  ["sessions", "Browse and resume previous sessions"],
119
133
  ["continue", "Resume the most recent session"],
120
134
  ["serve", "Start the HTTP/WebSocket API server"],
@@ -129,7 +143,7 @@ function printHelp() {
129
143
  const opts = [
130
144
  ["-h, --help", "Show this help message"],
131
145
  ["-v, --version", "Show version number"],
132
- ["--provider <name>", "AI provider (anthropic, openai, glm, moonshot)"],
146
+ ["--provider <name>", "AI provider (anthropic, xiaomi, openai, glm, moonshot)"],
133
147
  ["--model <name>", "Model to use (e.g. claude-sonnet-4-6, gpt-4.1)"],
134
148
  ["--max-turns <n>", "Maximum agent turns per prompt"],
135
149
  ["--system-prompt <text>", "Override the system prompt"],
@@ -230,6 +244,13 @@ function main() {
230
244
  });
231
245
  return;
232
246
  }
247
+ if (subcommand === "doctor") {
248
+ runDoctor().catch((err) => {
249
+ process.stderr.write(formatUserError(err) + "\n");
250
+ process.exit(1);
251
+ });
252
+ return;
253
+ }
233
254
  if (subcommand === "continue") {
234
255
  // Remove "continue" so parseArgs handles remaining flags
235
256
  process.argv.splice(2, 1);
@@ -260,7 +281,7 @@ function main() {
260
281
  if (values.json) {
261
282
  const message = positionals[0] ?? "";
262
283
  const jsonProvider = (values.provider ?? "anthropic");
263
- const jsonModel = values.model ?? "claude-opus-4-6";
284
+ const jsonModel = values.model ?? "claude-opus-4-7";
264
285
  const maxTurns = values["max-turns"] ? parseInt(values["max-turns"], 10) : undefined;
265
286
  const systemPrompt = values["system-prompt"];
266
287
  const cwd = process.cwd();
@@ -280,7 +301,7 @@ function main() {
280
301
  // RPC mode — headless JSON-over-stdio for IDE integrations
281
302
  if (values.rpc) {
282
303
  const rpcProvider = (values.provider ?? "anthropic");
283
- const rpcModel = values.model ?? "claude-opus-4-6";
304
+ const rpcModel = values.model ?? "claude-opus-4-7";
284
305
  const systemPrompt = values["system-prompt"];
285
306
  const cwd = process.cwd();
286
307
  runRpcMode({
@@ -295,36 +316,24 @@ function main() {
295
316
  return;
296
317
  }
297
318
  // Load saved settings for model/provider persistence
298
- let savedProvider;
299
- let savedModel;
300
- let savedThinkingEnabled = false;
301
- let savedTheme = "auto";
302
- try {
303
- const raw = JSON.parse(fs.readFileSync(getAppPaths().settingsFile, "utf-8"));
304
- if (raw.defaultProvider)
305
- savedProvider = raw.defaultProvider;
306
- if (raw.defaultModel)
307
- savedModel = raw.defaultModel;
308
- if (raw.thinkingEnabled === true)
309
- savedThinkingEnabled = true;
310
- if (raw.theme === "dark" || raw.theme === "light" || raw.theme === "auto")
311
- savedTheme = raw.theme;
312
- }
313
- catch {
314
- // No settings file or invalid JSON — use defaults
315
- }
316
- const provider = savedProvider ?? "anthropic";
319
+ const saved = loadSavedSettings();
320
+ const savedTheme = saved.theme;
321
+ const provider = saved.provider ?? "anthropic";
317
322
  function getHardcodedDefault(p) {
318
323
  if (p === "openai")
319
- return "gpt-5.3-codex";
324
+ return "gpt-5.4";
320
325
  if (p === "glm")
321
326
  return "glm-5.1";
322
327
  if (p === "moonshot")
323
328
  return "kimi-k2.5";
324
- return "claude-opus-4-6";
329
+ if (p === "minimax")
330
+ return "MiniMax-M2.7";
331
+ if (p === "openrouter")
332
+ return "qwen/qwen3.6-plus";
333
+ return "claude-opus-4-7";
325
334
  }
326
- const model = savedModel ?? getHardcodedDefault(provider);
327
- const thinkingLevel = savedThinkingEnabled ? "medium" : undefined;
335
+ const model = saved.model ?? getHardcodedDefault(provider);
336
+ const thinkingLevel = saved.thinkingEnabled ? "medium" : undefined;
328
337
  // Interactive mode (Ink TUI)
329
338
  const cwd = process.cwd();
330
339
  const continueRecent = subcommand === "continue";
@@ -355,11 +364,23 @@ async function runInkTUI(opts) {
355
364
  model,
356
365
  thinking: opts.thinkingLevel,
357
366
  });
367
+ // Wire stream stall diagnostics into the debug log
368
+ setStreamDiagnostic((phase, data) => {
369
+ log("INFO", "stream", phase, data);
370
+ });
358
371
  const authStorage = new AuthStorage(paths.authFile);
359
372
  await authStorage.load();
360
373
  const creds = await authStorage.resolveCredentials(provider);
361
374
  // Detect all logged-in providers and preload their credentials
362
- const allProviders = ["anthropic", "openai", "glm", "moonshot"];
375
+ const allProviders = [
376
+ "anthropic",
377
+ "xiaomi",
378
+ "openai",
379
+ "glm",
380
+ "moonshot",
381
+ "minimax",
382
+ "openrouter",
383
+ ];
363
384
  const loggedInProviders = [];
364
385
  const credentialsByProvider = {};
365
386
  for (const p of allProviders) {
@@ -371,6 +392,7 @@ async function runInkTUI(opts) {
371
392
  credentialsByProvider[p] = {
372
393
  accessToken: resolved.accessToken,
373
394
  accountId: resolved.accountId,
395
+ baseUrl: resolved.baseUrl,
374
396
  };
375
397
  }
376
398
  catch {
@@ -548,8 +570,20 @@ async function runLogin() {
548
570
  },
549
571
  };
550
572
  let creds;
551
- if (provider === "glm" || provider === "moonshot") {
552
- const keyLabel = provider === "glm" ? "Z.AI" : "Moonshot";
573
+ if (provider === "glm" ||
574
+ provider === "moonshot" ||
575
+ provider === "xiaomi" ||
576
+ provider === "minimax" ||
577
+ provider === "openrouter") {
578
+ const keyLabel = provider === "glm"
579
+ ? "Z.AI"
580
+ : provider === "xiaomi"
581
+ ? "Xiaomi MiMo"
582
+ : provider === "minimax"
583
+ ? "MiniMax"
584
+ : provider === "openrouter"
585
+ ? "OpenRouter"
586
+ : "Moonshot";
553
587
  const apiKey = await rl.question(chalk.hex("#60a5fa")(`Paste your ${keyLabel} API key: `));
554
588
  if (!apiKey.trim()) {
555
589
  console.log(chalk.hex("#ef4444")("No API key provided. Login cancelled."));
@@ -559,6 +593,7 @@ async function runLogin() {
559
593
  accessToken: apiKey.trim(),
560
594
  refreshToken: "",
561
595
  expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000 * 100, // ~100 years
596
+ ...(provider === "xiaomi" ? { baseUrl: "https://token-plan-sgp.xiaomimimo.com/v1" } : {}),
562
597
  };
563
598
  }
564
599
  else {
@@ -574,6 +609,244 @@ async function runLogin() {
574
609
  closeLogger();
575
610
  }
576
611
  }
612
+ // ── Doctor ─────────────────────────────────────────────────
613
+ async function runDoctor() {
614
+ process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
615
+ const os = await import("node:os");
616
+ const fsP = await import("node:fs/promises");
617
+ const dim = chalk.hex("#6b7280");
618
+ const primary = chalk.hex("#60a5fa");
619
+ const accent = chalk.hex("#a78bfa");
620
+ const good = chalk.hex("#4ade80");
621
+ const warn = chalk.hex("#fbbf24");
622
+ const bad = chalk.hex("#ef4444");
623
+ // ── Banner ──────────────────────────────────────────────────
624
+ const LOGO = LOGO_LINES;
625
+ const GAP = " ";
626
+ console.log();
627
+ console.log(` ${gradientLine(LOGO[0])}${GAP}` +
628
+ primary.bold("EZ Coder") +
629
+ dim(` v${CLI_VERSION}`));
630
+ console.log(` ${gradientLine(LOGO[1])}${GAP}` + accent("Doctor"));
631
+ console.log(` ${gradientLine(LOGO[2])}${GAP}` + dim("Diagnose & Fix"));
632
+ console.log();
633
+ const home = os.homedir();
634
+ const ggDir = path.join(home, ".ezcoder");
635
+ const authFile = path.join(ggDir, "auth.json");
636
+ const lockFile = authFile + ".lock";
637
+ const myUid = process.getuid();
638
+ let fixed = 0;
639
+ // ── Environment ─────────────────────────────────────────────
640
+ console.log(accent(" Environment\n"));
641
+ console.log(dim(` Home: ${home}`));
642
+ console.log(dim(` $HOME: ${process.env.HOME ?? "(not set)"}`));
643
+ console.log(dim(` Node.js: ${process.version}`));
644
+ console.log(dim(` Platform: ${process.platform} ${process.arch}`));
645
+ console.log(dim(` UID: ${myUid} EUID: ${process.geteuid()}`));
646
+ if (process.env.HOME && process.env.HOME !== home) {
647
+ console.log(warn("\n ⚠ $HOME differs from os.homedir() — this can cause auth mismatches"));
648
+ }
649
+ if (myUid !== process.geteuid()) {
650
+ console.log(warn(" ⚠ uid ≠ euid — running with elevated privileges (sudo?)"));
651
+ console.log(dim(" Running ezcoder with sudo can cause ownership issues."));
652
+ console.log(dim(" Use without sudo, or fix after: sudo chown -R $(whoami) ~/.gg"));
653
+ }
654
+ console.log();
655
+ // ── Config Directory ────────────────────────────────────────
656
+ console.log(accent(" Config Directory\n"));
657
+ try {
658
+ const stat = await fsP.stat(ggDir);
659
+ const mode = stat.mode & 0o777;
660
+ console.log(dim(` Path: ${ggDir}`));
661
+ console.log(dim(` Mode: 0o${mode.toString(8)} UID: ${stat.uid}`));
662
+ // Fix ownership
663
+ if (stat.uid !== myUid) {
664
+ console.log(warn(` ⚠ Owned by uid ${stat.uid}, expected ${myUid}`));
665
+ try {
666
+ await fsP.chown(ggDir, myUid, process.getgid());
667
+ console.log(good(" ✓ Fixed directory ownership"));
668
+ fixed++;
669
+ }
670
+ catch {
671
+ console.log(bad(` ✗ Cannot fix — try: sudo chown -R $(whoami) ${ggDir}`));
672
+ }
673
+ }
674
+ // Fix permissions (should be 0o700)
675
+ if (mode !== 0o700) {
676
+ try {
677
+ await fsP.chmod(ggDir, 0o700);
678
+ console.log(good(" ✓ Fixed directory permissions → 0o700"));
679
+ fixed++;
680
+ }
681
+ catch {
682
+ console.log(bad(` ✗ Cannot fix — try: chmod 700 ${ggDir}`));
683
+ }
684
+ }
685
+ }
686
+ catch {
687
+ console.log(warn(` ${ggDir} missing — creating...`));
688
+ try {
689
+ await fsP.mkdir(ggDir, { recursive: true, mode: 0o700 });
690
+ console.log(good(` ✓ Created ${ggDir}`));
691
+ fixed++;
692
+ }
693
+ catch (mkErr) {
694
+ console.log(bad(` ✗ Cannot create: ${mkErr instanceof Error ? mkErr.message : String(mkErr)}`));
695
+ console.log();
696
+ return;
697
+ }
698
+ }
699
+ console.log();
700
+ // ── Lock File ───────────────────────────────────────────────
701
+ try {
702
+ const lockStat = await fsP.stat(lockFile);
703
+ const ageMs = Date.now() - lockStat.mtimeMs;
704
+ console.log(accent(" Lock File\n"));
705
+ console.log(warn(` ⚠ Stale lock found (age: ${Math.round(ageMs / 1000)}s)`));
706
+ await fsP.unlink(lockFile);
707
+ console.log(good(" ✓ Removed"));
708
+ fixed++;
709
+ console.log();
710
+ }
711
+ catch {
712
+ // No lock file — good, skip section entirely
713
+ }
714
+ // ── Auth File ───────────────────────────────────────────────
715
+ console.log(accent(" Auth File\n"));
716
+ let authData = null;
717
+ let authNeedsRewrite = false;
718
+ try {
719
+ const stat = await fsP.stat(authFile);
720
+ const mode = stat.mode & 0o777;
721
+ console.log(dim(` Path: ${authFile}`));
722
+ console.log(dim(` Size: ${stat.size} bytes Mode: 0o${mode.toString(8)} UID: ${stat.uid}`));
723
+ // Fix ownership
724
+ if (stat.uid !== myUid) {
725
+ console.log(warn(` ⚠ Owned by uid ${stat.uid}, expected ${myUid}`));
726
+ try {
727
+ await fsP.chown(authFile, myUid, process.getgid());
728
+ console.log(good(" ✓ Fixed file ownership"));
729
+ fixed++;
730
+ }
731
+ catch {
732
+ console.log(bad(` ✗ Cannot fix — try: sudo chown $(whoami) ${authFile}`));
733
+ }
734
+ }
735
+ // Fix permissions (should be 0o600)
736
+ if (mode !== 0o600) {
737
+ try {
738
+ await fsP.chmod(authFile, 0o600);
739
+ console.log(good(" ✓ Fixed file permissions → 0o600"));
740
+ fixed++;
741
+ }
742
+ catch {
743
+ console.log(bad(` ✗ Cannot fix — try: chmod 600 ${authFile}`));
744
+ }
745
+ }
746
+ // Try to read and parse
747
+ try {
748
+ const content = await fsP.readFile(authFile, "utf-8");
749
+ try {
750
+ authData = JSON.parse(content);
751
+ }
752
+ catch {
753
+ console.log(bad(" ✗ Invalid JSON — backing up and resetting"));
754
+ const backupName = `auth.json.corrupt.${Date.now()}`;
755
+ await fsP.copyFile(authFile, path.join(ggDir, backupName));
756
+ await fsP.writeFile(authFile, "{}", { encoding: "utf-8", mode: 0o600 });
757
+ console.log(good(` ✓ Corrupt file backed up as ${backupName}`));
758
+ console.log(dim(' Run "ezcoder login" to re-authenticate'));
759
+ authData = {};
760
+ fixed++;
761
+ }
762
+ }
763
+ catch (readErr) {
764
+ const code = readErr.code;
765
+ if (code === "EACCES") {
766
+ console.log(bad(" ✗ Permission denied reading auth.json"));
767
+ console.log(dim(` Try: sudo chown $(whoami) ${authFile} && chmod 600 ${authFile}`));
768
+ }
769
+ else {
770
+ console.log(bad(` ✗ Read error: ${readErr instanceof Error ? readErr.message : String(readErr)}`));
771
+ }
772
+ }
773
+ }
774
+ catch {
775
+ console.log(dim(` Path: ${authFile}`));
776
+ console.log(warn(' Not found — run "ezcoder login" to authenticate'));
777
+ }
778
+ console.log();
779
+ // ── Credentials ─────────────────────────────────────────────
780
+ if (authData && Object.keys(authData).length > 0) {
781
+ console.log(accent(" Credentials\n"));
782
+ for (const p of Object.keys(authData)) {
783
+ const cred = authData[p];
784
+ if (!cred || typeof cred !== "object") {
785
+ console.log(bad(` ✗ ${p}: invalid entry — removing`));
786
+ delete authData[p];
787
+ authNeedsRewrite = true;
788
+ fixed++;
789
+ continue;
790
+ }
791
+ if (!cred.accessToken || typeof cred.accessToken !== "string") {
792
+ console.log(bad(` ✗ ${p}: missing accessToken — removing`));
793
+ delete authData[p];
794
+ authNeedsRewrite = true;
795
+ fixed++;
796
+ continue;
797
+ }
798
+ const token = String(cred.accessToken);
799
+ const masked = token.slice(0, 8) + "..." + token.slice(-4);
800
+ const expires = typeof cred.expiresAt === "number" ? new Date(cred.expiresAt).toISOString() : "unknown";
801
+ const expired = typeof cred.expiresAt === "number" && Date.now() > cred.expiresAt;
802
+ if (expired) {
803
+ console.log(warn(` ⚠ ${p}: ${masked} expired ${expires}`));
804
+ }
805
+ else {
806
+ console.log(good(` ✓ ${p}: ${masked} expires ${expires}`));
807
+ }
808
+ }
809
+ if (authNeedsRewrite) {
810
+ try {
811
+ await fsP.writeFile(authFile, JSON.stringify(authData, null, 2), {
812
+ encoding: "utf-8",
813
+ mode: 0o600,
814
+ });
815
+ console.log(good(" ✓ Cleaned up auth.json"));
816
+ }
817
+ catch {
818
+ console.log(bad(" ✗ Failed to write cleaned auth.json"));
819
+ }
820
+ }
821
+ console.log();
822
+ }
823
+ // ── Temp Files ──────────────────────────────────────────────
824
+ try {
825
+ const entries = await fsP.readdir(ggDir);
826
+ const tmpFiles = entries.filter((e) => e.startsWith("auth.json.") && e.endsWith(".tmp"));
827
+ if (tmpFiles.length > 0) {
828
+ console.log(accent(" Temp Files\n"));
829
+ console.log(warn(` ⚠ ${tmpFiles.length} orphaned temp file(s) from interrupted writes`));
830
+ for (const tmp of tmpFiles) {
831
+ await fsP.unlink(path.join(ggDir, tmp)).catch(() => { });
832
+ }
833
+ console.log(good(` ✓ Removed ${tmpFiles.length} file(s)`));
834
+ fixed++;
835
+ console.log();
836
+ }
837
+ }
838
+ catch {
839
+ // Can't read directory — already flagged above
840
+ }
841
+ // ── Summary ─────────────────────────────────────────────────
842
+ if (fixed > 0) {
843
+ console.log(good(` ✓ Fixed ${fixed} issue${fixed > 1 ? "s" : ""}.`));
844
+ }
845
+ else {
846
+ console.log(good(" ✓ Everything looks good."));
847
+ }
848
+ console.log();
849
+ }
577
850
  // ── Logout ─────────────────────────────────────────────────
578
851
  async function runLogout() {
579
852
  const paths = await ensureAppDirs();
@@ -599,37 +872,21 @@ async function runSessions() {
599
872
  closeLogger();
600
873
  process.exit(0);
601
874
  }
602
- // Load saved settings for provider/model/theme
603
- let savedProvider;
604
- let savedModel;
605
- let savedThinkingEnabled = false;
606
- let savedTheme = "auto";
607
- try {
608
- const raw = JSON.parse(fs.readFileSync(paths.settingsFile, "utf-8"));
609
- if (raw.defaultProvider)
610
- savedProvider = raw.defaultProvider;
611
- if (raw.defaultModel)
612
- savedModel = raw.defaultModel;
613
- if (raw.thinkingEnabled === true)
614
- savedThinkingEnabled = true;
615
- if (raw.theme === "dark" || raw.theme === "light" || raw.theme === "auto")
616
- savedTheme = raw.theme;
617
- }
618
- catch {
619
- // No settings file — use defaults
620
- }
621
- const provider = savedProvider ?? "anthropic";
875
+ const saved2 = loadSavedSettings(paths.settingsFile);
876
+ const provider = saved2.provider ?? "anthropic";
622
877
  function getDefault(p) {
623
878
  if (p === "openai")
624
- return "gpt-5.3-codex";
879
+ return "gpt-5.4";
625
880
  if (p === "glm")
626
881
  return "glm-5.1";
627
882
  if (p === "moonshot")
628
883
  return "kimi-k2.5";
629
- return "claude-opus-4-6";
884
+ if (p === "minimax")
885
+ return "MiniMax-M2.7";
886
+ return "claude-opus-4-7";
630
887
  }
631
- const model = savedModel ?? getDefault(provider);
632
- const thinkingLevel = savedThinkingEnabled ? "medium" : undefined;
888
+ const model = saved2.model ?? getDefault(provider);
889
+ const thinkingLevel = saved2.thinkingEnabled ? "medium" : undefined;
633
890
  closeLogger();
634
891
  await runInkTUI({
635
892
  provider,
@@ -637,7 +894,7 @@ async function runSessions() {
637
894
  cwd,
638
895
  thinkingLevel,
639
896
  resumeSessionPath: selectedPath,
640
- theme: savedTheme,
897
+ theme: saved2.theme,
641
898
  });
642
899
  }
643
900
  async function loadTelegramConfig() {
@@ -806,34 +1063,21 @@ async function runServe() {
806
1063
  chalk.hex("#6b7280")(" ezcoder serve --bot-token TOKEN --user-id ID"));
807
1064
  process.exit(1);
808
1065
  }
809
- // Load saved settings
810
- let savedProvider;
811
- let savedModel;
812
- let savedThinkingEnabled = false;
813
- try {
814
- const raw = JSON.parse(fs.readFileSync(getAppPaths().settingsFile, "utf-8"));
815
- if (raw.defaultProvider)
816
- savedProvider = raw.defaultProvider;
817
- if (raw.defaultModel)
818
- savedModel = raw.defaultModel;
819
- if (raw.thinkingEnabled === true)
820
- savedThinkingEnabled = true;
821
- }
822
- catch {
823
- // No settings file
824
- }
825
- const provider = serveValues.provider ?? savedProvider ?? "anthropic";
1066
+ const saved3 = loadSavedSettings();
1067
+ const provider = serveValues.provider ?? saved3.provider ?? "anthropic";
826
1068
  function getDefault(p) {
827
1069
  if (p === "openai")
828
- return "gpt-5.3-codex";
1070
+ return "gpt-5.4";
829
1071
  if (p === "glm")
830
1072
  return "glm-5.1";
831
1073
  if (p === "moonshot")
832
1074
  return "kimi-k2.5";
833
- return "claude-opus-4-6";
1075
+ if (p === "minimax")
1076
+ return "MiniMax-M2.7";
1077
+ return "claude-opus-4-7";
834
1078
  }
835
- const model = serveValues.model ?? savedModel ?? getDefault(provider);
836
- const thinkingLevel = savedThinkingEnabled ? "medium" : undefined;
1079
+ const model = serveValues.model ?? saved3.model ?? getDefault(provider);
1080
+ const thinkingLevel = saved3.thinkingEnabled ? "medium" : undefined;
837
1081
  const paths = await ensureAppDirs();
838
1082
  initLogger(paths.logFile, {
839
1083
  version: CLI_VERSION,
@@ -854,10 +1098,16 @@ async function runServe() {
854
1098
  function displayName(provider) {
855
1099
  if (provider === "anthropic")
856
1100
  return "Anthropic";
1101
+ if (provider === "xiaomi")
1102
+ return "Xiaomi (MiMo)";
857
1103
  if (provider === "glm")
858
1104
  return "Z.AI (GLM)";
859
1105
  if (provider === "moonshot")
860
1106
  return "Moonshot";
1107
+ if (provider === "minimax")
1108
+ return "MiniMax";
1109
+ if (provider === "openrouter")
1110
+ return "OpenRouter";
861
1111
  return "OpenAI";
862
1112
  }
863
1113
  function extractText(content) {