monk-pi 0.1.0 → 0.2.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/README.md CHANGED
@@ -74,6 +74,7 @@ monk-pi --model monk "帮我设计一套分布式系统的架构方案"
74
74
 
75
75
  ## 🧰 专属管理命令
76
76
 
77
+ ### 终端 CLI 指令
77
78
  | 命令 | 说明 |
78
79
  | :--- | :--- |
79
80
  | `monk-pi login` (或 `auth`) | 交互式配置或更新 Monk API Key,并自动同步至 Pi |
@@ -81,6 +82,21 @@ monk-pi --model monk "帮我设计一套分布式系统的架构方案"
81
82
  | `monk-pi model [name]` | 查看或切换默认主力模型 (`monk-coding` / `monk-fast` / `monk`) |
82
83
  | `monk-pi doctor` | 一键诊断 Node、Pi 运行时、配置文件健康度与网络状况 |
83
84
 
85
+ ### TUI 内部 Slash 指令 (`/monk`)
86
+ 在交互式编码过程中,随时输入 `/monk` 即可呼出 Monk 专属控制菜单:
87
+ - `/monk`:呼出图形化快捷选择菜单
88
+ - `/monk model`:在当前会话中秒级免重启切换主力模型 (`monk-coding` / `monk-fast` / `monk`)
89
+ - `/monk ping`:实时测试当前 Monk API 连接与端到端延迟
90
+ - `/monk account`:查询用量与账号到期状态
91
+
92
+ ---
93
+
94
+ ## 🎨 原生 TUI 状态栏与上下文溢出自动恢复
95
+
96
+ Monk-Pi 自动为 Pi 部署原生扩展 (`~/.pi/agent/extensions/monk.ts`):
97
+ 1. **底部状态栏 (Footer Status)**:实时显示当前激活的 Monk 模型、1M 上下文标识以及 Turn 轮次状态。
98
+ 2. **上下文溢出自动恢复 (Compaction Recovery)**:在超长代码重构会话中,智能拦截 Monk 上游的 Token/Context 溢出异常,自动标准化为 Pi 识别的溢出信号,**无感触发智能压缩与自动重试**,杜绝会话意外中断。
99
+
84
100
  ---
85
101
 
86
102
  ## ⚙️ 自动注入的 Pi 最佳配置
@@ -137,10 +153,10 @@ monk-pi --model monk "帮我设计一套分布式系统的架构方案"
137
153
  - [x] Pi 运行时环境检测与自动注入
138
154
  - [x] `status` / `doctor` / `model` 诊断指令
139
155
  - [x] 完整参数透传与 TTY 会话继承
140
- - [ ] **Phase 2: Monk 原生 Pi Extension**
141
- - [ ] TUI 底部状态栏展示 Monk 订阅剩余天数与今日用量
142
- - [ ] 针对 Monk 代理的上游报错拦截与自动 Context Compaction 重试机制
143
- - [ ] `/monk` 专属 Slash 快捷指令
156
+ - [x] **Phase 2: Monk 原生 Pi Extension**
157
+ - [x] TUI 底部状态栏展示 Monk 状态与活跃模型
158
+ - [x] 针对 Monk 代理的上游报错拦截与自动 Context Compaction 重试机制
159
+ - [x] `/monk` 专属 Slash 快捷指令(秒切模型、测速与用量查询)
144
160
  - [ ] **Phase 3: 生态共建**
145
161
  - [ ] 提交收录至 monk.party 帮助中心客户端推荐列表
146
162
 
