min-agent 0.5.1 → 0.6.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 (169) hide show
  1. package/README.md +18 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +6 -6
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +4 -2
  6. package/skills/self-config/reference.md +4 -1
  7. package/dist/agent.js +0 -1388
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/ctx.js +0 -7
  11. package/dist/cli/commands/exec.js +0 -35
  12. package/dist/cli/commands/history.js +0 -58
  13. package/dist/cli/commands/index.js +0 -249
  14. package/dist/cli/commands/init.js +0 -18
  15. package/dist/cli/commands/mcp.js +0 -173
  16. package/dist/cli/commands/memory.js +0 -87
  17. package/dist/cli/commands/models.js +0 -21
  18. package/dist/cli/commands/permission.js +0 -12
  19. package/dist/cli/commands/rules.js +0 -33
  20. package/dist/cli/commands/sandbox.js +0 -13
  21. package/dist/cli/commands/serve.js +0 -9
  22. package/dist/cli/commands/setup.js +0 -56
  23. package/dist/cli/commands/shared.js +0 -25
  24. package/dist/cli/commands/skills.js +0 -119
  25. package/dist/cli/commands/think.js +0 -12
  26. package/dist/cli/commands/update.js +0 -7
  27. package/dist/cli/commands/write-config.js +0 -52
  28. package/dist/cli/errors.js +0 -36
  29. package/dist/cli/exec-prompt.js +0 -26
  30. package/dist/cli/option-helpers.js +0 -65
  31. package/dist/cli/program.js +0 -223
  32. package/dist/cli/setup/detect.js +0 -17
  33. package/dist/cli/setup/flags.js +0 -12
  34. package/dist/cli/setup/flow.js +0 -108
  35. package/dist/cli/setup/provider-form.js +0 -102
  36. package/dist/cli/setup/ui.js +0 -534
  37. package/dist/clipboard.js +0 -142
  38. package/dist/code-mode.js +0 -184
  39. package/dist/compaction.js +0 -644
  40. package/dist/config.js +0 -363
  41. package/dist/confirm.js +0 -172
  42. package/dist/context-window.js +0 -256
  43. package/dist/ctx-cli.js +0 -30
  44. package/dist/ctx.js +0 -80
  45. package/dist/doom-loop.js +0 -278
  46. package/dist/fetch-timeout.js +0 -152
  47. package/dist/http-approvals.js +0 -60
  48. package/dist/http.js +0 -119
  49. package/dist/instructions.js +0 -196
  50. package/dist/logger.js +0 -95
  51. package/dist/markdown.js +0 -259
  52. package/dist/mcp.js +0 -1036
  53. package/dist/memory-cli.js +0 -33
  54. package/dist/memory.js +0 -295
  55. package/dist/model-catalog.js +0 -285
  56. package/dist/ollama-model.js +0 -234
  57. package/dist/ollama-openai-bridge.js +0 -383
  58. package/dist/output.js +0 -76
  59. package/dist/paste-handler.js +0 -41
  60. package/dist/permission-cli.js +0 -40
  61. package/dist/plugins.js +0 -131
  62. package/dist/pricing.js +0 -119
  63. package/dist/provider.js +0 -63
  64. package/dist/question-format.js +0 -60
  65. package/dist/reasoning-stream.js +0 -158
  66. package/dist/sandbox-cli.js +0 -79
  67. package/dist/sandbox.js +0 -403
  68. package/dist/save-throttle.js +0 -45
  69. package/dist/scope.js +0 -23
  70. package/dist/serve/common.js +0 -425
  71. package/dist/serve/routes-chat.js +0 -367
  72. package/dist/serve/routes-mcp.js +0 -212
  73. package/dist/serve/routes-memory.js +0 -95
  74. package/dist/serve/routes-meta.js +0 -268
  75. package/dist/serve/routes-sessions.js +0 -61
  76. package/dist/serve/routes-skills.js +0 -70
  77. package/dist/serve.js +0 -138
  78. package/dist/sessions.js +0 -265
  79. package/dist/skills.js +0 -599
  80. package/dist/structured-output.js +0 -29
  81. package/dist/synthetic.js +0 -7
  82. package/dist/think-cli.js +0 -36
  83. package/dist/thinking-wire.js +0 -239
  84. package/dist/thinking.js +0 -166
  85. package/dist/title-gen.js +0 -55
  86. package/dist/token-display.js +0 -39
  87. package/dist/tool-display.js +0 -178
  88. package/dist/tool-output.js +0 -126
  89. package/dist/tools/apply_patch.js +0 -265
  90. package/dist/tools/atomic-file.js +0 -35
  91. package/dist/tools/backend.js +0 -61
  92. package/dist/tools/bash.js +0 -231
  93. package/dist/tools/code_search.js +0 -98
  94. package/dist/tools/edit.js +0 -68
  95. package/dist/tools/explore.js +0 -232
  96. package/dist/tools/glob.js +0 -40
  97. package/dist/tools/grep.js +0 -174
  98. package/dist/tools/index.js +0 -34
  99. package/dist/tools/question.js +0 -54
  100. package/dist/tools/read.js +0 -109
  101. package/dist/tools/search-searxng.js +0 -223
  102. package/dist/tools/search-serper.js +0 -189
  103. package/dist/tools/task.js +0 -165
  104. package/dist/tools/todo.js +0 -213
  105. package/dist/tools/web_fetch.js +0 -195
  106. package/dist/tools/web_search.js +0 -228
  107. package/dist/tools/write.js +0 -55
  108. package/dist/tui/App.js +0 -138
  109. package/dist/tui/ConfirmBar.js +0 -66
  110. package/dist/tui/CtxPicker.js +0 -68
  111. package/dist/tui/InputBar.js +0 -618
  112. package/dist/tui/MessageList.js +0 -718
  113. package/dist/tui/ModelPicker.js +0 -113
  114. package/dist/tui/QuestionBar.js +0 -136
  115. package/dist/tui/SessionPicker.js +0 -79
  116. package/dist/tui/Spinner.js +0 -6
  117. package/dist/tui/StatusBar.js +0 -22
  118. package/dist/tui/ThinkPicker.js +0 -75
  119. package/dist/tui/agent-runner.js +0 -223
  120. package/dist/tui/bracketed-paste.js +0 -37
  121. package/dist/tui/caret-pos.js +0 -179
  122. package/dist/tui/caret.js +0 -69
  123. package/dist/tui/click-count.js +0 -13
  124. package/dist/tui/diff-view.js +0 -61
  125. package/dist/tui/drag-state.js +0 -49
  126. package/dist/tui/hydrate.js +0 -129
  127. package/dist/tui/index.js +0 -264
  128. package/dist/tui/input-history.js +0 -125
  129. package/dist/tui/layout.js +0 -105
  130. package/dist/tui/mouse.js +0 -46
  131. package/dist/tui/overlay-input.js +0 -12
  132. package/dist/tui/paste-draft.js +0 -173
  133. package/dist/tui/prompt-queue.js +0 -24
  134. package/dist/tui/selection.js +0 -232
  135. package/dist/tui/session-switch.js +0 -28
  136. package/dist/tui/slash-commands.js +0 -129
  137. package/dist/tui/slash-handler.js +0 -615
  138. package/dist/tui/text-width.js +0 -113
  139. package/dist/tui/theme.js +0 -12
  140. package/dist/tui/token-info.js +0 -7
  141. package/dist/tui/tool-children.js +0 -19
  142. package/dist/tui/types.js +0 -1
  143. package/dist/tui/undo-stack.js +0 -14
  144. package/dist/tui/use-sgr-mouse.js +0 -29
  145. package/dist/tui-chat.js +0 -478
  146. package/dist/updater.js +0 -116
  147. package/dist/xml-search.js +0 -194
  148. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  149. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  150. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  151. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  152. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  153. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  154. package/docs/superpowers/plans/2026-08-23-cli-setup.md +0 -501
  155. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  156. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  157. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  158. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  159. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  160. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  161. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  162. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  163. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  164. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  165. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  166. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  167. package/docs/superpowers/specs/2026-08-23-cli-setup-design.md +0 -282
  168. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  169. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
