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.
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/api-XGC7D5AW.js +162 -0
- package/dist/auth-DNQWYQKT.js +21 -0
- package/dist/background-2EGCAAQH.js +14 -0
- package/dist/backlog-Q2NZCLNY.js +24 -0
- package/dist/chunk-2CMSCWQW.js +162 -0
- package/dist/chunk-2LJ2DVEB.js +167 -0
- package/dist/chunk-3RPFCQKJ.js +288 -0
- package/dist/chunk-43OLRXM5.js +263 -0
- package/dist/chunk-4DVYJAJL.js +57 -0
- package/dist/chunk-6OL3GA3P.js +173 -0
- package/dist/chunk-AUHU7ALH.js +2023 -0
- package/dist/chunk-B6A2AKLN.js +139 -0
- package/dist/chunk-BE46C7JW.js +46 -0
- package/dist/chunk-CUVAUOXL.js +58 -0
- package/dist/chunk-GH7E2OJE.js +223 -0
- package/dist/chunk-GOOTEPBK.js +271 -0
- package/dist/chunk-GPPMJYSM.js +133 -0
- package/dist/chunk-GU2FZQ6A.js +69 -0
- package/dist/chunk-IOPKN5GD.js +190 -0
- package/dist/chunk-IXOIOGR5.js +1505 -0
- package/dist/chunk-KRPOPWGA.js +251 -0
- package/dist/chunk-MGLJ53QN.js +219 -0
- package/dist/chunk-MV4TTRYX.js +533 -0
- package/dist/chunk-OPZYEVYR.js +150 -0
- package/dist/chunk-QTSLP47C.js +166 -0
- package/dist/chunk-R3GPQC7I.js +393 -0
- package/dist/chunk-RKB2JOV2.js +43 -0
- package/dist/chunk-RNG3K465.js +80 -0
- package/dist/chunk-TGTYKBGC.js +86 -0
- package/dist/chunk-U5SGAIMM.js +681 -0
- package/dist/chunk-V5UHPPSY.js +140 -0
- package/dist/chunk-WHLVZCQY.js +245 -0
- package/dist/chunk-XDRCBMZZ.js +66 -0
- package/dist/chunk-XOS6HPEF.js +134 -0
- package/dist/chunk-ZSBHUGWR.js +262 -0
- package/dist/claude-NSQ442XD.js +12 -0
- package/dist/commands-CK3WFAGI.js +128 -0
- package/dist/commands-U63OEO5J.js +1044 -0
- package/dist/commands-ZE6GD3WC.js +232 -0
- package/dist/config-4EW42BSF.js +8 -0
- package/dist/config-loader-SXO674TF.js +24 -0
- package/dist/diagnose-AFW3ZTZ4.js +12 -0
- package/dist/display-IIUBEYWN.js +58 -0
- package/dist/extractor-QV53W2YJ.js +129 -0
- package/dist/history-WMSCHERZ.js +180 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +406 -0
- package/dist/instance-registry-YSIJXSO7.js +15 -0
- package/dist/keybindings-JAAMLH3G.js +15 -0
- package/dist/loader-WHNTZTLP.js +58 -0
- package/dist/network-MM6YWPGO.js +279 -0
- package/dist/notify-HPTALZDC.js +14 -0
- package/dist/openai-compat-UQWJXBEK.js +12 -0
- package/dist/permissions-JUKXMNDH.js +10 -0
- package/dist/prompt-QV45TXRL.js +166 -0
- package/dist/quality-ST7PPNFR.js +16 -0
- package/dist/repl-RT3AHL7M.js +3375 -0
- package/dist/roadmap-5OBEKROY.js +17 -0
- package/dist/server-PORT7OEG.js +57 -0
- package/dist/session-4VUNDWLH.js +21 -0
- package/dist/skills-V4A35XKG.js +175 -0
- package/dist/store-Y4LU5QTO.js +25 -0
- package/dist/team-HO7Z4SIM.js +385 -0
- package/dist/telemetry-6R4EIE6O.js +30 -0
- package/dist/test-runner-ZQH5Y6OJ.js +619 -0
- package/dist/theme-3SYJ3UQA.js +14 -0
- package/dist/upgrade-7TGI3SXO.js +83 -0
- package/dist/verify-JUDKTPKZ.js +14 -0
- package/dist/web/static/app.js +562 -0
- package/dist/web/static/index.html +132 -0
- package/dist/web/static/mirror.css +1001 -0
- package/dist/web/static/mirror.html +184 -0
- package/dist/web/static/mirror.js +1125 -0
- package/dist/web/static/onboard.css +302 -0
- package/dist/web/static/onboard.html +140 -0
- package/dist/web/static/onboard.js +260 -0
- package/dist/web/static/style.css +602 -0
- package/dist/web/static/workspace.css +1568 -0
- package/dist/web/static/workspace.html +408 -0
- package/dist/web/static/workspace.js +1683 -0
- package/dist/web-Z5HSCQHW.js +39 -0
- 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
|
+
};
|