min-agent 0.2.0 → 0.3.0

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 (81) hide show
  1. package/README.md +146 -18
  2. package/dist/agent.js +293 -408
  3. package/dist/assistant-stream.js +11 -7
  4. package/dist/cli.js +403 -140
  5. package/dist/clipboard.js +59 -23
  6. package/dist/code-mode.js +3 -3
  7. package/dist/compaction.js +182 -81
  8. package/dist/config.js +186 -35
  9. package/dist/confirm.js +55 -6
  10. package/dist/context-window.js +67 -54
  11. package/dist/doom-loop.js +19 -12
  12. package/dist/http.js +119 -0
  13. package/dist/instructions.js +51 -33
  14. package/dist/logger.js +66 -0
  15. package/dist/markdown.js +3 -44
  16. package/dist/mcp.js +547 -100
  17. package/dist/memory.js +48 -6
  18. package/dist/output.js +36 -27
  19. package/dist/paste-handler.js +3 -3
  20. package/dist/plugins.js +33 -6
  21. package/dist/pricing.js +119 -0
  22. package/dist/provider.js +17 -15
  23. package/dist/serve.js +658 -369
  24. package/dist/sessions.js +151 -13
  25. package/dist/skills.js +466 -76
  26. package/dist/synthetic.js +7 -0
  27. package/dist/title-gen.js +2 -1
  28. package/dist/tool-display.js +173 -0
  29. package/dist/tool-output.js +54 -45
  30. package/dist/tools/apply_patch.js +191 -0
  31. package/dist/tools/backend.js +61 -0
  32. package/dist/tools/bash.js +147 -70
  33. package/dist/tools/code_search.js +6 -5
  34. package/dist/tools/edit.js +23 -7
  35. package/dist/tools/explore.js +80 -12
  36. package/dist/tools/glob.js +3 -3
  37. package/dist/tools/grep.js +146 -14
  38. package/dist/tools/index.js +7 -7
  39. package/dist/tools/question.js +4 -22
  40. package/dist/tools/read.js +71 -11
  41. package/dist/tools/task.js +33 -20
  42. package/dist/tools/todo.js +83 -73
  43. package/dist/tools/web_fetch.js +150 -46
  44. package/dist/tools/web_search.js +706 -28
  45. package/dist/tools/write.js +13 -7
  46. package/dist/tui/App.js +40 -6
  47. package/dist/tui/ConfirmBar.js +24 -3
  48. package/dist/tui/InputBar.js +390 -45
  49. package/dist/tui/MessageList.js +533 -20
  50. package/dist/tui/ModelPicker.js +108 -0
  51. package/dist/tui/QuestionBar.js +104 -0
  52. package/dist/tui/StatusBar.js +19 -11
  53. package/dist/tui/agent-runner.js +103 -0
  54. package/dist/tui/caret-pos.js +134 -0
  55. package/dist/tui/caret.js +69 -0
  56. package/dist/tui/diff-view.js +61 -0
  57. package/dist/tui/drag-state.js +44 -0
  58. package/dist/tui/index.js +153 -24
  59. package/dist/tui/input-history.js +44 -0
  60. package/dist/tui/layout.js +17 -0
  61. package/dist/tui/mouse.js +46 -0
  62. package/dist/tui/selection.js +134 -0
  63. package/dist/tui/slash-commands.js +90 -0
  64. package/dist/tui/slash-handler.js +370 -0
  65. package/dist/tui/text-width.js +91 -0
  66. package/dist/tui/theme.js +12 -0
  67. package/dist/tui/undo-stack.js +14 -0
  68. package/dist/tui/use-sgr-mouse.js +27 -0
  69. package/dist/tui-chat.js +111 -331
  70. package/dist/updater.js +57 -0
  71. package/docs/API.md +160 -14
  72. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +1510 -0
  73. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +2105 -0
  74. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +1595 -0
  75. package/docs/superpowers/plans/2026-08-16-input-caret.md +782 -0
  76. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +183 -0
  77. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +220 -0
  78. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +196 -0
  79. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +63 -0
  80. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +116 -0
  81. package/package.json +7 -8