@@ -1,615 +0,0 @@
1
- export function sysMsg(tui, content) {
2
- tui.addMessage({
3
- id: `sys-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
4
- role: "system",
5
- content,
6
- timestamp: Date.now(),
7
- });
8
- }
9
- export async function handleSlashCommand(input, tui, messages, currentModel, tracker, setModel, requestExit, state) {
10
- const { parseSlashArgs } = await import("./slash-commands.js");
11
- const parsed = parseSlashArgs(input);
12
- const rawParts = input.slice(1).trim().split(/\s+/);
13
- const cmd = (parsed?.command || rawParts[0] || "").toLowerCase();
14
- const rest = parsed ? [...parsed.tokens, ...(parsed.partial ? [parsed.partial] : [])] : rawParts.slice(1);
15
- const arg = rest.join(" ");
16
- const { truncateDisplay } = await import("../tool-display.js");
17
- switch (cmd) {
18
- case "exit":
19
- case "quit":
20
- case "q":
21
- requestExit();
22
- break;
23
- case "new":
24
- state.startNewSession();
25
- break;
26
- case "undo": {
27
- const { popTurn } = await import("./undo-stack.js");
28
- const popped = popTurn(state.undoStack);
29
- if (!popped) {
30
- sysMsg(tui, "没有可撤销的内容");
31
- break;
32
- }
33
- state.setUndoStack(popped.stack);
34
- messages.splice(popped.turn.start, popped.turn.end - popped.turn.start);
35
- tui.truncateMessages(popped.turn.uiEnd);
36
- sysMsg(tui, "✓ 已撤销 1 轮");
37
- break;
38
- }
39
- case "redo": {
40
- if (!state.lastUserMessage) {
41
- sysMsg(tui, "没有可重发的消息");
42
- break;
43
- }
44
- const text = state.lastUserMessage;
45
- tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: text, timestamp: Date.now() });
46
- messages.push({ role: "user", content: text });
47
- return "run";
48
- }
49
- case "diff": {
50
- const { getWorkingTreeDiff } = await import("./diff-view.js");
51
- const result = await getWorkingTreeDiff();
52
- if (!result.ok) {
53
- const msg = result.reason === "not_git"
54
- ? "非 git 仓库,无法显示 diff"
55
- : result.reason === "no_changes"
56
- ? "工作区无未提交变更"
57
- : `diff 失败: ${result.message}`;
58
- sysMsg(tui, msg);
59
- }
60
- else
61
- sysMsg(tui, result.diff);
62
- break;
63
- }
64
- case "plan": {
65
- state.togglePlanMode();
66
- sysMsg(tui, state.planMode ? "✓ 计划模式已开启:仅只读工具可用,输出计划后再次输入 /plan 确认并执行" : "✓ 已退出计划模式");
67
- break;
68
- }
69
- case "reload":
70
- await state.reloadSystemPrompt();
71
- break;
72
- case "resume":
73
- case "sessions": {
74
- const id = arg.trim();
75
- if (!id) {
76
- state.openSessionPicker();
77
- break;
78
- }
79
- state.applySession(id);
80
- break;
81
- }
82
- case "rename": {
83
- const title = arg.trim();
84
- if (!title) {
85
- sysMsg(tui, "用法: /rename <标题>");
86
- break;
87
- }
88
- const { saveSession, renameSession } = await import("../sessions.js");
89
- let id = state.sessionId;
90
- if (!id) {
91
- id = saveSession(messages, undefined, undefined, undefined, state.taskState);
92
- state.setSessionId(id);
93
- }
94
- if (renameSession(id, title))
95
- sysMsg(tui, `✓ 会话已重命名为: ${title}`);
96
- else
97
- sysMsg(tui, "重命名失败: 会话不存在");
98
- break;
99
- }
100
- case "attach":
101
- case "image": {
102
- const filePath = arg;
103
- if (!filePath) {
104
- sysMsg(tui, "用法: /attach <图片路径>");
105
- break;
106
- }
107
- const { loadImageParts } = await import("../agent.js");
108
- const notify = (kind, imgPath) => {
109
- if (kind === "not_found")
110
- sysMsg(tui, `⚠ 图片不存在: ${imgPath}`);
111
- else if (kind === "too_large")
112
- sysMsg(tui, `⚠ 图片超过 5MB 限制: ${imgPath}`);
113
- };
114
- const parts = [{ type: "text", text: "" }, ...loadImageParts([filePath], notify)];
115
- if (parts.length <= 1)
116
- break;
117
- messages.push({ role: "user", content: parts });
118
- tui.addMessage({
119
- id: `user-${Date.now()}`,
120
- role: "user",
121
- content: `[📎 图片] ${filePath}`,
122
- timestamp: Date.now(),
123
- });
124
- return "run";
125
- }
126
- case "compact": {
127
- const { compactMessages } = await import("../compaction.js");
128
- const { estimateTokens } = await import("../compaction.js");
129
- const { resolveModel } = await import("../provider.js");
130
- const { clearStack } = await import("./undo-stack.js");
131
- if (messages.length < 4)
132
- sysMsg(tui, "消息太少,无需压缩");
133
- else {
134
- sysMsg(tui, "⟳ 正在压缩上下文...");
135
- const model = resolveModel(currentModel);
136
- state.setUndoStack(clearStack(state.undoStack));
137
- const result = await compactMessages(messages, model, {
138
- keepRecentTurns: 2,
139
- taskGoal: state.taskState.goal || undefined,
140
- });
141
- messages.length = 0;
142
- messages.push(...result.messages);
143
- if (tracker)
144
- tracker.resetContext();
145
- sysMsg(tui, `✓ 已压缩 (约 ${estimateTokens(messages)} tokens)`);
146
- const { snapshotTokenInfo } = await import("./token-info.js");
147
- tui.setTokenInfo(await snapshotTokenInfo(tracker, currentModel, messages));
148
- }
149
- break;
150
- }
151
- case "provider": {
152
- const { loadConfig, saveConfig } = await import("../config.js");
153
- const cfg = loadConfig();
154
- const providers = cfg.providers ?? [];
155
- if (arg) {
156
- const target = providers.find((p) => p.name === arg);
157
- if (!target) {
158
- sysMsg(tui, `Provider "${arg}" 不存在`);
159
- break;
160
- }
161
- cfg.activeProvider = arg;
162
- saveConfig(cfg);
163
- setModel(target.defaultModel ?? "");
164
- sysMsg(tui, `✓ 当前 Provider 已切换为: ${arg}`);
165
- }
166
- else {
167
- if (providers.length === 0) {
168
- sysMsg(tui, "无已配置 Provider,运行: min-agent setup");
169
- break;
170
- }
171
- const lines = providers.map((p) => {
172
- const cur = p.name === cfg.activeProvider ? " ← 当前" : "";
173
- return ` ${p.name} ${p.baseURL}${cur}`;
174
- });
175
- sysMsg(tui, `Providers:\n${lines.join("\n")}\n\n切换: /provider <name>`);
176
- }
177
- break;
178
- }
179
- case "model": {
180
- if (arg) {
181
- const config = await import("../config.js");
182
- const cfg = config.loadConfig();
183
- const provider = config.getActiveProvider(cfg);
184
- if (provider) {
185
- provider.defaultModel = arg;
186
- config.saveConfig(cfg);
187
- }
188
- setModel(arg);
189
- sysMsg(tui, `✓ 默认模型已设为: ${arg}`);
190
- }
191
- else
192
- tui.showModelPicker();
193
- break;
194
- }
195
- case "models": {
196
- const { fetchModels } = await import("../config.js");
197
- const { loadConfig, getActiveProvider } = await import("../config.js");
198
- const cfg = loadConfig();
199
- const provider = getActiveProvider(cfg);
200
- if (!provider?.baseURL || !provider?.apiKey) {
201
- sysMsg(tui, "未配置 Provider,运行: min-agent setup");
202
- break;
203
- }
204
- sysMsg(tui, "正在获取模型列表...");
205
- const models = await fetchModels(provider.baseURL, provider.apiKey);
206
- if (models.length === 0)
207
- sysMsg(tui, "无法获取模型列表");
208
- else {
209
- const lines = models.map((m) => {
210
- const marker = m === provider.defaultModel ? " ← 默认" : "";
211
- return ` ${m}${marker}`;
212
- });
213
- sysMsg(tui, `可用模型 (${models.length}):\n${lines.join("\n")}`);
214
- }
215
- break;
216
- }
217
- case "memory": {
218
- const { takeScopeFlags } = await import("../scope.js");
219
- const { addMemory, loadMemories, defaultMemoryScope, formatMemoryLine, parseMemoryMode, resolveMemoryMode, setMemoryMode, setMemoryOverride, isMemoryEnabled, } = await import("../memory.js");
220
- const { scope, rest: memRest } = takeScopeFlags(rest);
221
- const parsedMode = memRest.length === 1 ? parseMemoryMode(memRest[0]) : undefined;
222
- if (parsedMode) {
223
- const resolved = scope === "project" ? "project" : "global";
224
- setMemoryMode(parsedMode, resolved);
225
- setMemoryOverride(parsedMode);
226
- sysMsg(tui, `✓ 记忆已${parsedMode === "on" ? "开启" : "关闭"}(${resolved === "project" ? "项目" : "全局"})`);
227
- break;
228
- }
229
- const text = memRest.join(" ").trim();
230
- if (text) {
231
- const resolved = scope ?? defaultMemoryScope();
232
- addMemory(text, [], resolved);
233
- const hint = isMemoryEnabled() ? "" : "(当前未开启,下一轮对话不会用到;可用 /memory on 开启)";
234
- sysMsg(tui, `✓ 已保存${resolved === "project" ? "项目" : "全局"}记忆: "${text}"${hint}`);
235
- }
236
- else {
237
- const snap = resolveMemoryMode();
238
- const sourceLabel = snap.source === "cli"
239
- ? "本次启动参数"
240
- : snap.source === "project"
241
- ? "项目"
242
- : snap.source === "global"
243
- ? "全局"
244
- : "默认";
245
- const status = `记忆:${snap.memory === "on" ? "开" : "关"}(${sourceLabel})`;
246
- const scopes = scope ? [scope] : ["project", "global"];
247
- const sections = scopes.map((s) => {
248
- const memories = loadMemories(s);
249
- const label = s === "project" ? "项目记忆" : "全局记忆";
250
- if (memories.length === 0)
251
- return `${label}: 无`;
252
- const lines = memories.map((m, i) => formatMemoryLine(m, i));
253
- return `${label} (${memories.length}):\n${lines.join("\n")}`;
254
- });
255
- const empty = !scope && loadMemories("project").length === 0 && loadMemories("global").length === 0;
256
- sysMsg(tui, [
257
- status,
258
- empty ? "无已保存的记忆" : sections.join("\n"),
259
- "设置: /memory on|off [--project]",
260
- "保存: /memory <文本> [--project]",
261
- ].join("\n"));
262
- }
263
- break;
264
- }
265
- case "path":
266
- case "pwd":
267
- sysMsg(tui, process.cwd());
268
- break;
269
- case "paste": {
270
- const { getClipboardImage } = await import("../clipboard.js");
271
- const img = getClipboardImage();
272
- if (!img) {
273
- sysMsg(tui, "剪贴板中未找到图片");
274
- break;
275
- }
276
- sysMsg(tui, `📎 剪贴板图片 (${(img.data.length / 1024).toFixed(1)} KB)`);
277
- const text = arg || "What's in this image?";
278
- const content = [
279
- { type: "text", text },
280
- { type: "image", image: img.data, mimeType: img.mimeType },
281
- ];
282
- messages.push({ role: "user", content });
283
- tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: `${text} [📎 图片]`, timestamp: Date.now() });
284
- return "run";
285
- }
286
- case "tokens": {
287
- const { getContextWindowInfo } = await import("../context-window.js");
288
- const { getModelPrice, estimateCost, formatCost } = await import("../pricing.js");
289
- const { estimateTokens } = await import("../compaction.js");
290
- const ctx = await getContextWindowInfo(currentModel);
291
- const estimated = estimateTokens(messages);
292
- const occupied = tracker.lastInputTokens > 0 ? tracker.lastInputTokens : estimated;
293
- const pct = ctx.tokens > 0 && occupied > 0 ? Math.round((occupied / ctx.tokens) * 100) : 0;
294
- const price = await getModelPrice(currentModel ?? "");
295
- const cost = estimateCost({ inputTokens: tracker.totalInputTokens, outputTokens: tracker.totalOutputTokens }, price);
296
- const occupancyNote = tracker.lastInputTokens > 0 ? "" : ",按对话内容估算";
297
- const windowNote = ctx.source === "fallback" ? `,未读到接口窗口,按 ${ctx.tokens} 默认` : "";
298
- sysMsg(tui, [
299
- `上下文: ${occupied} / ${ctx.tokens} tokens (${pct}%${occupancyNote}${windowNote})`,
300
- `累计: ${tracker.totalInputTokens} in / ${tracker.totalOutputTokens} out`,
301
- ...(cost != null ? [`成本: 约 ${formatCost(cost)}`] : []),
302
- `消息: ${messages.length}`,
303
- ].join("\n"));
304
- break;
305
- }
306
- case "budget": {
307
- const { takeScopeFlags } = await import("../scope.js");
308
- const { getBudgetSnapshot, setBudgetMaxCost } = await import("../config.js");
309
- const { getModelPrice, estimateCost, formatCost } = await import("../pricing.js");
310
- const { scope, rest: budgetRest } = takeScopeFlags(rest);
311
- const amount = budgetRest.join(" ").trim();
312
- if (amount) {
313
- const n = parseFloat(amount);
314
- if (Number.isNaN(n) || n <= 0) {
315
- sysMsg(tui, "用法: /budget <USD 金额> [--project]");
316
- break;
317
- }
318
- const resolved = scope === "project" ? "project" : "global";
319
- setBudgetMaxCost(n, resolved);
320
- sysMsg(tui, `✓ 预算上限已设为: $${n}(${resolved === "project" ? "项目" : "全局"})`);
321
- break;
322
- }
323
- const snap = getBudgetSnapshot();
324
- const maxCost = snap.maxCostUSD;
325
- const sourceLabel = snap.source === "project" ? "项目" : snap.source === "global" ? "全局" : null;
326
- const price = await getModelPrice(currentModel ?? "");
327
- const cost = estimateCost({ inputTokens: tracker.totalInputTokens, outputTokens: tracker.totalOutputTokens }, price);
328
- const costStr = cost != null ? `约 ${formatCost(cost)}` : "--";
329
- const cap = maxCost != null ? `上限 $${maxCost}${sourceLabel ? `(${sourceLabel})` : ""}` : "未设置上限";
330
- sysMsg(tui, `预算: 已用${costStr} / ${cap}\n设置: /budget <金额> 或 /budget <金额> --project`);
331
- break;
332
- }
333
- case "permission": {
334
- const { takeScopeFlags } = await import("../scope.js");
335
- const { getPermissionSnapshot, parsePermissionMode, permissionModeLabel, setPermissionMode } = await import("../config.js");
336
- const { getPermissionOverride, setPermissionOverride } = await import("../confirm.js");
337
- const { scope, rest: permRest } = takeScopeFlags(rest);
338
- const sourceLabel = (source) => source === "cli" ? "本次启动参数" : source === "project" ? "项目" : source === "global" ? "全局" : "默认";
339
- if (permRest.length === 0) {
340
- const snap = getPermissionSnapshot();
341
- const override = getPermissionOverride();
342
- const mode = override ?? snap.permission;
343
- const source = override ? "cli" : snap.source;
344
- sysMsg(tui, [
345
- `当前确认: ${permissionModeLabel(mode)}(${sourceLabel(source)})`,
346
- "设置: /permission ask|accept-edits|allow-all [--project]",
347
- ].join("\n"));
348
- break;
349
- }
350
- const parsed = parsePermissionMode(permRest[0]);
351
- if (!parsed || permRest.length > 1) {
352
- sysMsg(tui, "用法: /permission ask|accept-edits|allow-all [--project]");
353
- break;
354
- }
355
- const resolved = scope === "project" ? "project" : "global";
356
- setPermissionMode(parsed, resolved);
357
- setPermissionOverride(parsed);
358
- sysMsg(tui, `✓ 确认已设为${permissionModeLabel(parsed)}(${resolved === "project" ? "项目" : "全局"})`);
359
- break;
360
- }
361
- case "think":
362
- case "thinking": {
363
- const { takeScopeFlags } = await import("../scope.js");
364
- const { parseThinkingEffort, thinkingEffortLabel, thinkingChoicesForModel, setThinkingEffort, setThinkingOverride, } = await import("../thinking.js");
365
- const { scope, rest: thinkRest } = takeScopeFlags(rest);
366
- if (thinkRest.length === 0) {
367
- tui.showThinkPicker(scope === "project" ? "project" : "global");
368
- break;
369
- }
370
- const parsed = parseThinkingEffort(thinkRest[0]);
371
- const choices = thinkingChoicesForModel(currentModel);
372
- if (!parsed || thinkRest.length > 1) {
373
- sysMsg(tui, `用法: /think [${choices.join("|")}] [--project]`);
374
- break;
375
- }
376
- if (!choices.includes(parsed)) {
377
- sysMsg(tui, `该模型支持: ${choices.join(", ")}`);
378
- break;
379
- }
380
- const resolved = scope === "project" ? "project" : "global";
381
- setThinkingEffort(parsed, resolved);
382
- setThinkingOverride(parsed);
383
- sysMsg(tui, `✓ 思考强度已设为 ${thinkingEffortLabel(parsed)}(${resolved === "project" ? "项目" : "全局"})`);
384
- break;
385
- }
386
- case "ctx": {
387
- const { getActiveProvider, getEffectiveConfig } = await import("../config.js");
388
- const { CTX_CHOICES, parseCtxChoice, setOllamaContextChoice, ctxChoiceLabel } = await import("../ctx.js");
389
- if (getActiveProvider(getEffectiveConfig())?.type !== "ollama") {
390
- sysMsg(tui, "窗口档位仅本地模型可用");
391
- break;
392
- }
393
- if (rest.length === 0) {
394
- tui.showCtxPicker();
395
- break;
396
- }
397
- const parsed = parseCtxChoice(rest[0]);
398
- if (!parsed || rest.length > 1) {
399
- sysMsg(tui, `用法: /ctx [${CTX_CHOICES.join("|")}]`);
400
- break;
401
- }
402
- const result = setOllamaContextChoice(parsed);
403
- if (!result.ok) {
404
- sysMsg(tui, "窗口档位仅本地模型可用");
405
- break;
406
- }
407
- state.refreshTokenBar();
408
- sysMsg(tui, `✓ 上下文窗口已设为${ctxChoiceLabel(parsed)}`);
409
- break;
410
- }
411
- case "mcp": {
412
- const { loadMcpConfig } = await import("../mcp.js");
413
- const { getMcpStatus } = await import("../mcp.js");
414
- const config = loadMcpConfig();
415
- const servers = Object.entries(config.mcpServers);
416
- if (servers.length === 0)
417
- sysMsg(tui, "无 MCP 服务器配置");
418
- else {
419
- const status = getMcpStatus();
420
- const lines = servers.map(([name, cfg]) => {
421
- const disabled = cfg.enabled === false;
422
- const st = status[name];
423
- const connected = st?.connected ?? false;
424
- const toolCount = st?.tools.length ?? 0;
425
- const extra = [
426
- toolCount > 0 ? `${toolCount} tools` : "",
427
- (st?.resource_count ?? 0) > 0 ? `${st.resource_count} resources` : "",
428
- (st?.prompt_count ?? 0) > 0 ? `${st.prompt_count} prompts` : "",
429
- ]
430
- .filter(Boolean)
431
- .join(", ");
432
- const state = disabled ? "disabled" : connected ? "connected" : "disconnected";
433
- const err = !disabled && !connected && st?.error ? ` — ${st.error}` : "";
434
- return ` ${name}: ${state}${extra ? ` (${extra})` : ""}${err}`;
435
- });
436
- sysMsg(tui, `MCP 服务器 (${servers.length}):\n${lines.join("\n")}`);
437
- }
438
- break;
439
- }
440
- case "skills": {
441
- const { discoverSkills, getAllSkills, getSkill, getStaleDisabledSkills, setSkillEnabled } = await import("../skills.js");
442
- discoverSkills({ silent: true });
443
- const tokens = rest.filter(Boolean);
444
- const action = tokens[0];
445
- const all = getAllSkills().sort((a, b) => a.name.localeCompare(b.name));
446
- if (action === "enable" || action === "disable") {
447
- const scope = tokens.includes("--project") ? "project" : "global";
448
- const names = tokens.slice(1).filter((t) => t !== "--project" && t !== "--global");
449
- if (names.length === 0) {
450
- sysMsg(tui, `用法: /skills ${action} <name...> [--project]`);
451
- break;
452
- }
453
- const known = new Set(all.map((s) => s.name));
454
- const unknown = names.filter((n) => !known.has(n));
455
- if (unknown.length > 0) {
456
- sysMsg(tui, `未找到 skill: ${unknown.join(", ")}\n可用: ${known.size ? [...known].join(", ") : "无"}`);
457
- break;
458
- }
459
- const results = setSkillEnabled(names, action === "enable", scope);
460
- const notes = results
461
- .filter((r) => r.blockedBy)
462
- .map((r) => ` ⚠ ${r.name} 仍为${r.enabled ? "启用" : "禁用"}(被 ${r.blockedBy} 范围覆盖)`);
463
- sysMsg(tui, [`✓ 已${action === "enable" ? "启用" : "禁用"}(${scope}): ${names.join(", ")}`, ...notes].join("\n"));
464
- break;
465
- }
466
- if (action === "info") {
467
- const name = tokens[1];
468
- const skill = name ? getSkill(name) : undefined;
469
- if (!skill) {
470
- sysMsg(tui, `未找到 skill: ${name ?? "(未指定)"}\n可用: ${all.length ? all.map((s) => s.name).join(", ") : "无"}`);
471
- break;
472
- }
473
- const status = skill.enabled ? "enabled" : `disabled(${skill.disabledScope === "project" ? "proj" : "glob"})`;
474
- const meta = [
475
- `Name: ${skill.name}`,
476
- `Status: ${status}`,
477
- ...(skill.builtin ? ["Source: 内置"] : []),
478
- ...(skill.version ? [`Version: ${skill.version}`] : []),
479
- `Description: ${skill.description.replace(/\s+/g, " ")}`,
480
- `Location: ${skill.location}`,
481
- ...(skill.allowedTools.length ? [`Tools: ${skill.allowedTools.join(", ")}`] : []),
482
- ...(skill.requiredBins.length ? [`Requires: ${skill.requiredBins.join(", ")}`] : []),
483
- ];
484
- const content = skill.content.trim();
485
- sysMsg(tui, [...meta, ...(content ? ["", "--- Content ---", "", content.slice(0, 4000)] : [])].join("\n"));
486
- break;
487
- }
488
- if (action && action !== "list") {
489
- sysMsg(tui, "用法: /skills [list|info <name>|enable <name...> [--project]|disable <name...> [--project]]");
490
- break;
491
- }
492
- const stale = getStaleDisabledSkills();
493
- if (all.length === 0 && stale.length === 0) {
494
- sysMsg(tui, "无可用 skills。添加 SKILL.md 到 .min-agent/skills/<name>/");
495
- break;
496
- }
497
- const lines = all.map((s) => {
498
- const status = s.enabled ? "enabled" : `disabled(${s.disabledScope === "project" ? "proj" : "glob"})`;
499
- const origin = s.builtin ? " 内置" : "";
500
- return ` ${s.name} ${status}${origin} ${truncateDisplay(s.description.replace(/\s+/g, " "), 56)}`;
501
- });
502
- if (stale.length > 0)
503
- lines.push(` ⚠ disabledSkills/enabledSkills 中无对应 skill: ${stale.join(", ")}`);
504
- sysMsg(tui, `Skills (${all.filter((s) => s.enabled).length} enabled / ${all.length}):\n${lines.join("\n")}\n切换: /skills disable <name> [--project]`);
505
- break;
506
- }
507
- case "sandbox": {
508
- const { takeScopeFlags } = await import("../scope.js");
509
- const { getSandboxSnapshot, setSandboxConfig } = await import("../config.js");
510
- const { parseSandboxMode, parseNetworkPolicy, getEffectiveSandboxPolicy, sandboxEnforcementCaveat, sandboxModeLabel, setSandboxOverride, } = await import("../sandbox.js");
511
- const { scope, rest: sbRest } = takeScopeFlags(rest);
512
- const sourceLabel = (source) => source === "cli"
513
- ? "本次启动参数"
514
- : source === "env"
515
- ? "环境变量"
516
- : source === "project"
517
- ? "项目"
518
- : source === "global"
519
- ? "全局"
520
- : "默认";
521
- if (sbRest.length === 0) {
522
- const snap = getSandboxSnapshot();
523
- const extras = [
524
- ...(snap.extraWriteRoots.length > 0 ? [`额外可写目录: ${snap.extraWriteRoots.join(", ")}`] : []),
525
- ...(snap.extraReadRoots.length > 0 ? [`额外可读目录: ${snap.extraReadRoots.join(", ")}`] : []),
526
- ];
527
- const caveat = sandboxEnforcementCaveat(getEffectiveSandboxPolicy().mode);
528
- sysMsg(tui, [
529
- `当前隔离: ${snap.label}(${sourceLabel(snap.source)})`,
530
- ...extras,
531
- ...(caveat ? [caveat] : []),
532
- "设置: /sandbox off|workspace|strict [--project]",
533
- " /sandbox network allow|deny [--project]",
534
- ].join("\n"));
535
- break;
536
- }
537
- const resolved = scope === "project" ? "project" : "global";
538
- const scopeLabel = resolved === "project" ? "项目" : "全局";
539
- if (sbRest[0] === "network") {
540
- const parsed = parseNetworkPolicy(sbRest[1]);
541
- if (!parsed || sbRest.length > 2) {
542
- sysMsg(tui, "用法: /sandbox network allow|deny [--project]");
543
- break;
544
- }
545
- setSandboxConfig({ network: parsed }, resolved);
546
- setSandboxOverride({ network: parsed });
547
- sysMsg(tui, `✓ 联网已设为${parsed === "deny" ? "禁止" : "允许"}(${scopeLabel})`);
548
- break;
549
- }
550
- const parsed = parseSandboxMode(sbRest[0]);
551
- if (!parsed || sbRest.length > 1) {
552
- sysMsg(tui, "用法: /sandbox off|workspace|strict [--project]\n /sandbox network allow|deny [--project]");
553
- break;
554
- }
555
- setSandboxConfig({ mode: parsed }, resolved);
556
- setSandboxOverride({ mode: parsed });
557
- const caveat = sandboxEnforcementCaveat(parsed);
558
- sysMsg(tui, [`✓ 隔离已设为${sandboxModeLabel(parsed)}(${scopeLabel})`, ...(caveat ? [caveat] : [])].join("\n"));
559
- break;
560
- }
561
- case "help":
562
- sysMsg(tui, [
563
- "命令:",
564
- " 输入 / 弹出命令菜单 (输入过滤, Tab/Enter 执行, Esc 关闭)",
565
- " /new 新建会话(保留当前)",
566
- " /compact 压缩上下文",
567
- " /model [n] 交互式选择/切换模型",
568
- " /models 列出可用模型",
569
- " /provider [n] 查看/切换 Provider",
570
- " /memory 查看记忆开关与已保存内容(/memory on|off 开关;也可 /memory 文本 保存)",
571
- " /tokens 显示上下文占用、累计用量与成本",
572
- " /ctx 选择本地模型上下文窗口(也可 /ctx 2k|4k|8k|12k|16k|32k|64k|128k|256k|auto)",
573
- " /budget [n] 查看/设置预算上限(--project 写入当前项目)",
574
- " /permission 查看/设置确认模式(ask|accept-edits|allow-all,--project 写入当前项目)",
575
- " /think 交互式选择思考强度(档位随当前模型;也可 /think off|low|medium|high|max)",
576
- " /sandbox 查看/设置隔离(off|workspace|strict,--project 写入当前项目)",
577
- " /undo 撤销上一轮",
578
- " /redo 重发最后一条消息",
579
- " /diff 显示工作区未提交变更",
580
- " /plan 切换只读计划模式",
581
- " /sessions 列出/切换已保存会话",
582
- " /resume [id] 同 /sessions",
583
- " /reload 重新加载规则",
584
- " /rename <t> 重命名当前会话",
585
- " /attach <p> 附加本地图片",
586
- " /mcp 显示 MCP 状态",
587
- " /skills 显示/启用/禁用 skills(/skills info <name> 查看详情)",
588
- " /path 显示工作目录",
589
- " /paste [t] 粘贴剪贴板图片 + 可选提示",
590
- " /help 显示帮助",
591
- " /exit 退出",
592
- "",
593
- "快捷键:",
594
- " Esc 取消当前任务",
595
- " Ctrl+J 输入框换行",
596
- " Ctrl+V 粘贴剪贴板图片到输入框",
597
- " 多行粘贴 收成片段,发送时带上全文",
598
- " Ctrl+C 清空输入(↑↓ 可找回);生成中则中断,空输入连按两次退出",
599
- " Ctrl+B 消息区选择模式",
600
- " Home/End 行首/行尾",
601
- " PageUp/Down 滚动消息",
602
- " 鼠标滚轮 滚动消息",
603
- " 鼠标拖拽 选择文本并自动复制",
604
- " 鼠标双击 选词并复制",
605
- " 鼠标三击 选中该条消息并复制",
606
- " 鼠标点击 工具调用行展开/收起",
607
- " ↑↓ 翻阅输入历史",
608
- " ← → 移动输入框光标",
609
- " 鼠标点击 输入框内定位光标",
610
- ].join("\n"));
611
- break;
612
- default:
613
- sysMsg(tui, `未知命令: /${cmd},输入 /help 查看可用命令`);
614
- }
615
- }