hypercore-cli 1.1.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/dist/api-XGC7D5AW.js +162 -0
  4. package/dist/auth-DNQWYQKT.js +21 -0
  5. package/dist/background-2EGCAAQH.js +14 -0
  6. package/dist/backlog-Q2NZCLNY.js +24 -0
  7. package/dist/chunk-2CMSCWQW.js +162 -0
  8. package/dist/chunk-2LJ2DVEB.js +167 -0
  9. package/dist/chunk-3RPFCQKJ.js +288 -0
  10. package/dist/chunk-43OLRXM5.js +263 -0
  11. package/dist/chunk-4DVYJAJL.js +57 -0
  12. package/dist/chunk-6OL3GA3P.js +173 -0
  13. package/dist/chunk-AUHU7ALH.js +2023 -0
  14. package/dist/chunk-B6A2AKLN.js +139 -0
  15. package/dist/chunk-BE46C7JW.js +46 -0
  16. package/dist/chunk-CUVAUOXL.js +58 -0
  17. package/dist/chunk-GH7E2OJE.js +223 -0
  18. package/dist/chunk-GOOTEPBK.js +271 -0
  19. package/dist/chunk-GPPMJYSM.js +133 -0
  20. package/dist/chunk-GU2FZQ6A.js +69 -0
  21. package/dist/chunk-IOPKN5GD.js +190 -0
  22. package/dist/chunk-IXOIOGR5.js +1505 -0
  23. package/dist/chunk-KRPOPWGA.js +251 -0
  24. package/dist/chunk-MGLJ53QN.js +219 -0
  25. package/dist/chunk-MV4TTRYX.js +533 -0
  26. package/dist/chunk-OPZYEVYR.js +150 -0
  27. package/dist/chunk-QTSLP47C.js +166 -0
  28. package/dist/chunk-R3GPQC7I.js +393 -0
  29. package/dist/chunk-RKB2JOV2.js +43 -0
  30. package/dist/chunk-RNG3K465.js +80 -0
  31. package/dist/chunk-TGTYKBGC.js +86 -0
  32. package/dist/chunk-U5SGAIMM.js +681 -0
  33. package/dist/chunk-V5UHPPSY.js +140 -0
  34. package/dist/chunk-WHLVZCQY.js +245 -0
  35. package/dist/chunk-XDRCBMZZ.js +66 -0
  36. package/dist/chunk-XOS6HPEF.js +134 -0
  37. package/dist/chunk-ZSBHUGWR.js +262 -0
  38. package/dist/claude-NSQ442XD.js +12 -0
  39. package/dist/commands-CK3WFAGI.js +128 -0
  40. package/dist/commands-U63OEO5J.js +1044 -0
  41. package/dist/commands-ZE6GD3WC.js +232 -0
  42. package/dist/config-4EW42BSF.js +8 -0
  43. package/dist/config-loader-SXO674TF.js +24 -0
  44. package/dist/diagnose-AFW3ZTZ4.js +12 -0
  45. package/dist/display-IIUBEYWN.js +58 -0
  46. package/dist/extractor-QV53W2YJ.js +129 -0
  47. package/dist/history-WMSCHERZ.js +180 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.js +406 -0
  50. package/dist/instance-registry-YSIJXSO7.js +15 -0
  51. package/dist/keybindings-JAAMLH3G.js +15 -0
  52. package/dist/loader-WHNTZTLP.js +58 -0
  53. package/dist/network-MM6YWPGO.js +279 -0
  54. package/dist/notify-HPTALZDC.js +14 -0
  55. package/dist/openai-compat-UQWJXBEK.js +12 -0
  56. package/dist/permissions-JUKXMNDH.js +10 -0
  57. package/dist/prompt-QV45TXRL.js +166 -0
  58. package/dist/quality-ST7PPNFR.js +16 -0
  59. package/dist/repl-RT3AHL7M.js +3375 -0
  60. package/dist/roadmap-5OBEKROY.js +17 -0
  61. package/dist/server-PORT7OEG.js +57 -0
  62. package/dist/session-4VUNDWLH.js +21 -0
  63. package/dist/skills-V4A35XKG.js +175 -0
  64. package/dist/store-Y4LU5QTO.js +25 -0
  65. package/dist/team-HO7Z4SIM.js +385 -0
  66. package/dist/telemetry-6R4EIE6O.js +30 -0
  67. package/dist/test-runner-ZQH5Y6OJ.js +619 -0
  68. package/dist/theme-3SYJ3UQA.js +14 -0
  69. package/dist/upgrade-7TGI3SXO.js +83 -0
  70. package/dist/verify-JUDKTPKZ.js +14 -0
  71. package/dist/web/static/app.js +562 -0
  72. package/dist/web/static/index.html +132 -0
  73. package/dist/web/static/mirror.css +1001 -0
  74. package/dist/web/static/mirror.html +184 -0
  75. package/dist/web/static/mirror.js +1125 -0
  76. package/dist/web/static/onboard.css +302 -0
  77. package/dist/web/static/onboard.html +140 -0
  78. package/dist/web/static/onboard.js +260 -0
  79. package/dist/web/static/style.css +602 -0
  80. package/dist/web/static/workspace.css +1568 -0
  81. package/dist/web/static/workspace.html +408 -0
  82. package/dist/web/static/workspace.js +1683 -0
  83. package/dist/web-Z5HSCQHW.js +39 -0
  84. package/package.json +67 -0