package/dist/tui-chat.js CHANGED
@@ -1,23 +1,48 @@
1
1
  import { TuiRenderer } from "./tui/index.js";
2
- import { initMcp, shutdownMcp, getMcpStatus, loadMcpConfig } from "./mcp.js";
3
- import { discoverSkills, getSkills } from "./skills.js";
2
+ import { initMcp, shutdownMcp } from "./mcp.js";
3
+ import { discoverSkills, getAllSkills, getStaleDisabledSkills } from "./skills.js";
4
+ import { setSlashArgProvider } from "./tui/slash-commands.js";
4
5
  import { loadInstructions } from "./instructions.js";
5
- import { TokenTracker, estimateTokens, compactMessages } from "./compaction.js";
6
- import { runOnce, runOnceWithSystem } from "./agent.js";
6
+ import { TokenTracker } from "./compaction.js";
7
+ import { loadImageParts } from "./agent.js";
8
+ import { createUndoStack } from "./tui/undo-stack.js";
7
9
  import { killActiveProcesses } from "./tools/bash.js";
8
- import { setTuiConfirm } from "./confirm.js";
9
- import { getContextWindow } from "./context-window.js";
10
- import { loadConfig } from "./config.js";
11
- import { resolveModel } from "./provider.js";
10
+ import { setTuiConfirm, setTuiQuestion } from "./confirm.js";
11
+ import { loadConfig, getActiveProvider } from "./config.js";
12
12
  import { scanProject, buildCodeSystemPrompt } from "./code-mode.js";
13
+ import { handleSlashCommand, sysMsg } from "./tui/slash-handler.js";
14
+ import { createAgentRunner } from "./tui/agent-runner.js";
13
15
  let confirmResolver = null;