@@ -46,9 +46,216 @@ function getPiModelsFile() {
46
46
  function getPiSettingsFile() {
47
47
  return path.join(getPiHomeDir(), "settings.json");
48
48
  }
49
+ function getPiExtensionsDir() {
50
+ return path.join(getPiHomeDir(), "extensions");
51
+ }
52
+ function getPiMonkExtensionFile() {
53
+ return path.join(getPiExtensionsDir(), "monk.ts");
54
+ }
49
55
 
50
56
  // src/config.ts
51
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, /monk slash command, and context overflow auto-compaction
63
+
64
+ const MONK_MODELS = [
65
+ { id: "monk-coding", label: "monk-coding (\u4EE3\u7801\u4E3B\u529B - \u63A8\u8350 \xB7 \u6DF1\u5EA6\u5DE5\u5177\u8C03\u7528)" },
66
+ { id: "monk-fast", label: "monk-fast (\u6781\u901F\u63A8\u7406 \xB7 \u5FEB\u901F\u95EE\u7B54\u4E0E\u8F7B\u91CF\u4EFB\u52A1)" },
67
+ { id: "monk", label: "monk (\u878D\u5408\u65D7\u8230 \xB7 \u8D28\u91CF\u4F18\u5148\u4E0E\u7EFC\u5408\u63A8\u7406)" },
68
+ ];
69
+
70
+ const OVERFLOW_PATTERNS = [
71
+ /context.*length/i,
72
+ /maximum.*tokens/i,
73
+ /token.*limit/i,
74
+ /too many tokens/i,
75
+ /prompt.*too long/i,
76
+ /request.*too large/i,
77
+ /context_window_exceeded/i,
78
+ /exceed.*context/i,
79
+ ];
80
+
81
+ export default function monkExtension(pi) {
82
+ let turnCount = 0;
83
+
84
+ function updateStatus(ctx) {
85
+ if (!ctx.ui) return;
86
+ const model = ctx.model;
87
+ const isMonk = model && model.provider === "monk";
88
+
89
+ if (!isMonk) {
90
+ ctx.ui.setStatus("monk", undefined);
91
+ return;
92
+ }
93
+
94
+ const theme = ctx.ui.theme;
95
+ const indicator = theme.fg("success", "\u25CF");
96
+ const label = theme.fg("accent", \`Monk: \${model.id}\`);
97
+ const meta = theme.fg("dim", " (1M ctx)");
98
+ ctx.ui.setStatus("monk", \`\${indicator} \${label}\${meta}\`);
99
+ }
100
+
101
+ // 1. Lifecycle Events: update footer status
102
+ pi.on("session_start", async (_event, ctx) => {
103
+ updateStatus(ctx);
104
+ });
105
+
106
+ pi.on("model_select", async (_event, ctx) => {
107
+ updateStatus(ctx);
108
+ });
109
+
110
+ pi.on("turn_start", async (_event, ctx) => {
111
+ turnCount++;
112
+ if (!ctx.ui) return;
113
+ const model = ctx.model;
114
+ if (model && model.provider === "monk") {
115
+ const theme = ctx.ui.theme;
116
+ const spinner = theme.fg("warning", "\u25B2");
117
+ const label = theme.fg("accent", \`Monk: \${model.id}\`);
118
+ const text = theme.fg("dim", \` [Turn \${turnCount}]\`);
119
+ ctx.ui.setStatus("monk", \`\${spinner} \${label}\${text}\`);
120
+ }
121
+ });
122
+
123
+ pi.on("turn_end", async (_event, ctx) => {
124
+ updateStatus(ctx);
125
+ });
126
+
127
+ // 2. Intelligent Context Overflow & Compaction Recovery
128
+ pi.on("message_end", async (event, ctx) => {
129
+ const message = event.message;
130
+ if (!message || message.role !== "assistant") return;
131
+ if (message.stopReason !== "error") return;
132
+
133
+ const isMonk = message.provider === "monk" || (ctx.model && ctx.model.provider === "monk");
134
+ if (!isMonk) return;
135
+
136
+ const errorMsg = message.errorMessage || "";
137
+ if (errorMsg.includes("context_length_exceeded")) return;
138
+
139
+ const isOverflow = OVERFLOW_PATTERNS.some((p) => p.test(errorMsg));
140
+ if (isOverflow) {
141
+ ctx.ui && ctx.ui.notify(
142
+ "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...",
143
+ "warning"
144
+ );
145
+ return {
146
+ message: {
147
+ ...message,
148
+ errorMessage: \`context_length_exceeded: \${errorMsg}\`,
149
+ },
150
+ };
151
+ }
152
+ });
153
+
154
+ // 3. Custom Slash Command: /monk
155
+ pi.registerCommand("monk", {
156
+ description: "Monk \u4E13\u5C5E\u63A7\u5236\u53F0 (/monk [model|ping|status|account])",
157
+ handler: async (args, ctx) => {
158
+ const sub = (args || "").trim().toLowerCase();
159
+
160
+ if (sub === "model" || sub === "switch") {
161
+ await handleModelSwitch(pi, ctx);
162
+ return;
163
+ }
164
+
165
+ if (sub === "ping" || sub === "status") {
166
+ await handlePing(ctx);
167
+ return;
168
+ }
169
+
170
+ if (sub === "account" || sub === "quota") {
171
+ ctx.ui && ctx.ui.notify("Monk \u7528\u91CF\u4E0E\u5230\u671F\u67E5\u8BE2: https://monk.party/account/", "info");
172
+ return;
173
+ }
174
+
175
+ // Default: interactive menu
176
+ await handleMenu(pi, ctx);
177
+ },
178
+ });
179
+ }
180
+
181
+ async function handleModelSwitch(pi, ctx) {
182
+ if (!ctx.ui) return;
183
+
184
+ const choices = MONK_MODELS.map((m) => m.label);
185
+ const selectedLabel = await ctx.ui.select("\u9009\u62E9\u5F53\u524D\u4F1A\u8BDD\u7684 Monk \u6A21\u578B:", choices);
186
+
187
+ if (!selectedLabel) return;
188
+
189
+ const matched = MONK_MODELS.find((m) => m.label === selectedLabel);
190
+ if (!matched) return;
191
+
192
+ const model = ctx.modelRegistry.find("monk", matched.id);
193
+ if (!model) {
194
+ ctx.ui.notify(\`\u672A\u5728\u914D\u7F6E\u4E2D\u627E\u5230\u6A21\u578B: monk/\${matched.id}\`, "error");
195
+ return;
196
+ }
197
+
198
+ const success = await pi.setModel(model);
199
+ if (success) {
200
+ ctx.ui.notify(\`\u4E3B\u529B\u6A21\u578B\u5DF2\u5207\u6362\u81F3: \${matched.id}\`, "info");
201
+ } else {
202
+ ctx.ui.notify(\`\u5207\u6362\u5931\u8D25\uFF0C\u672A\u80FD\u8BBE\u7F6E\u6A21\u578B: \${matched.id}\`, "error");
203
+ }
204
+ }
205
+
206
+ async function handlePing(ctx) {
207
+ if (!ctx.ui) return;
208
+
209
+ const startTime = Date.now();
210
+ const apiKey = process.env.MONK_API_KEY || "";
211
+
212
+ try {
213
+ const controller = new AbortController();
214
+ const timeout = setTimeout(() => controller.abort(), 6000);
215
+
216
+ const res = await fetch("https://monk.party/v1/models", {
217
+ headers: { Authorization: \`Bearer \${apiKey}\` },
218
+ signal: controller.signal,
219
+ });
220
+
221
+ clearTimeout(timeout);
222
+ const latency = Date.now() - startTime;
223
+
224
+ if (res.ok) {
225
+ ctx.ui.notify(\`Monk API \u8FDE\u901A\u6B63\u5E38 \xB7 \u5EF6\u8FDF \${latency}ms\`, "info");
226
+ } else {
227
+ ctx.ui.notify(\`Monk API \u54CD\u5E94\u5F02\u5E38 (HTTP \${res.status})\`, "warning");
228
+ }
229
+ } catch (err) {
230
+ ctx.ui.notify(
231
+ \`Monk API \u8FDE\u63A5\u5931\u8D25: \${err instanceof Error ? err.message : String(err)}\`,
232
+ "error"
233
+ );
234
+ }
235
+ }
236
+
237
+ async function handleMenu(pi, ctx) {
238
+ if (!ctx.ui) return;
239
+
240
+ const choice = await ctx.ui.select("Monk API \u63A7\u5236\u53F0", [
241
+ "1. \u5207\u6362\u4F1A\u8BDD\u6A21\u578B (Switch Model)",
242
+ "2. \u63A2\u6D4B\u7F51\u7EDC\u5EF6\u8FDF (Ping API)",
243
+ "3. \u67E5\u8BE2\u7528\u91CF\u4E0E\u5230\u671F\u65F6\u95F4 (Account Info)",
244
+ ]);
245
+
246
+ if (!choice) return;
247
+
248
+ if (choice.startsWith("1")) {
249
+ await handleModelSwitch(pi, ctx);
250
+ } else if (choice.startsWith("2")) {
251
+ await handlePing(ctx);
252
+ } else if (choice.startsWith("3")) {
253
+ ctx.ui.notify("\u8BF7\u5728\u6D4F\u89C8\u5668\u6253\u5F00 https://monk.party/account/ \u67E5\u770B\u7528\u91CF\u4E0E\u5269\u4F59\u5929\u6570", "info");
254
+ }
255
+ }
256
+ `;
257
+
258
+ // src/config.ts
52
259
  function loadMonkConfig() {
53
260
  const configFile = getMonkConfigFile();
54
261
  try {
@@ -147,6 +354,23 @@ function syncPiModelsJson(apiKey) {
147
354
  };
148
355
  }
149
356
  }
357
+ function syncPiExtension() {
358
+ const extDir = getPiExtensionsDir();
359
+ const extFile = getPiMonkExtensionFile();
360
+ try {
361
+ if (!fs.existsSync(extDir)) {
362
+ fs.mkdirSync(extDir, { recursive: true });
363
+ }
364
+ fs.writeFileSync(extFile, MONK_EXTENSION_CODE.trim() + "\n", "utf-8");
365
+ return { success: true, path: extFile };
366
+ } catch (err) {
367
+ return {
368
+ success: false,
369
+ path: extFile,
370
+ error: err instanceof Error ? err.message : String(err)
371
+ };
372
+ }
373
+ }
150
374
  function getDefaultModel() {
151
375
  const config = loadMonkConfig();
152
376
  if (config.defaultModel && MONK_MODELS.some((m) => m.id === config.defaultModel)) {
@@ -583,6 +807,18 @@ async function doctorCommand() {
583
807
  syncPiModelsJson(key);
584
808
  logSuccess(`Pi \u914D\u7F6E\u6587\u4EF6: \u521B\u5EFA\u5B8C\u6210`);
585
809
  }
810
+ const extFile = getPiMonkExtensionFile();
811
+ if (fs3.existsSync(extFile)) {
812
+ logSuccess(`Pi \u539F\u751F\u6269\u5C55: ${pc3.bold("\u5DF2\u5B89\u88C5")} (/monk \u6307\u4EE4\u3001\u72B6\u6001\u680F\u4E0E\u6EA2\u51FA\u81EA\u52A8\u91CD\u8BD5\u5C31\u7EEA)`);
813
+ } else {
814
+ logWarn(`Pi \u539F\u751F\u6269\u5C55: \u672A\u5B89\u88C5\uFF0C\u6B63\u5728\u4E3A\u60A8\u81EA\u52A8\u90E8\u7F72...`);
815
+ const extRes = syncPiExtension();
816
+ if (extRes.success) {
817
+ logSuccess(`Pi \u539F\u751F\u6269\u5C55: \u90E8\u7F72\u5B8C\u6210`);
818
+ } else {
819
+ logError(`Pi \u539F\u751F\u6269\u5C55\u90E8\u7F72\u5931\u8D25: ${extRes.error}`);
820
+ }
821
+ }
586
822
  if (key) {
587
823
  process.stdout.write(` ${pc3.dim("\u23F3 \u6B63\u5728\u63A2\u6D4B\u4E0E monk.party \u63A5\u53E3\u7684\u8FDE\u901A\u6027...")} `);
588
824
  const val = await validateApiKey(key);
@@ -631,11 +867,15 @@ async function loginCommand() {
631
867
  });
632
868
  logSuccess(`\u51ED\u8BC1\u5DF2\u4FDD\u5B58\u81F3\u672C\u5730\u914D\u7F6E`);
633
869
  const syncResult = syncPiModelsJson(apiKey);
870
+ const extResult = syncPiExtension();
634
871
  if (syncResult.success) {
635
872
  logSuccess(`\u5DF2\u6210\u529F\u540C\u6B65\u5E76\u4F18\u5316 Pi \u914D\u7F6E\u6587\u4EF6: ${pc4.dim(syncResult.path)}`);
636
873
  } else {
637
874
  logWarn(`\u540C\u6B65 Pi \u914D\u7F6E\u6587\u4EF6\u5931\u8D25: ${syncResult.error}`);
638
875
  }
876
+ if (extResult.success) {
877
+ logSuccess(`\u5DF2\u5B89\u88C5 Monk \u539F\u751F TUI \u6269\u5C55: ${pc4.dim(extResult.path)}`);
878
+ }
639
879
  console.log();
640
880
  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`);
641
881
  console.log();
@@ -699,6 +939,7 @@ async function runCommand(passthroughArgs) {
699
939
  return 1;
700
940
  }
701
941
  syncPiModelsJson(key);
942
+ syncPiExtension();
702
943
  const defaultModel = getDefaultModel();
703
944
  return launchPi({
704
945
  apiKey: key,
@@ -782,10 +1023,13 @@ export {
782
1023
  getPiHomeDir,
783
1024
  getPiModelsFile,
784
1025
  getPiSettingsFile,
1026
+ getPiExtensionsDir,
1027
+ getPiMonkExtensionFile,
785
1028
  loadMonkConfig,
786
1029
  saveMonkConfig,
787
1030
  resolveApiKey,
788
1031
  syncPiModelsJson,
1032
+ syncPiExtension,
789
1033
  getDefaultModel,
790
1034
  validateApiKey,
791
1035
  testChatCompletion,
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  printBanner,
9
9
  runCommand,
10
10
  statusCommand
11
- } from "./chunk-DHF5QVTM.js";
11
+ } from "./chunk-6OMOTI3T.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import { Command } from "commander";
package/dist/index.d.ts CHANGED
@@ -30,6 +30,8 @@ declare function getMonkConfigFile(): string;
30
30
  declare function getPiHomeDir(): string;
31
31
  declare function getPiModelsFile(): string;
32
32
  declare function getPiSettingsFile(): string;
33
+ declare function getPiExtensionsDir(): string;
34
+ declare function getPiMonkExtensionFile(): string;
33
35
 
34
36
  interface MonkLocalConfig {
35
37
  apiKey?: string;
@@ -56,6 +58,14 @@ declare function syncPiModelsJson(apiKey?: string): {
56
58
  path: string;
57
59
  error?: string;
58
60
  };
61
+ /**
62
+ * Safely writes or updates the Monk native extension in ~/.pi/agent/extensions/monk.ts
63
+ */
64
+ declare function syncPiExtension(): {
65
+ success: boolean;
66
+ path: string;
67
+ error?: string;
68
+ };
59
69
  /**
60
70
  * Gets the current default model
61
71
  */
@@ -149,4 +159,4 @@ declare function doctorCommand(): Promise<void>;
149
159
 
150
160
  declare function runCommand(passthroughArgs: string[]): Promise<number>;
151
161
 
152
- export { type CompletionTestResult, DEFAULT_MONK_MODEL, type LaunchPiOptions, MONK_ACCOUNT_URL, MONK_BASE_URL, MONK_MODELS, MONK_WEBSITE, type MonkLocalConfig, type MonkModelId, type PiBinaryType, type ResolvedPi, type ValidateResult, detectPackageManager, detectPi, doctorCommand, getDefaultModel, getMonkConfigFile, getMonkHomeDir, getPiHomeDir, getPiModelsFile, getPiSettingsFile, installPi, launchPi, loadMonkConfig, logError, logInfo, logStep, logSuccess, logWarn, loginCommand, maskKey, modelCommand, printBanner, promptForApiKey, promptInstallPi, promptSelectModel, resolveApiKey, resolvePiBinary, runCommand, saveMonkConfig, statusCommand, syncPiModelsJson, testChatCompletion, validateApiKey };
162
+ export { type CompletionTestResult, DEFAULT_MONK_MODEL, type LaunchPiOptions, MONK_ACCOUNT_URL, MONK_BASE_URL, MONK_MODELS, MONK_WEBSITE, type MonkLocalConfig, type MonkModelId, type PiBinaryType, type ResolvedPi, type ValidateResult, detectPackageManager, detectPi, doctorCommand, getDefaultModel, getMonkConfigFile, getMonkHomeDir, getPiExtensionsDir, getPiHomeDir, getPiModelsFile, getPiMonkExtensionFile, getPiSettingsFile, installPi, launchPi, loadMonkConfig, logError, logInfo, logStep, logSuccess, logWarn, loginCommand, maskKey, modelCommand, printBanner, promptForApiKey, promptInstallPi, promptSelectModel, resolveApiKey, resolvePiBinary, runCommand, saveMonkConfig, statusCommand, syncPiExtension, syncPiModelsJson, testChatCompletion, validateApiKey };
package/dist/index.js CHANGED
@@ -10,8 +10,10 @@ import {
10
10
  getDefaultModel,
11
11
  getMonkConfigFile,
12
12
  getMonkHomeDir,
13
+ getPiExtensionsDir,
13
14
  getPiHomeDir,
14
15
  getPiModelsFile,
16
+ getPiMonkExtensionFile,
15
17
  getPiSettingsFile,
16
18
  installPi,
17
19
  launchPi,
@@ -33,10 +35,11 @@ import {
33
35
  runCommand,
34
36
  saveMonkConfig,
35
37
  statusCommand,
38
+ syncPiExtension,
36
39
  syncPiModelsJson,
37
40
  testChatCompletion,
38
41
  validateApiKey
39
- } from "./chunk-DHF5QVTM.js";
42
+ } from "./chunk-6OMOTI3T.js";
40
43
  export {
41
44
  DEFAULT_MONK_MODEL,
42
45
  MONK_ACCOUNT_URL,
@@ -49,8 +52,10 @@ export {
49
52
  getDefaultModel,
50
53
  getMonkConfigFile,
51
54
  getMonkHomeDir,
55
+ getPiExtensionsDir,
52
56
  getPiHomeDir,
53
57
  getPiModelsFile,
58
+ getPiMonkExtensionFile,
54
59
  getPiSettingsFile,
55
60
  installPi,
56
61
  launchPi,
@@ -72,6 +77,7 @@ export {
72
77
  runCommand,
73
78
  saveMonkConfig,
74
79
  statusCommand,
80
+ syncPiExtension,
75
81
  syncPiModelsJson,
76
82
  testChatCompletion,
77
83
  validateApiKey
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monk-pi",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Zero-config harness for running Pi Coding Agent with Monk API (monk.party)",
5
5
  "type": "module",
6
6
  "bin": "./dist/cli.js",
@@ -25,7 +25,15 @@
25
25
  "harness",
26
26
  "cli"
27
27
  ],
28
- "author": "",
28
+ "author": "yaoleifly",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/yaoleifly/monk-pi.git"
32
+ },
33
+ "homepage": "https://github.com/yaoleifly/monk-pi#readme",
34
+ "bugs": {
35
+ "url": "https://github.com/yaoleifly/monk-pi/issues"
36
+ },
29
37
  "license": "MIT",
30
38
  "engines": {
31
39
  "node": ">=18.0.0"