@@ -0,0 +1,3375 @@
1
+ import {
2
+ loadKeyBindings,
3
+ matchKeyBinding
4
+ } from "./chunk-CUVAUOXL.js";
5
+ import {
6
+ notifyIfLong,
7
+ setNotificationsEnabled
8
+ } from "./chunk-4DVYJAJL.js";
9
+ import {
10
+ createSubAgentTool,
11
+ runSubAgent
12
+ } from "./chunk-QTSLP47C.js";
13
+ import {
14
+ createMultiplexer,
15
+ createWebServerAutoPort,
16
+ emitToGUI,
17
+ getActivePort,
18
+ gitBranch,
19
+ gitCommit,
20
+ gitCreateBranch,
21
+ gitDefaultBranch,
22
+ gitDeleteBranch,
23
+ gitDiffFile,
24
+ gitDiffFiles,
25
+ gitDiffRange,
26
+ gitDiffStaged,
27
+ gitDiffStagedStat,
28
+ gitDiffUnstaged,
29
+ gitListBranches,
30
+ gitLog,
31
+ gitPushUpstream,
32
+ gitStageFiles,
33
+ gitStash,
34
+ gitStashDrop,
35
+ gitStashList,
36
+ gitStashPop,
37
+ gitStatus,
38
+ gitStatusSummary,
39
+ gitSwitchBranch,
40
+ gitWorktreeAdd,
41
+ gitWorktreeList,
42
+ gitWorktreeRemove,
43
+ hasGUIClients,
44
+ isGitRepo,
45
+ registerREPLContext
46
+ } from "./chunk-AUHU7ALH.js";
47
+ import "./chunk-2LJ2DVEB.js";
48
+ import "./chunk-6OL3GA3P.js";
49
+ import {
50
+ deleteSession,
51
+ exportSession,
52
+ forkSession,
53
+ generateSessionId,
54
+ listSessions,
55
+ loadSession,
56
+ renameSession,
57
+ saveSession
58
+ } from "./chunk-XOS6HPEF.js";
59
+ import "./chunk-XDRCBMZZ.js";
60
+ import {
61
+ getCanonicalDashboardPort,
62
+ registerInstance,
63
+ unregisterInstance,
64
+ updateInstanceRuntime
65
+ } from "./chunk-GPPMJYSM.js";
66
+ import {
67
+ handleCheckpoint
68
+ } from "./chunk-RKB2JOV2.js";
69
+ import {
70
+ renderMarkdown
71
+ } from "./chunk-GH7E2OJE.js";
72
+ import {
73
+ Engine
74
+ } from "./chunk-MV4TTRYX.js";
75
+ import {
76
+ closeMCPConnections,
77
+ createToolRegistry
78
+ } from "./chunk-IXOIOGR5.js";
79
+ import "./chunk-KRPOPWGA.js";
80
+ import "./chunk-GU2FZQ6A.js";
81
+ import {
82
+ showBanner,
83
+ showCapabilityQuickGuide,
84
+ showError,
85
+ showLineList,
86
+ showREPLHelp,
87
+ showResponseStats,
88
+ showRunComplete,
89
+ showSessionCost,
90
+ showSlashCommandsGrouped,
91
+ showStationComplete,
92
+ showStationRetry,
93
+ showStationSkipped,
94
+ showStationStart,
95
+ showThinking,
96
+ showToolCall,
97
+ showToolCallDone,
98
+ showWelcome
99
+ } from "./chunk-R3GPQC7I.js";
100
+ import "./chunk-BE46C7JW.js";
101
+ import {
102
+ setTheme
103
+ } from "./chunk-RNG3K465.js";
104
+ import {
105
+ createLLMClient,
106
+ streamCallLLM
107
+ } from "./chunk-43OLRXM5.js";
108
+ import {
109
+ createOpenAIClient,
110
+ streamOpenAIChat
111
+ } from "./chunk-GOOTEPBK.js";
112
+ import {
113
+ hookManager
114
+ } from "./chunk-B6A2AKLN.js";
115
+ import {
116
+ HYPERCORE_DIR,
117
+ MODEL_ALIASES,
118
+ MODEL_PROVIDERS,
119
+ loadConfig
120
+ } from "./chunk-V5UHPPSY.js";
121
+ import {
122
+ listLines
123
+ } from "./chunk-WHLVZCQY.js";
124
+ import "./chunk-TGTYKBGC.js";
125
+
126
+ // src/repl/repl.ts
127
+ import { select } from "@inquirer/prompts";
128
+ import chalk3 from "chalk";
129
+ import ora from "ora";
130
+ import { createInterface } from "readline";
131
+ import { existsSync as existsSync2, statSync, readdirSync, appendFileSync } from "fs";
132
+ import { readFile as readFile2 } from "fs/promises";
133
+ import { join as join2, basename, dirname, resolve, isAbsolute } from "path";
134
+ import os from "os";
135
+
136
+ // src/repl/commands.ts
137
+ var CommandRegistry = class {
138
+ commands = /* @__PURE__ */ new Map();
139
+ aliasMap = /* @__PURE__ */ new Map();
140
+ // alias → name
141
+ /** 注册命令 */
142
+ register(cmd) {
143
+ this.commands.set(cmd.name, cmd);
144
+ if (cmd.aliases) {
145
+ for (const alias of cmd.aliases) {
146
+ this.aliasMap.set(alias, cmd.name);
147
+ }
148
+ }
149
+ }
150
+ /** 批量注册 */
151
+ registerAll(cmds) {
152
+ for (const cmd of cmds) {
153
+ this.register(cmd);
154
+ }
155
+ }
156
+ /** 注销命令 */
157
+ unregister(name) {
158
+ const cmd = this.commands.get(name);
159
+ if (cmd?.aliases) {
160
+ for (const alias of cmd.aliases) {
161
+ this.aliasMap.delete(alias);
162
+ }
163
+ }
164
+ this.commands.delete(name);
165
+ }
166
+ /** 按名称或别名获取命令 */
167
+ get(nameOrAlias) {
168
+ const direct = this.commands.get(nameOrAlias);
169
+ if (direct) return direct;
170
+ const resolvedName = this.aliasMap.get(nameOrAlias);
171
+ if (resolvedName) return this.commands.get(resolvedName);
172
+ return void 0;
173
+ }
174
+ /** 是否存在该命令 */
175
+ has(nameOrAlias) {
176
+ return this.commands.has(nameOrAlias) || this.aliasMap.has(nameOrAlias);
177
+ }
178
+ /** 列出所有命令 */
179
+ list() {
180
+ return Array.from(this.commands.values());
181
+ }
182
+ /** 按类别筛选 */
183
+ listByCategory(cat) {
184
+ return this.list().filter((c) => c.category === cat);
185
+ }
186
+ /** 命令总数 */
187
+ get size() {
188
+ return this.commands.size;
189
+ }
190
+ };
191
+
192
+ // src/repl/compact.ts
193
+ async function compactHistory(history, client, config, systemPrompt) {
194
+ if (history.length < 4) {
195
+ throw new Error("\u5BF9\u8BDD\u592A\u77ED\uFF0C\u65E0\u9700\u538B\u7F29");
196
+ }
197
+ const originalChars = history.reduce((s, m) => s + m.content.length, 0);
198
+ const conversationText = history.map((m) => `[${m.role}]: ${m.content.slice(0, 500)}`).join("\n\n");
199
+ const compactPrompt = `\u8BF7\u5C06\u4EE5\u4E0B\u5BF9\u8BDD\u5386\u53F2\u538B\u7F29\u4E3A\u4E00\u6BB5\u7B80\u6D01\u7684\u6458\u8981\uFF08200-400 \u5B57\uFF09\uFF0C\u4FDD\u7559\u5173\u952E\u4FE1\u606F\uFF1A
200
+ - \u8BA8\u8BBA\u7684\u4E3B\u9898\u548C\u7ED3\u8BBA
201
+ - \u505A\u51FA\u7684\u91CD\u8981\u51B3\u7B56
202
+ - \u4FEE\u6539\u8FC7\u7684\u6587\u4EF6\u548C\u4EE3\u7801\u53D8\u66F4
203
+ - \u672A\u5B8C\u6210\u7684\u4EFB\u52A1
204
+
205
+ \u5BF9\u8BDD\u5386\u53F2\uFF1A
206
+ ${conversationText}
207
+
208
+ \u8BF7\u76F4\u63A5\u8F93\u51FA\u6458\u8981\uFF0C\u4E0D\u8981\u6DFB\u52A0\u989D\u5916\u683C\u5F0F\u6216\u524D\u7F00\u3002`;
209
+ let summaryText;
210
+ if (config.modelConfig.sdkType === "openai") {
211
+ const { streamOpenAIChat: streamOpenAIChat2 } = await import("./openai-compat-UQWJXBEK.js");
212
+ const OpenAI = (await import("openai")).default;
213
+ const openaiClient = client;
214
+ const result = await streamOpenAIChat2(openaiClient, [
215
+ { role: "system", content: "\u4F60\u662F\u4E00\u4E2A\u5BF9\u8BDD\u6458\u8981\u52A9\u624B\u3002\u7B80\u6D01\u3001\u7CBE\u51C6\u5730\u538B\u7F29\u5BF9\u8BDD\u3002" },
216
+ { role: "user", content: compactPrompt }
217
+ ], {
218
+ model: config.modelConfig.model,
219
+ tools: [],
220
+ onChunk: () => {
221
+ },
222
+ onToolCall: () => {
223
+ }
224
+ });
225
+ summaryText = result.content;
226
+ } else {
227
+ const { streamCallLLM: streamCallLLM2 } = await import("./claude-NSQ442XD.js");
228
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
229
+ const anthropicClient = client;
230
+ const result = await streamCallLLM2(anthropicClient, {
231
+ systemPrompt: "\u4F60\u662F\u4E00\u4E2A\u5BF9\u8BDD\u6458\u8981\u52A9\u624B\u3002\u7B80\u6D01\u3001\u7CBE\u51C6\u5730\u538B\u7F29\u5BF9\u8BDD\u3002",
232
+ userPrompt: compactPrompt,
233
+ history: [],
234
+ tools: [],
235
+ model: config.modelConfig.model,
236
+ onText: () => {
237
+ },
238
+ onToolCall: () => {
239
+ }
240
+ });
241
+ summaryText = result.output;
242
+ }
243
+ const summary = [
244
+ {
245
+ role: "assistant",
246
+ content: `[\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29] ${summaryText}`
247
+ }
248
+ ];
249
+ const newChars = summary.reduce((s, m) => s + m.content.length, 0);
250
+ const savedChars = originalChars - newChars;
251
+ return { summary, savedChars };
252
+ }
253
+
254
+ // src/repl/custom-commands.ts
255
+ import { existsSync } from "fs";
256
+ import { readFile, readdir } from "fs/promises";
257
+ import { join } from "path";
258
+ import chalk from "chalk";
259
+ function parseCmdMd(content) {
260
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)/);
261
+ if (!fmMatch) return null;
262
+ const [, frontmatter, prompt] = fmMatch;
263
+ const meta = {};
264
+ for (const line of frontmatter.split("\n")) {
265
+ const colonIdx = line.indexOf(":");
266
+ if (colonIdx === -1) continue;
267
+ const key = line.slice(0, colonIdx).trim().toLowerCase();
268
+ const value = line.slice(colonIdx + 1).trim();
269
+ switch (key) {
270
+ case "name":
271
+ meta.name = value;
272
+ break;
273
+ case "description":
274
+ meta.description = value;
275
+ break;
276
+ case "aliases":
277
+ meta.aliases = value.split(",").map((s) => s.trim()).filter(Boolean);
278
+ break;
279
+ }
280
+ }
281
+ if (!meta.name) return null;
282
+ return {
283
+ meta: {
284
+ name: meta.name,
285
+ description: meta.description || `\u81EA\u5B9A\u4E49\u547D\u4EE4: ${meta.name}`,
286
+ aliases: meta.aliases
287
+ },
288
+ prompt: prompt.trim()
289
+ };
290
+ }
291
+ async function loadFromDir(dir) {
292
+ if (!existsSync(dir)) return [];
293
+ const files = await readdir(dir);
294
+ const results = [];
295
+ for (const file of files) {
296
+ if (!file.endsWith(".cmd.md")) continue;
297
+ try {
298
+ const content = await readFile(join(dir, file), "utf-8");
299
+ const parsed = parseCmdMd(content);
300
+ if (parsed) results.push(parsed);
301
+ } catch {
302
+ }
303
+ }
304
+ return results;
305
+ }
306
+ async function loadCustomCommands() {
307
+ const commands = [];
308
+ const systemDir = join(HYPERCORE_DIR, "commands");
309
+ const systemCmds = await loadFromDir(systemDir);
310
+ const projectDir = join(process.cwd(), ".hypercore", "commands");
311
+ const projectCmds = await loadFromDir(projectDir);
312
+ const all = [...systemCmds, ...projectCmds];
313
+ const seen = /* @__PURE__ */ new Set();
314
+ for (const { meta, prompt } of all.reverse()) {
315
+ if (seen.has(meta.name)) continue;
316
+ seen.add(meta.name);
317
+ commands.push({
318
+ name: meta.name,
319
+ aliases: meta.aliases,
320
+ description: meta.description,
321
+ category: "util",
322
+ handler: createCustomHandler(prompt)
323
+ });
324
+ }
325
+ return commands;
326
+ }
327
+ function createCustomHandler(promptTemplate) {
328
+ return async (args, ctx) => {
329
+ const argsStr = args.join(" ");
330
+ let prompt = promptTemplate.replace(/\$ARGS/g, argsStr).replace(/\$CWD/g, process.cwd()).replace(/\$MODEL/g, ctx.config.modelConfig.model).replace(/\$SESSION/g, ctx.sessionId);
331
+ if (!argsStr && prompt.includes("$ARGS")) {
332
+ console.log(chalk.dim("\n \u8BE5\u547D\u4EE4\u9700\u8981\u53C2\u6570\n"));
333
+ return;
334
+ }
335
+ console.log(chalk.dim(`
336
+ [\u81EA\u5B9A\u4E49\u547D\u4EE4] \u2192 AI
337
+ `));
338
+ ctx.chatHistory.push({ role: "user", content: prompt });
339
+ try {
340
+ if (ctx.config.modelConfig.sdkType === "openai") {
341
+ const { streamOpenAIChat: streamOpenAIChat2 } = await import("./openai-compat-UQWJXBEK.js");
342
+ const OpenAI = (await import("openai")).default;
343
+ const openaiClient = ctx.getClient();
344
+ const messages = [
345
+ { role: "system", content: ctx.systemPrompt },
346
+ ...ctx.chatHistory.map((m) => ({
347
+ role: m.role,
348
+ content: m.content
349
+ }))
350
+ ];
351
+ const result = await streamOpenAIChat2(openaiClient, messages, {
352
+ model: ctx.config.modelConfig.model,
353
+ tools: ctx.tools,
354
+ onChunk: (text) => process.stdout.write(text),
355
+ onToolCall: (name) => console.log(chalk.dim(` \u{1F527} ${name}`))
356
+ });
357
+ console.log();
358
+ ctx.chatHistory.push({ role: "assistant", content: result.content });
359
+ ctx.sessionTokens.inputTokens += result.tokenUsage.inputTokens;
360
+ ctx.sessionTokens.outputTokens += result.tokenUsage.outputTokens;
361
+ } else {
362
+ const { streamCallLLM: streamCallLLM2 } = await import("./claude-NSQ442XD.js");
363
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
364
+ const anthropicClient = ctx.getClient();
365
+ const result = await streamCallLLM2(anthropicClient, {
366
+ systemPrompt: ctx.systemPrompt,
367
+ userPrompt: prompt,
368
+ history: ctx.chatHistory.slice(0, -1),
369
+ tools: ctx.tools,
370
+ model: ctx.config.modelConfig.model,
371
+ onText: (text) => process.stdout.write(text),
372
+ onToolCall: (name) => console.log(chalk.dim(` \u{1F527} ${name}`))
373
+ });
374
+ console.log();
375
+ ctx.chatHistory.push({ role: "assistant", content: result.output });
376
+ ctx.sessionTokens.inputTokens += result.tokenUsage.inputTokens;
377
+ ctx.sessionTokens.outputTokens += result.tokenUsage.outputTokens;
378
+ }
379
+ } catch (err) {
380
+ console.log(chalk.red(`
381
+ \u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
382
+ `));
383
+ }
384
+ };
385
+ }
386
+
387
+ // src/repl/dashboard.ts
388
+ function isSharedDashboardMode(args) {
389
+ const mode = args[0]?.toLowerCase();
390
+ return mode === "all" || mode === "global" || mode === "shared";
391
+ }
392
+
393
+ // src/ui/statusbar.ts
394
+ function renderStatusBar(_data, isFirstPrompt = false) {
395
+ if (isFirstPrompt) {
396
+ return "";
397
+ }
398
+ return "\n";
399
+ }
400
+
401
+ // src/ui/vim-mode.ts
402
+ import chalk2 from "chalk";
403
+ function createVimState(enabled = false) {
404
+ return { mode: "insert", enabled, pending: "", yank: "" };
405
+ }
406
+ function getVimModeIndicator(state) {
407
+ if (!state.enabled) return "";
408
+ return state.mode === "normal" ? chalk2.bgBlue.white(" N ") : chalk2.bgGreen.black(" I ");
409
+ }
410
+ function handleVimKeypress(state, rl, key) {
411
+ if (!state.enabled) return false;
412
+ if (key.name === "escape") {
413
+ state.mode = "normal";
414
+ state.pending = "";
415
+ return true;
416
+ }
417
+ if (state.mode === "insert") {
418
+ return false;
419
+ }
420
+ const ch = key.sequence || key.name || "";
421
+ const line = rl.line || "";
422
+ const cursor = rl.cursor || 0;
423
+ if (state.pending) {
424
+ const combo = state.pending + ch;
425
+ state.pending = "";
426
+ if (combo === "dd") {
427
+ state.yank = line;
428
+ rl.line = "";
429
+ rl.cursor = 0;
430
+ refreshLine(rl);
431
+ return true;
432
+ }
433
+ if (combo === "yy") {
434
+ state.yank = line;
435
+ return true;
436
+ }
437
+ if (combo === "cc") {
438
+ state.yank = line;
439
+ rl.line = "";
440
+ rl.cursor = 0;
441
+ refreshLine(rl);
442
+ state.mode = "insert";
443
+ return true;
444
+ }
445
+ return true;
446
+ }
447
+ switch (ch) {
448
+ // --- 模式切换 ---
449
+ case "i":
450
+ state.mode = "insert";
451
+ return true;
452
+ case "a":
453
+ state.mode = "insert";
454
+ moveCursor(rl, cursor + 1, line.length);
455
+ return true;
456
+ case "I":
457
+ state.mode = "insert";
458
+ moveCursor(rl, 0, line.length);
459
+ return true;
460
+ case "A":
461
+ state.mode = "insert";
462
+ moveCursor(rl, line.length, line.length);
463
+ return true;
464
+ // --- 移动 ---
465
+ case "h":
466
+ moveCursor(rl, cursor - 1, line.length);
467
+ return true;
468
+ case "l":
469
+ moveCursor(rl, cursor + 1, line.length);
470
+ return true;
471
+ case "0":
472
+ moveCursor(rl, 0, line.length);
473
+ return true;
474
+ case "$":
475
+ moveCursor(rl, line.length, line.length);
476
+ return true;
477
+ case "w": {
478
+ const nextSpace = line.indexOf(" ", cursor + 1);
479
+ moveCursor(rl, nextSpace === -1 ? line.length : nextSpace + 1, line.length);
480
+ return true;
481
+ }
482
+ case "b": {
483
+ const prevSpace = line.lastIndexOf(" ", cursor - 2);
484
+ moveCursor(rl, prevSpace === -1 ? 0 : prevSpace + 1, line.length);
485
+ return true;
486
+ }
487
+ // --- 编辑 ---
488
+ case "x": {
489
+ if (cursor < line.length) {
490
+ const newLine = line.slice(0, cursor) + line.slice(cursor + 1);
491
+ setLine(rl, newLine, Math.min(cursor, newLine.length - 1));
492
+ }
493
+ return true;
494
+ }
495
+ case "D": {
496
+ state.yank = line.slice(cursor);
497
+ setLine(rl, line.slice(0, cursor), Math.max(0, cursor - 1));
498
+ return true;
499
+ }
500
+ case "C": {
501
+ state.yank = line.slice(cursor);
502
+ setLine(rl, line.slice(0, cursor), cursor);
503
+ state.mode = "insert";
504
+ return true;
505
+ }
506
+ case "p": {
507
+ if (state.yank) {
508
+ const newLine = line.slice(0, cursor + 1) + state.yank + line.slice(cursor + 1);
509
+ setLine(rl, newLine, cursor + state.yank.length);
510
+ }
511
+ return true;
512
+ }
513
+ // --- 等待第二键 ---
514
+ case "d":
515
+ case "y":
516
+ case "c":
517
+ state.pending = ch;
518
+ return true;
519
+ default:
520
+ return true;
521
+ }
522
+ }
523
+ function moveCursor(rl, pos, lineLen) {
524
+ const newPos = Math.max(0, Math.min(pos, lineLen));
525
+ rl.cursor = newPos;
526
+ refreshLine(rl);
527
+ }
528
+ function setLine(rl, line, cursor) {
529
+ rl.line = line;
530
+ rl.cursor = Math.max(0, cursor);
531
+ refreshLine(rl);
532
+ }
533
+ function refreshLine(rl) {
534
+ const rlAny = rl;
535
+ if (typeof rlAny._refreshLine === "function") {
536
+ rlAny._refreshLine();
537
+ }
538
+ }
539
+
540
+ // src/repl/repl.ts
541
+ function isGreetingOrCapabilityQuery(input) {
542
+ const trimmed = input.trim();
543
+ if (!trimmed) return false;
544
+ if (trimmed.length > 60) return false;
545
+ const normalized = trimmed.toLowerCase().replace(/\s+/g, "");
546
+ const greetingSet = /* @__PURE__ */ new Set(["hi", "hello", "hey", "\u4F60\u597D", "\u55E8", "\u54C8\u55BD"]);
547
+ if (greetingSet.has(normalized)) return true;
548
+ const capabilityPatterns = [
549
+ /你可[以能].{0,6}做什[么麽]/,
550
+ /你会什[么麽]/,
551
+ /能帮我.{0,6}什[么麽]/,
552
+ /支持什[么麽]/,
553
+ /输出什[么麽]任务/,
554
+ /怎么用/,
555
+ /可用命令/
556
+ ];
557
+ return capabilityPatterns.some((re) => re.test(trimmed));
558
+ }
559
+ function editDistance(a, b) {
560
+ const n = a.length;
561
+ const m = b.length;
562
+ if (n === 0) return m;
563
+ if (m === 0) return n;
564
+ const dp = Array.from({ length: m + 1 }, (_, i) => i);
565
+ for (let i = 1; i <= n; i++) {
566
+ let prev = dp[0];
567
+ dp[0] = i;
568
+ for (let j = 1; j <= m; j++) {
569
+ const temp = dp[j];
570
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
571
+ dp[j] = Math.min(
572
+ dp[j] + 1,
573
+ dp[j - 1] + 1,
574
+ prev + cost
575
+ );
576
+ prev = temp;
577
+ }
578
+ }
579
+ return dp[m];
580
+ }
581
+ function suggestSlashCommands(input, candidates, limit = 3) {
582
+ const needle = input.toLowerCase().trim();
583
+ if (!needle) return [];
584
+ const ranked = candidates.map((cmd) => {
585
+ const c = cmd.toLowerCase();
586
+ let score = editDistance(needle, c);
587
+ if (c.startsWith(needle)) score -= 2;
588
+ else if (c.includes(needle)) score -= 1;
589
+ return { cmd, score };
590
+ });
591
+ ranked.sort((a, b) => a.score - b.score);
592
+ return ranked.slice(0, limit).map((item) => item.cmd);
593
+ }
594
+ function showCommandHelpDetail(helpCmd) {
595
+ console.log(chalk3.bold(`
596
+ /${helpCmd.name}`));
597
+ if (helpCmd.aliases?.length) {
598
+ console.log(chalk3.dim(` \u522B\u540D: ${helpCmd.aliases.map((a) => "/" + a).join(", ")}`));
599
+ }
600
+ console.log(` ${helpCmd.description}`);
601
+ if (helpCmd.usage) {
602
+ console.log(chalk3.dim(`
603
+ \u7528\u6CD5: ${helpCmd.usage}`));
604
+ }
605
+ if (helpCmd.examples?.length) {
606
+ console.log(chalk3.dim(" \u793A\u4F8B:"));
607
+ for (const ex of helpCmd.examples) {
608
+ console.log(chalk3.dim(` ${ex}`));
609
+ }
610
+ }
611
+ console.log();
612
+ }
613
+ function parseInput(raw) {
614
+ const args = [];
615
+ let current = "";
616
+ let inQuote = false;
617
+ let quoteChar = "";
618
+ for (const ch of raw) {
619
+ if (inQuote) {
620
+ if (ch === quoteChar) inQuote = false;
621
+ else current += ch;
622
+ } else if (ch === '"' || ch === "'") {
623
+ inQuote = true;
624
+ quoteChar = ch;
625
+ } else if (ch === " " || ch === " ") {
626
+ if (current) {
627
+ args.push(current);
628
+ current = "";
629
+ }
630
+ } else {
631
+ current += ch;
632
+ }
633
+ }
634
+ if (current) args.push(current);
635
+ return args;
636
+ }
637
+ function extractOptions(args) {
638
+ const positional = [];
639
+ const options = {};
640
+ for (let i = 0; i < args.length; i++) {
641
+ if (args[i].startsWith("--") && i + 1 < args.length) {
642
+ options[args[i].slice(2)] = args[i + 1];
643
+ i++;
644
+ } else {
645
+ positional.push(args[i]);
646
+ }
647
+ }
648
+ return { positional, options };
649
+ }
650
+ async function resolveAtPathRefs(content, baseDir, visited = /* @__PURE__ */ new Set(), depth = 0) {
651
+ if (depth >= 5) return content;
652
+ const refPattern = /(?<![`\\])@((?:~\/|\.\/|\.\.\/|\/)[^\s`"'<>]+)/g;
653
+ let result = content;
654
+ const matches = [...content.matchAll(refPattern)];
655
+ for (const match of matches) {
656
+ const rawPath = match[1];
657
+ let resolvedPath;
658
+ if (rawPath.startsWith("~/")) {
659
+ resolvedPath = join2(os.homedir(), rawPath.slice(2));
660
+ } else if (isAbsolute(rawPath)) {
661
+ resolvedPath = rawPath;
662
+ } else {
663
+ resolvedPath = resolve(baseDir, rawPath);
664
+ }
665
+ if (visited.has(resolvedPath)) continue;
666
+ if (!existsSync2(resolvedPath)) continue;
667
+ try {
668
+ visited.add(resolvedPath);
669
+ let imported = await readFile2(resolvedPath, "utf-8");
670
+ imported = await resolveAtPathRefs(imported, dirname(resolvedPath), visited, depth + 1);
671
+ result = result.replace(match[0], `
672
+ ${imported.trim()}
673
+ `);
674
+ } catch {
675
+ }
676
+ }
677
+ return result;
678
+ }
679
+ async function loadHyperMd() {
680
+ const layers = [];
681
+ const cwd = process.cwd();
682
+ const systemPath = join2(HYPERCORE_DIR, "HYPER.md");
683
+ if (existsSync2(systemPath)) {
684
+ try {
685
+ let content = await readFile2(systemPath, "utf-8");
686
+ content = await resolveAtPathRefs(content, dirname(systemPath));
687
+ layers.push(content);
688
+ } catch {
689
+ }
690
+ }
691
+ const projectHash = Buffer.from(cwd).toString("base64url").slice(0, 16);
692
+ const userProjectPath = join2(HYPERCORE_DIR, "projects", projectHash, "HYPER.md");
693
+ if (existsSync2(userProjectPath)) {
694
+ try {
695
+ let content = await readFile2(userProjectPath, "utf-8");
696
+ content = await resolveAtPathRefs(content, dirname(userProjectPath));
697
+ layers.push(content);
698
+ } catch {
699
+ }
700
+ }
701
+ const projectPath = join2(cwd, "HYPER.md");
702
+ if (existsSync2(projectPath) && projectPath !== systemPath) {
703
+ try {
704
+ let content = await readFile2(projectPath, "utf-8");
705
+ content = await resolveAtPathRefs(content, cwd);
706
+ layers.push(content);
707
+ } catch {
708
+ }
709
+ }
710
+ const localPath = join2(cwd, ".hypercore", "HYPER.md");
711
+ if (existsSync2(localPath)) {
712
+ try {
713
+ let content = await readFile2(localPath, "utf-8");
714
+ content = await resolveAtPathRefs(content, join2(cwd, ".hypercore"));
715
+ layers.push(content);
716
+ } catch {
717
+ }
718
+ }
719
+ const rulesDirs = [
720
+ join2(HYPERCORE_DIR, "rules"),
721
+ // 系统级 rules
722
+ join2(cwd, ".hyper", "rules")
723
+ // 项目级 rules
724
+ ];
725
+ for (const rulesDir of rulesDirs) {
726
+ if (existsSync2(rulesDir)) {
727
+ try {
728
+ const files = readdirSync(rulesDir).filter((f) => f.endsWith(".md")).sort();
729
+ for (const file of files) {
730
+ try {
731
+ let content = await readFile2(join2(rulesDir, file), "utf-8");
732
+ content = await resolveAtPathRefs(content, rulesDir);
733
+ layers.push(`<!-- rule: ${file} -->
734
+ ${content}`);
735
+ } catch {
736
+ }
737
+ }
738
+ } catch {
739
+ }
740
+ }
741
+ }
742
+ return layers.join("\n\n---\n\n");
743
+ }
744
+ async function detectProject() {
745
+ const cwd = process.cwd();
746
+ let projectName = basename(cwd);
747
+ let projectType = "";
748
+ const pkgPath = join2(cwd, "package.json");
749
+ if (existsSync2(pkgPath)) {
750
+ try {
751
+ const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
752
+ projectName = pkg.name || projectName;
753
+ projectType = "Node.js";
754
+ if (pkg.devDependencies?.typescript || pkg.dependencies?.typescript) projectType += "/TypeScript";
755
+ } catch {
756
+ }
757
+ }
758
+ if (!projectType) return null;
759
+ return { name: projectName, type: projectType };
760
+ }
761
+ var MAX_HISTORY_CHARS = 2e4;
762
+ var KEEP_RECENT_ROUNDS = 6;
763
+ function trimChatHistory(history) {
764
+ const totalChars = history.reduce((sum, m) => sum + m.content.length, 0);
765
+ if (totalChars <= MAX_HISTORY_CHARS) return;
766
+ const keepCount = KEEP_RECENT_ROUNDS * 2;
767
+ if (history.length <= keepCount) return;
768
+ const oldMessages = history.splice(0, history.length - keepCount);
769
+ const oldTopics = oldMessages.filter((m) => m.role === "user").map((m) => m.content.slice(0, 50)).slice(-3).join("\u3001");
770
+ history.unshift({
771
+ role: "assistant",
772
+ content: `[\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29] \u4E4B\u524D\u8BA8\u8BBA\u4E86\uFF1A${oldTopics || "\u591A\u4E2A\u8BDD\u9898"}\u3002\u5982\u9700\u8BE6\u60C5\u8BF7\u91CD\u65B0\u63D0\u95EE\u3002`
773
+ });
774
+ }
775
+ function registerBuiltinCommands(registry, deps) {
776
+ const { config, engine } = deps;
777
+ registry.registerAll([
778
+ // /model — 显示/切换模型
779
+ {
780
+ name: "model",
781
+ description: "\u663E\u793A\u5F53\u524D\u6A21\u578B\u6216\u5207\u6362\uFF08/model sonnet|flash|deepseek|...\uFF09",
782
+ category: "model",
783
+ handler: async (args, ctx) => {
784
+ const alias = args[0]?.toLowerCase();
785
+ if (!alias) {
786
+ const currentProviderName = MODEL_PROVIDERS[ctx.config.modelConfig.provider]?.name || ctx.config.modelConfig.provider;
787
+ console.log(chalk3.bold(`
788
+ \u5F53\u524D\u6A21\u578B: ${currentProviderName} \u2192 ${ctx.config.modelConfig.model}
789
+ `));
790
+ console.log(chalk3.dim(" \u53EF\u7528\u522B\u540D:"));
791
+ const grouped = {};
792
+ for (const [name, info] of Object.entries(MODEL_ALIASES)) {
793
+ const key = info.provider;
794
+ if (!grouped[key]) grouped[key] = [];
795
+ grouped[key].push(`${name} \u2192 ${info.model}`);
796
+ }
797
+ for (const [provider, items] of Object.entries(grouped)) {
798
+ const pName = MODEL_PROVIDERS[provider]?.name || provider;
799
+ console.log(chalk3.dim(` ${pName}:`));
800
+ for (const item of items) console.log(chalk3.dim(` ${item}`));
801
+ }
802
+ console.log(chalk3.dim("\n \u7528\u6CD5: /model <\u522B\u540D> \u6216 /model <\u5B8C\u6574\u6A21\u578B\u540D>\n"));
803
+ return;
804
+ }
805
+ const aliasInfo = MODEL_ALIASES[alias];
806
+ let newProvider;
807
+ let newModel;
808
+ if (aliasInfo) {
809
+ newProvider = aliasInfo.provider;
810
+ newModel = aliasInfo.model;
811
+ } else {
812
+ if (alias.startsWith("claude-")) newProvider = "anthropic";
813
+ else if (alias.startsWith("gemini-")) newProvider = "gemini";
814
+ else if (alias.startsWith("deepseek-")) newProvider = "deepseek";
815
+ else if (alias.startsWith("minimax") || alias.startsWith("MiniMax")) newProvider = "minimax";
816
+ else {
817
+ console.log(chalk3.red(`
818
+ \u672A\u77E5\u6A21\u578B\u522B\u540D: ${alias}`));
819
+ console.log(chalk3.dim(" \u4F7F\u7528 /model \u67E5\u770B\u53EF\u7528\u522B\u540D\n"));
820
+ return;
821
+ }
822
+ newModel = alias;
823
+ }
824
+ const newProviderInfo = MODEL_PROVIDERS[newProvider];
825
+ const newSdkType = newProviderInfo.sdkType;
826
+ const oldModel = ctx.config.modelConfig.model;
827
+ const oldProvider = ctx.config.modelConfig.provider;
828
+ const currentConfig = ctx.config.modelConfig;
829
+ let apiKey = currentConfig.apiKey;
830
+ if (newProvider !== currentConfig.provider) {
831
+ const providerKey = ctx.config.providerKeys[newProvider];
832
+ if (providerKey) {
833
+ apiKey = providerKey;
834
+ } else {
835
+ const envKeys = {
836
+ anthropic: "ANTHROPIC_API_KEY",
837
+ gemini: "GEMINI_API_KEY",
838
+ deepseek: "DEEPSEEK_API_KEY",
839
+ minimax: "MINIMAX_API_KEY",
840
+ "openai-compatible": "OPENAI_API_KEY"
841
+ };
842
+ const envKey = process.env[envKeys[newProvider] || ""];
843
+ if (envKey) {
844
+ apiKey = envKey;
845
+ } else {
846
+ console.log(chalk3.yellow(`
847
+ \u26A0\uFE0F \u5207\u6362\u5230 ${newProviderInfo.name} \u53EF\u80FD\u9700\u8981\u4E0D\u540C\u7684 API Key`));
848
+ console.log(chalk3.dim(` \u8BF7\u5728 config.toml [keys] \u6BB5\u6DFB\u52A0\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${envKeys[newProvider]}`));
849
+ console.log(chalk3.dim(" \u5C06\u4F7F\u7528\u5F53\u524D API Key \u5C1D\u8BD5...\n"));
850
+ }
851
+ }
852
+ }
853
+ ctx.config.modelConfig.provider = newProvider;
854
+ ctx.config.modelConfig.model = newModel;
855
+ ctx.config.modelConfig.sdkType = newSdkType;
856
+ ctx.config.modelConfig.baseURL = newProviderInfo.baseURL;
857
+ ctx.config.modelConfig.apiKey = apiKey;
858
+ ctx.reinitClient();
859
+ await hookManager.trigger("onModelSwitch", {
860
+ sessionId: ctx.sessionId,
861
+ model: newModel,
862
+ provider: newProvider,
863
+ previousModel: oldModel,
864
+ previousProvider: oldProvider
865
+ }).catch(() => {
866
+ });
867
+ await updateInstanceRuntime(process.pid, {
868
+ model: newModel,
869
+ provider: newProvider
870
+ }).catch(() => {
871
+ });
872
+ if (hasGUIClients()) {
873
+ emitToGUI("state_sync", {
874
+ sessionId: ctx.sessionId,
875
+ model: ctx.config.modelConfig.model,
876
+ provider: ctx.config.modelConfig.provider,
877
+ tokens: ctx.sessionTokens,
878
+ toolCount: ctx.tools.length,
879
+ cwd: process.cwd()
880
+ });
881
+ }
882
+ console.log(chalk3.green(`
883
+ \u2705 \u5DF2\u5207\u6362: ${newProviderInfo.name} \u2192 ${newModel}`));
884
+ console.log(chalk3.dim(` SDK: ${newSdkType} | \u8FD0\u884C\u65F6\u5207\u6362\uFF08\u4E0D\u5199\u5165\u914D\u7F6E\u6587\u4EF6\uFF09
885
+ `));
886
+ }
887
+ },
888
+ // /config — 显示配置
889
+ {
890
+ name: "config",
891
+ description: "\u663E\u793A\u5F53\u524D\u914D\u7F6E",
892
+ category: "debug",
893
+ handler: async (_args, ctx) => {
894
+ console.log(chalk3.bold("\n \u5F53\u524D\u914D\u7F6E\uFF1A"));
895
+ const currentProviderName = MODEL_PROVIDERS[ctx.config.modelConfig.provider]?.name || ctx.config.modelConfig.provider;
896
+ console.log(chalk3.dim(` \u6A21\u578B\u63D0\u4F9B\u5546: ${currentProviderName}`));
897
+ console.log(chalk3.dim(` \u6A21\u578B: ${ctx.config.modelConfig.model}`));
898
+ console.log(chalk3.dim(` SDK: ${ctx.config.modelConfig.sdkType}`));
899
+ console.log(chalk3.dim(` \u8F93\u51FA\u76EE\u5F55: ${ctx.config.outputDir}`));
900
+ console.log(chalk3.dim(` Tavily: ${ctx.config.tavilyApiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`));
901
+ console.log(chalk3.dim(` \u914D\u7F6E\u6587\u4EF6: ~/.hypercore/config.toml
902
+ `));
903
+ }
904
+ },
905
+ // /cost — Token 用量 + 费用估算
906
+ {
907
+ name: "cost",
908
+ description: "\u663E\u793A Token \u7528\u91CF\u4E0E\u8D39\u7528",
909
+ category: "util",
910
+ handler: async (_args, ctx) => {
911
+ showSessionCost(
912
+ ctx.sessionTokens.inputTokens,
913
+ ctx.sessionTokens.outputTokens,
914
+ ctx.config.modelConfig.model,
915
+ ctx.chatHistory.filter((m) => m.role === "user").length
916
+ );
917
+ }
918
+ },
919
+ // /history — 对话历史
920
+ {
921
+ name: "history",
922
+ description: "\u663E\u793A\u5BF9\u8BDD\u5386\u53F2",
923
+ category: "session",
924
+ handler: async (_args, ctx) => {
925
+ if (ctx.chatHistory.length === 0) {
926
+ console.log(chalk3.dim("\n \u6682\u65E0\u5BF9\u8BDD\u5386\u53F2\n"));
927
+ } else {
928
+ console.log(chalk3.bold(`
929
+ \u5BF9\u8BDD\u5386\u53F2\uFF08${ctx.chatHistory.length} \u6761\uFF09\uFF1A
930
+ `));
931
+ for (const msg of ctx.chatHistory.slice(-10)) {
932
+ const role = msg.role === "user" ? chalk3.cyan("\u4F60") : chalk3.green("AI");
933
+ const preview = msg.content.slice(0, 60).replace(/\n/g, " ");
934
+ console.log(` ${role}: ${chalk3.dim(preview)}${msg.content.length > 60 ? "\u2026" : ""}`);
935
+ }
936
+ console.log();
937
+ }
938
+ }
939
+ },
940
+ // /new — 新建会话
941
+ {
942
+ name: "new",
943
+ description: "\u65B0\u5EFA\u4F1A\u8BDD\uFF08\u4FDD\u5B58\u5F53\u524D\uFF09",
944
+ category: "session",
945
+ handler: async (_args, ctx) => {
946
+ if (ctx.chatHistory.length > 0) {
947
+ await saveSession(ctx.sessionId, ctx.chatHistory);
948
+ }
949
+ ctx.resetHistory();
950
+ ctx.resetTokens();
951
+ const newId = generateSessionId();
952
+ ctx.setSessionId(newId);
953
+ console.log(chalk3.green(`
954
+ \u2705 \u65B0\u4F1A\u8BDD\u5DF2\u5F00\u59CB (${newId})
955
+ `));
956
+ }
957
+ },
958
+ // /clear — 清屏
959
+ {
960
+ name: "clear",
961
+ aliases: ["cls"],
962
+ description: "\u6E05\u5C4F",
963
+ category: "util",
964
+ handler: async () => {
965
+ console.clear();
966
+ showBanner();
967
+ }
968
+ },
969
+ // /resume — 恢复会话
970
+ {
971
+ name: "resume",
972
+ description: "\u6062\u590D\u5386\u53F2\u4F1A\u8BDD",
973
+ category: "session",
974
+ handler: async (_args, ctx) => {
975
+ const sessions = await listSessions();
976
+ if (sessions.length === 0) {
977
+ console.log(chalk3.dim("\n \u6CA1\u6709\u5386\u53F2\u4F1A\u8BDD\n"));
978
+ return;
979
+ }
980
+ const choices = sessions.map((s) => ({
981
+ value: s.id,
982
+ name: `${s.id} (${s.messageCount} \u6761\u6D88\u606F)`
983
+ }));
984
+ try {
985
+ const selected = await select({
986
+ message: "\u9009\u62E9\u8981\u6062\u590D\u7684\u4F1A\u8BDD\uFF1A",
987
+ choices
988
+ });
989
+ const messages = await loadSession(selected);
990
+ ctx.resetHistory();
991
+ ctx.chatHistory.push(...messages);
992
+ console.log(chalk3.green(`
993
+ \u2705 \u5DF2\u6062\u590D\u4F1A\u8BDD ${selected}\uFF08${messages.length} \u6761\u6D88\u606F\uFF09
994
+ `));
995
+ } catch {
996
+ console.log(chalk3.dim("\n \u5DF2\u53D6\u6D88\n"));
997
+ }
998
+ }
999
+ },
1000
+ // /save — 手动保存
1001
+ {
1002
+ name: "save",
1003
+ description: "\u624B\u52A8\u4FDD\u5B58\u4F1A\u8BDD",
1004
+ category: "session",
1005
+ handler: async (_args, ctx) => {
1006
+ if (ctx.chatHistory.length === 0) {
1007
+ console.log(chalk3.dim("\n \u6682\u65E0\u5BF9\u8BDD\u9700\u8981\u4FDD\u5B58\n"));
1008
+ } else {
1009
+ await saveSession(ctx.sessionId, ctx.chatHistory);
1010
+ console.log(chalk3.green(`
1011
+ \u2705 \u5DF2\u4FDD\u5B58\u4F1A\u8BDD ${ctx.sessionId}\uFF08${ctx.chatHistory.length} \u6761\u6D88\u606F\uFF09
1012
+ `));
1013
+ }
1014
+ }
1015
+ },
1016
+ // /rename — 重命名当前会话
1017
+ {
1018
+ name: "rename",
1019
+ description: "\u91CD\u547D\u540D\u5F53\u524D\u4F1A\u8BDD\uFF08/rename <\u65B0\u540D\u79F0>\uFF09",
1020
+ category: "session",
1021
+ handler: async (args, ctx) => {
1022
+ const newName = args[0];
1023
+ if (!newName) {
1024
+ console.log(chalk3.dim("\n \u7528\u6CD5: /rename <\u65B0\u540D\u79F0>\n"));
1025
+ return;
1026
+ }
1027
+ try {
1028
+ if (ctx.chatHistory.length > 0) {
1029
+ await saveSession(ctx.sessionId, ctx.chatHistory);
1030
+ }
1031
+ await renameSession(ctx.sessionId, newName);
1032
+ ctx.setSessionId(newName);
1033
+ console.log(chalk3.green(`
1034
+ \u2705 \u4F1A\u8BDD\u5DF2\u91CD\u547D\u540D\u4E3A ${newName}
1035
+ `));
1036
+ } catch (err) {
1037
+ console.log(chalk3.red(`
1038
+ \u91CD\u547D\u540D\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1039
+ `));
1040
+ }
1041
+ }
1042
+ },
1043
+ // /fork — 复制当前会话为新分支
1044
+ {
1045
+ name: "fork",
1046
+ description: "Fork \u5F53\u524D\u4F1A\u8BDD\uFF08\u4FDD\u7559\u5386\u53F2\uFF0C\u65B0\u5206\u652F\u7EE7\u7EED\uFF09",
1047
+ category: "session",
1048
+ handler: async (_args, ctx) => {
1049
+ try {
1050
+ if (ctx.chatHistory.length > 0) {
1051
+ await saveSession(ctx.sessionId, ctx.chatHistory);
1052
+ }
1053
+ const newId = await forkSession(ctx.sessionId);
1054
+ ctx.setSessionId(newId);
1055
+ console.log(chalk3.green(`
1056
+ \u2705 \u5DF2 Fork \u4E3A\u65B0\u4F1A\u8BDD ${newId}
1057
+ `));
1058
+ } catch (err) {
1059
+ console.log(chalk3.red(`
1060
+ Fork \u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1061
+ `));
1062
+ }
1063
+ }
1064
+ },
1065
+ // /export — 导出会话为 Markdown
1066
+ {
1067
+ name: "export",
1068
+ description: "\u5BFC\u51FA\u5F53\u524D\u4F1A\u8BDD\u4E3A Markdown \u6587\u4EF6",
1069
+ category: "session",
1070
+ handler: async (args, ctx) => {
1071
+ try {
1072
+ if (ctx.chatHistory.length > 0) {
1073
+ await saveSession(ctx.sessionId, ctx.chatHistory);
1074
+ }
1075
+ const outputPath = args[0];
1076
+ const dest = await exportSession(ctx.sessionId, outputPath);
1077
+ console.log(chalk3.green(`
1078
+ \u2705 \u5DF2\u5BFC\u51FA\u5230 ${dest}
1079
+ `));
1080
+ } catch (err) {
1081
+ console.log(chalk3.red(`
1082
+ \u5BFC\u51FA\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1083
+ `));
1084
+ }
1085
+ }
1086
+ },
1087
+ // /delete — 删除历史会话
1088
+ {
1089
+ name: "delete",
1090
+ aliases: ["rm"],
1091
+ description: "\u5220\u9664\u5386\u53F2\u4F1A\u8BDD\uFF08/delete <session-id>\uFF09",
1092
+ category: "session",
1093
+ handler: async (args) => {
1094
+ const targetId = args[0];
1095
+ if (!targetId) {
1096
+ console.log(chalk3.dim("\n \u7528\u6CD5: /delete <session-id>\n"));
1097
+ return;
1098
+ }
1099
+ try {
1100
+ await deleteSession(targetId);
1101
+ console.log(chalk3.green(`
1102
+ \u2705 \u5DF2\u5220\u9664\u4F1A\u8BDD ${targetId}
1103
+ `));
1104
+ } catch (err) {
1105
+ console.log(chalk3.red(`
1106
+ \u5220\u9664\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1107
+ `));
1108
+ }
1109
+ }
1110
+ },
1111
+ // /rewind — 回退最后一轮对话
1112
+ {
1113
+ name: "rewind",
1114
+ aliases: ["undo"],
1115
+ description: "\u56DE\u9000\u6700\u540E\u4E00\u8F6E\u5BF9\u8BDD",
1116
+ category: "context",
1117
+ handler: async (_args, ctx) => {
1118
+ if (ctx.chatHistory.length < 2) {
1119
+ console.log(chalk3.dim("\n \u65E0\u6CD5\u56DE\u9000\uFF1A\u5BF9\u8BDD\u4E0D\u8DB3\u4E00\u8F6E\n"));
1120
+ return;
1121
+ }
1122
+ ctx.chatHistory.pop();
1123
+ ctx.chatHistory.pop();
1124
+ console.log(chalk3.green("\n \u2705 \u5DF2\u56DE\u9000\u6700\u540E\u4E00\u8F6E\u5BF9\u8BDD\n"));
1125
+ }
1126
+ },
1127
+ // /copy — 复制最后一条响应
1128
+ {
1129
+ name: "copy",
1130
+ description: "\u590D\u5236\u6700\u540E\u4E00\u6761 AI \u54CD\u5E94\u5230\u526A\u8D34\u677F",
1131
+ category: "util",
1132
+ handler: async (_args, ctx) => {
1133
+ const lastAssistant = [...ctx.chatHistory].reverse().find((m) => m.role === "assistant");
1134
+ if (!lastAssistant) {
1135
+ console.log(chalk3.dim("\n \u6682\u65E0 AI \u54CD\u5E94\u53EF\u590D\u5236\n"));
1136
+ return;
1137
+ }
1138
+ try {
1139
+ const { execSync } = await import("child_process");
1140
+ const platform = process.platform;
1141
+ if (platform === "darwin") {
1142
+ execSync("pbcopy", { input: lastAssistant.content });
1143
+ } else {
1144
+ execSync("xclip -selection clipboard", { input: lastAssistant.content });
1145
+ }
1146
+ console.log(chalk3.green("\n \u2705 \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F\n"));
1147
+ } catch {
1148
+ console.log(chalk3.dim("\n \u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u590D\u5236\n"));
1149
+ }
1150
+ }
1151
+ },
1152
+ // /stats — 会话统计
1153
+ {
1154
+ name: "stats",
1155
+ description: "\u663E\u793A\u4F1A\u8BDD\u7EDF\u8BA1",
1156
+ category: "debug",
1157
+ handler: async (_args, ctx) => {
1158
+ const userMsgs = ctx.chatHistory.filter((m) => m.role === "user").length;
1159
+ const aiMsgs = ctx.chatHistory.filter((m) => m.role === "assistant").length;
1160
+ const totalChars = ctx.chatHistory.reduce((s, m) => s + m.content.length, 0);
1161
+ console.log(chalk3.bold("\n \u4F1A\u8BDD\u7EDF\u8BA1\uFF1A"));
1162
+ console.log(chalk3.dim(` \u4F1A\u8BDD ID: ${ctx.sessionId}`));
1163
+ console.log(chalk3.dim(` \u5BF9\u8BDD\u8F6E\u6570: ${userMsgs} \u8F6E`));
1164
+ console.log(chalk3.dim(` \u6D88\u606F\u6570: ${userMsgs} \u7528\u6237 / ${aiMsgs} AI`));
1165
+ console.log(chalk3.dim(` \u5386\u53F2\u5B57\u7B26: ${totalChars.toLocaleString()}`));
1166
+ console.log(chalk3.dim(` Token: \u8F93\u5165 ${ctx.sessionTokens.inputTokens.toLocaleString()} / \u8F93\u51FA ${ctx.sessionTokens.outputTokens.toLocaleString()}`));
1167
+ console.log(chalk3.dim(` \u5DE5\u5177: ${ctx.tools.length} \u4E2A\u5DF2\u6CE8\u518C`));
1168
+ console.log(chalk3.dim(` \u6A21\u578B: ${ctx.config.modelConfig.model}
1169
+ `));
1170
+ }
1171
+ },
1172
+ // /debug — 切换调试模式(占位)
1173
+ {
1174
+ name: "debug",
1175
+ description: "\u5207\u6362\u8C03\u8BD5\u6A21\u5F0F",
1176
+ category: "debug",
1177
+ handler: async () => {
1178
+ console.log(chalk3.dim("\n \u8C03\u8BD5\u6A21\u5F0F\uFF08\u5F00\u53D1\u4E2D\uFF09\n"));
1179
+ }
1180
+ },
1181
+ // /hooks — 查看已注册的钩子
1182
+ {
1183
+ name: "hooks",
1184
+ description: "\u663E\u793A\u5DF2\u6CE8\u518C\u7684\u751F\u547D\u5468\u671F\u94A9\u5B50\uFF08/hooks test <event> \u6D4B\u8BD5\uFF09",
1185
+ category: "debug",
1186
+ usage: "/hooks [test <event>]",
1187
+ handler: async (args) => {
1188
+ if (args[0] === "test" && args[1]) {
1189
+ const event = args[1];
1190
+ console.log(chalk3.dim(`
1191
+ \u6D4B\u8BD5\u89E6\u53D1\u4E8B\u4EF6: ${event}...`));
1192
+ const result = await hookManager.trigger(event, { event, sessionId: "test" });
1193
+ if (result.intercepted) {
1194
+ console.log(chalk3.yellow(` \u62E6\u622A: ${result.reason}`));
1195
+ } else {
1196
+ console.log(chalk3.green(" \u2713 \u89E6\u53D1\u5B8C\u6210\uFF08\u65E0\u62E6\u622A\uFF09"));
1197
+ }
1198
+ console.log();
1199
+ return;
1200
+ }
1201
+ const sysHooks = join2(HYPERCORE_DIR, "hooks.json");
1202
+ const projHooks = join2(process.cwd(), ".hypercore", "hooks.json");
1203
+ if (hookManager.count === 0) {
1204
+ console.log(chalk3.dim("\n \u6682\u65E0\u6CE8\u518C\u94A9\u5B50"));
1205
+ console.log(chalk3.dim(` \u7CFB\u7EDF\u7EA7: ${sysHooks} ${existsSync2(sysHooks) ? "(\u5B58\u5728)" : "(\u672A\u521B\u5EFA)"}`));
1206
+ console.log(chalk3.dim(` \u9879\u76EE\u7EA7: ${projHooks} ${existsSync2(projHooks) ? "(\u5B58\u5728)" : "(\u672A\u521B\u5EFA)"}`));
1207
+ console.log(chalk3.dim(" \u4E8B\u4EF6: onSessionStart, onSessionEnd, onPromptStart, onPromptEnd,"));
1208
+ console.log(chalk3.dim(" onToolCall, onToolResult, onModelSwitch, onError\n"));
1209
+ return;
1210
+ }
1211
+ console.log(chalk3.bold(`
1212
+ \u5DF2\u6CE8\u518C\u94A9\u5B50 (${hookManager.count}):
1213
+ `));
1214
+ console.log(chalk3.dim(` \u914D\u7F6E: ${existsSync2(sysHooks) ? "\u7CFB\u7EDF\u7EA7 \u2713" : ""} ${existsSync2(projHooks) ? "\u9879\u76EE\u7EA7 \u2713" : ""}
1215
+ `));
1216
+ const grouped = hookManager.listByEvent();
1217
+ for (const [event, hooks] of Object.entries(grouped)) {
1218
+ console.log(chalk3.dim(` ${event}:`));
1219
+ for (const h of hooks) {
1220
+ const label = h.name || h.command.slice(0, 40);
1221
+ const flags = [
1222
+ h.blocking ? "blocking" : "async",
1223
+ h.intercept ? "\u{1F6E1}\uFE0F intercept" : "",
1224
+ h.match?.toolName ? `tool=${h.match.toolName}` : ""
1225
+ ].filter(Boolean).join(", ");
1226
+ console.log(chalk3.dim(` - ${label} (${flags})`));
1227
+ }
1228
+ }
1229
+ console.log(chalk3.dim("\n \u6D4B\u8BD5: /hooks test <event>\n"));
1230
+ }
1231
+ },
1232
+ // /compact — LLM 压缩上下文
1233
+ {
1234
+ name: "compact",
1235
+ description: "\u4F7F\u7528 LLM \u538B\u7F29\u5BF9\u8BDD\u5386\u53F2\uFF08\u8282\u7701\u4E0A\u4E0B\u6587\uFF09",
1236
+ category: "context",
1237
+ handler: async (_args, ctx) => {
1238
+ if (ctx.chatHistory.length < 4) {
1239
+ console.log(chalk3.dim("\n \u5BF9\u8BDD\u592A\u77ED\uFF0C\u65E0\u9700\u538B\u7F29\n"));
1240
+ return;
1241
+ }
1242
+ console.log(chalk3.dim("\n \u6B63\u5728\u538B\u7F29\u5BF9\u8BDD\u5386\u53F2..."));
1243
+ try {
1244
+ const client = ctx.getClient();
1245
+ const { summary, savedChars } = await compactHistory(
1246
+ ctx.chatHistory,
1247
+ client,
1248
+ ctx.config,
1249
+ ctx.systemPrompt
1250
+ );
1251
+ const oldCount = ctx.chatHistory.length;
1252
+ ctx.chatHistory.length = 0;
1253
+ ctx.chatHistory.push(...summary);
1254
+ console.log(chalk3.green(` \u2705 \u5DF2\u538B\u7F29: ${oldCount} \u6761\u6D88\u606F \u2192 ${summary.length} \u6761\u6458\u8981`));
1255
+ console.log(chalk3.dim(` \u8282\u7701 ~${savedChars.toLocaleString()} \u5B57\u7B26
1256
+ `));
1257
+ } catch (err) {
1258
+ console.log(chalk3.red(` \u538B\u7F29\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1259
+ `));
1260
+ }
1261
+ }
1262
+ },
1263
+ // /context — 上下文可视化(分层详情)
1264
+ {
1265
+ name: "context",
1266
+ aliases: ["ctx"],
1267
+ description: "\u663E\u793A\u4E0A\u4E0B\u6587\u4F7F\u7528\u60C5\u51B5\uFF08\u5206\u5C42\u8BE6\u60C5\uFF09",
1268
+ category: "context",
1269
+ handler: async (_args, ctx) => {
1270
+ const cwd = process.cwd();
1271
+ const systemSources = [];
1272
+ const hyperPaths = [
1273
+ [join2(HYPERCORE_DIR, "HYPER.md"), "HYPER.md (\u7CFB\u7EDF\u7EA7)"],
1274
+ [join2(cwd, "HYPER.md"), "HYPER.md (\u9879\u76EE\u7EA7)"],
1275
+ [join2(cwd, ".hypercore", "HYPER.md"), "HYPER.md (\u9879\u76EE\u672C\u5730)"],
1276
+ [join2(cwd, "CLAUDE.md"), "CLAUDE.md"],
1277
+ [join2(cwd, ".claude", "CLAUDE.md"), ".claude/CLAUDE.md"]
1278
+ ];
1279
+ for (const [p, label] of hyperPaths) {
1280
+ if (existsSync2(p)) {
1281
+ try {
1282
+ systemSources.push({ name: label, chars: statSync(p).size });
1283
+ } catch {
1284
+ systemSources.push({ name: label, chars: 0 });
1285
+ }
1286
+ }
1287
+ }
1288
+ const rulesDirs = [
1289
+ join2(HYPERCORE_DIR, "rules"),
1290
+ join2(cwd, ".hyper", "rules")
1291
+ ];
1292
+ for (const dir of rulesDirs) {
1293
+ if (existsSync2(dir)) {
1294
+ try {
1295
+ const files = readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
1296
+ for (const f of files) {
1297
+ systemSources.push({ name: `rules/${f}`, chars: statSync(join2(dir, f)).size });
1298
+ }
1299
+ } catch {
1300
+ }
1301
+ }
1302
+ }
1303
+ const systemTotal = systemSources.reduce((s, src) => s + src.chars, 0);
1304
+ const historyChars = ctx.chatHistory.reduce((s, m) => s + m.content.length, 0);
1305
+ const toolCallCount = ctx.chatHistory.filter(
1306
+ (m) => m.content.includes("[tool_call]") || m.content.includes("tool_use")
1307
+ ).length;
1308
+ const toolChars = ctx.tools.reduce((s, t) => s + JSON.stringify(t.definition).length, 0);
1309
+ const totalChars = ctx.systemPrompt.length + historyChars + toolChars;
1310
+ const estimatedTokens = Math.round(totalChars / 3);
1311
+ const model = ctx.config.modelConfig.model;
1312
+ let maxTokens = 2e5;
1313
+ if (model.includes("gemini")) maxTokens = 1e6;
1314
+ else if (model.includes("deepseek")) maxTokens = 64e3;
1315
+ else if (model.includes("gpt-4o")) maxTokens = 128e3;
1316
+ const usagePercent = Math.round(estimatedTokens / maxTokens * 100);
1317
+ const usageBar = "\u2588".repeat(Math.min(Math.round(usagePercent / 5), 20)) + "\u2591".repeat(Math.max(20 - Math.round(usagePercent / 5), 0));
1318
+ console.log(chalk3.bold("\n \u4E0A\u4E0B\u6587\u6982\u89C8\n"));
1319
+ console.log(chalk3.dim(" \u250C\u2500 \u7CFB\u7EDF\u5C42 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1320
+ if (systemSources.length > 0) {
1321
+ for (const src of systemSources) {
1322
+ const size = src.chars > 1024 ? `${(src.chars / 1024).toFixed(1)}K` : `${src.chars}`;
1323
+ console.log(` \u2502 ${src.name.padEnd(28)} ${chalk3.cyan(size)} \u5B57\u7B26`);
1324
+ }
1325
+ console.log(` \u2502${"".padEnd(29)}${"\u2500".repeat(12)}`);
1326
+ const sysSize = systemTotal > 1024 ? `${(systemTotal / 1024).toFixed(1)}K` : `${systemTotal}`;
1327
+ console.log(` \u2502 ${chalk3.dim("\u5C0F\u8BA1:").padEnd(28)} ${chalk3.cyan(sysSize)} \u5B57\u7B26`);
1328
+ } else {
1329
+ console.log(` \u2502 ${chalk3.dim("\uFF08\u65E0\u7CFB\u7EDF\u6307\u4EE4\u6587\u4EF6\uFF09")}`);
1330
+ }
1331
+ console.log(chalk3.dim(" \u2502"));
1332
+ console.log(chalk3.dim(" \u251C\u2500 \u5BF9\u8BDD\u5C42 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1333
+ console.log(` \u2502 \u6D88\u606F: ${ctx.chatHistory.length} \u6761 / ${historyChars.toLocaleString()} \u5B57\u7B26`);
1334
+ console.log(` \u2502 \u5DE5\u5177\u8C03\u7528: ~${toolCallCount} \u6B21`);
1335
+ console.log(chalk3.dim(" \u2502"));
1336
+ console.log(chalk3.dim(" \u251C\u2500 \u5DE5\u5177\u5C42 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1337
+ console.log(` \u2502 \u5DF2\u6CE8\u518C: ${ctx.tools.length} \u4E2A / ~${toolChars.toLocaleString()} \u5B57\u7B26`);
1338
+ console.log(chalk3.dim(" \u2502"));
1339
+ console.log(chalk3.dim(" \u2514\u2500 \u603B\u8BA1 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1340
+ console.log(` ~${estimatedTokens.toLocaleString()} tokens / ${(maxTokens / 1e3).toFixed(0)}K`);
1341
+ console.log(` [${usageBar}] ${usagePercent}%`);
1342
+ if (usagePercent > 70) {
1343
+ console.log(chalk3.yellow(`
1344
+ \u26A0\uFE0F \u4E0A\u4E0B\u6587\u4F7F\u7528\u7387\u8F83\u9AD8\uFF0C\u5EFA\u8BAE\u4F7F\u7528 /compact \u538B\u7F29`));
1345
+ }
1346
+ console.log();
1347
+ }
1348
+ },
1349
+ // /git — Git 状态总览
1350
+ {
1351
+ name: "git",
1352
+ description: "\u663E\u793A Git \u4ED3\u5E93\u72B6\u6001\u603B\u89C8",
1353
+ category: "git",
1354
+ handler: async () => {
1355
+ if (!isGitRepo()) {
1356
+ console.log(chalk3.dim("\n \u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93\n"));
1357
+ return;
1358
+ }
1359
+ const branch = gitBranch();
1360
+ const status = gitStatus();
1361
+ const recent = gitLog(5);
1362
+ console.log(chalk3.bold(`
1363
+ Git \u72B6\u6001 \u2014 ${branch}
1364
+ `));
1365
+ if (status.staged.length) {
1366
+ console.log(chalk3.green(` \u5DF2\u6682\u5B58 (${status.staged.length}):`));
1367
+ for (const f of status.staged.slice(0, 8)) console.log(chalk3.green(` + ${f}`));
1368
+ if (status.staged.length > 8) console.log(chalk3.dim(` ... +${status.staged.length - 8} \u66F4\u591A`));
1369
+ }
1370
+ if (status.modified.length) {
1371
+ console.log(chalk3.yellow(` \u5DF2\u4FEE\u6539 (${status.modified.length}):`));
1372
+ for (const f of status.modified.slice(0, 8)) console.log(chalk3.yellow(` M ${f}`));
1373
+ if (status.modified.length > 8) console.log(chalk3.dim(` ... +${status.modified.length - 8} \u66F4\u591A`));
1374
+ }
1375
+ if (status.untracked.length) {
1376
+ console.log(chalk3.dim(` \u672A\u8DDF\u8E2A (${status.untracked.length}):`));
1377
+ for (const f of status.untracked.slice(0, 5)) console.log(chalk3.dim(` ? ${f}`));
1378
+ if (status.untracked.length > 5) console.log(chalk3.dim(` ... +${status.untracked.length - 5} \u66F4\u591A`));
1379
+ }
1380
+ if (!status.staged.length && !status.modified.length && !status.untracked.length) {
1381
+ console.log(chalk3.green(" \u2728 \u5DE5\u4F5C\u533A\u5E72\u51C0"));
1382
+ }
1383
+ if (recent.length) {
1384
+ console.log(chalk3.bold(`
1385
+ \u6700\u8FD1\u63D0\u4EA4\uFF1A`));
1386
+ for (const entry of recent) {
1387
+ console.log(chalk3.dim(` ${entry.shortHash} ${entry.message}`));
1388
+ }
1389
+ }
1390
+ console.log();
1391
+ }
1392
+ },
1393
+ // /commit — AI 辅助提交
1394
+ {
1395
+ name: "commit",
1396
+ aliases: ["ci"],
1397
+ description: "AI \u5206\u6790\u53D8\u66F4\u5E76\u751F\u6210 commit\uFF08\u53EF --all \u81EA\u52A8\u6682\u5B58\uFF09",
1398
+ category: "git",
1399
+ handler: async (args, ctx) => {
1400
+ if (!isGitRepo()) {
1401
+ console.log(chalk3.dim("\n \u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93\n"));
1402
+ return;
1403
+ }
1404
+ const autoStage = args.includes("--all") || args.includes("-a");
1405
+ const status = gitStatus();
1406
+ const hasChanges = status.staged.length > 0 || status.modified.length > 0;
1407
+ if (!hasChanges && !autoStage) {
1408
+ console.log(chalk3.dim("\n \u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4\u3002\u4F7F\u7528 /commit --all \u81EA\u52A8\u6682\u5B58\u6240\u6709\u4FEE\u6539\n"));
1409
+ return;
1410
+ }
1411
+ if (autoStage && status.modified.length > 0) {
1412
+ gitStageFiles(status.modified);
1413
+ console.log(chalk3.dim(`
1414
+ \u5DF2\u6682\u5B58 ${status.modified.length} \u4E2A\u4FEE\u6539\u6587\u4EF6`));
1415
+ }
1416
+ const diff = gitDiffStaged();
1417
+ const diffStat = gitDiffStagedStat();
1418
+ if (!diff && !diffStat) {
1419
+ console.log(chalk3.dim("\n \u6682\u5B58\u533A\u4E3A\u7A7A\uFF0C\u8BF7\u5148 git add \u6587\u4EF6\n"));
1420
+ return;
1421
+ }
1422
+ console.log(chalk3.dim("\n \u5206\u6790\u53D8\u66F4\u4E2D...\n"));
1423
+ const recentLogs = gitLog(5);
1424
+ const styleHint = recentLogs.length > 0 ? `\u6700\u8FD1\u7684\u63D0\u4EA4\u98CE\u683C\u53C2\u8003\uFF1A
1425
+ ${recentLogs.map((l) => ` - ${l.message}`).join("\n")}` : "";
1426
+ const commitPrompt = `\u8BF7\u6839\u636E\u4EE5\u4E0B Git diff \u751F\u6210\u4E00\u6761\u7B80\u6D01\u7684 commit \u6D88\u606F\u3002
1427
+
1428
+ \u89C4\u5219\uFF1A
1429
+ - \u683C\u5F0F\uFF1A<type>(<scope>): <subject>
1430
+ - type: feat/fix/docs/style/refactor/perf/test/chore
1431
+ - scope: \u53D8\u66F4\u6D89\u53CA\u7684\u6A21\u5757\uFF08\u53EF\u9009\uFF09
1432
+ - subject: \u4E00\u53E5\u8BDD\u63CF\u8FF0\u53D8\u66F4\u7684"\u4E3A\u4EC0\u4E48"\uFF0C\u4E0D\u8D85\u8FC7 72 \u5B57\u7B26
1433
+ - \u5982\u679C\u53D8\u66F4\u590D\u6742\uFF0C\u53EF\u5728\u7B2C\u4E8C\u884C\u7A7A\u884C\u540E\u6DFB\u52A0 body \u8BF4\u660E
1434
+ ${styleHint}
1435
+
1436
+ Diff stat:
1437
+ ${diffStat}
1438
+
1439
+ Diff (\u524D 3000 \u5B57\u7B26):
1440
+ ${diff.slice(0, 3e3)}
1441
+
1442
+ \u76F4\u63A5\u8F93\u51FA commit \u6D88\u606F\uFF0C\u4E0D\u8981\u6DFB\u52A0\u4EFB\u4F55\u89E3\u91CA\u6216\u683C\u5F0F\u5305\u88F9\u3002`;
1443
+ const startTime = Date.now();
1444
+ let commitMsg = "";
1445
+ try {
1446
+ if (ctx.config.modelConfig.sdkType === "openai") {
1447
+ const { streamOpenAIChat: streamOpenAIChat2 } = await import("./openai-compat-UQWJXBEK.js");
1448
+ const OpenAI = (await import("openai")).default;
1449
+ const openaiClient = ctx.getClient();
1450
+ const result = await streamOpenAIChat2(openaiClient, [
1451
+ { role: "system", content: "\u4F60\u662F\u4E00\u4E2A Git commit \u6D88\u606F\u751F\u6210\u5668\u3002\u53EA\u8F93\u51FA commit \u6D88\u606F\uFF0C\u4E0D\u8981\u5176\u4ED6\u5185\u5BB9\u3002" },
1452
+ { role: "user", content: commitPrompt }
1453
+ ], {
1454
+ model: ctx.config.modelConfig.model,
1455
+ tools: [],
1456
+ onChunk: () => {
1457
+ },
1458
+ onToolCall: () => {
1459
+ }
1460
+ });
1461
+ commitMsg = result.content.trim();
1462
+ } else {
1463
+ const { streamCallLLM: streamCallLLM2 } = await import("./claude-NSQ442XD.js");
1464
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
1465
+ const anthropicClient = ctx.getClient();
1466
+ const result = await streamCallLLM2(anthropicClient, {
1467
+ systemPrompt: "\u4F60\u662F\u4E00\u4E2A Git commit \u6D88\u606F\u751F\u6210\u5668\u3002\u53EA\u8F93\u51FA commit \u6D88\u606F\uFF0C\u4E0D\u8981\u5176\u4ED6\u5185\u5BB9\u3002",
1468
+ userPrompt: commitPrompt,
1469
+ history: [],
1470
+ tools: [],
1471
+ model: ctx.config.modelConfig.model,
1472
+ onText: () => {
1473
+ },
1474
+ onToolCall: () => {
1475
+ }
1476
+ });
1477
+ commitMsg = result.output.trim();
1478
+ }
1479
+ } catch (err) {
1480
+ console.log(chalk3.red(` AI \u751F\u6210\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1481
+ `));
1482
+ return;
1483
+ }
1484
+ commitMsg = commitMsg.replace(/^```[\w]*\n?/, "").replace(/\n?```$/, "").trim();
1485
+ const elapsed = Date.now() - startTime;
1486
+ console.log(chalk3.bold(" \u751F\u6210\u7684 Commit \u6D88\u606F\uFF1A\n"));
1487
+ console.log(` ${chalk3.cyan(commitMsg)}
1488
+ `);
1489
+ console.log(chalk3.dim(` (${elapsed}ms)`));
1490
+ try {
1491
+ const { select: selectPrompt } = await import("@inquirer/prompts");
1492
+ const action = await selectPrompt({
1493
+ message: "\u64CD\u4F5C\uFF1A",
1494
+ choices: [
1495
+ { value: "commit", name: "\u2705 \u63D0\u4EA4" },
1496
+ { value: "edit", name: "\u270F\uFE0F \u7F16\u8F91\u540E\u63D0\u4EA4" },
1497
+ { value: "cancel", name: "\u274C \u53D6\u6D88" }
1498
+ ]
1499
+ });
1500
+ if (action === "cancel") {
1501
+ console.log(chalk3.dim("\n \u5DF2\u53D6\u6D88\n"));
1502
+ return;
1503
+ }
1504
+ let finalMsg = commitMsg;
1505
+ if (action === "edit") {
1506
+ const { input: inputPrompt } = await import("@inquirer/prompts");
1507
+ finalMsg = await inputPrompt({
1508
+ message: "Commit \u6D88\u606F:",
1509
+ default: commitMsg
1510
+ });
1511
+ }
1512
+ const result = gitCommit(finalMsg);
1513
+ console.log(chalk3.green(`
1514
+ \u2705 ${result}
1515
+ `));
1516
+ } catch {
1517
+ console.log(chalk3.dim("\n \u5DF2\u53D6\u6D88\n"));
1518
+ }
1519
+ }
1520
+ },
1521
+ // /pr — 创建 Pull Request
1522
+ {
1523
+ name: "pr",
1524
+ description: "AI \u751F\u6210 PR \u6807\u9898 + \u6458\u8981\uFF0C\u63A8\u9001\u5E76\u521B\u5EFA PR",
1525
+ category: "git",
1526
+ handler: async (args, ctx) => {
1527
+ if (!isGitRepo()) {
1528
+ console.log(chalk3.dim("\n \u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93\n"));
1529
+ return;
1530
+ }
1531
+ const branch = gitBranch();
1532
+ const defaultBranch = gitDefaultBranch();
1533
+ if (branch === defaultBranch) {
1534
+ console.log(chalk3.yellow(`
1535
+ \u26A0\uFE0F \u5F53\u524D\u5728 ${defaultBranch} \u5206\u652F\uFF0C\u8BF7\u5148\u5207\u6362\u5230\u529F\u80FD\u5206\u652F
1536
+ `));
1537
+ return;
1538
+ }
1539
+ const diffFiles = gitDiffFiles(defaultBranch);
1540
+ const diffRange = gitDiffRange(defaultBranch);
1541
+ const commits = gitLog(20);
1542
+ const branchCommits = commits.filter((c) => c.message);
1543
+ if (!diffFiles && !diffRange) {
1544
+ console.log(chalk3.dim(`
1545
+ \u4E0E ${defaultBranch} \u6CA1\u6709\u5DEE\u5F02\uFF0C\u65E0\u9700\u521B\u5EFA PR
1546
+ `));
1547
+ return;
1548
+ }
1549
+ console.log(chalk3.dim(`
1550
+ \u5206\u6790 ${branch} \u2192 ${defaultBranch} \u53D8\u66F4...
1551
+ `));
1552
+ const prPrompt = `\u8BF7\u6839\u636E\u4EE5\u4E0B\u4FE1\u606F\u751F\u6210 Pull Request \u7684\u6807\u9898\u548C\u63CF\u8FF0\u3002
1553
+
1554
+ \u5206\u652F: ${branch} \u2192 ${defaultBranch}
1555
+
1556
+ \u6587\u4EF6\u53D8\u66F4:
1557
+ ${diffFiles}
1558
+
1559
+ \u53D8\u66F4\u7EDF\u8BA1:
1560
+ ${diffRange}
1561
+
1562
+ \u6700\u8FD1\u63D0\u4EA4:
1563
+ ${branchCommits.slice(0, 10).map((c) => `- ${c.message}`).join("\n")}
1564
+
1565
+ \u8981\u6C42:
1566
+ 1. \u6807\u9898: \u7B80\u6D01\u660E\u4E86\uFF0C\u4E0D\u8D85\u8FC7 70 \u5B57\u7B26
1567
+ 2. \u63CF\u8FF0: \u5305\u542B Summary\uFF083-5 \u4E2A\u8981\u70B9\uFF09\u548C Test Plan
1568
+ 3. \u683C\u5F0F:
1569
+ TITLE: <\u6807\u9898>
1570
+ BODY:
1571
+ ## Summary
1572
+ - <\u8981\u70B9>
1573
+
1574
+ ## Test Plan
1575
+ - <\u6D4B\u8BD5\u9879>
1576
+
1577
+ \u76F4\u63A5\u8F93\u51FA\uFF0C\u4E0D\u8981\u4EE3\u7801\u5757\u5305\u88F9\u3002`;
1578
+ let prContent = "";
1579
+ try {
1580
+ if (ctx.config.modelConfig.sdkType === "openai") {
1581
+ const { streamOpenAIChat: streamOpenAIChat2 } = await import("./openai-compat-UQWJXBEK.js");
1582
+ const OpenAI = (await import("openai")).default;
1583
+ const openaiClient = ctx.getClient();
1584
+ const result = await streamOpenAIChat2(openaiClient, [
1585
+ { role: "system", content: "\u4F60\u662F\u4E00\u4E2A PR \u63CF\u8FF0\u751F\u6210\u5668\u3002\u6309\u8981\u6C42\u8F93\u51FA TITLE \u548C BODY\u3002" },
1586
+ { role: "user", content: prPrompt }
1587
+ ], {
1588
+ model: ctx.config.modelConfig.model,
1589
+ tools: [],
1590
+ onChunk: () => {
1591
+ },
1592
+ onToolCall: () => {
1593
+ }
1594
+ });
1595
+ prContent = result.content.trim();
1596
+ } else {
1597
+ const { streamCallLLM: streamCallLLM2 } = await import("./claude-NSQ442XD.js");
1598
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
1599
+ const anthropicClient = ctx.getClient();
1600
+ const result = await streamCallLLM2(anthropicClient, {
1601
+ systemPrompt: "\u4F60\u662F\u4E00\u4E2A PR \u63CF\u8FF0\u751F\u6210\u5668\u3002\u6309\u8981\u6C42\u8F93\u51FA TITLE \u548C BODY\u3002",
1602
+ userPrompt: prPrompt,
1603
+ history: [],
1604
+ tools: [],
1605
+ model: ctx.config.modelConfig.model,
1606
+ onText: () => {
1607
+ },
1608
+ onToolCall: () => {
1609
+ }
1610
+ });
1611
+ prContent = result.output.trim();
1612
+ }
1613
+ } catch (err) {
1614
+ console.log(chalk3.red(` AI \u751F\u6210\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1615
+ `));
1616
+ return;
1617
+ }
1618
+ const titleMatch = prContent.match(/TITLE:\s*(.+)/);
1619
+ const bodyMatch = prContent.match(/BODY:\s*([\s\S]+)/);
1620
+ const prTitle = titleMatch?.[1]?.trim() || `${branch}: changes`;
1621
+ const prBody = bodyMatch?.[1]?.trim() || prContent;
1622
+ console.log(chalk3.bold(" PR \u6807\u9898:"));
1623
+ console.log(` ${chalk3.cyan(prTitle)}
1624
+ `);
1625
+ console.log(chalk3.bold(" PR \u63CF\u8FF0:"));
1626
+ console.log(chalk3.dim(` ${prBody.split("\n").join("\n ")}
1627
+ `));
1628
+ try {
1629
+ const { select: selectPrompt } = await import("@inquirer/prompts");
1630
+ const action = await selectPrompt({
1631
+ message: "\u64CD\u4F5C\uFF1A",
1632
+ choices: [
1633
+ { value: "push-pr", name: "\u{1F680} \u63A8\u9001\u5E76\u521B\u5EFA PR (gh)" },
1634
+ { value: "push", name: "\u{1F4E4} \u4EC5\u63A8\u9001\u5230\u8FDC\u7A0B" },
1635
+ { value: "cancel", name: "\u274C \u53D6\u6D88" }
1636
+ ]
1637
+ });
1638
+ if (action === "cancel") {
1639
+ console.log(chalk3.dim("\n \u5DF2\u53D6\u6D88\n"));
1640
+ return;
1641
+ }
1642
+ console.log(chalk3.dim(` \u63A8\u9001 ${branch} \u2192 origin...`));
1643
+ try {
1644
+ gitPushUpstream("origin", branch);
1645
+ console.log(chalk3.green(" \u2705 \u63A8\u9001\u6210\u529F"));
1646
+ } catch (err) {
1647
+ console.log(chalk3.red(` \u63A8\u9001\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`));
1648
+ return;
1649
+ }
1650
+ if (action === "push-pr") {
1651
+ try {
1652
+ const { execFileSync } = await import("child_process");
1653
+ const ghResult = execFileSync(
1654
+ "gh",
1655
+ ["pr", "create", "--title", prTitle, "--body", prBody],
1656
+ { encoding: "utf-8", timeout: 3e4 }
1657
+ ).trim();
1658
+ console.log(chalk3.green(` \u2705 PR \u5DF2\u521B\u5EFA: ${ghResult}
1659
+ `));
1660
+ } catch (err) {
1661
+ const msg = err instanceof Error ? err.message : String(err);
1662
+ if (msg.includes("not found") || msg.includes("command not found")) {
1663
+ console.log(chalk3.yellow(" \u26A0\uFE0F gh CLI \u672A\u5B89\u88C5\uFF0C\u8BF7\u624B\u52A8\u521B\u5EFA PR"));
1664
+ console.log(chalk3.dim(" \u5B89\u88C5: brew install gh\n"));
1665
+ } else {
1666
+ console.log(chalk3.red(` \u521B\u5EFA PR \u5931\u8D25: ${msg}
1667
+ `));
1668
+ }
1669
+ }
1670
+ } else {
1671
+ console.log(chalk3.dim(" \u63A8\u9001\u5B8C\u6210\uFF0C\u8BF7\u624B\u52A8\u521B\u5EFA PR\n"));
1672
+ }
1673
+ } catch {
1674
+ console.log(chalk3.dim("\n \u5DF2\u53D6\u6D88\n"));
1675
+ }
1676
+ }
1677
+ },
1678
+ // /agent — 手动启动子代理
1679
+ {
1680
+ name: "agent",
1681
+ aliases: ["task"],
1682
+ description: "\u542F\u52A8\u5B50\u4EE3\u7406\u6267\u884C\u4EFB\u52A1\uFF08/agent <type> <\u63CF\u8FF0>\uFF09",
1683
+ category: "util",
1684
+ handler: async (args, ctx) => {
1685
+ if (args.length === 0) {
1686
+ console.log(chalk3.dim("\n \u7528\u6CD5: /agent [type] <\u4EFB\u52A1\u63CF\u8FF0>"));
1687
+ console.log(chalk3.dim(" \u7C7B\u578B: explore, code, research, plan, general"));
1688
+ console.log(chalk3.dim(" \u4F8B: /agent explore \u627E\u5230\u6240\u6709 API \u7AEF\u70B9"));
1689
+ console.log(chalk3.dim(" \u4F8B: /agent code \u6DFB\u52A0\u4E00\u4E2A health check \u7AEF\u70B9\n"));
1690
+ return;
1691
+ }
1692
+ const validTypes = ["explore", "code", "research", "plan", "general"];
1693
+ let agentType = "general";
1694
+ let description;
1695
+ if (validTypes.includes(args[0])) {
1696
+ agentType = args[0];
1697
+ description = args.slice(1).join(" ");
1698
+ } else {
1699
+ description = args.join(" ");
1700
+ }
1701
+ if (!description) {
1702
+ console.log(chalk3.dim("\n \u8BF7\u63D0\u4F9B\u4EFB\u52A1\u63CF\u8FF0\n"));
1703
+ return;
1704
+ }
1705
+ console.log(chalk3.bold(`
1706
+ \u{1F916} \u542F\u52A8\u5B50\u4EE3\u7406 [${agentType}]
1707
+ `));
1708
+ console.log(chalk3.dim(` \u4EFB\u52A1: ${description}
1709
+ `));
1710
+ const result = await runSubAgent(
1711
+ { description, type: agentType },
1712
+ ctx.getClient(),
1713
+ ctx.config,
1714
+ ctx.tools,
1715
+ ctx.systemPrompt
1716
+ );
1717
+ console.log();
1718
+ if (result.output && result.output !== "[\u5B50\u4EE3\u7406\u8D85\u65F6]") {
1719
+ console.log(chalk3.dim(" \u2500\u2500\u2500"));
1720
+ const rendered = renderMarkdown(result.output);
1721
+ process.stdout.write(rendered);
1722
+ }
1723
+ console.log(chalk3.dim(`
1724
+ \u5B50\u4EE3\u7406\u5B8C\u6210: ${(result.elapsed / 1e3).toFixed(1)}s | ${result.toolCalls.length} \u6B21\u5DE5\u5177 | ${result.tokenUsage.inputTokens + result.tokenUsage.outputTokens} tokens`));
1725
+ if (result.timedOut) {
1726
+ console.log(chalk3.yellow(" \u26A0\uFE0F \u5B50\u4EE3\u7406\u8D85\u65F6"));
1727
+ }
1728
+ console.log();
1729
+ ctx.sessionTokens.inputTokens += result.tokenUsage.inputTokens;
1730
+ ctx.sessionTokens.outputTokens += result.tokenUsage.outputTokens;
1731
+ ctx.chatHistory.push({ role: "user", content: `[\u5B50\u4EE3\u7406\u4EFB\u52A1: ${agentType}] ${description}` });
1732
+ ctx.chatHistory.push({ role: "assistant", content: result.output });
1733
+ }
1734
+ },
1735
+ // /dashboard — 打开 Dashboard 数据面板(所有终端共享同一个 URL)
1736
+ {
1737
+ name: "dashboard",
1738
+ aliases: ["db"],
1739
+ description: "\u6253\u5F00\u5F53\u524D\u7EC8\u7AEF Dashboard\uFF08\u53EF\u9009 /dashboard all \u6253\u5F00\u5171\u4EAB\u9762\u677F\uFF09",
1740
+ category: "util",
1741
+ handler: async (args) => {
1742
+ if (isSharedDashboardMode(args)) {
1743
+ const canonicalPort = await getCanonicalDashboardPort(getActivePort());
1744
+ openInBrowser(`http://127.0.0.1:${canonicalPort}/dashboard`);
1745
+ return;
1746
+ }
1747
+ openInBrowser(`http://127.0.0.1:${getActivePort()}/dashboard`);
1748
+ }
1749
+ },
1750
+ // /bg — 后台任务管理
1751
+ {
1752
+ name: "bg",
1753
+ description: "\u67E5\u770B\u540E\u53F0\u4EFB\u52A1\uFF08/bg [id] [stop|clear]\uFF09",
1754
+ category: "util",
1755
+ handler: async (args) => {
1756
+ const { listBackgroundTasks, getTaskOutput: getBgTask, stopTask, clearCompletedTasks } = await import("./background-2EGCAAQH.js");
1757
+ if (args[0] === "clear") {
1758
+ const count = clearCompletedTasks();
1759
+ console.log(chalk3.dim(`
1760
+ \u5DF2\u6E05\u7406 ${count} \u4E2A\u5DF2\u5B8C\u6210\u4EFB\u52A1
1761
+ `));
1762
+ return;
1763
+ }
1764
+ if (args[0] === "stop" && args[1]) {
1765
+ const ok = stopTask(args[1]);
1766
+ console.log(ok ? chalk3.green(`
1767
+ \u2705 \u5DF2\u53D1\u9001\u7EC8\u6B62\u4FE1\u53F7: ${args[1]}
1768
+ `) : chalk3.red(`
1769
+ \u4EFB\u52A1 ${args[1]} \u4E0D\u5B58\u5728\u6216\u5DF2\u7ED3\u675F
1770
+ `));
1771
+ return;
1772
+ }
1773
+ if (args[0]) {
1774
+ const task = getBgTask(args[0]);
1775
+ if (!task) {
1776
+ console.log(chalk3.dim(`
1777
+ \u4EFB\u52A1 ${args[0]} \u4E0D\u5B58\u5728
1778
+ `));
1779
+ return;
1780
+ }
1781
+ const elapsed = ((Date.now() - task.startTime.getTime()) / 1e3).toFixed(1);
1782
+ const statusColor = task.status === "running" ? chalk3.yellow : task.status === "done" ? chalk3.green : chalk3.red;
1783
+ console.log(chalk3.bold(`
1784
+ ${task.id} [${statusColor(task.status)}] \u2014 ${elapsed}s`));
1785
+ console.log(chalk3.dim(` \u547D\u4EE4: ${task.command}`));
1786
+ if (task.output) {
1787
+ console.log(chalk3.dim("\n --- stdout ---"));
1788
+ console.log(` ${task.output.slice(-2e3).split("\n").join("\n ")}`);
1789
+ }
1790
+ if (task.error) {
1791
+ console.log(chalk3.red("\n --- stderr ---"));
1792
+ console.log(` ${task.error.slice(-1e3).split("\n").join("\n ")}`);
1793
+ }
1794
+ console.log();
1795
+ return;
1796
+ }
1797
+ const tasks = listBackgroundTasks();
1798
+ if (tasks.length === 0) {
1799
+ console.log(chalk3.dim("\n \u6CA1\u6709\u540E\u53F0\u4EFB\u52A1\n"));
1800
+ return;
1801
+ }
1802
+ console.log(chalk3.bold("\n \u540E\u53F0\u4EFB\u52A1\uFF1A\n"));
1803
+ for (const t of tasks) {
1804
+ const elapsed = ((Date.now() - t.startTime.getTime()) / 1e3).toFixed(1);
1805
+ const statusColor = t.status === "running" ? chalk3.yellow : t.status === "done" ? chalk3.green : chalk3.red;
1806
+ const icon = t.status === "running" ? "\u23F3" : t.status === "done" ? "\u2705" : "\u274C";
1807
+ console.log(` ${icon} ${t.id} [${statusColor(t.status)}] ${elapsed}s \u2014 ${t.command.slice(0, 60)}`);
1808
+ }
1809
+ console.log(chalk3.dim("\n /bg <id> \u67E5\u770B\u8BE6\u60C5 | /bg stop <id> \u7EC8\u6B62 | /bg clear \u6E05\u7406\n"));
1810
+ }
1811
+ },
1812
+ // /diff — Git diff 可视化
1813
+ {
1814
+ name: "diff",
1815
+ description: "\u663E\u793A Git \u5DEE\u5F02\uFF08/diff [--staged] [file]\uFF09",
1816
+ category: "git",
1817
+ handler: async (args) => {
1818
+ if (!isGitRepo()) {
1819
+ console.log(chalk3.dim("\n \u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93\n"));
1820
+ return;
1821
+ }
1822
+ const staged = args.includes("--staged") || args.includes("-s");
1823
+ const fileArg = args.find((a) => !a.startsWith("-"));
1824
+ let diffOutput;
1825
+ if (fileArg) {
1826
+ diffOutput = gitDiffFile(fileArg, staged);
1827
+ } else if (staged) {
1828
+ diffOutput = gitDiffStaged();
1829
+ } else {
1830
+ diffOutput = gitDiffUnstaged();
1831
+ }
1832
+ if (!diffOutput) {
1833
+ console.log(chalk3.dim(`
1834
+ \u6CA1\u6709${staged ? "\u5DF2\u6682\u5B58" : "\u672A\u6682\u5B58"}\u7684\u53D8\u66F4
1835
+ `));
1836
+ return;
1837
+ }
1838
+ console.log();
1839
+ for (const line of diffOutput.split("\n")) {
1840
+ if (line.startsWith("+") && !line.startsWith("+++")) {
1841
+ console.log(chalk3.green(` ${line}`));
1842
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
1843
+ console.log(chalk3.red(` ${line}`));
1844
+ } else if (line.startsWith("@@")) {
1845
+ console.log(chalk3.cyan(` ${line}`));
1846
+ } else if (line.startsWith("diff ") || line.startsWith("index ")) {
1847
+ console.log(chalk3.bold(` ${line}`));
1848
+ } else {
1849
+ console.log(chalk3.dim(` ${line}`));
1850
+ }
1851
+ }
1852
+ console.log();
1853
+ }
1854
+ },
1855
+ // /branch — 分支管理
1856
+ {
1857
+ name: "branch",
1858
+ aliases: ["br"],
1859
+ description: "\u5206\u652F\u7BA1\u7406\uFF08/branch [name] [-d name]\uFF09",
1860
+ category: "git",
1861
+ handler: async (args) => {
1862
+ if (!isGitRepo()) {
1863
+ console.log(chalk3.dim("\n \u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93\n"));
1864
+ return;
1865
+ }
1866
+ if (args[0] === "-d" || args[0] === "-D") {
1867
+ const branchName = args[1];
1868
+ if (!branchName) {
1869
+ console.log(chalk3.dim("\n \u7528\u6CD5: /branch -d <\u5206\u652F\u540D>\n"));
1870
+ return;
1871
+ }
1872
+ try {
1873
+ gitDeleteBranch(branchName, args[0] === "-D");
1874
+ console.log(chalk3.green(`
1875
+ \u2705 \u5DF2\u5220\u9664\u5206\u652F ${branchName}
1876
+ `));
1877
+ } catch (err) {
1878
+ console.log(chalk3.red(`
1879
+ \u5220\u9664\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1880
+ `));
1881
+ }
1882
+ return;
1883
+ }
1884
+ if (args[0]) {
1885
+ try {
1886
+ gitCreateBranch(args[0]);
1887
+ console.log(chalk3.green(`
1888
+ \u2705 \u5DF2\u521B\u5EFA\u5E76\u5207\u6362\u5230 ${args[0]}
1889
+ `));
1890
+ } catch (err) {
1891
+ try {
1892
+ gitSwitchBranch(args[0]);
1893
+ console.log(chalk3.green(`
1894
+ \u2705 \u5DF2\u5207\u6362\u5230 ${args[0]}
1895
+ `));
1896
+ } catch (err2) {
1897
+ console.log(chalk3.red(`
1898
+ \u5207\u6362\u5931\u8D25: ${err2 instanceof Error ? err2.message : String(err2)}
1899
+ `));
1900
+ }
1901
+ }
1902
+ return;
1903
+ }
1904
+ const branches = gitListBranches();
1905
+ if (branches.length === 0) {
1906
+ console.log(chalk3.dim("\n \u6CA1\u6709\u672C\u5730\u5206\u652F\n"));
1907
+ return;
1908
+ }
1909
+ console.log(chalk3.bold("\n \u672C\u5730\u5206\u652F\uFF1A\n"));
1910
+ for (const br of branches) {
1911
+ const marker = br.current ? chalk3.green("\u25CF ") : chalk3.dim(" ");
1912
+ const name = br.current ? chalk3.green(br.name) : br.name;
1913
+ console.log(` ${marker}${name}`);
1914
+ }
1915
+ console.log();
1916
+ }
1917
+ },
1918
+ // /stash — 暂存管理
1919
+ {
1920
+ name: "stash",
1921
+ description: "Git stash \u7BA1\u7406\uFF08/stash [pop|list|drop] [message]\uFF09",
1922
+ category: "git",
1923
+ handler: async (args) => {
1924
+ if (!isGitRepo()) {
1925
+ console.log(chalk3.dim("\n \u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93\n"));
1926
+ return;
1927
+ }
1928
+ const subCmd = args[0]?.toLowerCase();
1929
+ if (subCmd === "pop") {
1930
+ try {
1931
+ const result = gitStashPop();
1932
+ console.log(chalk3.green(`
1933
+ \u2705 Stash popped
1934
+ `));
1935
+ if (result) console.log(chalk3.dim(` ${result}
1936
+ `));
1937
+ } catch (err) {
1938
+ console.log(chalk3.red(`
1939
+ Pop \u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1940
+ `));
1941
+ }
1942
+ return;
1943
+ }
1944
+ if (subCmd === "list") {
1945
+ const list = gitStashList();
1946
+ if (!list) {
1947
+ console.log(chalk3.dim("\n Stash \u4E3A\u7A7A\n"));
1948
+ } else {
1949
+ console.log(chalk3.bold("\n Stash \u5217\u8868\uFF1A\n"));
1950
+ for (const line of list.split("\n")) {
1951
+ console.log(chalk3.dim(` ${line}`));
1952
+ }
1953
+ console.log();
1954
+ }
1955
+ return;
1956
+ }
1957
+ if (subCmd === "drop") {
1958
+ const parsed = args[1] ? parseInt(args[1], 10) : NaN;
1959
+ const index = Number.isNaN(parsed) ? void 0 : parsed;
1960
+ try {
1961
+ gitStashDrop(index);
1962
+ console.log(chalk3.green(`
1963
+ \u2705 Stash dropped
1964
+ `));
1965
+ } catch (err) {
1966
+ console.log(chalk3.red(`
1967
+ Drop \u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1968
+ `));
1969
+ }
1970
+ return;
1971
+ }
1972
+ try {
1973
+ const message = args.join(" ") || void 0;
1974
+ const result = gitStash(message);
1975
+ console.log(chalk3.green(`
1976
+ \u2705 \u5DF2 stash${message ? `: ${message}` : ""}
1977
+ `));
1978
+ if (result) console.log(chalk3.dim(` ${result}
1979
+ `));
1980
+ } catch (err) {
1981
+ console.log(chalk3.red(`
1982
+ Stash \u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
1983
+ `));
1984
+ }
1985
+ }
1986
+ },
1987
+ // /worktree — Git worktree 管理
1988
+ {
1989
+ name: "worktree",
1990
+ aliases: ["wt"],
1991
+ description: "Git worktree \u7BA1\u7406\uFF08/worktree [list|remove <path>] [branch]\uFF09",
1992
+ category: "git",
1993
+ handler: async (args) => {
1994
+ if (!isGitRepo()) {
1995
+ console.log(chalk3.dim("\n \u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93\n"));
1996
+ return;
1997
+ }
1998
+ const subCmd = args[0]?.toLowerCase();
1999
+ if (subCmd === "list" || !subCmd) {
2000
+ const list = gitWorktreeList();
2001
+ if (!list) {
2002
+ console.log(chalk3.dim("\n \u6CA1\u6709\u989D\u5916\u7684 worktree\n"));
2003
+ } else {
2004
+ console.log(chalk3.bold("\n Worktrees\uFF1A\n"));
2005
+ for (const line of list.split("\n")) {
2006
+ console.log(chalk3.dim(` ${line}`));
2007
+ }
2008
+ console.log();
2009
+ }
2010
+ return;
2011
+ }
2012
+ if (subCmd === "remove" || subCmd === "rm") {
2013
+ const path = args[1];
2014
+ if (!path) {
2015
+ console.log(chalk3.dim("\n \u7528\u6CD5: /worktree remove <path>\n"));
2016
+ return;
2017
+ }
2018
+ try {
2019
+ gitWorktreeRemove(path);
2020
+ console.log(chalk3.green(`
2021
+ \u2705 Worktree removed: ${path}
2022
+ `));
2023
+ } catch (err) {
2024
+ console.log(chalk3.red(`
2025
+ \u5220\u9664\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
2026
+ `));
2027
+ }
2028
+ return;
2029
+ }
2030
+ const wtPath = args[0];
2031
+ const branch = args[1] || basename(wtPath);
2032
+ try {
2033
+ gitWorktreeAdd(wtPath, branch, true);
2034
+ console.log(chalk3.green(`
2035
+ \u2705 Worktree \u5DF2\u521B\u5EFA: ${wtPath} (\u5206\u652F: ${branch})
2036
+ `));
2037
+ } catch (err) {
2038
+ console.log(chalk3.red(`
2039
+ \u521B\u5EFA\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}
2040
+ `));
2041
+ }
2042
+ }
2043
+ },
2044
+ // /workspace — 打开 Workspace 交互工作台(绑定当前终端 REPL)
2045
+ {
2046
+ name: "workspace",
2047
+ aliases: ["ws"],
2048
+ description: "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 Workspace \u4EA4\u4E92\u5DE5\u4F5C\u53F0",
2049
+ category: "util",
2050
+ handler: async () => {
2051
+ openInBrowser(`http://127.0.0.1:${getActivePort()}/workspace`);
2052
+ }
2053
+ },
2054
+ // /tools — 列出所有已注册工具
2055
+ {
2056
+ name: "tools",
2057
+ description: "\u5217\u51FA\u6240\u6709\u5DF2\u6CE8\u518C\u5DE5\u5177\uFF08\u6309\u6765\u6E90\u5206\u7EC4\uFF09",
2058
+ category: "debug",
2059
+ handler: async (_args, ctx) => {
2060
+ const builtinNames = [
2061
+ "file_read",
2062
+ "file_write",
2063
+ "file_edit",
2064
+ "bash",
2065
+ "task_output",
2066
+ "glob",
2067
+ "grep",
2068
+ "web_fetch",
2069
+ "web_search",
2070
+ "memory_save",
2071
+ "memory_search",
2072
+ "notebook_read",
2073
+ "notebook_edit",
2074
+ "sub_agent"
2075
+ ];
2076
+ const builtin = [];
2077
+ const mcp = [];
2078
+ const plugin = [];
2079
+ for (const t of ctx.tools) {
2080
+ const name = t.definition.name;
2081
+ if (builtinNames.includes(name)) {
2082
+ builtin.push(name);
2083
+ } else if (name.startsWith("mcp_")) {
2084
+ mcp.push(name);
2085
+ } else {
2086
+ plugin.push(name);
2087
+ }
2088
+ }
2089
+ console.log(chalk3.bold(`
2090
+ \u5DF2\u6CE8\u518C\u5DE5\u5177 (${ctx.tools.length}):
2091
+ `));
2092
+ if (builtin.length) {
2093
+ console.log(chalk3.dim(` \u5185\u7F6E (${builtin.length}):`));
2094
+ for (let i = 0; i < builtin.length; i += 4) {
2095
+ console.log(" " + builtin.slice(i, i + 4).join(", "));
2096
+ }
2097
+ }
2098
+ if (plugin.length) {
2099
+ console.log(chalk3.dim(`
2100
+ \u63D2\u4EF6 (${plugin.length}):`));
2101
+ for (const p of plugin) console.log(` ${p}`);
2102
+ }
2103
+ if (mcp.length) {
2104
+ console.log(chalk3.dim(`
2105
+ MCP (${mcp.length}):`));
2106
+ for (const m of mcp) console.log(` ${m}`);
2107
+ }
2108
+ console.log();
2109
+ }
2110
+ },
2111
+ // /doctor — 系统健康检查
2112
+ {
2113
+ name: "doctor",
2114
+ description: "\u7CFB\u7EDF\u5065\u5EB7\u8BCA\u65AD",
2115
+ category: "debug",
2116
+ handler: async (_args, ctx) => {
2117
+ const { execSync: execSyncDoc } = await import("child_process");
2118
+ console.log(chalk3.bold("\n \u{1F3E5} \u7CFB\u7EDF\u8BCA\u65AD\n"));
2119
+ const checks = [];
2120
+ const nodeVer = process.version;
2121
+ const nodeMajor = parseInt(nodeVer.slice(1), 10);
2122
+ checks.push({ label: "Node.js", ok: nodeMajor >= 18, detail: `${nodeVer} ${nodeMajor >= 18 ? "" : "(\u9700\u8981 \u2265 18)"}` });
2123
+ const hasKey = !!ctx.config.modelConfig.apiKey;
2124
+ checks.push({ label: "API Key", ok: hasKey, detail: hasKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E" });
2125
+ checks.push({ label: "\u6A21\u578B", ok: true, detail: `${ctx.config.modelConfig.model} (${ctx.config.modelConfig.provider})` });
2126
+ let hasRg = false;
2127
+ try {
2128
+ execSyncDoc("rg --version", { stdio: "pipe" });
2129
+ hasRg = true;
2130
+ } catch {
2131
+ }
2132
+ checks.push({ label: "ripgrep", ok: hasRg, detail: hasRg ? "\u53EF\u7528" : "\u672A\u5B89\u88C5\uFF08\u4F7F\u7528 Node.js \u56DE\u9000\uFF09" });
2133
+ const hasTavily = !!ctx.config.tavilyApiKey;
2134
+ checks.push({ label: "Tavily", ok: hasTavily, detail: hasTavily ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E\uFF08web_search \u4E0D\u53EF\u7528\uFF09" });
2135
+ const cfgPath = join2(HYPERCORE_DIR, "config.toml");
2136
+ checks.push({ label: "\u914D\u7F6E\u6587\u4EF6", ok: existsSync2(cfgPath), detail: existsSync2(cfgPath) ? cfgPath : "\u672A\u627E\u5230" });
2137
+ const hPaths = [
2138
+ join2(HYPERCORE_DIR, "HYPER.md"),
2139
+ join2(process.cwd(), "HYPER.md"),
2140
+ join2(process.cwd(), "CLAUDE.md")
2141
+ ];
2142
+ const foundH = hPaths.find((p) => existsSync2(p));
2143
+ checks.push({ label: "\u7CFB\u7EDF\u6307\u4EE4", ok: !!foundH, detail: foundH ? basename(foundH) : "\u672A\u627E\u5230 HYPER.md/CLAUDE.md" });
2144
+ const hasHooksFile = existsSync2(join2(HYPERCORE_DIR, "hooks.json")) || existsSync2(join2(process.cwd(), ".hypercore", "hooks.json"));
2145
+ checks.push({ label: "Hooks", ok: true, detail: hasHooksFile ? `${hookManager.count} \u4E2A\u5DF2\u6CE8\u518C` : "\u65E0\u914D\u7F6E\u6587\u4EF6" });
2146
+ const hasMcpFile = existsSync2(join2(HYPERCORE_DIR, "mcp.json")) || existsSync2(join2(process.cwd(), ".hypercore", "mcp.json"));
2147
+ const mcpToolCount = ctx.tools.filter((t) => t.definition.name.startsWith("mcp_")).length;
2148
+ checks.push({ label: "MCP", ok: true, detail: hasMcpFile ? `${mcpToolCount} \u4E2A\u5DE5\u5177\u5DF2\u8FDE\u63A5` : "\u65E0\u914D\u7F6E" });
2149
+ checks.push({ label: "\u5DE5\u5177\u603B\u6570", ok: ctx.tools.length > 0, detail: `${ctx.tools.length} \u4E2A` });
2150
+ for (const c of checks) {
2151
+ const icon = c.ok ? chalk3.green("\u2713") : chalk3.yellow("\u26A0");
2152
+ console.log(` ${icon} ${c.label.padEnd(12)} ${chalk3.dim(c.detail)}`);
2153
+ }
2154
+ const passCount = checks.filter((c) => c.ok).length;
2155
+ console.log(chalk3.dim(`
2156
+ \u8BCA\u65AD\u5B8C\u6210: ${passCount}/${checks.length} \u901A\u8FC7
2157
+ `));
2158
+ }
2159
+ },
2160
+ // /init — 初始化项目配置
2161
+ {
2162
+ name: "init",
2163
+ description: "\u521D\u59CB\u5316\u9879\u76EE .hypercore/ \u914D\u7F6E\u76EE\u5F55",
2164
+ category: "util",
2165
+ handler: async () => {
2166
+ const { mkdirSync, writeFileSync } = await import("fs");
2167
+ const projectDir = join2(process.cwd(), ".hypercore");
2168
+ console.log(chalk3.bold("\n \u{1F680} \u521D\u59CB\u5316\u9879\u76EE\u914D\u7F6E\n"));
2169
+ if (existsSync2(projectDir)) {
2170
+ console.log(chalk3.dim(" .hypercore/ \u76EE\u5F55\u5DF2\u5B58\u5728"));
2171
+ } else {
2172
+ mkdirSync(projectDir, { recursive: true });
2173
+ console.log(chalk3.green(" \u2713 \u521B\u5EFA .hypercore/"));
2174
+ }
2175
+ const subdirs = ["commands", "rules", "skills", "tools"];
2176
+ for (const sub of subdirs) {
2177
+ const dir = join2(projectDir, sub);
2178
+ if (!existsSync2(dir)) {
2179
+ mkdirSync(dir, { recursive: true });
2180
+ console.log(chalk3.green(` \u2713 \u521B\u5EFA .hypercore/${sub}/`));
2181
+ }
2182
+ }
2183
+ const hyperPath = join2(process.cwd(), "HYPER.md");
2184
+ if (!existsSync2(hyperPath)) {
2185
+ writeFileSync(hyperPath, [
2186
+ "# \u9879\u76EE\u6307\u4EE4",
2187
+ "",
2188
+ "<!-- \u5728\u6B64\u7F16\u5199\u9879\u76EE\u7EA7\u7CFB\u7EDF\u6307\u4EE4 -->",
2189
+ "",
2190
+ `\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55: ${process.cwd()}`,
2191
+ ""
2192
+ ].join("\n"), "utf-8");
2193
+ console.log(chalk3.green(" \u2713 \u521B\u5EFA HYPER.md"));
2194
+ }
2195
+ const ignorePath = join2(process.cwd(), ".hyperignore");
2196
+ if (!existsSync2(ignorePath)) {
2197
+ writeFileSync(ignorePath, [
2198
+ "# Hypercore \u5FFD\u7565\u89C4\u5219",
2199
+ "node_modules/",
2200
+ "dist/",
2201
+ "build/",
2202
+ ".git/",
2203
+ "*.log",
2204
+ ""
2205
+ ].join("\n"), "utf-8");
2206
+ console.log(chalk3.green(" \u2713 \u521B\u5EFA .hyperignore"));
2207
+ }
2208
+ const hooksFile = join2(projectDir, "hooks.json");
2209
+ if (!existsSync2(hooksFile)) {
2210
+ writeFileSync(hooksFile, JSON.stringify({ hooks: [] }, null, 2), "utf-8");
2211
+ console.log(chalk3.green(" \u2713 \u521B\u5EFA .hypercore/hooks.json"));
2212
+ }
2213
+ console.log(chalk3.bold("\n \u2705 \u9879\u76EE\u521D\u59CB\u5316\u5B8C\u6210\n"));
2214
+ }
2215
+ },
2216
+ // /plan — 规划模式
2217
+ {
2218
+ name: "plan",
2219
+ description: "\u542F\u52A8\u89C4\u5212\u6A21\u5F0F\uFF08\u63A2\u7D22\u2192\u65B9\u6848\u2192\u5BA1\u6279\u2192\u6267\u884C\uFF09",
2220
+ category: "util",
2221
+ usage: "/plan <\u4EFB\u52A1\u63CF\u8FF0>",
2222
+ examples: ["/plan \u91CD\u6784\u8BA4\u8BC1\u6A21\u5757", "/plan \u6DFB\u52A0\u6697\u8272\u6A21\u5F0F"],
2223
+ handler: async (args, ctx) => {
2224
+ if (args.length === 0) {
2225
+ console.log(chalk3.dim("\n \u7528\u6CD5: /plan <\u4EFB\u52A1\u63CF\u8FF0>"));
2226
+ console.log(chalk3.dim(" \u5DE5\u4F5C\u6D41: \u63A2\u7D22\u4EE3\u7801 \u2192 \u8F93\u51FA\u65B9\u6848 \u2192 \u7528\u6237\u5BA1\u6279 \u2192 \u6267\u884C\n"));
2227
+ return;
2228
+ }
2229
+ const description = args.join(" ");
2230
+ console.log(chalk3.bold("\n \u{1F4CB} \u89C4\u5212\u6A21\u5F0F\n"));
2231
+ console.log(chalk3.dim(` \u4EFB\u52A1: ${description}`));
2232
+ console.log(chalk3.dim(" \u9636\u6BB51: \u63A2\u7D22\u4EE3\u7801\uFF0C\u5236\u5B9A\u65B9\u6848...\n"));
2233
+ const planResult = await runSubAgent(
2234
+ {
2235
+ description: `\u8BF7\u5206\u6790\u4EE5\u4E0B\u4EFB\u52A1\uFF0C\u63A2\u7D22\u76F8\u5173\u4EE3\u7801\uFF0C\u8F93\u51FA\u8BE6\u7EC6\u7684\u5B9E\u65BD\u65B9\u6848\uFF1A
2236
+
2237
+ ${description}
2238
+
2239
+ \u65B9\u6848\u5E94\u5305\u542B\uFF1A
2240
+ 1. \u9700\u8981\u4FEE\u6539\u7684\u6587\u4EF6\u5217\u8868
2241
+ 2. \u6BCF\u4E2A\u6587\u4EF6\u7684\u5177\u4F53\u6539\u52A8
2242
+ 3. \u5B9E\u65BD\u6B65\u9AA4
2243
+ 4. \u98CE\u9669\u4E0E\u6CE8\u610F\u4E8B\u9879`,
2244
+ type: "plan",
2245
+ planMode: true
2246
+ },
2247
+ ctx.getClient(),
2248
+ ctx.config,
2249
+ ctx.tools,
2250
+ ctx.systemPrompt
2251
+ );
2252
+ console.log();
2253
+ if (planResult.output && planResult.output !== "[\u5B50\u4EE3\u7406\u8D85\u65F6]") {
2254
+ console.log(chalk3.dim(" \u2500\u2500\u2500 \u65B9\u6848 \u2500\u2500\u2500"));
2255
+ const rendered = renderMarkdown(planResult.output);
2256
+ process.stdout.write(rendered);
2257
+ }
2258
+ console.log(chalk3.dim(`
2259
+ \u89C4\u5212\u5B8C\u6210: ${(planResult.elapsed / 1e3).toFixed(1)}s
2260
+ `));
2261
+ const checkpointResult = await handleCheckpoint("approval", planResult.output, "\u662F\u5426\u6309\u6B64\u65B9\u6848\u6267\u884C?");
2262
+ if (checkpointResult.action === "approve") {
2263
+ console.log(chalk3.dim("\n \u9636\u6BB52: \u6309\u65B9\u6848\u6267\u884C\u4E2D...\n"));
2264
+ const execResult = await runSubAgent(
2265
+ {
2266
+ description: `\u6309\u7167\u4EE5\u4E0B\u65B9\u6848\u6267\u884C\u4FEE\u6539\uFF1A
2267
+
2268
+ ${planResult.output}${checkpointResult.feedback ? `
2269
+
2270
+ \u7528\u6237\u53CD\u9988: ${checkpointResult.feedback}` : ""}`,
2271
+ type: "code"
2272
+ },
2273
+ ctx.getClient(),
2274
+ ctx.config,
2275
+ ctx.tools,
2276
+ ctx.systemPrompt
2277
+ );
2278
+ console.log();
2279
+ if (execResult.output) {
2280
+ const rendered = renderMarkdown(execResult.output);
2281
+ process.stdout.write(rendered);
2282
+ }
2283
+ console.log(chalk3.green(`
2284
+ \u2705 \u6267\u884C\u5B8C\u6210 (${(execResult.elapsed / 1e3).toFixed(1)}s)
2285
+ `));
2286
+ ctx.sessionTokens.inputTokens += execResult.tokenUsage.inputTokens;
2287
+ ctx.sessionTokens.outputTokens += execResult.tokenUsage.outputTokens;
2288
+ ctx.chatHistory.push({ role: "user", content: `[\u89C4\u5212\u6267\u884C] ${description}` });
2289
+ ctx.chatHistory.push({ role: "assistant", content: execResult.output });
2290
+ } else {
2291
+ console.log(chalk3.dim("\n \u5DF2\u53D6\u6D88\n"));
2292
+ }
2293
+ ctx.sessionTokens.inputTokens += planResult.tokenUsage.inputTokens;
2294
+ ctx.sessionTokens.outputTokens += planResult.tokenUsage.outputTokens;
2295
+ }
2296
+ },
2297
+ // /permissions — 权限管理
2298
+ {
2299
+ name: "permissions",
2300
+ aliases: ["perm"],
2301
+ description: "\u663E\u793A/\u5207\u6362\u6743\u9650\u6A21\u5F0F\uFF08full/safe/ask\uFF09",
2302
+ category: "debug",
2303
+ usage: "/permissions [full|safe|ask]",
2304
+ handler: async (args, ctx) => {
2305
+ const { getToolPermission } = await import("./permissions-JUKXMNDH.js");
2306
+ const mode = args[0];
2307
+ if (mode && ["full", "safe", "ask"].includes(mode)) {
2308
+ ctx.config.permissionMode = mode;
2309
+ const modeDesc = {
2310
+ full: "\u6240\u6709\u5DE5\u5177\u76F4\u63A5\u6267\u884C",
2311
+ safe: "\u4EC5\u5B89\u5168\u5DE5\u5177\u53EF\u7528",
2312
+ ask: "\u5199\u5165/\u5371\u9669\u64CD\u4F5C\u9700\u786E\u8BA4"
2313
+ };
2314
+ console.log(chalk3.green(`
2315
+ \u2705 \u6743\u9650\u6A21\u5F0F: ${mode} \u2014 ${modeDesc[mode]}
2316
+ `));
2317
+ return;
2318
+ }
2319
+ const currentMode = ctx.config.permissionMode || "full";
2320
+ console.log(chalk3.bold(`
2321
+ \u6743\u9650\u6A21\u5F0F: ${currentMode}
2322
+ `));
2323
+ const levels = { safe: [], write: [], dangerous: [] };
2324
+ for (const t of ctx.tools) {
2325
+ const level = getToolPermission(t.definition.name);
2326
+ levels[level].push(t.definition.name);
2327
+ }
2328
+ console.log(chalk3.green(` \u{1F7E2} safe (${levels.safe.length}): ${levels.safe.join(", ")}`));
2329
+ console.log(chalk3.yellow(` \u{1F7E1} write (${levels.write.length}): ${levels.write.join(", ")}`));
2330
+ console.log(chalk3.red(` \u{1F534} dangerous (${levels.dangerous.length}): ${levels.dangerous.join(", ")}`));
2331
+ console.log(chalk3.dim("\n \u5207\u6362: /permissions full | safe | ask\n"));
2332
+ }
2333
+ },
2334
+ // /skills — 列出可用技能
2335
+ {
2336
+ name: "skills",
2337
+ description: "\u5217\u51FA\u6240\u6709\u53EF\u7528\u6280\u80FD",
2338
+ category: "util",
2339
+ handler: async () => {
2340
+ const { listAvailableSkills } = await import("./skills-V4A35XKG.js");
2341
+ const skills = await listAvailableSkills();
2342
+ if (skills.length === 0) {
2343
+ console.log(chalk3.dim("\n \u6682\u65E0\u53EF\u7528\u6280\u80FD"));
2344
+ console.log(chalk3.dim(" \u521B\u5EFA: ~/.hypercore/skills/ \u6216 .hypercore/skills/ \u4E0B\u653E .skill.md \u6587\u4EF6\n"));
2345
+ return;
2346
+ }
2347
+ console.log(chalk3.bold(`
2348
+ \u53EF\u7528\u6280\u80FD (${skills.length}):
2349
+ `));
2350
+ for (const s of skills) {
2351
+ const aliasStr = s.aliases?.length ? ` (${s.aliases.join(", ")})` : "";
2352
+ const toolsStr = s.tools?.length ? chalk3.dim(` [${s.tools.join(", ")}]`) : "";
2353
+ console.log(` /${s.name}${aliasStr} \u2014 ${s.description}${toolsStr} ${chalk3.dim(`[${s.source}]`)}`);
2354
+ }
2355
+ console.log();
2356
+ }
2357
+ },
2358
+ // ===== UI/UX 命令 =====
2359
+ // /theme — 切换主题
2360
+ {
2361
+ name: "theme",
2362
+ description: "\u5207\u6362\u7EC8\u7AEF\u4E3B\u9898\uFF08dark/light/high-contrast\uFF09",
2363
+ category: "util",
2364
+ usage: "/theme [dark|light|high-contrast]",
2365
+ handler: async (args) => {
2366
+ const { getThemeName, setTheme: setTheme2, getAvailableThemes, THEME_LABELS } = await import("./theme-3SYJ3UQA.js");
2367
+ if (!args[0]) {
2368
+ console.log(chalk3.bold(`
2369
+ \u5F53\u524D\u4E3B\u9898: ${THEME_LABELS[getThemeName()]}
2370
+ `));
2371
+ console.log(chalk3.dim(" \u53EF\u7528\u4E3B\u9898:"));
2372
+ for (const t of getAvailableThemes()) {
2373
+ const mark = t === getThemeName() ? chalk3.green(" \u2713") : "";
2374
+ console.log(` ${THEME_LABELS[t]}${mark}`);
2375
+ }
2376
+ console.log(chalk3.dim(`
2377
+ \u7528\u6CD5: /theme <\u540D\u79F0>
2378
+ `));
2379
+ return;
2380
+ }
2381
+ const name = args[0].toLowerCase();
2382
+ if (name === "dark" || name === "light" || name === "high-contrast") {
2383
+ setTheme2(name);
2384
+ config.uiConfig = config.uiConfig || {};
2385
+ config.uiConfig.theme = name;
2386
+ console.log(chalk3.green(`
2387
+ \u2713 \u4E3B\u9898\u5DF2\u5207\u6362\u4E3A: ${THEME_LABELS[name]}
2388
+ `));
2389
+ } else {
2390
+ console.log(chalk3.red(`
2391
+ \u672A\u77E5\u4E3B\u9898: ${name}\u3002\u53EF\u9009: dark, light, high-contrast
2392
+ `));
2393
+ }
2394
+ }
2395
+ },
2396
+ // /todo — 任务列表
2397
+ {
2398
+ name: "todo",
2399
+ aliases: ["td"],
2400
+ description: "\u7BA1\u7406\u5F85\u529E\u5217\u8868",
2401
+ category: "util",
2402
+ usage: "/todo [add <\u5185\u5BB9> | done <#> | remove <#> | clear]",
2403
+ handler: async (args) => {
2404
+ const { readFile: rf, writeFile: wf } = await import("fs/promises");
2405
+ const todosPath = join2(HYPERCORE_DIR, "todos.json");
2406
+ let todos = [];
2407
+ try {
2408
+ if (existsSync2(todosPath)) {
2409
+ todos = JSON.parse(await rf(todosPath, "utf-8"));
2410
+ }
2411
+ } catch {
2412
+ }
2413
+ const save = async () => {
2414
+ await wf(todosPath, JSON.stringify(todos, null, 2));
2415
+ };
2416
+ const sub = args[0]?.toLowerCase();
2417
+ if (sub === "add" && args.length > 1) {
2418
+ const text = args.slice(1).join(" ");
2419
+ const id = todos.length > 0 ? Math.max(...todos.map((t) => t.id)) + 1 : 1;
2420
+ todos.push({ id, text, done: false, createdAt: (/* @__PURE__ */ new Date()).toISOString() });
2421
+ await save();
2422
+ console.log(chalk3.green(`
2423
+ \u2713 \u5DF2\u6DFB\u52A0 #${id}: ${text}
2424
+ `));
2425
+ return;
2426
+ }
2427
+ if (sub === "done" && args[1]) {
2428
+ const num = parseInt(args[1]);
2429
+ const item = todos.find((t) => t.id === num);
2430
+ if (item) {
2431
+ item.done = !item.done;
2432
+ await save();
2433
+ console.log(chalk3.green(`
2434
+ \u2713 #${num} ${item.done ? "\u5DF2\u5B8C\u6210" : "\u5DF2\u91CD\u5F00"}
2435
+ `));
2436
+ } else {
2437
+ console.log(chalk3.red(`
2438
+ \u672A\u627E\u5230 #${num}
2439
+ `));
2440
+ }
2441
+ return;
2442
+ }
2443
+ if (sub === "remove" && args[1]) {
2444
+ const num = parseInt(args[1]);
2445
+ const idx = todos.findIndex((t) => t.id === num);
2446
+ if (idx >= 0) {
2447
+ const removed = todos.splice(idx, 1)[0];
2448
+ await save();
2449
+ console.log(chalk3.green(`
2450
+ \u2713 \u5DF2\u5220\u9664 #${num}: ${removed.text}
2451
+ `));
2452
+ } else {
2453
+ console.log(chalk3.red(`
2454
+ \u672A\u627E\u5230 #${num}
2455
+ `));
2456
+ }
2457
+ return;
2458
+ }
2459
+ if (sub === "clear") {
2460
+ const before = todos.length;
2461
+ todos = todos.filter((t) => !t.done);
2462
+ await save();
2463
+ console.log(chalk3.green(`
2464
+ \u2713 \u5DF2\u6E05\u9664 ${before - todos.length} \u4E2A\u5DF2\u5B8C\u6210\u9879
2465
+ `));
2466
+ return;
2467
+ }
2468
+ if (todos.length === 0) {
2469
+ console.log(chalk3.dim("\n \u5F85\u529E\u5217\u8868\u4E3A\u7A7A\u3002\u4F7F\u7528 /todo add <\u5185\u5BB9> \u6DFB\u52A0\n"));
2470
+ return;
2471
+ }
2472
+ const doneCount = todos.filter((t) => t.done).length;
2473
+ console.log(chalk3.bold(`
2474
+ \u5F85\u529E\u5217\u8868 (${doneCount}/${todos.length}):
2475
+ `));
2476
+ for (const t of todos) {
2477
+ const icon = t.done ? "\u2705" : "\u2B1C";
2478
+ const text = t.done ? chalk3.dim.strikethrough(t.text) : chalk3.white(t.text);
2479
+ console.log(` ${icon} ${chalk3.dim(`${t.id}.`)} ${text}`);
2480
+ }
2481
+ console.log();
2482
+ }
2483
+ },
2484
+ // /vim — 切换 Vim 模式
2485
+ {
2486
+ name: "vim",
2487
+ description: "\u5207\u6362 Vim \u7F16\u8F91\u6A21\u5F0F",
2488
+ category: "util",
2489
+ handler: async (_args, ctx) => {
2490
+ const uiConf = ctx.config.uiConfig || {};
2491
+ uiConf.vimMode = !uiConf.vimMode;
2492
+ ctx.config.uiConfig = uiConf;
2493
+ if (uiConf.vimMode) {
2494
+ console.log(chalk3.green("\n \u2713 Vim \u6A21\u5F0F\u5DF2\u5F00\u542F\uFF08Esc=Normal, i=Insert\uFF09\n"));
2495
+ } else {
2496
+ console.log(chalk3.yellow("\n \u2713 Vim \u6A21\u5F0F\u5DF2\u5173\u95ED\n"));
2497
+ }
2498
+ }
2499
+ },
2500
+ // /notify — 桌面通知开关 + 测试
2501
+ {
2502
+ name: "notify",
2503
+ description: "\u684C\u9762\u901A\u77E5\u8BBE\u7F6E",
2504
+ category: "util",
2505
+ usage: "/notify [on|off|test]",
2506
+ handler: async (args) => {
2507
+ const { isNotificationsEnabled, setNotificationsEnabled: setNotificationsEnabled2, sendNotification } = await import("./notify-HPTALZDC.js");
2508
+ const sub = args[0]?.toLowerCase();
2509
+ if (sub === "on") {
2510
+ setNotificationsEnabled2(true);
2511
+ console.log(chalk3.green("\n \u2713 \u684C\u9762\u901A\u77E5\u5DF2\u5F00\u542F\n"));
2512
+ } else if (sub === "off") {
2513
+ setNotificationsEnabled2(false);
2514
+ console.log(chalk3.yellow("\n \u2713 \u684C\u9762\u901A\u77E5\u5DF2\u5173\u95ED\n"));
2515
+ } else if (sub === "test") {
2516
+ sendNotification("Hypercore", "\u8FD9\u662F\u4E00\u6761\u6D4B\u8BD5\u901A\u77E5 \u{1F41A}", "\u901A\u77E5\u6D4B\u8BD5");
2517
+ console.log(chalk3.green("\n \u2713 \u6D4B\u8BD5\u901A\u77E5\u5DF2\u53D1\u9001\n"));
2518
+ } else {
2519
+ console.log(chalk3.bold(`
2520
+ \u684C\u9762\u901A\u77E5: ${isNotificationsEnabled() ? chalk3.green("\u5F00\u542F") : chalk3.red("\u5173\u95ED")}`));
2521
+ console.log(chalk3.dim(" \u7528\u6CD5: /notify on|off|test\n"));
2522
+ }
2523
+ }
2524
+ },
2525
+ // /palette — 命令面板(支持模糊搜索)
2526
+ {
2527
+ name: "palette",
2528
+ aliases: ["cmdk"],
2529
+ description: "\u547D\u4EE4\u9762\u677F\uFF08Ctrl+K\uFF09",
2530
+ category: "util",
2531
+ usage: "/palette [\u5173\u952E\u8BCD]",
2532
+ handler: async (args, ctx) => {
2533
+ const allCommands = registry.list().filter((c) => c.name !== "palette");
2534
+ const rankCommands = (query2) => allCommands.map((c) => {
2535
+ if (!query2) return { cmd: c, score: 0 };
2536
+ const q = query2.toLowerCase();
2537
+ const aliasText = (c.aliases || []).join(" ").toLowerCase();
2538
+ const name = c.name.toLowerCase();
2539
+ const desc = c.description.toLowerCase();
2540
+ const keyText = `${name} ${aliasText} ${desc}`.trim();
2541
+ let score = editDistance(q, name);
2542
+ if (name.startsWith(q)) score -= 4;
2543
+ else if (name.includes(q)) score -= 2;
2544
+ else if (aliasText.includes(q)) score -= 1;
2545
+ else if (desc.includes(q)) score -= 1;
2546
+ if (!keyText.includes(q)) score += 2;
2547
+ return { cmd: c, score };
2548
+ }).sort((a, b) => a.score - b.score).slice(0, 20);
2549
+ let query = args.join(" ").trim();
2550
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
2551
+ const rankedText = rankCommands(query);
2552
+ if (rankedText.length === 0) {
2553
+ console.log(chalk3.dim("\n \u6CA1\u6709\u5339\u914D\u7684\u547D\u4EE4\n"));
2554
+ return;
2555
+ }
2556
+ console.log(chalk3.bold("\n \u547D\u4EE4\u9762\u677F\uFF08\u975E\u4EA4\u4E92\u6A21\u5F0F\uFF09\n"));
2557
+ for (const { cmd } of rankedText.slice(0, 10)) {
2558
+ console.log(` ${chalk3.cyan("/" + cmd.name.padEnd(14))} ${chalk3.dim(cmd.description)}`);
2559
+ }
2560
+ console.log(chalk3.dim("\n \u5728\u4EA4\u4E92\u7EC8\u7AEF\u4E2D\u53EF\u4F7F\u7528 Ctrl+K \u8FDB\u5165\u53EF\u9009\u62E9\u9762\u677F\n"));
2561
+ return;
2562
+ }
2563
+ const { input: askInput, select: askSelect } = await import("@inquirer/prompts");
2564
+ if (!query) {
2565
+ query = (await askInput({
2566
+ message: "\u547D\u4EE4\u9762\u677F\uFF1A\u8F93\u5165\u5173\u952E\u8BCD\uFF08\u7559\u7A7A\u67E5\u770B\u5168\u90E8\uFF09",
2567
+ default: ""
2568
+ })).trim();
2569
+ }
2570
+ const ranked = rankCommands(query);
2571
+ if (ranked.length === 0) {
2572
+ console.log(chalk3.dim("\n \u6CA1\u6709\u5339\u914D\u7684\u547D\u4EE4\n"));
2573
+ return;
2574
+ }
2575
+ const selectedName = await askSelect({
2576
+ message: "\u9009\u62E9\u547D\u4EE4",
2577
+ pageSize: 12,
2578
+ choices: [
2579
+ ...ranked.map(({ cmd }) => ({
2580
+ value: cmd.name,
2581
+ name: `/${cmd.name.padEnd(14)} ${cmd.description}`
2582
+ })),
2583
+ { value: "__cancel__", name: "\u53D6\u6D88" }
2584
+ ]
2585
+ });
2586
+ if (selectedName === "__cancel__") {
2587
+ console.log(chalk3.dim("\n \u5DF2\u53D6\u6D88\n"));
2588
+ return;
2589
+ }
2590
+ const selected = registry.get(String(selectedName));
2591
+ if (!selected) {
2592
+ console.log(chalk3.red("\n \u9009\u62E9\u7684\u547D\u4EE4\u4E0D\u5B58\u5728\n"));
2593
+ return;
2594
+ }
2595
+ const action = await askSelect({
2596
+ message: `/${selected.name}`,
2597
+ choices: [
2598
+ { value: "run", name: "\u6267\u884C\u547D\u4EE4" },
2599
+ { value: "help", name: "\u67E5\u770B\u5E2E\u52A9" },
2600
+ { value: "cancel", name: "\u53D6\u6D88" }
2601
+ ]
2602
+ });
2603
+ if (action === "cancel") {
2604
+ console.log(chalk3.dim("\n \u5DF2\u53D6\u6D88\n"));
2605
+ return;
2606
+ }
2607
+ if (action === "help") {
2608
+ showCommandHelpDetail(selected);
2609
+ return;
2610
+ }
2611
+ try {
2612
+ await selected.handler([], ctx);
2613
+ } catch (err) {
2614
+ const errMsg = err instanceof Error ? err.message : String(err);
2615
+ showError(errMsg);
2616
+ }
2617
+ }
2618
+ },
2619
+ // /keys — 快捷键列表
2620
+ {
2621
+ name: "keys",
2622
+ aliases: ["keybindings"],
2623
+ description: "\u67E5\u770B\u5FEB\u6377\u952E\u7ED1\u5B9A",
2624
+ category: "util",
2625
+ handler: async () => {
2626
+ const { listKeyBindings } = await import("./keybindings-JAAMLH3G.js");
2627
+ const bindings = listKeyBindings();
2628
+ console.log(chalk3.bold("\n \u5FEB\u6377\u952E\u7ED1\u5B9A:\n"));
2629
+ for (const b of bindings) {
2630
+ console.log(` ${chalk3.cyan(b.combo.padEnd(12))} \u2192 /${b.command}`);
2631
+ }
2632
+ console.log(chalk3.dim(`
2633
+ \u81EA\u5B9A\u4E49: ~/.hypercore/keybindings.json
2634
+ `));
2635
+ }
2636
+ }
2637
+ ]);
2638
+ }
2639
+ function getLocalIP() {
2640
+ try {
2641
+ const nets = os.networkInterfaces();
2642
+ for (const name of Object.keys(nets)) {
2643
+ for (const net of nets[name] || []) {
2644
+ if (net.family === "IPv4" && !net.internal) {
2645
+ return net.address;
2646
+ }
2647
+ }
2648
+ }
2649
+ } catch {
2650
+ }
2651
+ return "localhost";
2652
+ }
2653
+ function openInBrowser(url) {
2654
+ import("child_process").then(({ exec }) => {
2655
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2656
+ exec(`${cmd} ${url}`);
2657
+ console.log(chalk3.green(`
2658
+ \u2713 \u5DF2\u6253\u5F00 ${url}
2659
+ `));
2660
+ }).catch(() => {
2661
+ console.log(chalk3.yellow(`
2662
+ \u8BF7\u624B\u52A8\u6253\u5F00: ${url}
2663
+ `));
2664
+ });
2665
+ }
2666
+ async function readMultiLine(rl, firstLine) {
2667
+ const promptContinue = chalk3.dim("... ");
2668
+ if (firstLine.trim() === '"""' || firstLine.trim().startsWith('"""')) {
2669
+ const content = firstLine.trim().slice(3);
2670
+ const lines = content ? [content] : [];
2671
+ while (true) {
2672
+ const next = await new Promise((resolve2) => {
2673
+ rl.question(promptContinue, resolve2);
2674
+ });
2675
+ if (next.trim().endsWith('"""')) {
2676
+ const lastContent = next.trim().slice(0, -3);
2677
+ if (lastContent) lines.push(lastContent);
2678
+ break;
2679
+ }
2680
+ lines.push(next);
2681
+ }
2682
+ return lines.join("\n");
2683
+ }
2684
+ if (firstLine.endsWith("\\")) {
2685
+ const lines = [firstLine.slice(0, -1)];
2686
+ while (true) {
2687
+ const next = await new Promise((resolve2) => {
2688
+ rl.question(promptContinue, resolve2);
2689
+ });
2690
+ if (!next.endsWith("\\")) {
2691
+ lines.push(next);
2692
+ break;
2693
+ }
2694
+ lines.push(next.slice(0, -1));
2695
+ }
2696
+ return lines.join("\n");
2697
+ }
2698
+ return firstLine;
2699
+ }
2700
+ async function startREPL(options) {
2701
+ if (!existsSync2(HYPERCORE_DIR)) {
2702
+ showError("\u8BF7\u5148\u8FD0\u884C hyper init \u521D\u59CB\u5316");
2703
+ process.exit(1);
2704
+ }
2705
+ const config = await loadConfig();
2706
+ let client = config.modelConfig.sdkType === "openai" ? createOpenAIClient(config.modelConfig) : createLLMClient(config.modelConfig);
2707
+ const engine = new Engine(client, config, HYPERCORE_DIR);
2708
+ const tools = await createToolRegistry(config);
2709
+ let sessionId = generateSessionId();
2710
+ const chatHistory = [];
2711
+ const sessionTokens = { inputTokens: 0, outputTokens: 0 };
2712
+ let memoryRoundCount = 0;
2713
+ const hyperMd = await loadHyperMd();
2714
+ const project = await detectProject();
2715
+ const { loadMemoryForSystemPrompt } = await import("./loader-WHNTZTLP.js");
2716
+ const { memoryBlock } = await loadMemoryForSystemPrompt();
2717
+ let systemPrompt = "";
2718
+ if (hyperMd) {
2719
+ systemPrompt = hyperMd + "\n\n";
2720
+ } else {
2721
+ systemPrompt = "\u4F60\u662F\u8D85\u534F\u4F53 AI \u52A9\u624B\uFF0C\u4E00\u4E2A\u9762\u5411\u901A\u7528\u751F\u4EA7\u529B\u7684 AI-Native Shell\u3002\n";
2722
+ systemPrompt += "\u7B80\u6D01\u3001\u4E13\u4E1A\u5730\u56DE\u7B54\u95EE\u9898\u3002\u4F7F\u7528 Markdown \u683C\u5F0F\u8F93\u51FA\u3002\n";
2723
+ }
2724
+ if (memoryBlock) {
2725
+ systemPrompt += memoryBlock;
2726
+ }
2727
+ systemPrompt += "\n\n--- \u5BF9\u8BDD\u98CE\u683C\u8981\u6C42 ---\n";
2728
+ systemPrompt += "1. \u9ED8\u8BA4\u5148\u7ED9\u7ED3\u8BBA\u548C\u53EF\u6267\u884C\u6B65\u9AA4\uFF0C\u907F\u514D\u957F\u7BC7\u80CC\u666F\u94FA\u57AB\n";
2729
+ systemPrompt += "2. \u666E\u901A\u56DE\u590D\u63A7\u5236\u5728 4-10 \u884C\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u8BE6\u7EC6\u89E3\u91CA\n";
2730
+ systemPrompt += "3. \u7528\u6237\u95EE\u201C\u4F60\u80FD\u505A\u4EC0\u4E48\u201D\u65F6\uFF0C\u6700\u591A\u5217 6 \u6761\u80FD\u529B\u5E76\u7ED9 2-3 \u4E2A\u53EF\u76F4\u63A5\u590D\u5236\u7684\u793A\u4F8B\u6307\u4EE4\n";
2731
+ systemPrompt += "4. \u907F\u514D\u91CD\u590D\u548C\u7A7A\u8BDD\uFF0C\u4E0D\u8981\u8F93\u51FA\u5927\u6BB5\u6A21\u677F\u5316\u81EA\u6211\u4ECB\u7ECD\n";
2732
+ systemPrompt += "5. \u5217\u8868\u5C3D\u91CF\u7D27\u51D1\uFF0C\u5217\u8868\u9879\u4E4B\u95F4\u4E0D\u8981\u63D2\u5165\u7A7A\u884C\n";
2733
+ systemPrompt += "\n\n--- \u8F93\u51FA\u683C\u5F0F\u8981\u6C42 ---\n";
2734
+ systemPrompt += "1. \u5BF9\u6BD4\u4FE1\u606F\u6216\u53C2\u6570\u77E9\u9635\u65F6\u518D\u4F7F\u7528 Markdown \u8868\u683C\uFF0C\u5176\u4ED6\u573A\u666F\u4F18\u5148\u5217\u8868\n";
2735
+ systemPrompt += "2. \u53EF\u4EE5\u4F7F\u7528\u77ED\u6807\u9898\u548C\u7C97\u4F53\uFF0C\u4F46\u907F\u514D\u8FC7\u5EA6\u683C\u5F0F\u5316\n";
2736
+ systemPrompt += "3. \u4EE3\u7801\u5757\u4F7F\u7528 ``` \u5305\u88F9\u5E76\u6807\u6CE8\u8BED\u8A00\n";
2737
+ systemPrompt += "4. \u5217\u8868\u9ED8\u8BA4\u4F7F\u7528 -\uFF0C\u4EC5\u5728\u6D41\u7A0B\u6B65\u9AA4\u65F6\u4F7F\u7528\u6570\u5B57\u7F16\u53F7\n";
2738
+ systemPrompt += `
2739
+ \u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55: ${process.cwd()}`;
2740
+ if (project) {
2741
+ systemPrompt += `
2742
+ \u9879\u76EE: ${project.name} (${project.type})`;
2743
+ }
2744
+ const inGitRepo = isGitRepo();
2745
+ if (inGitRepo) {
2746
+ const branch = gitBranch();
2747
+ const statusStr = gitStatusSummary();
2748
+ systemPrompt += `
2749
+ Git: ${branch} (${statusStr})`;
2750
+ }
2751
+ tools.push(createSubAgentTool(() => client, config, tools, systemPrompt));
2752
+ const commandRegistry = new CommandRegistry();
2753
+ registerBuiltinCommands(commandRegistry, { config, engine });
2754
+ const customCmds = await loadCustomCommands();
2755
+ if (customCmds.length > 0) {
2756
+ commandRegistry.registerAll(customCmds);
2757
+ }
2758
+ const { createTeamSlashCommands } = await import("./commands-CK3WFAGI.js");
2759
+ commandRegistry.registerAll(createTeamSlashCommands());
2760
+ const { createMemorySlashCommands } = await import("./commands-ZE6GD3WC.js");
2761
+ commandRegistry.registerAll(createMemorySlashCommands());
2762
+ const { createAdminSlashCommands } = await import("./commands-U63OEO5J.js");
2763
+ commandRegistry.registerAll(createAdminSlashCommands());
2764
+ const { loadSkills } = await import("./skills-V4A35XKG.js");
2765
+ const skillCmds = await loadSkills();
2766
+ if (skillCmds.length > 0) {
2767
+ commandRegistry.registerAll(skillCmds);
2768
+ }
2769
+ const ctx = {
2770
+ config,
2771
+ chatHistory,
2772
+ sessionId,
2773
+ sessionTokens,
2774
+ tools,
2775
+ systemPrompt,
2776
+ setSessionId: (id) => {
2777
+ sessionId = id;
2778
+ ctx.sessionId = id;
2779
+ },
2780
+ resetHistory: () => {
2781
+ chatHistory.length = 0;
2782
+ },
2783
+ resetTokens: () => {
2784
+ sessionTokens.inputTokens = 0;
2785
+ sessionTokens.outputTokens = 0;
2786
+ },
2787
+ reinitClient: () => {
2788
+ client = config.modelConfig.sdkType === "openai" ? createOpenAIClient(config.modelConfig) : createLLMClient(config.modelConfig);
2789
+ },
2790
+ getClient: () => client
2791
+ };
2792
+ await hookManager.load();
2793
+ if (config.uiConfig?.theme) {
2794
+ setTheme(config.uiConfig.theme);
2795
+ }
2796
+ const vimState = createVimState(config.uiConfig?.vimMode || false);
2797
+ await loadKeyBindings();
2798
+ if (config.uiConfig?.notifications === false) {
2799
+ setNotificationsEnabled(false);
2800
+ }
2801
+ const guiEnabled = !process.argv.includes("--no-gui");
2802
+ let guiPort = 3210;
2803
+ if (guiEnabled) {
2804
+ const startPort = parseInt(process.env.HYPERCORE_PORT || "3210", 10);
2805
+ const hostBind = options?.host || "127.0.0.1";
2806
+ createWebServerAutoPort(config, startPort, hostBind, 10, true).then(async ({ port }) => {
2807
+ guiPort = port;
2808
+ registerREPLContext(ctx);
2809
+ if (hostBind === "0.0.0.0" || hostBind === "::") {
2810
+ const lanIP = getLocalIP();
2811
+ console.log(chalk3.dim(` \u{1F310} LAN \u56E2\u961F\u6A21\u5F0F\u5DF2\u5F00\u542F`));
2812
+ console.log(chalk3.dim(` \u672C\u673A\u5730\u5740: `) + chalk3.cyan(`http://${lanIP}:${port}`));
2813
+ console.log(chalk3.dim(` \u961F\u53CB\u52A0\u5165: `) + chalk3.cyan(`hypercore team join <CODE> --server http://${lanIP}:${port}`));
2814
+ console.log();
2815
+ }
2816
+ await registerInstance({
2817
+ pid: process.pid,
2818
+ port,
2819
+ cwd: process.cwd(),
2820
+ model: config.modelConfig.model,
2821
+ provider: config.modelConfig.provider,
2822
+ sessionId,
2823
+ gitBranch: inGitRepo ? gitBranch() : void 0,
2824
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
2825
+ projectName: project?.name
2826
+ });
2827
+ }).catch(() => {
2828
+ });
2829
+ }
2830
+ const gitBr = inGitRepo ? gitBranch() : void 0;
2831
+ showWelcome({
2832
+ model: config.modelConfig.model,
2833
+ project: project?.name,
2834
+ gitBranch: gitBr,
2835
+ toolCount: tools.length,
2836
+ commandCount: commandRegistry.size,
2837
+ hyperMdLoaded: !!hyperMd,
2838
+ hookCount: hookManager.count,
2839
+ cwd: process.cwd(),
2840
+ permissionMode: config.permissionMode
2841
+ });
2842
+ const { trackSessionStart: _trackStart, trackSessionEnd: _trackEnd, trackCmdExec: _trackCmd, trackError: _trackErr } = await import("./telemetry-6R4EIE6O.js");
2843
+ _trackStart(sessionId, config.modelConfig.model, config.modelConfig.provider);
2844
+ const _sessionStartMs = Date.now();
2845
+ let _sessionRounds = 0;
2846
+ await hookManager.trigger("onSessionStart", { sessionId, model: config.modelConfig.model });
2847
+ let _isFirstPrompt = true;
2848
+ function buildPrompt() {
2849
+ const statusLine = renderStatusBar({
2850
+ model: config.modelConfig.model,
2851
+ gitBranch: inGitRepo ? gitBranch() : void 0,
2852
+ gitDirty: inGitRepo ? gitStatusSummary() : void 0,
2853
+ inputTokens: sessionTokens.inputTokens,
2854
+ outputTokens: sessionTokens.outputTokens,
2855
+ toolCount: tools.length,
2856
+ hyperMdLoaded: !!hyperMd,
2857
+ permissionMode: config.permissionMode,
2858
+ inGitRepo
2859
+ }, _isFirstPrompt);
2860
+ if (statusLine) {
2861
+ process.stdout.write(statusLine);
2862
+ }
2863
+ if (_isFirstPrompt) {
2864
+ _isFirstPrompt = false;
2865
+ }
2866
+ const reportedW = process.stdout.columns || process.stderr.columns || 80;
2867
+ const envW = Number(process.env.COLUMNS || 0);
2868
+ const baseW = envW > 0 ? Math.min(reportedW, envW) : reportedW;
2869
+ const termW = Math.max(24, Math.min(baseW - 4, 96));
2870
+ const sepLine = chalk3.dim("\u2500".repeat(termW));
2871
+ process.stdout.write("\x1B[J");
2872
+ process.stdout.write(sepLine + "\n");
2873
+ process.stdout.write(chalk3.dim(" ? for shortcuts") + "\n");
2874
+ const vimIndicator = getVimModeIndicator(vimState);
2875
+ const vimPart = vimIndicator ? vimIndicator + " " : "";
2876
+ return vimPart + "\u276F ";
2877
+ }
2878
+ const historyFilePath = join2(HYPERCORE_DIR, "history");
2879
+ let persistedHistory = [];
2880
+ try {
2881
+ if (existsSync2(historyFilePath)) {
2882
+ const histContent = await readFile2(historyFilePath, "utf-8");
2883
+ persistedHistory = histContent.split("\n").filter(Boolean).slice(-500);
2884
+ }
2885
+ } catch {
2886
+ }
2887
+ const rl = createInterface({
2888
+ input: process.stdin,
2889
+ output: process.stdout,
2890
+ history: persistedHistory,
2891
+ historySize: 500,
2892
+ completer: (line) => {
2893
+ if (line.startsWith("/")) {
2894
+ const partial = line.slice(1).toLowerCase();
2895
+ const allCmds = commandRegistry.list().map((c) => `/${c.name}`);
2896
+ const hits2 = allCmds.filter((c) => c.startsWith(`/${partial}`));
2897
+ return [hits2.length ? hits2 : allCmds, line];
2898
+ }
2899
+ const builtins = ["run", "line ls", "clear", "help", "exit"];
2900
+ const hits = builtins.filter((c) => c.startsWith(line.toLowerCase()));
2901
+ return [hits, line];
2902
+ }
2903
+ });
2904
+ rl.on("line", (line) => {
2905
+ if (line.trim()) {
2906
+ try {
2907
+ appendFileSync(historyFilePath, line + "\n");
2908
+ } catch {
2909
+ }
2910
+ }
2911
+ });
2912
+ if (process.stdin.isTTY) {
2913
+ process.stdin.on("keypress", (_ch, key) => {
2914
+ if (!key) return;
2915
+ vimState.enabled = config.uiConfig?.vimMode || false;
2916
+ if (vimState.enabled && handleVimKeypress(vimState, rl, key)) {
2917
+ return;
2918
+ }
2919
+ if (key.ctrl && key.name === "t") {
2920
+ rl.line = "/todo";
2921
+ rl.cursor = 5;
2922
+ process.stdout.write("\r\x1B[K");
2923
+ rl.emit("line", "/todo");
2924
+ return;
2925
+ }
2926
+ const matched = matchKeyBinding(key);
2927
+ if (matched && key.ctrl) {
2928
+ const cmd = matched.startsWith("/") ? matched : `/${matched}`;
2929
+ rl.line = cmd;
2930
+ rl.cursor = cmd.length;
2931
+ process.stdout.write("\r\x1B[K");
2932
+ rl.emit("line", cmd);
2933
+ return;
2934
+ }
2935
+ });
2936
+ }
2937
+ const inputMux = createMultiplexer(rl);
2938
+ function promptInput() {
2939
+ return inputMux.nextInput(buildPrompt());
2940
+ }
2941
+ async function gracefulShutdown() {
2942
+ if (chatHistory.length > 0) {
2943
+ await saveSession(sessionId, chatHistory);
2944
+ }
2945
+ if (chatHistory.length >= 4) {
2946
+ try {
2947
+ const { extractMemories } = await import("./extractor-QV53W2YJ.js");
2948
+ await extractMemories(chatHistory, client, config, "personal");
2949
+ } catch {
2950
+ }
2951
+ }
2952
+ await unregisterInstance(process.pid).catch(() => {
2953
+ });
2954
+ _trackEnd(sessionId, _sessionRounds, sessionTokens.inputTokens, sessionTokens.outputTokens, Date.now() - _sessionStartMs);
2955
+ await hookManager.trigger("onSessionEnd", { sessionId, model: config.modelConfig.model });
2956
+ await closeMCPConnections();
2957
+ const { showExitSummary } = await import("./display-IIUBEYWN.js");
2958
+ showExitSummary(
2959
+ sessionTokens.inputTokens,
2960
+ sessionTokens.outputTokens,
2961
+ config.modelConfig.model,
2962
+ _sessionRounds,
2963
+ Date.now() - _sessionStartMs
2964
+ );
2965
+ process.exit(0);
2966
+ }
2967
+ process.on("SIGINT", gracefulShutdown);
2968
+ process.on("SIGTERM", gracefulShutdown);
2969
+ while (true) {
2970
+ let muxResult;
2971
+ try {
2972
+ muxResult = await promptInput();
2973
+ } catch {
2974
+ console.log(chalk3.dim("\n \u518D\u89C1\n"));
2975
+ break;
2976
+ }
2977
+ const raw = muxResult.content;
2978
+ const inputSource = muxResult.source;
2979
+ if (inputSource === "gui") {
2980
+ console.log(chalk3.magenta(`[GUI] `) + raw);
2981
+ }
2982
+ if (inputSource === "cli" && hasGUIClients()) {
2983
+ emitToGUI("user_input_cli", { content: raw });
2984
+ }
2985
+ const trimmed = raw.trim();
2986
+ if (!trimmed) continue;
2987
+ let input = trimmed;
2988
+ if (trimmed.startsWith('"""') || trimmed.endsWith("\\")) {
2989
+ try {
2990
+ input = await readMultiLine(rl, trimmed);
2991
+ } catch {
2992
+ continue;
2993
+ }
2994
+ }
2995
+ if (input.startsWith("!")) {
2996
+ const shellCmd = input.slice(1).trim();
2997
+ if (shellCmd) {
2998
+ const bashTool = tools.find((t) => t.definition.name === "bash");
2999
+ if (bashTool) {
3000
+ try {
3001
+ const result = await bashTool.handler({ command: shellCmd });
3002
+ console.log(result);
3003
+ } catch (err) {
3004
+ showError(err instanceof Error ? err.message : String(err));
3005
+ }
3006
+ } else {
3007
+ showError("bash \u5DE5\u5177\u672A\u6CE8\u518C");
3008
+ }
3009
+ }
3010
+ continue;
3011
+ }
3012
+ if (input.startsWith("/")) {
3013
+ const slashArgs = parseInput(input.slice(1));
3014
+ const slashCmd = slashArgs[0]?.toLowerCase();
3015
+ if (slashCmd) {
3016
+ if (slashCmd === "help") {
3017
+ const helpArgs = slashArgs.slice(1);
3018
+ if (helpArgs.length > 0) {
3019
+ const cmdName = helpArgs[0].replace(/^\//, "");
3020
+ const helpCmd = commandRegistry.get(cmdName);
3021
+ if (helpCmd) {
3022
+ showCommandHelpDetail(helpCmd);
3023
+ } else {
3024
+ console.log(chalk3.dim(`
3025
+ \u672A\u77E5\u547D\u4EE4: ${cmdName}
3026
+ `));
3027
+ }
3028
+ } else {
3029
+ showREPLHelp();
3030
+ showSlashCommandsGrouped(commandRegistry.list());
3031
+ }
3032
+ continue;
3033
+ }
3034
+ const command = commandRegistry.get(slashCmd);
3035
+ if (command) {
3036
+ const _cmdStart = Date.now();
3037
+ try {
3038
+ await command.handler(slashArgs.slice(1), ctx);
3039
+ _trackCmd("/" + slashCmd, slashArgs.slice(1).join(" "), Date.now() - _cmdStart, true);
3040
+ } catch (err) {
3041
+ const errMsg = err instanceof Error ? err.message : String(err);
3042
+ _trackCmd("/" + slashCmd, slashArgs.slice(1).join(" "), Date.now() - _cmdStart, false, err instanceof Error ? err.message : String(err));
3043
+ await hookManager.trigger("onError", {
3044
+ sessionId: ctx.sessionId,
3045
+ model: ctx.config.modelConfig.model,
3046
+ error: errMsg,
3047
+ userPrompt: input
3048
+ }).catch(() => {
3049
+ });
3050
+ showError(errMsg);
3051
+ }
3052
+ } else {
3053
+ const suggestionPool = Array.from(new Set(
3054
+ commandRegistry.list().flatMap((c) => [c.name, ...c.aliases || []])
3055
+ ));
3056
+ const suggestions = suggestSlashCommands(slashCmd, suggestionPool, 3);
3057
+ if (suggestions.length > 0) {
3058
+ showError(`\u672A\u77E5\u547D\u4EE4: /${slashCmd}\u3002\u4F60\u53EF\u80FD\u60F3\u8F93\u5165: ${suggestions.map((s) => `/${s}`).join(" ")}`);
3059
+ } else {
3060
+ showError(`\u672A\u77E5\u547D\u4EE4: /${slashCmd}\u3002\u8F93\u5165 /help \u67E5\u770B\u53EF\u7528\u547D\u4EE4`);
3061
+ }
3062
+ }
3063
+ }
3064
+ continue;
3065
+ }
3066
+ const args = parseInput(input);
3067
+ const cmd = args[0].toLowerCase();
3068
+ if (!["run", "line", "clear", "help", "exit", "quit"].includes(cmd) && isGreetingOrCapabilityQuery(input)) {
3069
+ showCapabilityQuickGuide();
3070
+ continue;
3071
+ }
3072
+ try {
3073
+ switch (cmd) {
3074
+ case "run": {
3075
+ const rest = args.slice(1);
3076
+ if (rest.length === 0) {
3077
+ showError('\u7528\u6CD5: run <\u7EBF\u540D> --topic "\u4E3B\u9898"');
3078
+ break;
3079
+ }
3080
+ const { positional, options: runOptions } = extractOptions(rest);
3081
+ const lineName = positional[0];
3082
+ if (!lineName) {
3083
+ showError("\u8BF7\u6307\u5B9A\u751F\u4EA7\u7EBF\u540D\u79F0");
3084
+ break;
3085
+ }
3086
+ const { loadLine } = await import("./config-loader-SXO674TF.js");
3087
+ const { input: inquirerInput } = await import("@inquirer/prompts");
3088
+ let lineDefForRun;
3089
+ try {
3090
+ lineDefForRun = await loadLine(HYPERCORE_DIR, lineName);
3091
+ } catch (lineErr) {
3092
+ showError(`\u65E0\u6CD5\u52A0\u8F7D\u751F\u4EA7\u7EBF "${lineName}"\uFF1A${lineErr instanceof Error ? lineErr.message : String(lineErr)}`);
3093
+ break;
3094
+ }
3095
+ const userInputs = { ...runOptions };
3096
+ for (const param of lineDefForRun.inputs) {
3097
+ if (param.required && !userInputs[param.name]) {
3098
+ try {
3099
+ const value = await inquirerInput({
3100
+ message: `\u{1F4DD} ${param.name}\uFF08${param.description}\uFF09\uFF1A`,
3101
+ default: param.defaultValue
3102
+ });
3103
+ if (!value.trim()) {
3104
+ showError(`\u5FC5\u586B\u53C2\u6570 "${param.name}" \u4E0D\u80FD\u4E3A\u7A7A`);
3105
+ break;
3106
+ }
3107
+ userInputs[param.name] = value.trim();
3108
+ } catch {
3109
+ showError("\u8F93\u5165\u5DF2\u53D6\u6D88");
3110
+ break;
3111
+ }
3112
+ }
3113
+ }
3114
+ console.log(chalk3.bold(`
3115
+ \u{1F3ED} \u8FD0\u884C\u751F\u4EA7\u7EBF\uFF1A${lineName}`));
3116
+ for (const [key, value] of Object.entries(userInputs)) {
3117
+ console.log(chalk3.dim(` ${key}: ${value}`));
3118
+ }
3119
+ const runProviderName = MODEL_PROVIDERS[config.modelConfig.provider]?.name || config.modelConfig.provider;
3120
+ console.log(chalk3.dim(` \u6A21\u578B: ${runProviderName} \u2192 ${config.modelConfig.model}`));
3121
+ let replRunStreaming = false;
3122
+ await engine.run(lineName, userInputs, {
3123
+ onStationStart: (idx, total, name, agentName) => {
3124
+ showStationStart(idx, total, name, agentName);
3125
+ console.log();
3126
+ replRunStreaming = false;
3127
+ },
3128
+ onStationText: (text) => {
3129
+ process.stdout.write(text);
3130
+ replRunStreaming = true;
3131
+ },
3132
+ onToolCall: (name, toolInput) => {
3133
+ if (replRunStreaming) {
3134
+ console.log();
3135
+ replRunStreaming = false;
3136
+ }
3137
+ showToolCall(name, toolInput);
3138
+ },
3139
+ onStationComplete: (idx, name) => {
3140
+ if (replRunStreaming) {
3141
+ console.log();
3142
+ replRunStreaming = false;
3143
+ }
3144
+ showStationComplete(idx, name);
3145
+ },
3146
+ onStationSkipped: (idx, name, reason) => {
3147
+ showStationSkipped(idx, name, reason);
3148
+ },
3149
+ onStationRetry: (idx, name, attempt, maxRetry, error) => {
3150
+ showStationRetry(idx, name, attempt, maxRetry, error);
3151
+ },
3152
+ onCheckpoint: handleCheckpoint,
3153
+ onComplete: (result) => showRunComplete(result, config.modelConfig.model)
3154
+ });
3155
+ break;
3156
+ }
3157
+ case "line": {
3158
+ const sub = args[1]?.toLowerCase();
3159
+ if (sub === "ls" || !sub) {
3160
+ const lines = await listLines(HYPERCORE_DIR);
3161
+ showLineList(lines);
3162
+ } else {
3163
+ showError(`\u672A\u77E5\u5B50\u547D\u4EE4: line ${sub}`);
3164
+ }
3165
+ break;
3166
+ }
3167
+ case "clear":
3168
+ console.clear();
3169
+ showBanner();
3170
+ break;
3171
+ case "help": {
3172
+ const helpArgs = args.slice(1);
3173
+ if (helpArgs.length > 0) {
3174
+ const cmdName = helpArgs[0].replace(/^\//, "");
3175
+ const helpCmd = commandRegistry.get(cmdName);
3176
+ if (helpCmd) {
3177
+ showCommandHelpDetail(helpCmd);
3178
+ } else {
3179
+ console.log(chalk3.dim(`
3180
+ \u672A\u77E5\u547D\u4EE4: ${cmdName}
3181
+ `));
3182
+ }
3183
+ } else {
3184
+ showREPLHelp();
3185
+ showSlashCommandsGrouped(commandRegistry.list());
3186
+ }
3187
+ break;
3188
+ }
3189
+ case "exit":
3190
+ case "quit":
3191
+ if (chatHistory.length > 0) {
3192
+ await saveSession(sessionId, chatHistory);
3193
+ console.log(chalk3.dim(`
3194
+ \u4F1A\u8BDD\u5DF2\u4FDD\u5B58 (${sessionId})`));
3195
+ }
3196
+ await unregisterInstance(process.pid).catch(() => {
3197
+ });
3198
+ await closeMCPConnections();
3199
+ console.log(chalk3.dim(" \u518D\u89C1\n"));
3200
+ rl.close();
3201
+ return;
3202
+ default: {
3203
+ let stopSpinnerOnce2 = function() {
3204
+ if (!spinnerStopped) {
3205
+ spinnerStopped = true;
3206
+ spinner.stop();
3207
+ }
3208
+ }, updateSpinnerProgress2 = function() {
3209
+ if (!spinnerStopped) {
3210
+ const len = chatBuffer.length;
3211
+ const display = len > 1e3 ? `${(len / 1e3).toFixed(1)}k` : `${len}`;
3212
+ spinner.text = chalk3.dim(`\u751F\u6210\u4E2D\u2026 ${display} \u5B57\u7B26`);
3213
+ }
3214
+ };
3215
+ var stopSpinnerOnce = stopSpinnerOnce2, updateSpinnerProgress = updateSpinnerProgress2;
3216
+ const startTime = Date.now();
3217
+ await hookManager.trigger("onPromptStart", {
3218
+ sessionId: ctx.sessionId,
3219
+ model: config.modelConfig.model,
3220
+ userPrompt: input
3221
+ }).catch(() => {
3222
+ });
3223
+ let assistantOutputForHook = "";
3224
+ let spinnerStopped = false;
3225
+ const spinner = ora({
3226
+ text: chalk3.dim("\u601D\u8003\u4E2D\u2026"),
3227
+ spinner: "dots2",
3228
+ indent: 2
3229
+ }).start();
3230
+ let chatBuffer = "";
3231
+ if (config.modelConfig.sdkType === "openai") {
3232
+ const openaiClient = client;
3233
+ const messages = [
3234
+ { role: "system", content: systemPrompt },
3235
+ ...chatHistory.map((m) => ({
3236
+ role: m.role,
3237
+ content: m.content
3238
+ })),
3239
+ { role: "user", content: input }
3240
+ ];
3241
+ console.log();
3242
+ let streamHasToolCalls = false;
3243
+ const result = await streamOpenAIChat(openaiClient, messages, {
3244
+ model: config.modelConfig.model,
3245
+ tools,
3246
+ onChunk: (text) => {
3247
+ chatBuffer += text;
3248
+ updateSpinnerProgress2();
3249
+ emitToGUI("assistant_text", { content: text });
3250
+ },
3251
+ onThinking: (text) => {
3252
+ stopSpinnerOnce2();
3253
+ showThinking(text);
3254
+ emitToGUI("assistant_text", { content: text, thinking: true });
3255
+ },
3256
+ onToolCall: (name, input2) => {
3257
+ stopSpinnerOnce2();
3258
+ streamHasToolCalls = true;
3259
+ showToolCall(name, input2);
3260
+ emitToGUI("tool_call", { name, input: input2 });
3261
+ },
3262
+ onToolCallDone: (name, durationMs) => {
3263
+ showToolCallDone(name, durationMs);
3264
+ }
3265
+ });
3266
+ stopSpinnerOnce2();
3267
+ const finalText = result.responseContent || chatBuffer;
3268
+ if (finalText) {
3269
+ if (streamHasToolCalls) {
3270
+ console.log("\n" + chalk3.dim(" \u2500\u2500\u2500"));
3271
+ }
3272
+ const rendered = renderMarkdown(finalText);
3273
+ process.stdout.write(rendered);
3274
+ }
3275
+ console.log();
3276
+ chatHistory.push({ role: "user", content: input });
3277
+ chatHistory.push({ role: "assistant", content: result.content });
3278
+ assistantOutputForHook = result.content;
3279
+ sessionTokens.inputTokens += result.tokenUsage.inputTokens;
3280
+ sessionTokens.outputTokens += result.tokenUsage.outputTokens;
3281
+ emitToGUI("assistant_done", {
3282
+ content: result.content,
3283
+ tokens: { input: result.tokenUsage.inputTokens, output: result.tokenUsage.outputTokens }
3284
+ });
3285
+ const elapsed = Date.now() - startTime;
3286
+ showResponseStats(elapsed, result.tokenUsage.inputTokens, result.tokenUsage.outputTokens, config.modelConfig.model);
3287
+ } else {
3288
+ chatHistory.push({ role: "user", content: input });
3289
+ console.log();
3290
+ let streamHasToolCalls = false;
3291
+ const result = await streamCallLLM(client, {
3292
+ systemPrompt,
3293
+ userPrompt: input,
3294
+ history: chatHistory.slice(0, -1),
3295
+ tools,
3296
+ model: config.modelConfig.model,
3297
+ onText: (text) => {
3298
+ chatBuffer += text;
3299
+ updateSpinnerProgress2();
3300
+ emitToGUI("assistant_text", { content: text });
3301
+ },
3302
+ onThinking: (text) => {
3303
+ stopSpinnerOnce2();
3304
+ showThinking(text);
3305
+ emitToGUI("assistant_text", { content: text, thinking: true });
3306
+ },
3307
+ onToolCall: (name, toolInput) => {
3308
+ stopSpinnerOnce2();
3309
+ streamHasToolCalls = true;
3310
+ showToolCall(name, toolInput);
3311
+ emitToGUI("tool_call", { name, input: toolInput });
3312
+ },
3313
+ onToolCallDone: (name, durationMs) => {
3314
+ showToolCallDone(name, durationMs);
3315
+ }
3316
+ });
3317
+ stopSpinnerOnce2();
3318
+ const finalText = result.responseText || chatBuffer;
3319
+ if (finalText) {
3320
+ if (streamHasToolCalls) {
3321
+ console.log("\n" + chalk3.dim(" \u2500\u2500\u2500"));
3322
+ }
3323
+ const rendered = renderMarkdown(finalText);
3324
+ process.stdout.write(rendered);
3325
+ }
3326
+ console.log();
3327
+ chatHistory.push({ role: "assistant", content: result.output });
3328
+ assistantOutputForHook = result.output;
3329
+ sessionTokens.inputTokens += result.tokenUsage.inputTokens;
3330
+ sessionTokens.outputTokens += result.tokenUsage.outputTokens;
3331
+ emitToGUI("assistant_done", {
3332
+ content: result.output,
3333
+ tokens: { input: result.tokenUsage.inputTokens, output: result.tokenUsage.outputTokens }
3334
+ });
3335
+ const elapsed = Date.now() - startTime;
3336
+ showResponseStats(elapsed, result.tokenUsage.inputTokens, result.tokenUsage.outputTokens, config.modelConfig.model);
3337
+ }
3338
+ await hookManager.trigger("onPromptEnd", {
3339
+ sessionId: ctx.sessionId,
3340
+ model: config.modelConfig.model,
3341
+ userPrompt: input,
3342
+ response: assistantOutputForHook
3343
+ }).catch(() => {
3344
+ });
3345
+ const totalElapsed = Date.now() - startTime;
3346
+ notifyIfLong("Hypercore", `\u54CD\u5E94\u5B8C\u6210 (${(totalElapsed / 1e3).toFixed(1)}s)`, totalElapsed);
3347
+ trimChatHistory(chatHistory);
3348
+ await saveSession(sessionId, chatHistory);
3349
+ _sessionRounds++;
3350
+ memoryRoundCount++;
3351
+ if (memoryRoundCount % 3 === 0) {
3352
+ const { triggerAutoExtraction } = await import("./extractor-QV53W2YJ.js");
3353
+ triggerAutoExtraction(chatHistory, client, config, ["personal"]);
3354
+ }
3355
+ }
3356
+ }
3357
+ } catch (err) {
3358
+ const msg = err instanceof Error ? err.message : String(err);
3359
+ await hookManager.trigger("onError", {
3360
+ sessionId,
3361
+ model: config.modelConfig.model,
3362
+ error: msg
3363
+ }).catch(() => {
3364
+ });
3365
+ if (msg.includes("429")) {
3366
+ showError("API \u8BF7\u6C42\u9650\u6D41\uFF0C\u8BF7\u7A0D\u7B49\u51E0\u79D2\u518D\u8BD5\uFF08Gemini \u514D\u8D39\u7248\u9650\u5236 10 \u6B21/\u5206\u949F\uFF09");
3367
+ } else {
3368
+ showError(msg);
3369
+ }
3370
+ }
3371
+ }
3372
+ }
3373
+ export {
3374
+ startREPL
3375
+ };