monk-pi 0.2.0 → 0.10.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.
@@ -0,0 +1,1772 @@
1
+ // src/constants.ts
2
+ import os from "os";
3
+ import path from "path";
4
+ var MONK_BASE_URL = "https://monk.party/v1";
5
+ var MONK_WEBSITE = "https://monk.party";
6
+ var MONK_ACCOUNT_URL = "https://monk.party/account/";
7
+ var MONK_MODELS = [
8
+ {
9
+ id: "monk-coding",
10
+ name: "Monk Coding (\u4EE3\u7801\u4E3B\u529B - \u63A8\u8350)",
11
+ description: "\u4E13\u4E3A Agent \u6DF1\u5EA6\u4F18\u5316\uFF0C\u517C\u5907\u9AD8\u541E\u5410\u903B\u8F91\u63A8\u6F14\u4E0E\u5DE5\u5177\u8C03\u7528\u80FD\u529B",
12
+ reasoning: true,
13
+ contextWindow: 1e6,
14
+ maxTokens: 64e3
15
+ },
16
+ {
17
+ id: "monk-fast",
18
+ name: "Monk Fast (\u6781\u901F\u63A8\u7406)",
19
+ description: "\u6781\u901F\u54CD\u5E94\uFF0C\u9002\u5408\u65E5\u5E38\u95EE\u7B54\u3001\u5FEB\u901F\u4EE3\u7801\u63A2\u7D22\u4E0E\u5355\u6587\u4EF6\u9605\u8BFB",
20
+ reasoning: true,
21
+ contextWindow: 1e6,
22
+ maxTokens: 64e3
23
+ },
24
+ {
25
+ id: "monk",
26
+ name: "Monk (\u878D\u5408\u6A21\u578B)",
27
+ description: "\u96C6\u4E09\u5927 Flash \u4E4B\u6240\u957F\uFF0C\u7EFC\u5408\u8D28\u91CF\u4E0E\u601D\u8003\u80FD\u529B\u4F18\u5148",
28
+ reasoning: true,
29
+ contextWindow: 1e6,
30
+ maxTokens: 64e3
31
+ }
32
+ ];
33
+ var DEFAULT_MONK_MODEL = "monk-coding";
34
+ function getMonkHomeDir() {
35
+ return path.join(os.homedir(), ".monk-pi");
36
+ }
37
+ function getMonkConfigFile() {
38
+ return path.join(getMonkHomeDir(), "config.json");
39
+ }
40
+ function getPiHomeDir() {
41
+ return process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
42
+ }
43
+ function getPiModelsFile() {
44
+ return path.join(getPiHomeDir(), "models.json");
45
+ }
46
+ function getPiSettingsFile() {
47
+ return path.join(getPiHomeDir(), "settings.json");
48
+ }
49
+ function getPiExtensionsDir() {
50
+ return path.join(getPiHomeDir(), "extensions");
51
+ }
52
+ function getPiMonkExtensionFile() {
53
+ return path.join(getPiExtensionsDir(), "monk.ts");
54
+ }
55
+
56
+ // src/config.ts
57
+ import fs from "fs";
58
+
59
+ // src/extension/code.ts
60
+ var MONK_EXTENSION_CODE = `// @monk-managed-pi-extension
61
+ // Monk \xD7 Pi Native Extension
62
+ // Features: Footer status, Starter Navigator widget, Tips, Context macros (@diff, @recent, @git, @staged), Chinese engineering prompt, /undo, /commit, /review, /monk, and auto-compaction
63
+
64
+ const fs = require("node:fs");
65
+ const path = require("node:path");
66
+
67
+ const MONK_MODELS = [
68
+ { id: "monk-coding", label: "monk-coding (\u4EE3\u7801\u4E3B\u529B - \u63A8\u8350 \xB7 \u6DF1\u5EA6\u5DE5\u5177\u8C03\u7528)" },
69
+ { id: "monk-fast", label: "monk-fast (\u6781\u901F\u63A8\u7406 \xB7 \u5FEB\u901F\u95EE\u7B54\u4E0E\u8F7B\u91CF\u4EFB\u52A1)" },
70
+ { id: "monk", label: "monk (\u878D\u5408\u65D7\u8230 \xB7 \u8D28\u91CF\u4F18\u5148\u4E0E\u7EFC\u5408\u63A8\u7406)" },
71
+ ];
72
+
73
+ const PRO_TIPS = [
74
+ "\u5728\u8F93\u5165\u6846\u8F93\u5165 @diff \u5373\u53EF\u79D2\u7EA7\u628A\u5F53\u524D\u672A\u63D0\u4EA4\u6539\u52A8\u5E26\u5165\u5206\u6790",
75
+ "\u8F93\u5165 /commit \u53EF\u6309 Conventional Commits \u89C4\u8303\u81EA\u52A8\u751F\u6210\u4E2D\u6587\u63D0\u4EA4",
76
+ "AI \u6539\u4E71\u4E86\u4EE3\u7801\u4E0D\u7528\u614C\uFF0C\u968F\u65F6\u8F93\u5165 /undo \u6216 /\u56DE\u9000 \u77AC\u95F4\u4E00\u952E\u590D\u539F",
77
+ "\u8F93\u5165 /review \u53EF\u4ECE\u7F3A\u9677\u3001\u6027\u80FD\u3001\u67B6\u6784\u56DB\u4E2A\u7EF4\u5EA6\u5BF9\u4EE3\u7801\u8FDB\u884C\u6DF1\u5EA6\u5BA1\u67E5",
78
+ "\u8F93\u5165 /monk \u53EF\u547C\u51FA\u56FE\u5F62\u5316\u63A7\u5236\u53F0\uFF0C\u79D2\u7EA7\u514D\u91CD\u542F\u5207\u6362\u4E3B\u529B\u6A21\u578B",
79
+ "\u8F93\u5165 @recent \u5FEB\u901F\u63D0\u53D6\u6700\u8FD1\u53D8\u52A8\u7684\u9879\u76EE\u6838\u5FC3\u6E90\u6587\u4EF6\u5217\u8868",
80
+ "\u8F93\u5165 /monk cn \u968F\u65F6\u5207\u6362\u4E2D\u6587\u5DE5\u7A0B\u7CBE\u70BC\u6A21\u5F0F\uFF08\u96F6\u5BA2\u5957\xB7\u884C\u52A8\u4F18\u5148\uFF09",
81
+ "\u7EC8\u7AEF\u8F93\u5165 monk-pi -r \u53EF\u89C6\u5316\u6062\u590D\u4EE5\u5F80\u4EFB\u610F\u5386\u53F2\u4EFB\u52A1\u65AD\u70B9\u7EED\u5199",
82
+ ];
83
+
84
+ const OVERFLOW_PATTERNS = [
85
+ /context.*length/i,
86
+ /maximum.*tokens/i,
87
+ /token.*limit/i,
88
+ /too many tokens/i,
89
+ /prompt.*too long/i,
90
+ /request.*too large/i,
91
+ /context_window_exceeded/i,
92
+ /exceed.*context/i,
93
+ ];
94
+
95
+ const CHINESE_ENGINEERING_PROMPT = \`
96
+ # \u4E2D\u6587\u5DE5\u7A0B\u4EA4\u4E92\u89C4\u8303 (Monk Chinese Engineering Persona)
97
+ 1. \u8BED\u8A00\u8868\u8FBE\u51C6\u5219\uFF1A
98
+ - \u5168\u7A0B\u4F7F\u7528\u5E72\u7EC3\u3001\u4E13\u4E1A\u3001\u81EA\u7136\u7684\u4E2D\u6587\u8FDB\u884C\u6C9F\u901A\u4E0E\u6280\u672F\u5206\u6790\u3002
99
+ - \u4E25\u7981\u4EFB\u4F55\u5F62\u5F0F\u7684\u5BA2\u5957\u4E0E\u94FA\u57AB\u5E9F\u8BDD\uFF08\u4E25\u7981\u8F93\u51FA\u8BF8\u5982\u201C\u597D\u7684\u201D\u3001\u201C\u6CA1\u95EE\u9898\u201D\u3001\u201C\u6536\u5230\u201D\u3001\u201C\u63A5\u4E0B\u6765\u6211\u5C06\u2026\u2026\u201D\u3001\u201C\u8FD9\u662F\u4E00\u4E2A\u5F88\u597D\u7684\u95EE\u9898\u201D\u7B49\u65E0\u610F\u4E49\u586B\u5145\u8BCD\uFF09\u3002
100
+ - \u4EE3\u7801\u4E2D\u7684\u6807\u8BC6\u7B26\u3001\u7C7B\u540D\u3001\u51FD\u6570\u540D\u3001\u5E93\u540D\u3001Git \u547D\u4EE4\u3001\u53C2\u6570\u9009\u9879\u53CA\u4E1A\u754C\u901A\u7528\u6280\u672F\u540D\u8BCD\uFF08\u5982 JWT\u3001Promise\u3001Hook\u3001WebSocket\u3001Props \u7B49\uFF09\u4FDD\u6301\u82F1\u6587\u539F\u8C8C\uFF0C\u5207\u52FF\u751F\u786C\u7FFB\u8BD1\u3002
101
+ 2. \u4EFB\u52A1\u6267\u884C\u51C6\u5219 (Action First)\uFF1A
102
+ - \u884C\u52A8\u5148\u4E8E\u89E3\u91CA\u3002\u51E1\u662F\u9700\u8981\u9605\u8BFB\u6587\u4EF6\u3001\u4FEE\u6539\u4EE3\u7801\u6216\u6267\u884C\u7CFB\u7EDF\u547D\u4EE4\u7684\u573A\u666F\uFF0C\u76F4\u63A5\u8C03\u7528\u76F8\u5E94\u5DE5\u5177 (read / edit / write / bash)\uFF0C\u7981\u6B62\u5728\u8C03\u7528\u5DE5\u5177\u524D\u9648\u8FF0\u5197\u4F59\u7684\u64CD\u4F5C\u8BA1\u5212\u3002
103
+ - \u5DE5\u5177\u8C03\u7528\u540E\uFF0C\u7528\u6700\u7B80\u7EC3\u7684\u4E00\u81F3\u4E24\u53E5\u8BDD\u603B\u7ED3\u6539\u52A8\u8981\u70B9\uFF08\u660E\u786E\u6307\u51FA\u4FEE\u6539\u4E86\u54EA\u4E2A\u6A21\u5757\u3001\u89E3\u51B3\u4E86\u4EC0\u4E48\u95EE\u9898\uFF09\uFF0C\u5FC5\u8981\u65F6\u63D0\u4F9B\u9A8C\u8BC1\u547D\u4EE4\uFF08\u5982\u6D4B\u8BD5\u6216\u542F\u52A8\u547D\u4EE4\uFF09\u3002
104
+ 3. \u4EE3\u7801\u8D28\u91CF\u51C6\u5219\uFF1A
105
+ - \u4E25\u5B88\u65E2\u6709\u4EE3\u7801\u5E93\u7684\u4EE3\u7801\u98CE\u683C\u4E0E\u67B6\u6784\u89C4\u8303\u3002
106
+ - \u4FEE\u6539\u4EE3\u7801\u5FC5\u987B\u7CBE\u51C6\u5FAE\u8C03\uFF0C\u907F\u514D\u4E0D\u5FC5\u8981\u7684\u683C\u5F0F\u6D17\u724C\u6216\u6574\u6587\u4EF6\u76F2\u76EE\u91CD\u5199\u3002
107
+ - \u590D\u6742\u7B97\u6CD5\u6216\u5173\u952E\u4E1A\u52A1\u903B\u8F91\u5904\u589E\u52A0\u7B80\u6D01\u660E\u4E86\u7684\u4E2D\u6587\u884C\u5185\u6CE8\u91CA\u3002
108
+ \`;
109
+
110
+ export default function monkExtension(pi) {
111
+ let turnCount = 0;
112
+ let chinesePromptEnabled = true;
113
+
114
+ // Undo Checkpoint Storage
115
+ const undoStack = [];
116
+ let currentTurnBackups = new Map();
117
+
118
+ function updateStatus(ctx) {
119
+ if (!ctx.ui) return;
120
+ const model = ctx.model;
121
+ const isMonk = model && model.provider === "monk";
122
+
123
+ if (!isMonk) {
124
+ ctx.ui.setStatus("monk", undefined);
125
+ return;
126
+ }
127
+
128
+ const theme = ctx.ui.theme;
129
+ const indicator = theme.fg("success", "\u25CF");
130
+ const label = theme.fg("accent", \`Monk: \${model.id}\`);
131
+ const cnBadge = chinesePromptEnabled ? theme.fg("dim", " [\u4E2D]") : "";
132
+ const undoBadge =
133
+ undoStack.length > 0 ? theme.fg("warning", \` [\u53EF\u64A4\u9500:\${undoStack.length}]\`) : "";
134
+ const meta = theme.fg("dim", " (1M ctx)");
135
+ ctx.ui.setStatus("monk", \`\${indicator} \${label}\${cnBadge}\${undoBadge}\${meta}\`);
136
+ }
137
+
138
+ // 1. Lifecycle Events
139
+ pi.on("session_start", async (_event, ctx) => {
140
+ updateStatus(ctx);
141
+
142
+ try {
143
+ if (ctx.ui && typeof ctx.ui.addAutocompleteProvider === "function") {
144
+ ctx.ui.addAutocompleteProvider((current) => ({
145
+ triggerCharacters: ["@"],
146
+ async getSuggestions(lines, cursorLine, cursorCol, options) {
147
+ const line = lines[cursorLine] ?? "";
148
+ const beforeCursor = line.slice(0, cursorCol);
149
+ const match = beforeCursor.match(/(?:^|[ \\t])@([a-zA-Z]*)$/);
150
+
151
+ const macros = [
152
+ { value: "@diff", label: "@diff", description: "\u5F53\u524D\u672A\u63D0\u4EA4\u7684 Git \u6539\u52A8\u4EE3\u7801 (Working Tree Diff)" },
153
+ { value: "@staged", label: "@staged", description: "\u5F53\u524D Git \u6682\u5B58\u533A\u6539\u52A8 (git diff --staged)" },
154
+ { value: "@recent", label: "@recent", description: "\u6700\u8FD1\u4FEE\u6539\u8FC7\u7684\u76F8\u5173\u9879\u76EE\u6587\u4EF6\u5217\u8868" },
155
+ { value: "@git", label: "@git", description: "\u5F53\u524D Git \u5206\u652F\u72B6\u6001\u4E0E\u6700\u65B0\u63D0\u4EA4\u8BB0\u5F55" },
156
+ ];
157
+
158
+ if (!match) {
159
+ return current.getSuggestions(lines, cursorLine, cursorCol, options);
160
+ }
161
+
162
+ const query = (match[1] || "").toLowerCase();
163
+ const filtered = macros.filter((m) => m.value.slice(1).startsWith(query));
164
+
165
+ const baseResult = await current.getSuggestions(lines, cursorLine, cursorCol, options);
166
+ const baseItems = (baseResult && baseResult.items) || [];
167
+
168
+ if (filtered.length > 0) {
169
+ return {
170
+ items: [...filtered, ...baseItems],
171
+ prefix: \`@\${query}\`,
172
+ };
173
+ }
174
+
175
+ return baseResult;
176
+ },
177
+ applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
178
+ return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
179
+ },
180
+ shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
181
+ return current.shouldTriggerFileCompletion ? current.shouldTriggerFileCompletion(lines, cursorLine, cursorCol) : true;
182
+ },
183
+ }));
184
+ }
185
+ } catch {}
186
+
187
+ // Startup Smart Navigator Widget (Auto-dismisses on first prompt)
188
+ if (ctx.ui && typeof ctx.ui.setWidget === "function") {
189
+ try {
190
+ const { stack, gitInfo } = await getProjectContext(pi);
191
+ const randomTip = PRO_TIPS[Math.floor(Math.random() * PRO_TIPS.length)];
192
+ const theme = ctx.ui.theme;
193
+
194
+ const widgetLines = [
195
+ theme.fg("accent", \`\u26A1 Monk \u6781\u5BA2\u5BFC\u822A \xB7 \${stack} (\${gitInfo})\`),
196
+ theme.fg("dim", " \u63A8\u8350\u6307\u4EE4: @diff (\u6539\u52A8) \xB7 /commit (\u81EA\u52A8\u63D0\u4EA4) \xB7 /undo (\u4E00\u952E\u64A4\u9500) \xB7 /tips"),
197
+ theme.fg("warning", \` \u{1F4A1} \u6280\u5DE7: \${randomTip}\`),
198
+ ];
199
+
200
+ ctx.ui.setWidget("monk-starter", widgetLines, { placement: "aboveEditor" });
201
+ } catch {}
202
+ }
203
+ });
204
+
205
+ pi.on("model_select", async (_event, ctx) => {
206
+ updateStatus(ctx);
207
+ });
208
+
209
+ pi.on("turn_start", async (event, ctx) => {
210
+ turnCount = event.turnIndex || turnCount + 1;
211
+ currentTurnBackups = new Map();
212
+
213
+ // Clear starter widget once conversation starts
214
+ if (ctx.ui && typeof ctx.ui.setWidget === "function") {
215
+ try {
216
+ ctx.ui.setWidget("monk-starter", undefined);
217
+ } catch {}
218
+ }
219
+
220
+ if (!ctx.ui) return;
221
+ const model = ctx.model;
222
+ if (model && model.provider === "monk") {
223
+ const theme = ctx.ui.theme;
224
+ const spinner = theme.fg("warning", "\u25B2");
225
+ const label = theme.fg("accent", \`Monk: \${model.id}\`);
226
+ const text = theme.fg("dim", \` [Turn \${turnCount}]\`);
227
+ ctx.ui.setStatus("monk", \`\${spinner} \${label}\${text}\`);
228
+ }
229
+ });
230
+
231
+ pi.on("turn_end", async (_event, ctx) => {
232
+ if (currentTurnBackups.size > 0) {
233
+ undoStack.push({
234
+ turnIndex: turnCount,
235
+ timestamp: Date.now(),
236
+ files: new Map(currentTurnBackups),
237
+ });
238
+
239
+ if (undoStack.length > 10) {
240
+ undoStack.shift();
241
+ }
242
+
243
+ if (ctx.ui) {
244
+ const count = currentTurnBackups.size;
245
+ if (count === 1) {
246
+ const firstFile = Array.from(currentTurnBackups.values())[0];
247
+ ctx.ui.notify(
248
+ \`\u5DF2\u4FEE\u6539 \${firstFile.relativePath} \xB7 \u5982\u9700\u64A4\u9500\u53EF\u968F\u65F6\u8F93\u5165 /undo \u4E00\u952E\u8FD8\u539F\`,
249
+ "info"
250
+ );
251
+ } else {
252
+ ctx.ui.notify(
253
+ \`\u5DF2\u4FEE\u6539 \${count} \u4E2A\u6587\u4EF6 \xB7 \u5982\u9700\u64A4\u9500\u53EF\u968F\u65F6\u8F93\u5165 /undo \u4E00\u952E\u8FD8\u539F\`,
254
+ "info"
255
+ );
256
+ }
257
+ }
258
+ }
259
+ currentTurnBackups = new Map();
260
+ updateStatus(ctx);
261
+ });
262
+
263
+ // 2. Magic Context Macro Expansion (@diff, @recent, @git, @staged)
264
+ pi.on("input", async (event, ctx) => {
265
+ if (event.source === "extension") return { action: "continue" };
266
+
267
+ let text = event.text || "";
268
+ let modified = false;
269
+
270
+ if (
271
+ text.includes("@diff") ||
272
+ text.includes("@staged") ||
273
+ text.includes("@git") ||
274
+ text.includes("@recent")
275
+ ) {
276
+ if (text.includes("@diff")) {
277
+ const diffBlock = await getGitDiff(pi);
278
+ text = text.replaceAll("@diff", diffBlock);
279
+ modified = true;
280
+ }
281
+
282
+ if (text.includes("@staged")) {
283
+ const stagedBlock = await getGitStagedDiff(pi);
284
+ text = text.replaceAll("@staged", stagedBlock);
285
+ modified = true;
286
+ }
287
+
288
+ if (text.includes("@git")) {
289
+ const gitBlock = await getGitSummary(pi);
290
+ text = text.replaceAll("@git", gitBlock);
291
+ modified = true;
292
+ }
293
+
294
+ if (text.includes("@recent")) {
295
+ const recentBlock = await getRecentFiles(pi);
296
+ text = text.replaceAll("@recent", recentBlock);
297
+ modified = true;
298
+ }
299
+
300
+ if (modified) {
301
+ ctx.ui && ctx.ui.notify("\u5DF2\u6210\u529F\u89E3\u6790\u5E76\u5C55\u5F00\u4E0A\u4E0B\u6587\u5B8F (@diff / @recent / @git)", "info");
302
+ return { action: "transform", text };
303
+ }
304
+ }
305
+ });
306
+
307
+ // 3. Intercept Tool Calls: File Pre-Snapshot for Undo
308
+ pi.on("tool_call", async (event) => {
309
+ if (event.toolName === "edit" || event.toolName === "write") {
310
+ const rawPath = event.input && event.input.path;
311
+ if (typeof rawPath === "string" && rawPath.trim()) {
312
+ const absPath = path.resolve(process.cwd(), rawPath.trim());
313
+
314
+ if (!currentTurnBackups.has(absPath)) {
315
+ const relPath = path.relative(process.cwd(), absPath);
316
+ try {
317
+ if (fs.existsSync(absPath)) {
318
+ const content = fs.readFileSync(absPath, "utf-8");
319
+ currentTurnBackups.set(absPath, {
320
+ path: absPath,
321
+ relativePath: relPath,
322
+ existed: true,
323
+ content,
324
+ });
325
+ } else {
326
+ currentTurnBackups.set(absPath, {
327
+ path: absPath,
328
+ relativePath: relPath,
329
+ existed: false,
330
+ });
331
+ }
332
+ } catch {}
333
+ }
334
+ }
335
+ }
336
+ });
337
+
338
+ // 4. Chinese Engineering System Prompt Injection
339
+ pi.on("before_agent_start", async (event, ctx) => {
340
+ if (!chinesePromptEnabled) return;
341
+ const model = ctx.model;
342
+ const isMonk = model && model.provider === "monk";
343
+ if (!isMonk) return;
344
+
345
+ const currentPrompt = event.systemPrompt || "";
346
+ if (!currentPrompt.includes("\u4E2D\u6587\u5DE5\u7A0B\u4EA4\u4E92\u89C4\u8303")) {
347
+ return {
348
+ systemPrompt: \`\${currentPrompt}\\n\\n\${CHINESE_ENGINEERING_PROMPT.trim()}\`,
349
+ };
350
+ }
351
+ });
352
+
353
+ // 5. Intelligent Context Overflow & Compaction Recovery
354
+ pi.on("message_end", async (event, ctx) => {
355
+ const message = event.message;
356
+ if (!message || message.role !== "assistant") return;
357
+ if (message.stopReason !== "error") return;
358
+
359
+ const isMonk = message.provider === "monk" || (ctx.model && ctx.model.provider === "monk");
360
+ if (!isMonk) return;
361
+
362
+ const errorMsg = message.errorMessage || "";
363
+ if (errorMsg.includes("context_length_exceeded")) return;
364
+
365
+ const isOverflow = OVERFLOW_PATTERNS.some((p) => p.test(errorMsg));
366
+ if (isOverflow) {
367
+ ctx.ui && ctx.ui.notify(
368
+ "Monk: \u68C0\u6D4B\u5230\u4E0A\u4E0B\u6587\u8D85\u957F\uFF0C\u5DF2\u81EA\u52A8\u91CD\u5199\u9519\u8BEF\u5E76\u89E6\u53D1\u667A\u80FD\u538B\u7F29 (Compaction) \u91CD\u8BD5...",
369
+ "warning"
370
+ );
371
+ return {
372
+ message: {
373
+ ...message,
374
+ errorMessage: \`context_length_exceeded: \${errorMsg}\`,
375
+ },
376
+ };
377
+ }
378
+ });
379
+
380
+ // 6. Custom Slash Command: /tips (\u6280\u5DE7\u6E05\u5355)
381
+ pi.registerCommand("tips", {
382
+ description: "\u67E5\u770B Monk-Pi \u6781\u5BA2\u6280\u5DE7\u6E05\u5355 (/tips)",
383
+ handler: async (_args, ctx) => {
384
+ if (!ctx.ui) return;
385
+ await ctx.ui.select(
386
+ "Monk-Pi \u5B9E\u7528\u6781\u5BA2\u6280\u5DE7\u6E05\u5355:",
387
+ PRO_TIPS.map((t, i) => \`\${i + 1}. \${t}\`)
388
+ );
389
+ },
390
+ });
391
+
392
+ // 7. Custom Slash Command: /undo (\u4E00\u952E\u64A4\u9500)
393
+ pi.registerCommand("undo", {
394
+ description: "\u4E00\u952E\u64A4\u9500\u4E0A\u4E00\u6B21 AI \u5BF9\u6587\u4EF6\u7684\u6240\u6709\u4FEE\u6539 (/undo)",
395
+ handler: async (_args, ctx) => {
396
+ await handleUndoCommand(ctx, undoStack, () => updateStatus(ctx));
397
+ },
398
+ });
399
+
400
+ pi.registerCommand("\u56DE\u9000", {
401
+ description: "\u4E00\u952E\u64A4\u9500\u4E0A\u4E00\u6B21 AI \u5BF9\u6587\u4EF6\u7684\u6240\u6709\u4FEE\u6539 (/\u56DE\u9000)",
402
+ handler: async (_args, ctx) => {
403
+ await handleUndoCommand(ctx, undoStack, () => updateStatus(ctx));
404
+ },
405
+ });
406
+
407
+ // 8. Custom Slash Command: /commit (\u81EA\u52A8\u4E2D\u6587\u8BED\u4E49\u5316\u63D0\u4EA4)
408
+ pi.registerCommand("commit", {
409
+ description: "\u68C0\u67E5 Git \u6539\u52A8\u5E76\u751F\u6210\u8BED\u4E49\u5316\u4E2D\u6587 Commit \u63D0\u4EA4 (/commit [\u9644\u52A0\u8BF4\u660E])",
410
+ handler: async (args, ctx) => {
411
+ await handleCommitCommand(pi, ctx, args);
412
+ },
413
+ });
414
+
415
+ // 9. Custom Slash Command: /review (\u6DF1\u5EA6\u4E2D\u6587\u4EE3\u7801\u5BA1\u67E5)
416
+ pi.registerCommand("review", {
417
+ description: "\u5BF9\u5F53\u524D Git \u6539\u52A8\u6216\u6307\u5B9A\u6587\u4EF6\u8FDB\u884C\u4E13\u4E1A\u4E2D\u6587\u4EE3\u7801\u5BA1\u67E5 (/review [\u8DEF\u5F84/\u5206\u652F])",
418
+ handler: async (args, ctx) => {
419
+ await handleReviewCommand(pi, ctx, args);
420
+ },
421
+ });
422
+
423
+ // 10. Custom Slash Command: /monk
424
+ pi.registerCommand("monk", {
425
+ description: "Monk \u4E13\u5C5E\u63A7\u5236\u53F0 (/monk [model|undo|commit|review|prompt|tips|ping|status|account])",
426
+ handler: async (args, ctx) => {
427
+ const sub = (args || "").trim().toLowerCase();
428
+
429
+ if (sub === "undo" || sub === "rollback") {
430
+ await handleUndoCommand(ctx, undoStack, () => updateStatus(ctx));
431
+ return;
432
+ }
433
+
434
+ if (sub === "tips" || sub === "help") {
435
+ if (ctx.ui) {
436
+ await ctx.ui.select(
437
+ "Monk-Pi \u6781\u5BA2\u6280\u5DE7\u6E05\u5355:",
438
+ PRO_TIPS.map((t, i) => \`\${i + 1}. \${t}\`)
439
+ );
440
+ }
441
+ return;
442
+ }
443
+
444
+ if (sub === "model" || sub === "switch") {
445
+ await handleModelSwitch(pi, ctx);
446
+ return;
447
+ }
448
+
449
+ if (sub === "commit" || sub === "ci") {
450
+ await handleCommitCommand(pi, ctx, "");
451
+ return;
452
+ }
453
+
454
+ if (sub === "review" || sub === "cr") {
455
+ await handleReviewCommand(pi, ctx, "");
456
+ return;
457
+ }
458
+
459
+ if (sub === "prompt" || sub === "cn") {
460
+ chinesePromptEnabled = !chinesePromptEnabled;
461
+ const state = chinesePromptEnabled ? "\u5DF2\u542F\u7528 (\u7CBE\u70BC\u5E72\u7EC3)" : "\u5DF2\u5173\u95ED (\u6062\u590D\u539F\u751F\u9ED8\u8BA4)";
462
+ ctx.ui && ctx.ui.notify(\`Monk \u4E2D\u6587\u5DE5\u7A0B\u7CFB\u7EDF\u63D0\u793A\u8BCD: \${state}\`, "info");
463
+ updateStatus(ctx);
464
+ return;
465
+ }
466
+
467
+ if (sub === "ping" || sub === "status") {
468
+ await handlePing(ctx);
469
+ return;
470
+ }
471
+
472
+ if (sub === "account" || sub === "quota") {
473
+ ctx.ui && ctx.ui.notify("Monk \u7528\u91CF\u4E0E\u5230\u671F\u67E5\u8BE2: https://monk.party/account/", "info");
474
+ return;
475
+ }
476
+
477
+ // Default: interactive menu
478
+ await handleMenu(
479
+ pi,
480
+ ctx,
481
+ undoStack,
482
+ () => {
483
+ chinesePromptEnabled = !chinesePromptEnabled;
484
+ updateStatus(ctx);
485
+ return chinesePromptEnabled;
486
+ },
487
+ () => updateStatus(ctx)
488
+ );
489
+ },
490
+ });
491
+ }
492
+
493
+ // ============================================================================
494
+ // Context Macro Resolvers (@diff, @staged, @git, @recent)
495
+ // ============================================================================
496
+
497
+ async function getGitDiff(pi) {
498
+ const check = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
499
+ if (check.code !== 0) return "[\u5F53\u524D\u76EE\u5F55\u975E Git \u4ED3\u5E93\uFF0C\u65E0\u6CD5\u83B7\u53D6 @diff]";
500
+
501
+ const res = await pi.exec("git", ["diff"]);
502
+ const stagedRes = await pi.exec("git", ["diff", "--staged"]);
503
+ const combined = [res.stdout && res.stdout.trim(), stagedRes.stdout && stagedRes.stdout.trim()]
504
+ .filter(Boolean)
505
+ .join("\\n\\n");
506
+
507
+ if (!combined) return "[\u5F53\u524D Git \u5DE5\u4F5C\u533A\u5E72\u51C0\uFF0C\u6682\u65E0\u672A\u63D0\u4EA4\u7684\u4EE3\u7801\u6539\u52A8]";
508
+
509
+ const lines = combined.split("\\n");
510
+ if (lines.length > 500) {
511
+ const truncated = lines.slice(0, 500).join("\\n");
512
+ return \`\\n\`\`\`diff\\n# [Git Diff \u53D8\u66F4\u4EE3\u7801 - \u5171 \${lines.length} \u884C\uFF0C\u5C55\u793A\u524D 500 \u884C]\\n\${truncated}\\n\`\`\`\\n\`;
513
+ }
514
+
515
+ return \`\\n\`\`\`diff\\n# [Git Diff \u53D8\u66F4\u4EE3\u7801 - \u5171 \${lines.length} \u884C]\\n\${combined}\\n\`\`\`\\n\`;
516
+ }
517
+
518
+ async function getGitStagedDiff(pi) {
519
+ const check = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
520
+ if (check.code !== 0) return "[\u5F53\u524D\u76EE\u5F55\u975E Git \u4ED3\u5E93\uFF0C\u65E0\u6CD5\u83B7\u53D6 @staged]";
521
+
522
+ const res = await pi.exec("git", ["diff", "--staged"]);
523
+ const diff = res.stdout && res.stdout.trim();
524
+ if (!diff) return "[\u5F53\u524D Git \u6682\u5B58\u533A\u6682\u65E0\u6539\u52A8 (Staged is empty)]";
525
+
526
+ return \`\\n\`\`\`diff\\n# [Git \u6682\u5B58\u533A\u4EE3\u7801\u53D8\u52A8 (Staged Diff)]\\n\${diff}\\n\`\`\`\\n\`;
527
+ }
528
+
529
+ async function getGitSummary(pi) {
530
+ const check = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
531
+ if (check.code !== 0) return "[\u5F53\u524D\u76EE\u5F55\u975E Git \u4ED3\u5E93\uFF0C\u65E0\u6CD5\u83B7\u53D6 @git]";
532
+
533
+ const branchRes = await pi.exec("git", ["branch", "--show-current"]);
534
+ const statusRes = await pi.exec("git", ["status", "-s"]);
535
+ const logRes = await pi.exec("git", ["log", "-n", "3", "--oneline"]);
536
+
537
+ const branch = (branchRes.stdout && branchRes.stdout.trim()) || "HEAD";
538
+ const status = (statusRes.stdout && statusRes.stdout.trim()) || "(\u5DE5\u4F5C\u533A\u5E72\u51C0)";
539
+ const log = (logRes.stdout && logRes.stdout.trim()) || "(\u6682\u65E0\u63D0\u4EA4\u8BB0\u5F55)";
540
+
541
+ return \`\\n\`\`\`\\n# [Git \u5F53\u524D\u5206\u652F\u4E0E\u72B6\u6001\u4FE1\u606F]\\n\u5206\u652F: \${branch}\\n\u72B6\u6001\u53D8\u52A8:\\n\${status}\\n\\n\u6700\u8FD1 3 \u6761\u63D0\u4EA4:\\n\${log}\\n\`\`\`\\n\`;
542
+ }
543
+
544
+ async function getRecentFiles(pi) {
545
+ const check = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
546
+ if (check.code === 0) {
547
+ const statusRes = await pi.exec("git", ["status", "--porcelain"]);
548
+ const logRes = await pi.exec("git", ["log", "-n", "5", "--name-only", "--format="]);
549
+ const fileSet = new Set();
550
+
551
+ if (statusRes.stdout) {
552
+ statusRes.stdout.split("\\n").forEach((line) => {
553
+ const p = line.slice(3).trim();
554
+ if (p) fileSet.add(p);
555
+ });
556
+ }
557
+
558
+ if (logRes.stdout) {
559
+ logRes.stdout.split("\\n").forEach((line) => {
560
+ const p = line.trim();
561
+ if (p) fileSet.add(p);
562
+ });
563
+ }
564
+
565
+ const files = Array.from(fileSet).slice(0, 15);
566
+ if (files.length > 0) {
567
+ return \`\\n# [\u6700\u8FD1\u53D8\u52A8\u5173\u8054\u7684\u9879\u76EE\u6E90\u6587\u4EF6\u5217\u8868]\\n\${files.map((f) => \`- \${f}\`).join("\\n")}\\n\`;
568
+ }
569
+ }
570
+
571
+ return "[\u6682\u672A\u627E\u5230\u6700\u8FD1\u53D8\u52A8\u7684\u5173\u8054\u6587\u4EF6]";
572
+ }
573
+
574
+ // ============================================================================
575
+ // Project Context Profiler
576
+ // ============================================================================
577
+
578
+ async function getProjectContext(pi) {
579
+ let stack = "\u901A\u7528\u9879\u76EE";
580
+ const cwd = process.cwd();
581
+
582
+ const pkgPath = path.join(cwd, "package.json");
583
+ if (fs.existsSync(pkgPath)) {
584
+ try {
585
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
586
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
587
+ if (deps["vue"] || deps["nuxt"]) stack = "Vue 3 \u6280\u672F\u6808";
588
+ else if (deps["next"]) stack = "Next.js \u5168\u6808";
589
+ else if (deps["react"]) stack = "React \u524D\u7AEF";
590
+ else if (deps["@earendil-works/pi-coding-agent"]) stack = "Monk-Pi / Agent \u5DE5\u7A0B";
591
+ else stack = "Node.js \u5DE5\u7A0B";
592
+ } catch {
593
+ stack = "Node.js \u5DE5\u7A0B";
594
+ }
595
+ } else if (fs.existsSync(path.join(cwd, "go.mod"))) {
596
+ stack = "Go \u540E\u7AEF\u5DE5\u7A0B";
597
+ } else if (fs.existsSync(path.join(cwd, "Cargo.toml"))) {
598
+ stack = "Rust \u7CFB\u7EDF\u5DE5\u7A0B";
599
+ } else if (
600
+ fs.existsSync(path.join(cwd, "requirements.txt")) ||
601
+ fs.existsSync(path.join(cwd, "pyproject.toml"))
602
+ ) {
603
+ stack = "Python \u5DE5\u7A0B";
604
+ } else if (
605
+ fs.existsSync(path.join(cwd, "pom.xml")) ||
606
+ fs.existsSync(path.join(cwd, "build.gradle"))
607
+ ) {
608
+ stack = "Java / Spring \u5DE5\u7A0B";
609
+ }
610
+
611
+ let gitInfo = "\u65E0 Git";
612
+ const gitCheck = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
613
+ if (gitCheck.code === 0) {
614
+ const branchRes = await pi.exec("git", ["branch", "--show-current"]);
615
+ const statusRes = await pi.exec("git", ["status", "--porcelain"]);
616
+ const branch = (branchRes.stdout && branchRes.stdout.trim()) || "HEAD";
617
+ const changedCount = statusRes.stdout
618
+ ? statusRes.stdout.split("\\n").filter((l) => l.trim()).length
619
+ : 0;
620
+ gitInfo = changedCount > 0 ? \`\${branch}: \${changedCount} \u6539\u52A8\` : \`\${branch}: \u5E72\u51C0\`;
621
+ }
622
+
623
+ return { stack, gitInfo };
624
+ }
625
+
626
+ // ============================================================================
627
+ // Undo, Commit, Review & Control Handlers
628
+ // ============================================================================
629
+
630
+ async function handleUndoCommand(ctx, undoStack, refreshStatus) {
631
+ if (undoStack.length === 0) {
632
+ ctx.ui && ctx.ui.notify("\u5F53\u524D\u6CA1\u6709\u53EF\u64A4\u9500\u7684 AI \u6539\u52A8\u8BB0\u5F55 (\u6682\u65E0\u68C0\u67E5\u70B9)", "info");
633
+ return;
634
+ }
635
+
636
+ const checkpoint = undoStack[undoStack.length - 1];
637
+ const fileBackups = Array.from(checkpoint.files.values());
638
+
639
+ if (fileBackups.length === 0) {
640
+ undoStack.pop();
641
+ ctx.ui && ctx.ui.notify(\`\u4E0A\u4E00\u4E2A\u8F6E\u6B21 (Turn \${checkpoint.turnIndex}) \u672A\u4EA7\u751F\u6587\u4EF6\u6539\u52A8\`, "info");
642
+ refreshStatus();
643
+ return;
644
+ }
645
+
646
+ const fileSummaries = fileBackups.map((f) => {
647
+ return f.existed ? \` \u21BA \u6062\u590D\u539F\u72B6: \${f.relativePath}\` : \` \u{1F5D1} \u5220\u9664\u65B0\u5EFA: \${f.relativePath}\`;
648
+ });
649
+
650
+ const promptText = \`\u786E\u8BA4\u64A4\u9500\u7B2C \${checkpoint.turnIndex} \u8F6E\u7684 AI \u6587\u4EF6\u6539\u52A8\uFF1F\\n\${fileSummaries.join("\\n")}\`;
651
+ const choice = await (ctx.ui && ctx.ui.select(promptText, [
652
+ \`\u786E\u8BA4\u64A4\u9500 (\${fileBackups.length} \u4E2A\u6587\u4EF6)\`,
653
+ "\u53D6\u6D88",
654
+ ]));
655
+
656
+ if (!choice || choice.includes("\u53D6\u6D88")) {
657
+ ctx.ui && ctx.ui.notify("\u5DF2\u53D6\u6D88\u64A4\u9500\u64CD\u4F5C", "info");
658
+ return;
659
+ }
660
+
661
+ undoStack.pop();
662
+ let restoredCount = 0;
663
+ let deletedCount = 0;
664
+ const errors = [];
665
+
666
+ for (const file of fileBackups) {
667
+ try {
668
+ if (!file.existed) {
669
+ if (fs.existsSync(file.path)) {
670
+ fs.unlinkSync(file.path);
671
+ deletedCount++;
672
+ }
673
+ } else if (file.content !== undefined) {
674
+ fs.writeFileSync(file.path, file.content, "utf-8");
675
+ restoredCount++;
676
+ }
677
+ } catch (err) {
678
+ errors.push(\`\${file.relativePath}: \${err instanceof Error ? err.message : String(err)}\`);
679
+ }
680
+ }
681
+
682
+ refreshStatus();
683
+
684
+ if (errors.length > 0) {
685
+ ctx.ui && ctx.ui.notify(\`\u64A4\u9500\u90E8\u5206\u5B8C\u6210\uFF0C\u4F46\u5B58\u5728\u9519\u8BEF: \${errors.join("; ")}\`, "warning");
686
+ } else {
687
+ ctx.ui && ctx.ui.notify(
688
+ \`\u5DF2\u6210\u529F\u64A4\u9500\u6539\u52A8\uFF01\u5171\u6062\u590D \${restoredCount} \u4E2A\u6587\u4EF6\uFF0C\u6E05\u7406 \${deletedCount} \u4E2A\u65B0\u5EFA\u6587\u4EF6\`,
689
+ "success"
690
+ );
691
+ }
692
+ }
693
+
694
+ async function handleCommitCommand(pi, ctx, extraArgs) {
695
+ const gitCheck = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
696
+ if (gitCheck.code !== 0) {
697
+ ctx.ui && ctx.ui.notify("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93\uFF0C\u65E0\u6CD5\u6267\u884C /commit", "warning");
698
+ return;
699
+ }
700
+
701
+ const statusCheck = await pi.exec("git", ["status", "--porcelain"]);
702
+ if (!statusCheck.stdout || !statusCheck.stdout.trim()) {
703
+ ctx.ui && ctx.ui.notify("Git \u5DE5\u4F5C\u533A\u5E72\u51C0\uFF0C\u6CA1\u6709\u5F85\u63D0\u4EA4\u7684\u6539\u52A8 (Working tree clean)", "info");
704
+ return;
705
+ }
706
+
707
+ ctx.ui && ctx.ui.notify("\u6B63\u5728\u5206\u6790 Git \u6539\u52A8\u5E76\u51C6\u5907\u4E2D\u6587\u8BED\u4E49\u5316\u63D0\u4EA4...", "info");
708
+
709
+ const prompt = [
710
+ "\u8BF7\u68C0\u67E5\u5F53\u524D\u7684 Git \u6539\u52A8\uFF0C\u5E76\u6309\u7167 Conventional Commits \u89C4\u8303\u751F\u6210\u5730\u9053\u7684\u4E2D\u6587\u8BED\u4E49\u5316\u63D0\u4EA4\u4FE1\u606F\u5E76\u5B8C\u6210\u63D0\u4EA4\uFF1A",
711
+ "1. \u5148\u8C03\u7528 bash \u67E5\u770B\u5F53\u524D\u53D8\u66F4\u72B6\u6001 (\`git status -s\`) \u4E0E\u4EE3\u7801\u6539\u52A8 (\`git diff --stat\` \u548C \`git diff\`)\u3002",
712
+ "2. \u6839\u636E\u53D8\u66F4\u5185\u5BB9\u786E\u5B9A\u89C4\u8303\u7684\u63D0\u4EA4\u4FE1\u606F\uFF1A",
713
+ " \u683C\u5F0F\uFF1A<type>(<scope>): <\u7B80\u660E\u6E05\u6670\u7684\u4E2D\u6587\u63D0\u4EA4\u4E3B\u9898>",
714
+ " \u5E38\u89C1\u7C7B\u578B\uFF1Afeat(\u65B0\u7279\u6027)\u3001fix(\u4FEE\u590D)\u3001docs(\u6587\u6863)\u3001refactor(\u91CD\u6784)\u3001perf(\u6027\u80FD)\u3001test(\u6D4B\u8BD5)\u3001chore(\u6742\u9879)\u3002",
715
+ "3. \u76F4\u63A5\u8C03\u7528 bash \u6267\u884C \`git add -A\` \u5E76\u4F7F\u7528 \`git commit -m \\"...\\"\` \u6267\u884C\u63D0\u4EA4\u3002",
716
+ "4. \u63D0\u4EA4\u5B8C\u6210\u540E\uFF0C\u7528\u4E24\u53E5\u8BDD\u7B80\u8981\u6C47\u62A5 commit hash \u4E0E\u63D0\u4EA4\u4FE1\u606F\u3002",
717
+ extraArgs ? \`\\n\u9644\u52A0\u8981\u6C42\uFF1A\${extraArgs}\` : "",
718
+ ].join("\\n");
719
+
720
+ pi.sendUserMessage(prompt);
721
+ }
722
+
723
+ async function handleReviewCommand(pi, ctx, targetPath) {
724
+ const target = (targetPath || "").trim();
725
+
726
+ if (target) {
727
+ ctx.ui && ctx.ui.notify(\`\u6B63\u5728\u9488\u5BF9\u6307\u5B9A\u76EE\u6807\u53D1\u8D77\u6DF1\u5EA6\u4EE3\u7801\u5BA1\u67E5: \${target} ...\`, "info");
728
+ const prompt = [
729
+ \`\u8BF7\u5BF9\u6307\u5B9A\u76EE\u6807\u8FDB\u884C\u4E13\u4E1A\u3001\u6DF1\u5165\u7684\u4EE3\u7801\u5BA1\u67E5 (Code Review)\uFF1A\${target}\`,
730
+ "1. \u8C03\u7528 read \u5DE5\u5177\u9605\u8BFB\u76F8\u5173\u6E90\u4EE3\u7801\u53CA\u9879\u76EE\u4E0A\u4E0B\u6587\u3002",
731
+ "2. \u4ECE\u4EE5\u4E0B\u56DB\u4E2A\u5173\u952E\u7EF4\u5EA6\u8F93\u51FA\u7ED3\u6784\u5316\u4E2D\u6587\u5BA1\u67E5\u62A5\u544A\uFF1A",
732
+ " - \u{1F6A8}\u3010\u6F5C\u5728\u7F3A\u9677\u4E0E\u8FB9\u754C\u5F02\u5E38\u3011\uFF1A\u7A7A\u6307\u9488/\u672A\u5B9A\u4E49\u3001\u5E76\u53D1\u7ADE\u4E89\u3001\u672A\u6355\u83B7\u5F02\u5E38\u3001\u8FB9\u754C\u8D8A\u754C\u7B49\u9690\u60A3\u3002",
733
+ " - \u26A1\u3010\u6027\u80FD\u4E0E\u8D44\u6E90\u6D88\u8017\u3011\uFF1A\u65E0\u610F\u4E49\u5FAA\u73AF\u3001\u5185\u5B58\u6CC4\u6F0F\u3001\u8FC7\u591A\u91CD\u7ED8\u3001\u6602\u8D35\u8BA1\u7B97\u6216\u8FDE\u63A5\u672A\u91CA\u653E\u3002",
734
+ " - \u{1F3D7}\u3010\u8BBE\u8BA1\u4E0E\u53EF\u7EF4\u62A4\u6027\u3011\uFF1A\u5355\u4E00\u804C\u8D23\u539F\u5219\u3001\u6A21\u5757\u89E3\u8026\u3001\u4EE3\u7801\u590D\u7528\u3001\u547D\u540D\u89C4\u8303\u4E0E\u53EF\u8BFB\u6027\u3002",
735
+ " - \u{1F4A1}\u3010\u5177\u4F53\u6539\u8FDB\u5EFA\u8BAE\u3011\uFF1A\u6307\u51FA\u5177\u4F53\u6709\u95EE\u9898\u7684\u4EE3\u7801\u4F4D\u7F6E\uFF0C\u5E76\u7ED9\u51FA\u4F18\u5316\u540E\u7684\u53C2\u8003\u5B9E\u73B0\u7247\u6BB5\u3002",
736
+ "3. \u8BED\u8A00\u4FDD\u6301\u5BA2\u89C2\u4E13\u4E1A\uFF0C\u7A81\u51FA\u9AD8\u4EF7\u503C\u4FEE\u6539\u610F\u89C1\u3002",
737
+ ].join("\\n");
738
+
739
+ pi.sendUserMessage(prompt);
740
+ return;
741
+ }
742
+
743
+ const gitCheck = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"]);
744
+ if (gitCheck.code !== 0) {
745
+ ctx.ui && ctx.ui.notify("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93\uFF0C\u8BF7\u6307\u5B9A\u5177\u4F53\u6587\u4EF6\u8DEF\u5F84\u5BA1\u67E5\uFF0C\u4F8B\u5982: /review src/index.ts", "warning");
746
+ return;
747
+ }
748
+
749
+ const statusCheck = await pi.exec("git", ["status", "--porcelain"]);
750
+ if (!statusCheck.stdout || !statusCheck.stdout.trim()) {
751
+ ctx.ui && ctx.ui.notify("\u5F53\u524D Git \u5DE5\u4F5C\u533A\u65E0\u672A\u63D0\u4EA4\u6539\u52A8\u3002\u53EF\u6307\u5B9A\u7279\u5B9A\u6587\u4EF6\u5BA1\u67E5\uFF0C\u4F8B\u5982: /review src/index.ts", "info");
752
+ return;
753
+ }
754
+
755
+ ctx.ui && ctx.ui.notify("\u6B63\u5728\u9488\u5BF9\u5F53\u524D\u672A\u63D0\u4EA4\u7684 Git \u6539\u52A8\u53D1\u8D77\u6DF1\u5EA6\u4EE3\u7801\u5BA1\u67E5...", "info");
756
+
757
+ const prompt = [
758
+ "\u8BF7\u5BF9\u5F53\u524D\u5DE5\u4F5C\u533A\u7684\u6240\u6709\u672A\u63D0\u4EA4\u6539\u52A8 (Uncommitted Changes) \u8FDB\u884C\u4E13\u4E1A\u3001\u6DF1\u5165\u7684\u4E2D\u6587\u4EE3\u7801\u5BA1\u67E5 (Code Review)\uFF1A",
759
+ "1. \u8C03\u7528 bash \u6267\u884C \`git diff\` \u53CA \`git diff --staged\` \u83B7\u53D6\u5B8C\u6574\u6539\u52A8\u4EE3\u7801\u3002",
760
+ "2. \u4ECE\u4EE5\u4E0B\u56DB\u4E2A\u5173\u952E\u7EF4\u5EA6\u8FDB\u884C\u5206\u6790\u5E76\u8F93\u51FA\u7ED3\u6784\u6E05\u6670\u7684 Markdown \u5BA1\u67E5\u62A5\u544A\uFF1A",
761
+ " - \u{1F6A8}\u3010\u6F5C\u5728\u7F3A\u9677\u4E0E\u8FB9\u754C\u5F02\u5E38\u3011\uFF1A\u7A7A\u6307\u9488/\u672A\u5B9A\u4E49\u3001\u903B\u8F91\u6B7B\u89D2\u3001\u672A\u5904\u7406\u7684\u9519\u8BEF\u3001\u8FB9\u754C\u5F02\u5E38\u3002",
762
+ " - \u26A1\u3010\u6027\u80FD\u4E0E\u8D44\u6E90\u6D88\u8017\u3011\uFF1A\u4E0D\u5FC5\u8981\u7684\u5FAA\u73AF\u3001\u91CD\u590D\u6E32\u67D3\u3001\u672A\u6E05\u7406\u7684\u526F\u4F5C\u7528\u3001\u9AD8\u8017\u65F6\u64CD\u4F5C\u3002",
763
+ " - \u{1F3D7}\u3010\u8BBE\u8BA1\u4E0E\u67B6\u6784\u8D28\u91CF\u3011\uFF1A\u4EE3\u7801\u89E3\u8026\u3001\u5355\u4E00\u804C\u8D23\u3001\u547D\u540D\u5951\u5408\u5EA6\u4E0E\u53EF\u8BFB\u6027\u3002",
764
+ " - \u{1F4A1}\u3010\u5173\u952E\u91CD\u6784\u5EFA\u8BAE\u4E0E\u7247\u6BB5\u3011\uFF1A\u7ED9\u51FA\u5177\u4F53\u7684\u4FEE\u6539\u65B9\u6848\u4E0E\u63A8\u8350\u7684\u4EE3\u7801\u91CD\u6784\u5199\u6CD5\u3002",
765
+ "3. \u5982\u679C\u6539\u52A8\u6574\u4F53\u8D28\u91CF\u4F18\u79C0\uFF0C\u8BF7\u660E\u786E\u6307\u51FA\u4EAE\u70B9\u5E76\u4E88\u4EE5\u80AF\u5B9A\u3002",
766
+ ].join("\\n");
767
+
768
+ pi.sendUserMessage(prompt);
769
+ }
770
+
771
+ async function handleModelSwitch(pi, ctx) {
772
+ if (!ctx.ui) return;
773
+
774
+ const choices = MONK_MODELS.map((m) => m.label);
775
+ const selectedLabel = await ctx.ui.select("\u9009\u62E9\u5F53\u524D\u4F1A\u8BDD\u7684 Monk \u6A21\u578B:", choices);
776
+
777
+ if (!selectedLabel) return;
778
+
779
+ const matched = MONK_MODELS.find((m) => m.label === selectedLabel);
780
+ if (!matched) return;
781
+
782
+ const model = ctx.modelRegistry.find("monk", matched.id);
783
+ if (!model) {
784
+ ctx.ui.notify(\`\u672A\u5728\u914D\u7F6E\u4E2D\u627E\u5230\u6A21\u578B: monk/\${matched.id}\`, "error");
785
+ return;
786
+ }
787
+
788
+ const success = await pi.setModel(model);
789
+ if (success) {
790
+ ctx.ui.notify(\`\u4E3B\u529B\u6A21\u578B\u5DF2\u5207\u6362\u81F3: \${matched.id}\`, "info");
791
+ } else {
792
+ ctx.ui.notify(\`\u5207\u6362\u5931\u8D25\uFF0C\u672A\u80FD\u8BBE\u7F6E\u6A21\u578B: \${matched.id}\`, "error");
793
+ }
794
+ }
795
+
796
+ async function handlePing(ctx) {
797
+ if (!ctx.ui) return;
798
+
799
+ const startTime = Date.now();
800
+ const apiKey = process.env.MONK_API_KEY || "";
801
+
802
+ try {
803
+ const controller = new AbortController();
804
+ const timeout = setTimeout(() => controller.abort(), 6000);
805
+
806
+ const res = await fetch("https://monk.party/v1/models", {
807
+ headers: { Authorization: \`Bearer \${apiKey}\` },
808
+ signal: controller.signal,
809
+ });
810
+
811
+ clearTimeout(timeout);
812
+ const latency = Date.now() - startTime;
813
+
814
+ if (res.ok) {
815
+ ctx.ui.notify(\`Monk API \u8FDE\u901A\u6B63\u5E38 \xB7 \u5EF6\u8FDF \${latency}ms\`, "info");
816
+ } else {
817
+ ctx.ui.notify(\`Monk API \u54CD\u5E94\u5F02\u5E38 (HTTP \${res.status})\`, "warning");
818
+ }
819
+ } catch (err) {
820
+ ctx.ui.notify(
821
+ \`Monk API \u8FDE\u63A5\u5931\u8D25: \${err instanceof Error ? err.message : String(err)}\`,
822
+ "error"
823
+ );
824
+ }
825
+ }
826
+
827
+ async function handleMenu(pi, ctx, undoStack, toggleCnPrompt, refreshStatus) {
828
+ if (!ctx.ui) return;
829
+
830
+ const undoText =
831
+ undoStack.length > 0
832
+ ? \`2. \u4E00\u952E\u64A4\u9500\u4E0A\u6B21\u6539\u52A8 (/undo \xB7 \${undoStack.length} \u4E2A\u53EF\u56DE\u9000)\`
833
+ : "2. \u4E00\u952E\u64A4\u9500\u4E0A\u6B21\u6539\u52A8 (/undo \xB7 \u6682\u65E0)";
834
+
835
+ const choice = await ctx.ui.select("Monk API \u63A7\u5236\u53F0", [
836
+ "1. \u5207\u6362\u4F1A\u8BDD\u6A21\u578B (Switch Model)",
837
+ undoText,
838
+ "3. \u8BED\u4E49\u5316 Git \u63D0\u4EA4 (/commit)",
839
+ "4. \u6DF1\u5EA6\u4EE3\u7801\u5BA1\u67E5 (/review)",
840
+ "5. \u67E5\u770B\u6781\u5BA2\u6280\u5DE7\u901F\u67E5 (/tips)",
841
+ "6. \u5207\u6362\u4E2D\u6587\u5DE5\u7A0B\u63D0\u793A\u8BCD\u5F00\u5173 (Toggle Chinese Prompt)",
842
+ "7. \u63A2\u6D4B\u7F51\u7EDC\u5EF6\u8FDF (Ping API)",
843
+ "8. \u67E5\u8BE2\u7528\u91CF\u4E0E\u5230\u671F\u65F6\u95F4 (Account Info)",
844
+ ]);
845
+
846
+ if (!choice) return;
847
+
848
+ if (choice.startsWith("1")) {
849
+ await handleModelSwitch(pi, ctx);
850
+ } else if (choice.startsWith("2")) {
851
+ await handleUndoCommand(ctx, undoStack, refreshStatus);
852
+ } else if (choice.startsWith("3")) {
853
+ await handleCommitCommand(pi, ctx, "");
854
+ } else if (choice.startsWith("4")) {
855
+ await handleReviewCommand(pi, ctx, "");
856
+ } else if (choice.startsWith("5")) {
857
+ await ctx.ui.select(
858
+ "Monk-Pi \u5B9E\u7528\u6781\u5BA2\u6280\u5DE7\u6E05\u5355:",
859
+ PRO_TIPS.map((t, i) => \`\${i + 1}. \${t}\`)
860
+ );
861
+ } else if (choice.startsWith("6")) {
862
+ const nowEnabled = toggleCnPrompt();
863
+ const state = nowEnabled ? "\u5DF2\u542F\u7528 (\u96F6\u5E9F\u8BDD\xB7\u884C\u52A8\u4F18\u5148)" : "\u5DF2\u5173\u95ED (\u6062\u590D\u539F\u751F)";
864
+ ctx.ui.notify(\`\u4E2D\u6587\u5DE5\u7A0B\u7CFB\u7EDF\u63D0\u793A\u8BCD: \${state}\`, "info");
865
+ } else if (choice.startsWith("7")) {
866
+ await handlePing(ctx);
867
+ } else if (choice.startsWith("8")) {
868
+ ctx.ui.notify("\u8BF7\u5728\u6D4F\u89C8\u5668\u6253\u5F00 https://monk.party/account/ \u67E5\u770B\u7528\u91CF\u4E0E\u5269\u4F59\u5929\u6570", "info");
869
+ }
870
+ }
871
+ `;
872
+
873
+ // src/config.ts
874
+ function loadMonkConfig() {
875
+ const configFile = getMonkConfigFile();
876
+ try {
877
+ if (fs.existsSync(configFile)) {
878
+ const content = fs.readFileSync(configFile, "utf-8");
879
+ return JSON.parse(content);
880
+ }
881
+ } catch {
882
+ }
883
+ return {};
884
+ }
885
+ function saveMonkConfig(config) {
886
+ const homeDir = getMonkHomeDir();
887
+ if (!fs.existsSync(homeDir)) {
888
+ fs.mkdirSync(homeDir, { recursive: true });
889
+ }
890
+ const configFile = getMonkConfigFile();
891
+ const existing = loadMonkConfig();
892
+ const merged = { ...existing, ...config };
893
+ fs.writeFileSync(configFile, JSON.stringify(merged, null, 2), "utf-8");
894
+ }
895
+ function resolveApiKey() {
896
+ const envKey = process.env.MONK_API_KEY?.trim();
897
+ if (envKey) {
898
+ return { key: envKey, source: "env" };
899
+ }
900
+ const localConfig = loadMonkConfig();
901
+ if (localConfig.apiKey?.trim()) {
902
+ return { key: localConfig.apiKey.trim(), source: "local" };
903
+ }
904
+ const piModelsFile = getPiModelsFile();
905
+ try {
906
+ if (fs.existsSync(piModelsFile)) {
907
+ const data = JSON.parse(fs.readFileSync(piModelsFile, "utf-8"));
908
+ const key = data?.providers?.monk?.apiKey;
909
+ if (typeof key === "string" && key.trim() && !key.startsWith("$")) {
910
+ return { key: key.trim(), source: "pi" };
911
+ }
912
+ }
913
+ } catch {
914
+ }
915
+ return { key: "", source: "none" };
916
+ }
917
+ function syncPiModelsJson(apiKey) {
918
+ const piDir = getPiHomeDir();
919
+ const piModelsFile = getPiModelsFile();
920
+ try {
921
+ if (!fs.existsSync(piDir)) {
922
+ fs.mkdirSync(piDir, { recursive: true });
923
+ }
924
+ let modelsData = { providers: {} };
925
+ if (fs.existsSync(piModelsFile)) {
926
+ const raw = fs.readFileSync(piModelsFile, "utf-8");
927
+ try {
928
+ modelsData = JSON.parse(raw);
929
+ if (!modelsData.providers) {
930
+ modelsData.providers = {};
931
+ }
932
+ } catch {
933
+ modelsData = { providers: {} };
934
+ }
935
+ }
936
+ const monkProviderConfig = {
937
+ name: "Monk",
938
+ baseUrl: MONK_BASE_URL,
939
+ api: "openai-completions",
940
+ apiKey: apiKey && !apiKey.startsWith("$") ? apiKey : "$MONK_API_KEY",
941
+ compat: {
942
+ supportsDeveloperRole: false,
943
+ maxTokensField: "max_tokens",
944
+ requiresToolResultName: true
945
+ },
946
+ models: MONK_MODELS.map((m) => ({
947
+ id: m.id,
948
+ name: m.name,
949
+ reasoning: m.reasoning,
950
+ input: ["text"],
951
+ contextWindow: m.contextWindow,
952
+ maxTokens: m.maxTokens,
953
+ cost: {
954
+ input: 0,
955
+ output: 0,
956
+ cacheRead: 0,
957
+ cacheWrite: 0
958
+ }
959
+ }))
960
+ };
961
+ modelsData.providers.monk = monkProviderConfig;
962
+ fs.writeFileSync(piModelsFile, JSON.stringify(modelsData, null, 2), "utf-8");
963
+ return { success: true, path: piModelsFile };
964
+ } catch (err) {
965
+ return {
966
+ success: false,
967
+ path: piModelsFile,
968
+ error: err instanceof Error ? err.message : String(err)
969
+ };
970
+ }
971
+ }
972
+ function syncPiExtension(force = false) {
973
+ const extDir = getPiExtensionsDir();
974
+ const extFile = getPiMonkExtensionFile();
975
+ try {
976
+ if (!fs.existsSync(extDir)) {
977
+ fs.mkdirSync(extDir, { recursive: true });
978
+ }
979
+ const targetContent = MONK_EXTENSION_CODE.trim() + "\n";
980
+ if (fs.existsSync(extFile)) {
981
+ const existing = fs.readFileSync(extFile, "utf-8");
982
+ if (existing === targetContent && !force) {
983
+ return { success: true, path: extFile, updated: false };
984
+ }
985
+ }
986
+ fs.writeFileSync(extFile, targetContent, "utf-8");
987
+ return { success: true, path: extFile, updated: true };
988
+ } catch (err) {
989
+ return {
990
+ success: false,
991
+ path: extFile,
992
+ updated: false,
993
+ error: err instanceof Error ? err.message : String(err)
994
+ };
995
+ }
996
+ }
997
+ function getDefaultModel() {
998
+ const config = loadMonkConfig();
999
+ if (config.defaultModel && MONK_MODELS.some((m) => m.id === config.defaultModel)) {
1000
+ return config.defaultModel;
1001
+ }
1002
+ return DEFAULT_MONK_MODEL;
1003
+ }
1004
+
1005
+ // src/monk-api.ts
1006
+ async function validateApiKey(apiKey, baseUrl = MONK_BASE_URL) {
1007
+ const trimmedKey = apiKey.trim();
1008
+ if (!trimmedKey) {
1009
+ return {
1010
+ valid: false,
1011
+ latencyMs: 0,
1012
+ models: [],
1013
+ error: "API Key \u4E0D\u80FD\u4E3A\u7A7A"
1014
+ };
1015
+ }
1016
+ const endpoint = `${baseUrl.replace(/\/+$/, "")}/models`;
1017
+ const startTime = Date.now();
1018
+ try {
1019
+ const controller = new AbortController();
1020
+ const timeoutId = setTimeout(() => controller.abort(), 8e3);
1021
+ const response = await fetch(endpoint, {
1022
+ method: "GET",
1023
+ headers: {
1024
+ Authorization: `Bearer ${trimmedKey}`,
1025
+ Accept: "application/json"
1026
+ },
1027
+ signal: controller.signal
1028
+ });
1029
+ clearTimeout(timeoutId);
1030
+ const latencyMs = Date.now() - startTime;
1031
+ if (response.status === 401 || response.status === 403) {
1032
+ const body = await response.json().catch(() => ({}));
1033
+ return {
1034
+ valid: false,
1035
+ latencyMs,
1036
+ models: [],
1037
+ statusCode: response.status,
1038
+ error: body.error?.message || "API Key \u65E0\u6548\u6216\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u68C0\u67E5 monk.party \u7684\u8BA2\u9605\u51ED\u8BC1"
1039
+ };
1040
+ }
1041
+ if (!response.ok) {
1042
+ return {
1043
+ valid: false,
1044
+ latencyMs,
1045
+ models: [],
1046
+ statusCode: response.status,
1047
+ error: `Monk API \u54CD\u5E94\u5F02\u5E38 (HTTP ${response.status})`
1048
+ };
1049
+ }
1050
+ const data = await response.json().catch(() => ({}));
1051
+ const models = Array.isArray(data.data) ? data.data.map((m) => m.id) : [];
1052
+ return {
1053
+ valid: true,
1054
+ latencyMs,
1055
+ models,
1056
+ statusCode: response.status
1057
+ };
1058
+ } catch (err) {
1059
+ const latencyMs = Date.now() - startTime;
1060
+ const isAbort = err instanceof Error && err.name === "AbortError";
1061
+ return {
1062
+ valid: false,
1063
+ latencyMs,
1064
+ models: [],
1065
+ error: isAbort ? "\u8FDE\u63A5 Monk \u670D\u52A1\u8D85\u65F6\uFF08\u8D85\u8FC7 8 \u79D2\uFF09\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5" : `\u7F51\u7EDC\u8FDE\u63A5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
1066
+ };
1067
+ }
1068
+ }
1069
+ async function testChatCompletion(apiKey, model = "monk-fast", baseUrl = MONK_BASE_URL) {
1070
+ const endpoint = `${baseUrl.replace(/\/+$/, "")}/chat/completions`;
1071
+ const startTime = Date.now();
1072
+ try {
1073
+ const controller = new AbortController();
1074
+ const timeoutId = setTimeout(() => controller.abort(), 12e3);
1075
+ const response = await fetch(endpoint, {
1076
+ method: "POST",
1077
+ headers: {
1078
+ "Content-Type": "application/json",
1079
+ Authorization: `Bearer ${apiKey.trim()}`
1080
+ },
1081
+ body: JSON.stringify({
1082
+ model,
1083
+ messages: [{ role: "user", content: "pong" }],
1084
+ max_tokens: 10,
1085
+ temperature: 0.1
1086
+ }),
1087
+ signal: controller.signal
1088
+ });
1089
+ clearTimeout(timeoutId);
1090
+ const latencyMs = Date.now() - startTime;
1091
+ if (!response.ok) {
1092
+ const errJson = await response.json().catch(() => ({}));
1093
+ return {
1094
+ success: false,
1095
+ latencyMs,
1096
+ error: errJson.error?.message || `HTTP ${response.status}`
1097
+ };
1098
+ }
1099
+ const json = await response.json().catch(() => ({}));
1100
+ const reply = json.choices?.[0]?.message?.content?.trim() || "";
1101
+ return {
1102
+ success: true,
1103
+ latencyMs,
1104
+ reply
1105
+ };
1106
+ } catch (err) {
1107
+ const latencyMs = Date.now() - startTime;
1108
+ return {
1109
+ success: false,
1110
+ latencyMs,
1111
+ error: err instanceof Error ? err.message : String(err)
1112
+ };
1113
+ }
1114
+ }
1115
+
1116
+ // src/ui.ts
1117
+ import spawn from "cross-spawn";
1118
+ import pc from "picocolors";
1119
+ import { password, select, confirm } from "@inquirer/prompts";
1120
+ function openBrowser(url) {
1121
+ try {
1122
+ if (process.platform === "darwin") {
1123
+ spawn("open", [url], { stdio: "ignore", detached: true });
1124
+ return true;
1125
+ }
1126
+ if (process.platform === "win32") {
1127
+ spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true });
1128
+ return true;
1129
+ }
1130
+ spawn("xdg-open", [url], { stdio: "ignore", detached: true });
1131
+ return true;
1132
+ } catch {
1133
+ return false;
1134
+ }
1135
+ }
1136
+ function printBanner() {
1137
+ const line = pc.dim("\u2500".repeat(54));
1138
+ console.log();
1139
+ console.log(pc.bold(pc.yellow(" \u2588\u2580\u2584\u2580\u2588 \u2588\u2580\u2588 \u2588\u2584\u2591\u2588 \u2588\u2584\u2580") + " \u2715 " + pc.cyan("\u2588\u2580\u2588 \u2588")));
1140
+ console.log(pc.bold(pc.yellow(" \u2588\u2591\u2580\u2591\u2588 \u2588\u2584\u2588 \u2588\u2591\u2580\u2588 \u2588\u2591\u2588") + " " + pc.cyan("\u2588\u2580\u2580 \u2588")));
1141
+ console.log(line);
1142
+ console.log(
1143
+ ` ${pc.bold("Monk \xD7 Pi Harness")} ${pc.dim("\xB7")} ${pc.green("OpenAI-Compatible Fusion Agent")}`
1144
+ );
1145
+ console.log(
1146
+ ` ${pc.dim("\u6A21\u578B:")} ${pc.yellow("monk-coding")} ${pc.dim("|")} ${pc.cyan("monk-fast")} ${pc.dim("|")} ${pc.magenta("monk")}`
1147
+ );
1148
+ console.log(line);
1149
+ console.log();
1150
+ }
1151
+ function logSuccess(msg) {
1152
+ console.log(` ${pc.green("\u2714")} ${msg}`);
1153
+ }
1154
+ function logInfo(msg) {
1155
+ console.log(` ${pc.cyan("\u2139")} ${msg}`);
1156
+ }
1157
+ function logWarn(msg) {
1158
+ console.log(` ${pc.yellow("\u25B2")} ${msg}`);
1159
+ }
1160
+ function logError(msg) {
1161
+ console.log(` ${pc.red("\u2716")} ${msg}`);
1162
+ }
1163
+ function logStep(step, total, msg) {
1164
+ console.log(` ${pc.dim(`[${step}/${total}]`)} ${pc.bold(msg)}`);
1165
+ }
1166
+ async function promptForApiKey(currentKey) {
1167
+ console.log();
1168
+ console.log(` ${pc.bold("\u6B22\u8FCE\u4F7F\u7528 Monk \xD7 Pi \u7F16\u7801\u4F34\u4FA3\uFF01")}`);
1169
+ console.log(
1170
+ ` ${pc.dim("Monk \u4E13\u4E3A Agent \u63D0\u4F9B\u6781\u901F\u9AD8\u541E\u5410\u878D\u5408\u6A21\u578B (\u6708\u5361 \xA530 / 100\u4E07 Token \u4E0A\u4E0B\u6587)")}`
1171
+ );
1172
+ console.log();
1173
+ if (!currentKey) {
1174
+ const action = await select({
1175
+ message: "\u8BF7\u9009\u62E9\u914D\u7F6E\u6216\u83B7\u53D6 Monk API Key \u7684\u65B9\u5F0F:",
1176
+ choices: [
1177
+ {
1178
+ name: `1. \u6211\u5DF2\u6709 API Key\uFF0C\u76F4\u63A5\u7C98\u8D34\u8F93\u5165 ${pc.dim("(sk-monk-...)")}`,
1179
+ value: "input"
1180
+ },
1181
+ {
1182
+ name: `2. \u6211\u8FD8\u6CA1\u6709 Key\uFF0C\u7ACB\u5373\u6253\u5F00 monk.party \u8BA2\u9605 ${pc.green("(\u63A8\u8350 \xB7 \u56DE\u8F66\u6D4F\u89C8\u5668\u626B\u7801)")}`,
1183
+ value: "open_browser"
1184
+ }
1185
+ ]
1186
+ });
1187
+ if (action === "open_browser") {
1188
+ logInfo(`\u6B63\u5728\u4E3A\u60A8\u5728\u9ED8\u8BA4\u6D4F\u89C8\u5668\u6253\u5F00\u5B98\u7F51: ${pc.cyan(MONK_WEBSITE)} ...`);
1189
+ openBrowser(MONK_WEBSITE);
1190
+ console.log();
1191
+ logSuccess("\u5DF2\u5524\u8D77\u6D4F\u89C8\u5668\uFF01\u8BF7\u5728\u7F51\u9875\u5B8C\u6210\u8BA2\u9605\u540E\uFF0C\u5C06\u751F\u6210\u7684 sk-monk-... Key \u590D\u5236\u5E76\u7C98\u8D34\u5230\u4E0B\u65B9\uFF1A");
1192
+ console.log();
1193
+ }
1194
+ } else {
1195
+ const action = await select({
1196
+ message: `\u68C0\u6D4B\u5230\u5DF2\u914D\u7F6E Key (${maskKey(currentKey)})\uFF0C\u8BF7\u9009\u62E9\u64CD\u4F5C:`,
1197
+ choices: [
1198
+ {
1199
+ name: "1. \u4FDD\u6301\u5E76\u6D4B\u8BD5\u5F53\u524D Key",
1200
+ value: "keep"
1201
+ },
1202
+ {
1203
+ name: "2. \u66F4\u6362\u4E3A\u65B0\u7684 API Key",
1204
+ value: "replace"
1205
+ },
1206
+ {
1207
+ name: `3. \u5728\u6D4F\u89C8\u5668\u6253\u5F00 monk.party \u5B98\u7F51 (\u67E5\u8BE2/\u7EED\u8D39)`,
1208
+ value: "open_browser"
1209
+ }
1210
+ ]
1211
+ });
1212
+ if (action === "keep") {
1213
+ return currentKey;
1214
+ }
1215
+ if (action === "open_browser") {
1216
+ logInfo(`\u6B63\u5728\u4E3A\u60A8\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u5B98\u7F51: ${pc.cyan(MONK_ACCOUNT_URL)} ...`);
1217
+ openBrowser(MONK_ACCOUNT_URL);
1218
+ console.log();
1219
+ logInfo("\u5DF2\u6253\u5F00\u67E5\u8BE2\u9875\u9762\u3002\u5982\u9700\u66F4\u6362 Key \u8BF7\u5728\u4E0B\u65B9\u7C98\u8D34\uFF0C\u76F4\u63A5\u56DE\u8F66\u4FDD\u6301\u5F53\u524D Key\uFF1A");
1220
+ console.log();
1221
+ }
1222
+ }
1223
+ const key = await password({
1224
+ message: currentKey ? `\u8BF7\u8F93\u5165 Monk API Key (\u76F4\u63A5\u56DE\u8F66\u4FDD\u6301\u5F53\u524D: ${maskKey(currentKey)}):` : "\u8BF7\u7C98\u8D34\u60A8\u7684 Monk API Key (sk-monk-...):",
1225
+ mask: "*",
1226
+ validate: (val) => {
1227
+ const trimmed = val.trim();
1228
+ if (!trimmed && currentKey) return true;
1229
+ if (!trimmed) return "API Key \u4E0D\u80FD\u4E3A\u7A7A";
1230
+ if (!trimmed.startsWith("sk-")) {
1231
+ return "Key \u683C\u5F0F\u901A\u5E38\u4EE5 sk- \u5F00\u5934\uFF0C\u8BF7\u786E\u8BA4\u590D\u5236\u5B8C\u6574";
1232
+ }
1233
+ return true;
1234
+ }
1235
+ });
1236
+ const finalKey = key.trim() || currentKey || "";
1237
+ return finalKey;
1238
+ }
1239
+ async function promptSelectModel(currentModel) {
1240
+ const choices = MONK_MODELS.map((m) => ({
1241
+ name: `${m.name.padEnd(28)} ${pc.dim(m.description)}`,
1242
+ value: m.id,
1243
+ description: m.description
1244
+ }));
1245
+ const selected = await select({
1246
+ message: "\u8BF7\u9009\u62E9\u9ED8\u8BA4\u4E3B\u529B\u6A21\u578B:",
1247
+ choices,
1248
+ default: currentModel || "monk-coding"
1249
+ });
1250
+ return selected;
1251
+ }
1252
+ async function promptInstallPi() {
1253
+ logWarn("\u672A\u68C0\u6D4B\u5230\u5168\u5C40\u5B89\u88C5\u7684 pi \u7F16\u7801\u52A9\u624B (@earendil-works/pi-coding-agent)\u3002");
1254
+ console.log(` ${pc.dim("Monk Harness \u4F9D\u8D56 Pi \u4F5C\u4E3A\u5E95\u5C42 TUI/Agent \u8FD0\u884C\u65F6\u3002")}`);
1255
+ console.log();
1256
+ return await confirm({
1257
+ message: "\u662F\u5426\u7ACB\u5373\u4F7F\u7528 npm \u5168\u5C40\u5B89\u88C5 @earendil-works/pi-coding-agent\uFF1F",
1258
+ default: true
1259
+ });
1260
+ }
1261
+ function maskKey(key) {
1262
+ if (!key || key.length < 10) return "******";
1263
+ return `${key.slice(0, 7)}...${key.slice(-4)}`;
1264
+ }
1265
+
1266
+ // src/pi-runner.ts
1267
+ import fs2 from "fs";
1268
+ import path2 from "path";
1269
+ import { fileURLToPath } from "url";
1270
+ import spawn2 from "cross-spawn";
1271
+ import pc2 from "picocolors";
1272
+ function resolvePiBinary() {
1273
+ try {
1274
+ const globalCheck = spawn2.sync("pi", ["--version"], {
1275
+ encoding: "utf-8",
1276
+ stdio: ["ignore", "pipe", "ignore"]
1277
+ });
1278
+ if (globalCheck.status === 0 && globalCheck.stdout) {
1279
+ return {
1280
+ type: "global",
1281
+ command: "pi",
1282
+ argsPrefix: [],
1283
+ version: globalCheck.stdout.trim(),
1284
+ sourceDescription: "\u7CFB\u7EDF\u5168\u5C40\u547D\u4EE4 (PATH)"
1285
+ };
1286
+ }
1287
+ } catch {
1288
+ }
1289
+ const candidateBinDirs = [
1290
+ path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "../node_modules/.bin/pi"),
1291
+ path2.resolve(process.cwd(), "node_modules/.bin/pi")
1292
+ ];
1293
+ for (const binPath of candidateBinDirs) {
1294
+ if (fs2.existsSync(binPath)) {
1295
+ try {
1296
+ const localCheck = spawn2.sync(binPath, ["--version"], {
1297
+ encoding: "utf-8",
1298
+ stdio: ["ignore", "pipe", "ignore"]
1299
+ });
1300
+ if (localCheck.status === 0 && localCheck.stdout) {
1301
+ return {
1302
+ type: "local",
1303
+ command: binPath,
1304
+ argsPrefix: [],
1305
+ version: localCheck.stdout.trim(),
1306
+ sourceDescription: `\u672C\u5730\u4F9D\u8D56 (${path2.basename(path2.dirname(binPath))})`
1307
+ };
1308
+ }
1309
+ } catch {
1310
+ }
1311
+ }
1312
+ }
1313
+ try {
1314
+ const mainEntry = fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"));
1315
+ const cliEntry = path2.join(path2.dirname(mainEntry), "bundle", "cli.js");
1316
+ if (fs2.existsSync(cliEntry)) {
1317
+ const moduleCheck = spawn2.sync(process.execPath, [cliEntry, "--version"], {
1318
+ encoding: "utf-8",
1319
+ stdio: ["ignore", "pipe", "ignore"]
1320
+ });
1321
+ if (moduleCheck.status === 0 && moduleCheck.stdout) {
1322
+ return {
1323
+ type: "bundled",
1324
+ command: process.execPath,
1325
+ argsPrefix: [cliEntry],
1326
+ version: moduleCheck.stdout.trim(),
1327
+ sourceDescription: "Monk-Pi \u5185\u5D4C\u6838\u5FC3 (\u514D\u5B89\u88C5\u8FD0\u884C)"
1328
+ };
1329
+ }
1330
+ }
1331
+ } catch {
1332
+ }
1333
+ return {
1334
+ type: "missing",
1335
+ command: "",
1336
+ argsPrefix: [],
1337
+ sourceDescription: "\u672A\u68C0\u6D4B\u5230\u53EF\u7528\u7684 Pi \u6838\u5FC3"
1338
+ };
1339
+ }
1340
+ function detectPi() {
1341
+ const resolved = resolvePiBinary();
1342
+ return {
1343
+ installed: resolved.type !== "missing",
1344
+ version: resolved.version,
1345
+ type: resolved.type,
1346
+ description: resolved.sourceDescription
1347
+ };
1348
+ }
1349
+ function detectPackageManager() {
1350
+ try {
1351
+ const bunCheck = spawn2.sync("bun", ["--version"], { stdio: "ignore" });
1352
+ if (bunCheck.status === 0) return "bun";
1353
+ } catch {
1354
+ }
1355
+ try {
1356
+ const pnpmCheck = spawn2.sync("pnpm", ["--version"], { stdio: "ignore" });
1357
+ if (pnpmCheck.status === 0) return "pnpm";
1358
+ } catch {
1359
+ }
1360
+ return "npm";
1361
+ }
1362
+ async function installPi() {
1363
+ const pm = detectPackageManager();
1364
+ let cmd = "npm";
1365
+ let args = ["install", "-g", "@earendil-works/pi-coding-agent"];
1366
+ if (pm === "bun") {
1367
+ cmd = "bun";
1368
+ args = ["add", "-g", "@earendil-works/pi-coding-agent"];
1369
+ } else if (pm === "pnpm") {
1370
+ cmd = "pnpm";
1371
+ args = ["add", "-g", "@earendil-works/pi-coding-agent"];
1372
+ }
1373
+ logInfo(`\u68C0\u6D4B\u5230\u5305\u7BA1\u7406\u5668: ${pc2.bold(pm)}\uFF0C\u6B63\u5728\u6267\u884C: ${pc2.yellow(`${cmd} ${args.join(" ")}`)} ...`);
1374
+ try {
1375
+ const child = spawn2.sync(cmd, args, {
1376
+ stdio: "inherit"
1377
+ });
1378
+ if (child.status === 0) {
1379
+ logSuccess("Pi \u6838\u5FC3\u5957\u4EF6\u5B89\u88C5\u6210\u529F\uFF01");
1380
+ return true;
1381
+ }
1382
+ logError(`\u5B89\u88C5\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${child.status}`);
1383
+ logWarn(`\u82E5\u56E0\u6743\u9650\u95EE\u9898 (EACCES) \u5931\u8D25\uFF0C\u8BF7\u5C1D\u8BD5:`);
1384
+ console.log(` ${pc2.cyan(`sudo ${cmd} ${args.join(" ")}`)}`);
1385
+ console.log(` \u6216\u76F4\u63A5\u4F7F\u7528\u514D\u5B89\u88C5\u65B9\u6848: ${pc2.cyan("npx monk-pi")}`);
1386
+ return false;
1387
+ } catch (err) {
1388
+ logError(`\u6267\u884C\u5B89\u88C5\u5F02\u5E38: ${err instanceof Error ? err.message : String(err)}`);
1389
+ return false;
1390
+ }
1391
+ }
1392
+ function launchPi(options) {
1393
+ const resolved = resolvePiBinary();
1394
+ if (resolved.type === "missing") {
1395
+ logError("\u672A\u627E\u5230\u53EF\u7528\u7684 Pi \u6838\u5FC3\u8FD0\u884C\u73AF\u5883\u3002");
1396
+ return 1;
1397
+ }
1398
+ const { apiKey, defaultModel, passthroughArgs } = options;
1399
+ const extraArgs = [];
1400
+ const hasProvider = passthroughArgs.includes("--provider");
1401
+ const hasModel = passthroughArgs.some((arg, idx) => {
1402
+ return arg === "--model" || arg === "-m" || passthroughArgs[idx - 1] === "--model";
1403
+ });
1404
+ if (!hasProvider && !hasModel) {
1405
+ extraArgs.push("--provider", "monk", "--model", defaultModel);
1406
+ } else if (!hasProvider && hasModel) {
1407
+ extraArgs.push("--provider", "monk");
1408
+ }
1409
+ const fullArgs = [...resolved.argsPrefix, ...extraArgs, ...passthroughArgs];
1410
+ const env = {
1411
+ ...process.env,
1412
+ MONK_API_KEY: apiKey
1413
+ };
1414
+ const child = spawn2(resolved.command, fullArgs, {
1415
+ stdio: "inherit",
1416
+ env
1417
+ });
1418
+ const forwardSignal = (signal) => {
1419
+ try {
1420
+ child.kill(signal);
1421
+ } catch {
1422
+ }
1423
+ };
1424
+ process.on("SIGINT", () => forwardSignal("SIGINT"));
1425
+ process.on("SIGTERM", () => forwardSignal("SIGTERM"));
1426
+ return new Promise((resolve) => {
1427
+ child.on("close", (code) => {
1428
+ resolve(code ?? 0);
1429
+ });
1430
+ child.on("error", (err) => {
1431
+ logError(`\u542F\u52A8 Pi \u53D1\u751F\u9519\u8BEF: ${err.message}`);
1432
+ resolve(1);
1433
+ });
1434
+ });
1435
+ }
1436
+
1437
+ // src/commands/doctor.ts
1438
+ import fs3 from "fs";
1439
+ import os2 from "os";
1440
+ import pc3 from "picocolors";
1441
+ async function doctorCommand() {
1442
+ printBanner();
1443
+ console.log(` ${pc3.bold("\u3010Monk \xD7 Pi \u73AF\u5883\u8BCA\u65AD (Doctor)\u3011")}`);
1444
+ console.log();
1445
+ let hasIssue = false;
1446
+ const nodeVersion = process.version;
1447
+ const major = parseInt(nodeVersion.slice(1).split(".")[0], 10);
1448
+ if (major >= 18) {
1449
+ logSuccess(`Node.js \u7248\u672C: ${pc3.bold(nodeVersion)} (\u6EE1\u8DB3 >= 18 \u8981\u6C42)`);
1450
+ } else {
1451
+ hasIssue = true;
1452
+ logError(`Node.js \u7248\u672C: ${pc3.bold(nodeVersion)} (\u8FC7\u4F4E\uFF0C\u8BF7\u5347\u7EA7\u5230 Node 18+)`);
1453
+ }
1454
+ logInfo(`\u64CD\u4F5C\u7CFB\u7EDF: ${os2.type()} ${os2.release()} (${os2.arch()})`);
1455
+ const pi = detectPi();
1456
+ if (pi.installed) {
1457
+ logSuccess(
1458
+ `Pi \u6838\u5FC3\u5957\u4EF6: ${pc3.bold(`\u5DF2\u5C31\u7EEA (v${pi.version})`)} \xB7 ${pc3.dim(`[\u6765\u6E90: ${pi.description}]`)}`
1459
+ );
1460
+ } else {
1461
+ hasIssue = true;
1462
+ logError(`Pi \u6838\u5FC3\u5957\u4EF6: ${pc3.bold("\u672A\u627E\u5230\u53EF\u7528\u8FD0\u884C\u65F6")}`);
1463
+ console.log(` ${pc3.dim("\u89E3\u51B3\u529E\u6CD5: \u8FD0\u884C npm install -g @earendil-works/pi-coding-agent")}`);
1464
+ }
1465
+ const { key, source } = resolveApiKey();
1466
+ if (key) {
1467
+ logSuccess(`Monk API Key: ${pc3.bold(maskKey(key))} (\u6765\u6E90: ${source})`);
1468
+ } else {
1469
+ hasIssue = true;
1470
+ logError(`Monk API Key: \u672A\u914D\u7F6E`);
1471
+ console.log(` ${pc3.dim("\u89E3\u51B3\u529E\u6CD5: \u8FD0\u884C monk-pi login")}`);
1472
+ }
1473
+ const modelsFile = getPiModelsFile();
1474
+ if (fs3.existsSync(modelsFile)) {
1475
+ try {
1476
+ const data = JSON.parse(fs3.readFileSync(modelsFile, "utf-8"));
1477
+ const monk = data?.providers?.monk;
1478
+ if (monk) {
1479
+ const hasCompat = monk.compat?.supportsDeveloperRole === false && monk.compat?.maxTokensField === "max_tokens";
1480
+ if (hasCompat) {
1481
+ logSuccess(`Pi \u914D\u7F6E\u6587\u4EF6: ${pc3.bold("\u5DF2\u914D\u7F6E Monk \u4E13\u5C5E\u4F18\u5316\u517C\u5BB9\u53C2\u6570")}`);
1482
+ } else {
1483
+ logWarn(`Pi \u914D\u7F6E\u6587\u4EF6: \u5B58\u5728 Monk \u914D\u7F6E\uFF0C\u4F46\u7F3A\u5C11\u6700\u4F73 compat \u53C2\u6570`);
1484
+ console.log(` ${pc3.dim("\u6B63\u5728\u4E3A\u60A8\u81EA\u52A8\u7EA0\u6B63...")}`);
1485
+ syncPiModelsJson(key);
1486
+ logSuccess(`Pi \u914D\u7F6E\u6587\u4EF6: \u5DF2\u81EA\u52A8\u4FEE\u590D\u4F18\u5316`);
1487
+ }
1488
+ } else {
1489
+ logWarn(`Pi \u914D\u7F6E\u6587\u4EF6: \u5C1A\u672A\u6CE8\u5165 Monk \u4F9B\u5E94\u5546`);
1490
+ syncPiModelsJson(key);
1491
+ logSuccess(`Pi \u914D\u7F6E\u6587\u4EF6: \u5DF2\u6210\u529F\u81EA\u52A8\u6CE8\u5165`);
1492
+ }
1493
+ } catch {
1494
+ logError(`Pi \u914D\u7F6E\u6587\u4EF6: ${modelsFile} \u5B58\u5728\u8BED\u6CD5\u9519\u8BEF`);
1495
+ }
1496
+ } else {
1497
+ logWarn(`Pi \u914D\u7F6E\u6587\u4EF6: \u5C1A\u672A\u521B\u5EFA\uFF0C\u6B63\u5728\u4E3A\u60A8\u81EA\u52A8\u521B\u5EFA...`);
1498
+ syncPiModelsJson(key);
1499
+ logSuccess(`Pi \u914D\u7F6E\u6587\u4EF6: \u521B\u5EFA\u5B8C\u6210`);
1500
+ }
1501
+ const extRes = syncPiExtension();
1502
+ if (extRes.success) {
1503
+ if (extRes.updated) {
1504
+ logSuccess(
1505
+ `Pi \u539F\u751F\u6269\u5C55: ${pc3.bold("\u5DF2\u81EA\u52A8\u66F4\u65B0\u81F3\u6700\u65B0\u7248")} (\u4E2D\u6587\u5DE5\u7A0B\u63D0\u793A\u8BCD\u3001/monk \u6307\u4EE4\u4E0E\u81EA\u52A8\u91CD\u8BD5\u5C31\u7EEA)`
1506
+ );
1507
+ } else {
1508
+ logSuccess(
1509
+ `Pi \u539F\u751F\u6269\u5C55: ${pc3.bold("\u5DF2\u5C31\u7EEA")} (\u4E2D\u6587\u5DE5\u7A0B\u63D0\u793A\u8BCD\u3001/monk \u6307\u4EE4\u4E0E\u81EA\u52A8\u91CD\u8BD5\u5C31\u7EEA)`
1510
+ );
1511
+ }
1512
+ } else {
1513
+ logError(`Pi \u539F\u751F\u6269\u5C55\u90E8\u7F72\u5931\u8D25: ${extRes.error}`);
1514
+ }
1515
+ if (key) {
1516
+ process.stdout.write(` ${pc3.dim("\u23F3 \u6B63\u5728\u63A2\u6D4B\u4E0E monk.party \u63A5\u53E3\u7684\u8FDE\u901A\u6027...")} `);
1517
+ const val = await validateApiKey(key);
1518
+ process.stdout.write("\r" + " ".repeat(50) + "\r");
1519
+ if (val.valid) {
1520
+ logSuccess(`Monk \u8FDE\u901A\u6027: ${pc3.green("\u4F18\u79C0")} \xB7 \u5EF6\u8FDF ${val.latencyMs}ms`);
1521
+ } else {
1522
+ hasIssue = true;
1523
+ logError(`Monk \u8FDE\u901A\u6027\u5F02\u5E38: ${val.error}`);
1524
+ }
1525
+ }
1526
+ console.log();
1527
+ console.log(pc3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1528
+ if (hasIssue) {
1529
+ logWarn("\u68C0\u6D4B\u5230\u73AF\u5883\u5B58\u5728\u90E8\u5206\u95EE\u9898\uFF0C\u8BF7\u53C2\u8003\u4E0A\u65B9\u63D0\u793A\u5904\u7406\u3002");
1530
+ } else {
1531
+ logSuccess(pc3.bold("\u592A\u68D2\u4E86\uFF01\u6240\u6709\u68C0\u67E5\u9879\u5168\u90E8\u901A\u8FC7\uFF0C\u73AF\u5883\u5904\u4E8E\u6700\u4F73\u72B6\u6001\u3002"));
1532
+ }
1533
+ console.log();
1534
+ }
1535
+
1536
+ // src/commands/login.ts
1537
+ import pc4 from "picocolors";
1538
+ async function loginCommand() {
1539
+ printBanner();
1540
+ const current = resolveApiKey();
1541
+ const apiKey = await promptForApiKey(current.key || void 0);
1542
+ if (!apiKey) {
1543
+ logWarn("\u672A\u8F93\u5165 API Key\uFF0C\u64CD\u4F5C\u5DF2\u53D6\u6D88\u3002");
1544
+ return false;
1545
+ }
1546
+ logInfo("\u6B63\u5728\u8FDE\u63A5 Monk API \u9A8C\u8BC1\u5BC6\u94A5...");
1547
+ const result = await validateApiKey(apiKey);
1548
+ if (!result.valid) {
1549
+ logError(`\u9A8C\u8BC1\u5931\u8D25: ${result.error || "\u672A\u77E5\u9519\u8BEF"}`);
1550
+ logWarn("\u8BF7\u6838\u5BF9\u540E\u91CD\u65B0\u8FD0\u884C `monk-pi login`\u3002");
1551
+ return false;
1552
+ }
1553
+ logSuccess(`\u9A8C\u8BC1\u901A\u8FC7\uFF01\u7F51\u7EDC\u5EF6\u8FDF: ${pc4.green(`${result.latencyMs}ms`)}`);
1554
+ if (result.models.length > 0) {
1555
+ logInfo(`\u53EF\u7528\u6A21\u578B: ${pc4.yellow(result.models.join(", "))}`);
1556
+ }
1557
+ saveMonkConfig({
1558
+ apiKey,
1559
+ lastVerified: (/* @__PURE__ */ new Date()).toISOString()
1560
+ });
1561
+ logSuccess(`\u51ED\u8BC1\u5DF2\u4FDD\u5B58\u81F3\u672C\u5730\u914D\u7F6E`);
1562
+ const syncResult = syncPiModelsJson(apiKey);
1563
+ const extResult = syncPiExtension();
1564
+ if (syncResult.success) {
1565
+ logSuccess(`\u5DF2\u6210\u529F\u540C\u6B65\u5E76\u4F18\u5316 Pi \u914D\u7F6E\u6587\u4EF6: ${pc4.dim(syncResult.path)}`);
1566
+ } else {
1567
+ logWarn(`\u540C\u6B65 Pi \u914D\u7F6E\u6587\u4EF6\u5931\u8D25: ${syncResult.error}`);
1568
+ }
1569
+ if (extResult.success) {
1570
+ logSuccess(`\u5DF2\u5B89\u88C5 Monk \u539F\u751F TUI \u6269\u5C55: ${pc4.dim(extResult.path)}`);
1571
+ }
1572
+ console.log();
1573
+ logSuccess(`Monk \xD7 Pi \u5C31\u7EEA\uFF01\u73B0\u5728\u60A8\u53EF\u4EE5\u76F4\u63A5\u8F93\u5165 ${pc4.bold(pc4.yellow("monk-pi"))} \u5F00\u59CB\u7F16\u7801\u3002`);
1574
+ console.log();
1575
+ return true;
1576
+ }
1577
+
1578
+ // src/commands/model.ts
1579
+ import pc5 from "picocolors";
1580
+ async function modelCommand(targetModel) {
1581
+ printBanner();
1582
+ const currentModel = getDefaultModel();
1583
+ if (targetModel) {
1584
+ const matched2 = MONK_MODELS.find(
1585
+ (m) => m.id === targetModel || m.id.endsWith(targetModel)
1586
+ );
1587
+ if (!matched2) {
1588
+ logWarn(`\u672A\u77E5\u6A21\u578B: "${targetModel}"`);
1589
+ logInfo(`\u652F\u6301\u7684\u6A21\u578B\u5217\u8868: ${MONK_MODELS.map((m) => m.id).join(", ")}`);
1590
+ return;
1591
+ }
1592
+ saveMonkConfig({ defaultModel: matched2.id });
1593
+ logSuccess(`\u9ED8\u8BA4\u4E3B\u529B\u6A21\u578B\u5DF2\u5207\u6362\u4E3A: ${pc5.bold(pc5.yellow(matched2.name))}`);
1594
+ return;
1595
+ }
1596
+ const selected = await promptSelectModel(currentModel);
1597
+ saveMonkConfig({ defaultModel: selected });
1598
+ const matched = MONK_MODELS.find((m) => m.id === selected);
1599
+ console.log();
1600
+ logSuccess(`\u9ED8\u8BA4\u4E3B\u529B\u6A21\u578B\u5DF2\u6210\u529F\u8BBE\u5B9A\u4E3A: ${pc5.bold(pc5.yellow(matched?.name || selected))}`);
1601
+ console.log();
1602
+ }
1603
+
1604
+ // src/commands/run.ts
1605
+ import pc6 from "picocolors";
1606
+
1607
+ // src/extension/monk-extension.ts
1608
+ import fs4 from "fs";
1609
+ import path3 from "path";
1610
+ var PRO_TIPS = [
1611
+ "\u5728\u8F93\u5165\u6846\u8F93\u5165 @diff \u5373\u53EF\u79D2\u7EA7\u628A\u5F53\u524D\u672A\u63D0\u4EA4\u6539\u52A8\u5E26\u5165\u5206\u6790",
1612
+ "\u8F93\u5165 /commit \u53EF\u6309 Conventional Commits \u89C4\u8303\u81EA\u52A8\u751F\u6210\u4E2D\u6587\u63D0\u4EA4",
1613
+ "AI \u6539\u4E71\u4E86\u4EE3\u7801\u4E0D\u7528\u614C\uFF0C\u968F\u65F6\u8F93\u5165 /undo \u6216 /\u56DE\u9000 \u77AC\u95F4\u4E00\u952E\u590D\u539F",
1614
+ "\u8F93\u5165 /review \u53EF\u4ECE\u7F3A\u9677\u3001\u6027\u80FD\u3001\u67B6\u6784\u56DB\u4E2A\u7EF4\u5EA6\u5BF9\u4EE3\u7801\u8FDB\u884C\u6DF1\u5EA6\u5BA1\u67E5",
1615
+ "\u8F93\u5165 /monk \u53EF\u547C\u51FA\u56FE\u5F62\u5316\u63A7\u5236\u53F0\uFF0C\u79D2\u7EA7\u514D\u91CD\u542F\u5207\u6362\u4E3B\u529B\u6A21\u578B",
1616
+ "\u8F93\u5165 @recent \u5FEB\u901F\u63D0\u53D6\u6700\u8FD1\u53D8\u52A8\u7684\u9879\u76EE\u6838\u5FC3\u6E90\u6587\u4EF6\u5217\u8868",
1617
+ "\u8F93\u5165 /monk cn \u968F\u65F6\u5207\u6362\u4E2D\u6587\u5DE5\u7A0B\u7CBE\u70BC\u6A21\u5F0F\uFF08\u96F6\u5BA2\u5957\xB7\u884C\u52A8\u4F18\u5148\uFF09",
1618
+ "\u7EC8\u7AEF\u8F93\u5165 monk-pi -r \u53EF\u89C6\u5316\u6062\u590D\u4EE5\u5F80\u4EFB\u610F\u5386\u53F2\u4EFB\u52A1\u65AD\u70B9\u7EED\u5199"
1619
+ ];
1620
+
1621
+ // src/commands/run.ts
1622
+ async function runCommand(passthroughArgs) {
1623
+ const piCheck = detectPi();
1624
+ if (!piCheck.installed) {
1625
+ const shouldInstall = await promptInstallPi();
1626
+ if (shouldInstall) {
1627
+ const installed = await installPi();
1628
+ if (!installed) {
1629
+ logError("\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5 Pi\uFF0C\u8BF7\u624B\u52A8\u8FD0\u884C `npm install -g @earendil-works/pi-coding-agent` \u540E\u91CD\u8BD5\u3002");
1630
+ return 1;
1631
+ }
1632
+ } else {
1633
+ logWarn("\u5DF2\u53D6\u6D88\u8FD0\u884C\u3002Monk Harness \u9700\u8981 Pi \u4F5C\u4E3A\u8FD0\u884C\u5E95\u5EA7\u3002");
1634
+ return 1;
1635
+ }
1636
+ }
1637
+ let { key } = resolveApiKey();
1638
+ if (!key) {
1639
+ logWarn("\u672A\u68C0\u6D4B\u5230 Monk API Key\uFF0C\u6B63\u5728\u4E3A\u60A8\u542F\u52A8\u9996\u6B21\u914D\u7F6E\u5F15\u5BFC...");
1640
+ const ok = await loginCommand();
1641
+ if (!ok) {
1642
+ return 1;
1643
+ }
1644
+ const resolved = resolveApiKey();
1645
+ key = resolved.key;
1646
+ }
1647
+ if (!key) {
1648
+ logError("\u672A\u627E\u5230\u6709\u6548\u7684 Monk API Key\uFF0C\u9000\u51FA\u8FD0\u884C\u3002");
1649
+ return 1;
1650
+ }
1651
+ syncPiModelsJson(key);
1652
+ syncPiExtension();
1653
+ const defaultModel = getDefaultModel();
1654
+ if (!passthroughArgs.includes("-p") && !passthroughArgs.includes("--print")) {
1655
+ const tip = PRO_TIPS[Math.floor(Math.random() * PRO_TIPS.length)];
1656
+ console.log(` ${pc6.yellow("\u{1F4A1} \u6781\u5BA2\u5C0F\u8D34\u58EB:")} ${pc6.dim(tip)}`);
1657
+ console.log();
1658
+ }
1659
+ return launchPi({
1660
+ apiKey: key,
1661
+ defaultModel,
1662
+ passthroughArgs
1663
+ });
1664
+ }
1665
+
1666
+ // src/commands/status.ts
1667
+ import pc7 from "picocolors";
1668
+ async function statusCommand() {
1669
+ printBanner();
1670
+ console.log(` ${pc7.bold("\u3010\u670D\u52A1\u4E0E\u73AF\u5883\u72B6\u6001\u68C0\u6D4B\u3011")}`);
1671
+ console.log();
1672
+ const piDetect = detectPi();
1673
+ if (piDetect.installed) {
1674
+ logSuccess(
1675
+ `Pi \u8FD0\u884C\u73AF\u5883: ${pc7.bold("\u5DF2\u5C31\u7EEA")} (v${piDetect.version}) \xB7 ${pc7.dim(`[\u6765\u6E90: ${piDetect.description}]`)}`
1676
+ );
1677
+ } else {
1678
+ logWarn(`Pi \u8FD0\u884C\u73AF\u5883: ${pc7.red("\u672A\u68C0\u6D4B\u5230\u53EF\u7528\u8FD0\u884C\u65F6")}`);
1679
+ console.log(` ${pc7.dim("\u8BF7\u8FD0\u884C npm install -g @earendil-works/pi-coding-agent")}`);
1680
+ }
1681
+ const { key, source } = resolveApiKey();
1682
+ const defaultModel = getDefaultModel();
1683
+ if (!key) {
1684
+ logError(`Monk API Key: ${pc7.red("\u672A\u914D\u7F6E")}`);
1685
+ console.log(` ${pc7.dim("\u8BF7\u8FD0\u884C `monk-pi login` \u8F93\u5165\u60A8\u7684 Key")}`);
1686
+ return;
1687
+ }
1688
+ const sourceLabels = {
1689
+ env: "\u73AF\u5883\u53D8\u91CF ($MONK_API_KEY)",
1690
+ local: `\u672C\u5730\u914D\u7F6E (${getMonkConfigFile()})`,
1691
+ pi: `Pi \u914D\u7F6E\u6587\u4EF6 (${getPiModelsFile()})`,
1692
+ none: "\u65E0"
1693
+ };
1694
+ logSuccess(`Monk API Key: ${pc7.bold(maskKey(key))} ${pc7.dim(`[\u6765\u6E90: ${sourceLabels[source]}]`)}`);
1695
+ logInfo(`\u5F53\u524D\u9ED8\u8BA4\u4E3B\u529B\u6A21\u578B: ${pc7.yellow(defaultModel)}`);
1696
+ logInfo(`Monk \u57FA\u7840\u5730\u5740: ${pc7.dim(MONK_BASE_URL)}`);
1697
+ process.stdout.write(` ${pc7.dim("\u23F3 \u6B63\u5728\u6D4B\u8BD5 Monk API \u8FDE\u63A5\u4E0E\u8BA4\u8BC1...")} `);
1698
+ const valResult = await validateApiKey(key);
1699
+ if (valResult.valid) {
1700
+ process.stdout.write("\r" + " ".repeat(50) + "\r");
1701
+ logSuccess(
1702
+ `Monk API \u8FDE\u63A5\u6B63\u5E38 ${pc7.dim("\xB7")} \u8BA4\u8BC1\u6709\u6548 ${pc7.dim("\xB7")} \u5EF6\u8FDF: ${pc7.green(`${valResult.latencyMs}ms`)}`
1703
+ );
1704
+ if (valResult.models.length > 0) {
1705
+ logInfo(`\u652F\u6301\u7684\u6A21\u578B\u7AEF\u70B9: ${pc7.dim(valResult.models.join(" \xB7 "))}`);
1706
+ }
1707
+ } else {
1708
+ process.stdout.write("\r" + " ".repeat(50) + "\r");
1709
+ logError(`Monk API \u9A8C\u8BC1\u5F02\u5E38: ${valResult.error}`);
1710
+ return;
1711
+ }
1712
+ process.stdout.write(` ${pc7.dim(`\u23F3 \u6B63\u5728\u5BF9 ${defaultModel} \u53D1\u8D77\u6781\u7B80\u63E1\u624B\u63A8\u6F14...`)} `);
1713
+ const testRes = await testChatCompletion(key, defaultModel);
1714
+ if (testRes.success) {
1715
+ process.stdout.write("\r" + " ".repeat(50) + "\r");
1716
+ logSuccess(
1717
+ `\u63A8\u7406\u901A\u9053\u63E1\u624B\u6210\u529F ${pc7.dim("\xB7")} \u54CD\u5E94\u5EF6\u8FDF: ${pc7.green(`${testRes.latencyMs}ms`)}`
1718
+ );
1719
+ } else {
1720
+ process.stdout.write("\r" + " ".repeat(50) + "\r");
1721
+ logWarn(`\u63A8\u7406\u901A\u9053\u63E1\u624B\u8F7B\u5FAE\u544A\u8B66: ${testRes.error || "\u672A\u8FD4\u56DE\u5E38\u89C4\u5185\u5BB9"}`);
1722
+ }
1723
+ console.log();
1724
+ console.log(pc7.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1725
+ console.log(` ${pc7.dim("\u5B98\u65B9\u7F51\u7AD9:")} ${pc7.cyan(MONK_WEBSITE)}`);
1726
+ console.log(` ${pc7.dim("\u7528\u91CF\u4E0E\u5230\u671F\u67E5\u8BE2:")} ${pc7.cyan(MONK_ACCOUNT_URL)}`);
1727
+ console.log();
1728
+ }
1729
+
1730
+ export {
1731
+ MONK_BASE_URL,
1732
+ MONK_WEBSITE,
1733
+ MONK_ACCOUNT_URL,
1734
+ MONK_MODELS,
1735
+ DEFAULT_MONK_MODEL,
1736
+ getMonkHomeDir,
1737
+ getMonkConfigFile,
1738
+ getPiHomeDir,
1739
+ getPiModelsFile,
1740
+ getPiSettingsFile,
1741
+ getPiExtensionsDir,
1742
+ getPiMonkExtensionFile,
1743
+ loadMonkConfig,
1744
+ saveMonkConfig,
1745
+ resolveApiKey,
1746
+ syncPiModelsJson,
1747
+ syncPiExtension,
1748
+ getDefaultModel,
1749
+ validateApiKey,
1750
+ testChatCompletion,
1751
+ openBrowser,
1752
+ printBanner,
1753
+ logSuccess,
1754
+ logInfo,
1755
+ logWarn,
1756
+ logError,
1757
+ logStep,
1758
+ promptForApiKey,
1759
+ promptSelectModel,
1760
+ promptInstallPi,
1761
+ maskKey,
1762
+ resolvePiBinary,
1763
+ detectPi,
1764
+ detectPackageManager,
1765
+ installPi,
1766
+ launchPi,
1767
+ doctorCommand,
1768
+ loginCommand,
1769
+ modelCommand,
1770
+ runCommand,
1771
+ statusCommand
1772
+ };