16
+ let questionResolver = null;
17
+ function registerSkillCompletion() {
18
+ setSlashArgProvider("skills", (tokens) => {
19
+ const action = tokens[0];
20
+ if (action === undefined) {
21
+ return [
22
+ { value: "list", description: "列出全部 skills" },
23
+ { value: "info", description: "查看 skill 详情" },
24
+ { value: "enable", description: "启用 skill" },
25
+ { value: "disable", description: "禁用 skill" },
26
+ ];
27
+ }
28
+ if (action === "info")
29
+ return getAllSkills().map((s) => ({ value: s.name, description: s.description }));
30
+ if (action !== "enable" && action !== "disable")
31
+ return [];
32
+ const all = getAllSkills();
33
+ const pool = action === "enable"
34
+ ? [...all.filter((s) => !s.enabled).map((s) => ({ value: s.name, description: s.description })), ...getStaleDisabledSkills().map((name) => ({ value: name, description: "(配置中的孤儿条目)" }))]
35
+ : all.filter((s) => s.enabled).map((s) => ({ value: s.name, description: s.description }));
36
+ return [...pool.filter((o) => !tokens.includes(o.value)), ...(tokens.includes("--project") ? [] : [{ value: "--project", description: "仅对当前项目生效" }])];
37
+ });
38
+ }
14
39
  export async function runTui(opts) {
15
- const { modelId, resumeSessionId, mode } = opts;
40
+ const { modelId, resumeSessionId, mode, images, providerName } = opts;
16
41
  await initMcp();
17
42
  discoverSkills();
43
+ registerSkillCompletion();
18
44
  const instructions = await loadInstructions();
19
45
  const config = loadConfig();
20
- // Code mode: scan project and build code-specific system prompt
21
46
  let codeSystemPrompt;
22
47
  if (mode === "code") {
23
48
  const project = scanProject();
@@ -25,9 +50,12 @@ export async function runTui(opts) {
25
50
  }
26
51
  let messages = [];
27
52
  let sessionId = resumeSessionId;
53
+ let currentModel = modelId ?? getActiveProvider(config)?.defaultModel;
54
+ const currentProvider = providerName;
28
55
  const tracker = new TokenTracker();
29
- let abortController = null;
30
- // Resume session if requested
56
+ let undoStack = createUndoStack();
57
+ let lastUserMessage = null;
58
+ let planMode = false;
31
59
  if (resumeSessionId) {
32
60
  const { loadSession } = await import("./sessions.js");
33
61
  const session = loadSession(resumeSessionId);
@@ -36,26 +64,20 @@ export async function runTui(opts) {
36
64
  sessionId = resumeSessionId;
37
65
  }
38
66
  }
39
- // Forward reference — assigned after tui is created
40
- let executeAgent;
67
+ let runner = null;
41
68
  const tui = new TuiRenderer({
42
69
  onSubmit: async (text) => {
43
70
  if (text.startsWith("/")) {
44
- const result = await handleSlashCommand(text, tui, messages, modelId, tracker);
45
- if (result === "run") {
46
- await executeAgent();
47
- }
71
+ const result = await handleSlashCommand(text, tui, messages, currentModel, tracker, (model) => { currentModel = model; tui.setModel(model); }, requestExit, { undoStack, setUndoStack: (s) => { undoStack = s; }, lastUserMessage, setLastUserMessage: (t) => { lastUserMessage = t; }, sessionId, setSessionId: (id) => { sessionId = id; }, planMode, togglePlanMode: () => { planMode = !planMode; } });
72
+ if (result === "run" && runner)
73
+ await runner.run();
48
74
  return;
49
75
  }
50
- // Add user message
51
- tui.addMessage({
52
- id: `user-${Date.now()}`,
53
- role: "user",
54
- content: text,
55
- timestamp: Date.now(),
56
- });
76
+ lastUserMessage = text;
77
+ tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: text, timestamp: Date.now() });
57
78
  messages.push({ role: "user", content: text });
58
- await executeAgent();
79
+ if (runner)
80
+ await runner.run();
59
81
  },
60
82
  onConfirm: (accepted) => {
61
83
  tui.hideConfirm();
@@ -64,321 +86,79 @@ export async function runTui(opts) {
64
86
  confirmResolver = null;
65
87
  }
66
88
  },
67
- onExit: () => {
68
- if (abortController) {
69
- killActiveProcesses();
70
- abortController.abort();
71
- abortController = null;
72
- tui.setRunning(false);
73
- tui.addMessage({
74
- id: `sys-${Date.now()}`,
75
- role: "system",
76
- content: "(已取消)",
77
- timestamp: Date.now(),
78
- });
89
+ onQuestionAnswer: (answer) => {
90
+ tui.hideQuestion();
91
+ if (questionResolver) {
92
+ questionResolver(answer);
93
+ questionResolver = null;
79
94
  }
80
95
  },
81
- }, modelId ?? config.provider?.defaultModel);
82
- // Define the agent execution function (uses tui, messages, etc. from closure)
83
- executeAgent = async () => {
84
- abortController = new AbortController();
85
- tui.setRunning(true, "思考中...");
86
- const callbacks = {
87
- onAssistantDisplayDelta: (delta) => {
88
- // Pass raw delta — markdown formatting is applied at display time
89
- tui.appendToLast(delta);
90
- },
91
- onThinkingDelta: (delta) => {
92
- tui.setRunning(true, "推理中...");
93
- tui.appendToThinking(delta);
94
- },
95
- onToolCall: (toolName, input) => {
96
- tui.setRunning(true, `执行 ${toolName}...`);
97
- const args = typeof input === "object" && input
98
- ? Object.entries(input)
99
- .map(([k, v]) => `${k}=${typeof v === "string" ? (v.length > 40 ? v.slice(0, 40) + "..." : v) : JSON.stringify(v)}`)
100
- .join(" ")
101
- : "";
102
- tui.addMessage({
103
- id: `tool-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
104
- role: "tool",
105
- content: args,
106
- toolName,
107
- timestamp: Date.now(),
108
- });
109
- },
110
- onToolResult: (_toolName, _output) => {
111
- tui.setRunning(true, "思考中...");
112
- },
113
- onStreamError: (msg) => {
114
- tui.addMessage({
115
- id: `err-${Date.now()}`,
116
- role: "system",
117
- content: `错误: ${msg}`,
118
- timestamp: Date.now(),
119
- });
120
- },
121
- onRunFinish: async (info) => {
122
- abortController = null;
123
- tui.setRunning(false);
124
- if (info.usage) {
125
- const input = info.usage.inputTokens ?? 0;
126
- const output = info.usage.outputTokens ?? 0;
127
- const total = input + output;
128
- const ctxWindow = await getContextWindow(modelId);
129
- const pct = ctxWindow > 0 ? Math.round((input / ctxWindow) * 100) : 0;
130
- tui.setTokenInfo(input, output, ctxWindow);
131
- tui.addMessage({
132
- id: `done-${Date.now()}`,
133
- role: "system",
134
- content: `Done in ${info.stepCount} step(s) | Tokens: ${input} in / ${output} out / ${total} total | Context: ${pct}%`,
135
- timestamp: Date.now(),
136
- });
137
- }
138
- },
139
- };
140
- if (mode === "code" && codeSystemPrompt) {
141
- await runOnceWithSystem(messages, codeSystemPrompt, modelId, abortController?.signal, callbacks, tracker);
142
- }
143
- else {
144
- await runOnce(messages, instructions, modelId, abortController?.signal, callbacks, tracker);
96
+ onModelPick: (model) => {
97
+ tui.hideModelPicker();
98
+ const cfg = loadConfig();
99
+ const provider = getActiveProvider(cfg);
100
+ if (provider) {
101
+ provider.defaultModel = model;
102
+ import("./config.js").then((m) => m.saveConfig(cfg));
103
+ }
104
+ currentModel = model;
105
+ tui.setModel(model);
106
+ sysMsg(tui, `✓ 默认模型已设为: ${model}`);
107
+ },
108
+ onModelCancel: () => { tui.hideModelPicker(); },
109
+ onExit: () => { requestExit(); },
110
+ }, modelId ?? getActiveProvider(config)?.defaultModel);
111
+ runner = createAgentRunner({ tui, messages, tracker, getModel: () => currentModel, getProvider: () => currentProvider, getPlanMode: () => planMode, getUndoStack: () => undoStack, setUndoStack: (s) => { undoStack = s; }, mode, codeSystemPrompt });
112
+ setTuiConfirm((message) => new Promise((resolve) => { confirmResolver = resolve; tui.showConfirm(message); }));
113
+ setTuiQuestion((prompt, options) => new Promise((resolve) => { questionResolver = resolve; tui.showQuestion(prompt, options); }));
114
+ let exitResolve;
115
+ const exitPromise = new Promise((resolve) => { exitResolve = resolve; });
116
+ const requestExit = () => {
117
+ if (runner?.getController()) {
118
+ killActiveProcesses();
119
+ if (confirmResolver) {
120
+ confirmResolver(false);
121
+ confirmResolver = null;
122
+ tui.hideConfirm();
123
+ }
124
+ if (questionResolver) {
125
+ questionResolver(null);
126
+ questionResolver = null;
127
+ tui.hideQuestion();
128
+ }
129
+ runner.getController().abort();
130
+ tui.setRunning(false);
131
+ tui.addMessage({ id: `sys-${Date.now()}`, role: "system", content: "(已取消)", timestamp: Date.now() });
145
132
  }
146
- abortController = null;
147
- tui.setRunning(false);
133
+ else
134
+ exitResolve?.();
148
135
  };
149
- // Override the confirm module to use TUI overlay
150
- setTuiConfirm((message) => {
151
- return new Promise((resolve) => {
152
- confirmResolver = resolve;
153
- tui.showConfirm(message);
154
- });
155
- });
156
136
  tui.start();
157
- // Keep process alive until exit
158
- await new Promise((resolve) => {
159
- process.on("SIGINT", () => {
160
- if (abortController) {
161
- killActiveProcesses();
162
- abortController.abort();
163
- abortController = null;
164
- tui.setRunning(false);
165
- }
166
- else {
167
- resolve();
168
- }
169
- });
170
- });
171
- // Save session
137
+ if (images && images.length > 0) {
138
+ const notify = (kind, imgPath) => {
139
+ if (kind === "not_found")
140
+ sysMsg(tui, `⚠ 图片不存在: ${imgPath}`);
141
+ else if (kind === "too_large")
142
+ sysMsg(tui, `⚠ 图片超过 5 MB 限制: ${imgPath}`);
143
+ else
144
+ sysMsg(tui, `📎 ${imgPath}`);
145
+ };
146
+ const parts = [{ type: "text", text: "" }, ...loadImageParts(images, notify)];
147
+ if (parts.length > 1) {
148
+ messages.push({ role: "user", content: parts });
149
+ tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: `[📎 图片] ${images.join(", ")}`, timestamp: Date.now() });
150
+ await runner.run();
151
+ }
152
+ }
153
+ process.on("SIGINT", requestExit);
154
+ await exitPromise;
155
+ process.off("SIGINT", requestExit);
172
156
  if (messages.length > 0) {
173
157
  const { saveSession } = await import("./sessions.js");
174
158
  sessionId = saveSession(messages, sessionId);
175
159
  }
176
160
  setTuiConfirm(null);
161
+ setTuiQuestion(null);
177
162
  tui.destroy();
178
163
  await shutdownMcp();
179
164
  }
180
- async function handleSlashCommand(input, tui, messages, modelId, tracker) {
181
- const [cmd, ...rest] = input.slice(1).split(/\s+/);
182
- const arg = rest.join(" ");
183
- switch (cmd) {
184
- case "exit":
185
- case "quit":
186
- case "q":
187
- process.emit("SIGINT", "SIGINT");
188
- break;
189
- case "clear":
190
- messages.length = 0;
191
- tui.update({ messages: [] });
192
- sysMsg(tui, "✓ 对话已清除");
193
- break;
194
- case "compact": {
195
- if (messages.length < 4) {
196
- sysMsg(tui, "消息太少,无需压缩");
197
- }
198
- else {
199
- sysMsg(tui, "⟳ 正在压缩上下文...");
200
- const model = resolveModel(modelId);
201
- const result = await compactMessages(messages, model, { keepRecentTurns: 2, autoContinue: false });
202
- messages.length = 0;
203
- messages.push(...result.messages);
204
- if (tracker)
205
- tracker.resetContext();
206
- sysMsg(tui, `✓ 已压缩 (约 ${estimateTokens(messages)} tokens)`);
207
- }
208
- break;
209
- }
210
- case "model": {
211
- if (arg) {
212
- const config = await import("./config.js");
213
- const cfg = config.loadConfig();
214
- if (cfg.provider) {
215
- cfg.provider.defaultModel = arg;
216
- config.saveConfig(cfg);
217
- sysMsg(tui, `✓ 默认模型已设为: ${arg}`);
218
- }
219
- }
220
- else {
221
- const config = loadConfig();
222
- sysMsg(tui, `当前模型: ${config.provider?.defaultModel ?? "未设置"}`);
223
- }
224
- break;
225
- }
226
- case "models": {
227
- const { fetchModels } = await import("./config.js");
228
- const cfg = loadConfig();
229
- if (!cfg.provider?.baseURL || !cfg.provider?.apiKey) {
230
- sysMsg(tui, "未配置 Provider,运行: min-agent setup");
231
- break;
232
- }
233
- sysMsg(tui, "正在获取模型列表...");
234
- const models = await fetchModels(cfg.provider.baseURL, cfg.provider.apiKey);
235
- if (models.length === 0) {
236
- sysMsg(tui, "无法获取模型列表");
237
- }
238
- else {
239
- const lines = models.map((m) => {
240
- const marker = m === cfg.provider?.defaultModel ? " ← 默认" : "";
241
- return ` ${m}${marker}`;
242
- });
243
- sysMsg(tui, `可用模型 (${models.length}):\n${lines.join("\n")}`);
244
- }
245
- break;
246
- }
247
- case "memory": {
248
- if (arg) {
249
- const { addMemory } = await import("./memory.js");
250
- addMemory(arg);
251
- sysMsg(tui, `✓ 已保存记忆: "${arg}"`);
252
- }
253
- else {
254
- const { loadMemories } = await import("./memory.js");
255
- const memories = loadMemories();
256
- if (memories.length === 0) {
257
- sysMsg(tui, "无已保存的记忆");
258
- }
259
- else {
260
- const lines = memories.map((m, i) => {
261
- const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
262
- return ` #${i + 1}: ${m.content}${tags}`;
263
- });
264
- sysMsg(tui, `记忆 (${memories.length}):\n${lines.join("\n")}`);
265
- }
266
- }
267
- break;
268
- }
269
- case "path":
270
- case "pwd":
271
- sysMsg(tui, process.cwd());
272
- break;
273
- case "paste": {
274
- const { getClipboardImage } = await import("./clipboard.js");
275
- const img = getClipboardImage();
276
- if (!img) {
277
- sysMsg(tui, "剪贴板中未找到图片");
278
- break;
279
- }
280
- sysMsg(tui, `📎 剪贴板图片 (${(img.data.length / 1024).toFixed(1)} KB)`);
281
- const text = arg || "What's in this image?";
282
- const content = [
283
- { type: "text", text },
284
- { type: "image", image: img.data, mimeType: img.mimeType },
285
- ];
286
- messages.push({ role: "user", content });
287
- tui.addMessage({
288
- id: `user-${Date.now()}`,
289
- role: "user",
290
- content: `${text} [📎 图片]`,
291
- timestamp: Date.now(),
292
- });
293
- // Signal that we need to run the agent
294
- return "run";
295
- }
296
- case "tokens": {
297
- if (tracker && tracker.lastInputTokens > 0) {
298
- const ctxWindow = await getContextWindow(modelId);
299
- const pct = Math.round((tracker.lastInputTokens / ctxWindow) * 100);
300
- sysMsg(tui, [
301
- `Context: ${tracker.lastInputTokens} / ${ctxWindow} tokens (${pct}%)`,
302
- `Total: ${tracker.totalInputTokens} in / ${tracker.totalOutputTokens} out`,
303
- `Messages: ${messages.length}`,
304
- ].join("\n"));
305
- }
306
- else {
307
- sysMsg(tui, `估算 tokens: ${estimateTokens(messages)} | Messages: ${messages.length}`);
308
- }
309
- break;
310
- }
311
- case "mcp": {
312
- const config = loadMcpConfig();
313
- const servers = Object.entries(config.mcpServers);
314
- if (servers.length === 0) {
315
- sysMsg(tui, "无 MCP 服务器配置");
316
- }
317
- else {
318
- const status = getMcpStatus();
319
- const lines = servers.map(([name, cfg]) => {
320
- const disabled = cfg.enabled === false;
321
- const connected = status[name]?.connected ?? false;
322
- const toolCount = status[name]?.tools.length ?? 0;
323
- const state = disabled ? "disabled" : connected ? "connected" : "disconnected";
324
- return ` ${name}: ${state}${toolCount > 0 ? ` (${toolCount} tools)` : ""}`;
325
- });
326
- sysMsg(tui, `MCP 服务器 (${servers.length}):\n${lines.join("\n")}`);
327
- }
328
- break;
329
- }
330
- case "skills": {
331
- discoverSkills({ silent: true });
332
- const skills = getSkills();
333
- const cfg = loadConfig();
334
- const disabledNames = cfg.disabledSkills ?? [];
335
- if (skills.length === 0 && disabledNames.length === 0) {
336
- sysMsg(tui, "无可用 skills。添加 SKILL.md 到 .min-agent/skills/<name>/");
337
- }
338
- else {
339
- const lines = [];
340
- for (const s of skills) {
341
- lines.push(` ${s.name} enabled`);
342
- }
343
- for (const n of disabledNames) {
344
- lines.push(` ${n} disabled`);
345
- }
346
- sysMsg(tui, `Skills:\n${lines.join("\n")}`);
347
- }
348
- break;
349
- }
350
- case "help":
351
- sysMsg(tui, [
352
- "命令:",
353
- " /clear 清除对话历史",
354
- " /compact 压缩上下文",
355
- " /model [n] 显示/切换模型",
356
- " /models 列出可用模型",
357
- " /memory [t] 查看/保存记忆",
358
- " /tokens 显示 token 用量",
359
- " /mcp 显示 MCP 状态",
360
- " /skills 显示可用 skills",
361
- " /path 显示工作目录",
362
- " /paste [t] 粘贴剪贴板图片 + 可选提示",
363
- " /help 显示帮助",
364
- " /exit 退出",
365
- "",
366
- "快捷键:",
367
- " Esc 取消当前任务",
368
- " Ctrl+J 输入框换行",
369
- " Ctrl+C 退出程序",
370
- " PageUp/Down 滚动消息",
371
- ].join("\n"));
372
- break;
373
- default:
374
- sysMsg(tui, `未知命令: /${cmd},输入 /help 查看可用命令`);
375
- }
376
- }
377
- function sysMsg(tui, content) {
378
- tui.addMessage({
379
- id: `sys-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
380
- role: "system",
381
- content,
382
- timestamp: Date.now(),
383
- });
384
- }
@@ -0,0 +1,57 @@
1
+ import { readFileSync } from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { execSync } from "child_process";
5
+ export function localVersion() {
6
+ const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../package.json");
7
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
8
+ return pkg.version ?? "0.0.0";
9
+ }
10
+ export function compareVersions(a, b) {
11
+ const pa = a.split("-")[0].split(".").map(Number);
12
+ const pb = b.split("-")[0].split(".").map(Number);
13
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
14
+ const va = pa[i] ?? 0;
15
+ const vb = pb[i] ?? 0;
16
+ if (va !== vb)
17
+ return va - vb;
18
+ }
19
+ return 0;
20
+ }
21
+ export async function fetchLatestVersion() {
22
+ try {
23
+ const response = await fetch("https://registry.npmjs.org/min-agent/latest", {
24
+ signal: AbortSignal.timeout(10000),
25
+ headers: { Accept: "application/json" },
26
+ });
27
+ if (!response.ok)
28
+ return null;
29
+ const data = (await response.json());
30
+ return data.version ?? null;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ export async function runUpdate() {
37
+ const current = localVersion();
38
+ console.log(`当前版本: ${current}`);
39
+ const latest = await fetchLatestVersion();
40
+ if (!latest) {
41
+ console.error("无法检查新版本(网络不可用)。");
42
+ return;
43
+ }
44
+ if (compareVersions(current, latest) >= 0) {
45
+ console.log(`已是最新版本 (${latest})`);
46
+ return;
47
+ }
48
+ console.log(`发现新版本: ${latest}`);
49
+ console.log("正在升级 min-agent...");
50
+ try {
51
+ execSync("npm install -g min-agent@latest", { stdio: "inherit" });
52
+ console.log(`✓ 已升级到 ${latest},请重启终端后生效`);
53
+ }
54
+ catch {
55
+ console.error("自动升级失败。请手动执行: npm install -g min-agent@latest");
56
+ }
57
+ }