chaimi-keep-mcp 3.2.1-beta.4 → 3.2.1-beta.6
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 +17 -0
- package/bin/cli.js +26 -1
- package/oauth.js +13 -7
- package/package.json +1 -1
- package/server.js +9 -6
- package/utils/machine-id.js +58 -1
package/README.md
CHANGED
|
@@ -148,6 +148,23 @@ AI 会自动调用 `save_income` 工具记录收入。
|
|
|
148
148
|
|
|
149
149
|
## 更新日志
|
|
150
150
|
|
|
151
|
+
### v3.2.1-beta.7 (2026-04-26)
|
|
152
|
+
- **新增** 自动检测 Agent 类型
|
|
153
|
+
- 安装时根据配置文件路径自动识别 Agent 类型
|
|
154
|
+
- 自动设置 `AGENT_TYPE` 环境变量
|
|
155
|
+
- 支持:claude-desktop、cursor、windsurf、workbuddy、openclaw、mcporter
|
|
156
|
+
|
|
157
|
+
### v3.2.1-beta.6 (2026-04-26)
|
|
158
|
+
- **修复** agentType 设置问题
|
|
159
|
+
- 从环境变量 `AGENT_TYPE` 或 `MCP_AGENT_TYPE` 读取
|
|
160
|
+
- 支持用户在 `mcporter.json` 中配置 agentType
|
|
161
|
+
|
|
162
|
+
### v3.2.1-beta.5 (2026-04-26)
|
|
163
|
+
- **修复** 复用授权时 token 获取失败问题
|
|
164
|
+
- 云函数 `getDeviceToken` 支持 userCode 查询作为备选
|
|
165
|
+
- MCP Server 轮询时同时传入 userCode 和 deviceCode
|
|
166
|
+
- 彻底解决 deviceCode 不匹配导致的授权失败
|
|
167
|
+
|
|
151
168
|
### v3.2.1-beta.4 (2026-04-26)
|
|
152
169
|
- **修复** deviceCode 保存不一致问题
|
|
153
170
|
- 小程序端使用授权返回的实际 deviceCode,解决复用授权时 deviceCode 不匹配
|
package/bin/cli.js
CHANGED
|
@@ -125,6 +125,24 @@ function writeJSON(filePath, data) {
|
|
|
125
125
|
* 为单个 Agent 安装配置
|
|
126
126
|
* 原则:只添加缺失的配置,绝不覆盖用户已有配置
|
|
127
127
|
*/
|
|
128
|
+
/**
|
|
129
|
+
* 根据配置文件路径自动检测 Agent 类型
|
|
130
|
+
* @param {string} configPath - 配置文件路径
|
|
131
|
+
* @returns {string} Agent 类型标识
|
|
132
|
+
*/
|
|
133
|
+
function detectAgentTypeFromPath(configPath) {
|
|
134
|
+
const lowerPath = configPath.toLowerCase();
|
|
135
|
+
|
|
136
|
+
if (lowerPath.includes('claude')) return 'claude-desktop';
|
|
137
|
+
if (lowerPath.includes('cursor')) return 'cursor';
|
|
138
|
+
if (lowerPath.includes('windsurf') || lowerPath.includes('codeium')) return 'windsurf';
|
|
139
|
+
if (lowerPath.includes('workbuddy')) return 'workbuddy';
|
|
140
|
+
if (lowerPath.includes('openclaw')) return 'openclaw';
|
|
141
|
+
if (lowerPath.includes('mcporter') || lowerPath.includes('.mcporter')) return 'mcporter';
|
|
142
|
+
|
|
143
|
+
return 'unknown';
|
|
144
|
+
}
|
|
145
|
+
|
|
128
146
|
function installToAgent(agent) {
|
|
129
147
|
try {
|
|
130
148
|
// 平台检查
|
|
@@ -160,7 +178,14 @@ function installToAgent(agent) {
|
|
|
160
178
|
}
|
|
161
179
|
|
|
162
180
|
// 添加柴米记账配置
|
|
163
|
-
|
|
181
|
+
const chaimiConfig = JSON.parse(JSON.stringify(DEFAULT_CHAIMI_CONFIG));
|
|
182
|
+
|
|
183
|
+
// 【新增】自动检测并设置 Agent 类型
|
|
184
|
+
const agentType = detectAgentTypeFromPath(agent.configPath);
|
|
185
|
+
chaimiConfig.env.AGENT_TYPE = agentType;
|
|
186
|
+
console.log(`[配置] 自动设置 Agent 类型: ${agentType} (${agent.name})`);
|
|
187
|
+
|
|
188
|
+
config.mcpServers['柴米记账'] = chaimiConfig;
|
|
164
189
|
|
|
165
190
|
// 写入配置
|
|
166
191
|
writeJSON(agent.configPath, config);
|
package/oauth.js
CHANGED
|
@@ -64,8 +64,10 @@ class OAuthManager {
|
|
|
64
64
|
console.error('⏳ 等待用户授权,请勿关闭窗口...');
|
|
65
65
|
console.error(' (请在手机微信中完成授权操作)');
|
|
66
66
|
console.error('');
|
|
67
|
+
// 【新增】同时传入 userCode,用于复用授权场景
|
|
67
68
|
const token = await this.pollForToken(
|
|
68
69
|
deviceCodeRes.deviceCode,
|
|
70
|
+
deviceCodeRes.userCode,
|
|
69
71
|
deviceCodeRes.interval * 1000 || 5000
|
|
70
72
|
);
|
|
71
73
|
|
|
@@ -172,8 +174,14 @@ class OAuthManager {
|
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
// 单次轮询(用于后台轮询)
|
|
175
|
-
async pollForTokenOnce(deviceCode) {
|
|
177
|
+
async pollForTokenOnce(deviceCode, userCode) {
|
|
176
178
|
try {
|
|
179
|
+
const params = { deviceCode };
|
|
180
|
+
// 【新增】如果有 userCode,一并传入(用于复用授权场景)
|
|
181
|
+
if (userCode) {
|
|
182
|
+
params.userCode = userCode;
|
|
183
|
+
}
|
|
184
|
+
|
|
177
185
|
const response = await fetch(this.mcpOAuthUrl, {
|
|
178
186
|
method: 'POST',
|
|
179
187
|
headers: {
|
|
@@ -181,9 +189,7 @@ class OAuthManager {
|
|
|
181
189
|
},
|
|
182
190
|
body: JSON.stringify({
|
|
183
191
|
tool: 'deviceToken',
|
|
184
|
-
params
|
|
185
|
-
deviceCode
|
|
186
|
-
}
|
|
192
|
+
params
|
|
187
193
|
})
|
|
188
194
|
});
|
|
189
195
|
|
|
@@ -217,17 +223,17 @@ class OAuthManager {
|
|
|
217
223
|
}
|
|
218
224
|
}
|
|
219
225
|
|
|
220
|
-
async pollForToken(deviceCode, interval) {
|
|
226
|
+
async pollForToken(deviceCode, userCode, interval) {
|
|
221
227
|
const maxAttempts = 60;
|
|
222
228
|
let attempts = 0;
|
|
223
229
|
|
|
224
230
|
while (attempts < maxAttempts) {
|
|
225
231
|
attempts++;
|
|
226
|
-
|
|
227
232
|
await this.delay(interval);
|
|
228
233
|
|
|
229
234
|
try {
|
|
230
|
-
|
|
235
|
+
// 【新增】传入 userCode,用于复用授权场景
|
|
236
|
+
const token = await this.pollForTokenOnce(deviceCode, userCode);
|
|
231
237
|
if (token) {
|
|
232
238
|
return token;
|
|
233
239
|
}
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -482,7 +482,8 @@ async function getToken() {
|
|
|
482
482
|
} else {
|
|
483
483
|
// 验证码未过期,尝试直接获取token(用户可能已授权)
|
|
484
484
|
try {
|
|
485
|
-
|
|
485
|
+
// 【新增】同时传入 userCode,用于复用授权场景
|
|
486
|
+
const token = await oauthManager.pollForTokenOnce(savedAuthState.deviceCode, savedAuthState.userCode);
|
|
486
487
|
if (token) {
|
|
487
488
|
// 授权成功
|
|
488
489
|
cachedToken = token.accessToken;
|
|
@@ -2110,7 +2111,8 @@ async function startAuthFlow() {
|
|
|
2110
2111
|
authState.isWaiting = true;
|
|
2111
2112
|
console.error(`🔑 使用已保存的验证码:${savedAuthState.userCode}`);
|
|
2112
2113
|
// 启动后台轮询
|
|
2113
|
-
|
|
2114
|
+
// 【新增】同时传入 userCode,用于复用授权场景
|
|
2115
|
+
pollForAuthInBackground(savedAuthState.deviceCode, savedAuthState.userCode, 5000);
|
|
2114
2116
|
return;
|
|
2115
2117
|
}
|
|
2116
2118
|
}
|
|
@@ -2118,7 +2120,6 @@ async function startAuthFlow() {
|
|
|
2118
2120
|
// 【新增】获取或设置Agent类型
|
|
2119
2121
|
let agentType = await getAgentType();
|
|
2120
2122
|
if (!agentType) {
|
|
2121
|
-
// 首次使用,尝试从记账参数中获取,或使用默认值
|
|
2122
2123
|
agentType = process.env.AGENT_TYPE || 'unknown';
|
|
2123
2124
|
await saveAgentType(agentType);
|
|
2124
2125
|
console.error(`[Auth] 首次使用,设置Agent类型: ${agentType}`);
|
|
@@ -2152,7 +2153,8 @@ async function startAuthFlow() {
|
|
|
2152
2153
|
console.error('⏳ 请在小程序中完成授权,然后再次调用');
|
|
2153
2154
|
|
|
2154
2155
|
// 启动后台轮询(3分钟)
|
|
2155
|
-
|
|
2156
|
+
// 【新增】同时传入 userCode,用于复用授权场景
|
|
2157
|
+
pollForAuthInBackground(deviceCodeRes.deviceCode, deviceCodeRes.userCode, 5000);
|
|
2156
2158
|
|
|
2157
2159
|
} catch (err) {
|
|
2158
2160
|
authState.error = err.message;
|
|
@@ -2162,7 +2164,7 @@ async function startAuthFlow() {
|
|
|
2162
2164
|
}
|
|
2163
2165
|
|
|
2164
2166
|
// 后台轮询授权状态(3分钟)
|
|
2165
|
-
async function pollForAuthInBackground(deviceCode, interval) {
|
|
2167
|
+
async function pollForAuthInBackground(deviceCode, userCode, interval) {
|
|
2166
2168
|
const maxAttempts = 36; // 3分钟(36次 × 5秒)
|
|
2167
2169
|
let attempts = 0;
|
|
2168
2170
|
|
|
@@ -2174,7 +2176,8 @@ async function pollForAuthInBackground(deviceCode, interval) {
|
|
|
2174
2176
|
|
|
2175
2177
|
try {
|
|
2176
2178
|
// 调用云函数检查授权状态
|
|
2177
|
-
|
|
2179
|
+
// 【新增】同时传入 userCode,用于复用授权场景
|
|
2180
|
+
const token = await oauthManager.pollForTokenOnce(deviceCode, userCode);
|
|
2178
2181
|
|
|
2179
2182
|
if (token) {
|
|
2180
2183
|
// 授权成功
|
package/utils/machine-id.js
CHANGED
|
@@ -156,10 +156,67 @@ async function getMacAddresses() {
|
|
|
156
156
|
return macAddresses.sort();
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* 自动检测Agent类型
|
|
161
|
+
* 根据运行环境自动识别:trae、cursor、claude-desktop等
|
|
162
|
+
* @returns {string} 检测到的Agent类型
|
|
163
|
+
*/
|
|
164
|
+
function detectAgentType() {
|
|
165
|
+
// 检测环境变量
|
|
166
|
+
const envVars = {
|
|
167
|
+
TRAE: process.env.TRAE || process.env.TRAEPATH,
|
|
168
|
+
CURSOR: process.env.CURSOR || process.env.CURSOR_PATH,
|
|
169
|
+
CLAUDE: process.env.CLAUDE_DESKTOP || process.env.CLAUDE_APP,
|
|
170
|
+
VSCODE: process.env.VSCODE_CWD || process.env.VSCODE_PID,
|
|
171
|
+
WINDSURF: process.env.WINDSURF || process.env.WINDSURF_PATH
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// 根据环境变量判断
|
|
175
|
+
if (envVars.TRAEPATH || process.argv.some(arg => arg.includes('trae'))) {
|
|
176
|
+
return 'trae';
|
|
177
|
+
}
|
|
178
|
+
if (envVars.CURSOR || process.argv.some(arg => arg.includes('cursor'))) {
|
|
179
|
+
return 'cursor';
|
|
180
|
+
}
|
|
181
|
+
if (envVars.CLAUDE || process.argv.some(arg => arg.includes('claude'))) {
|
|
182
|
+
return 'claude-desktop';
|
|
183
|
+
}
|
|
184
|
+
if (envVars.WINDSURF) {
|
|
185
|
+
return 'windsurf';
|
|
186
|
+
}
|
|
187
|
+
if (envVars.VSCODE) {
|
|
188
|
+
return 'vscode';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 检测进程信息
|
|
192
|
+
try {
|
|
193
|
+
const ppid = process.ppid;
|
|
194
|
+
// 在 macOS/Linux 上可以通过父进程检测
|
|
195
|
+
if (process.platform === 'darwin' || process.platform === 'linux') {
|
|
196
|
+
try {
|
|
197
|
+
const { execSync } = require('child_process');
|
|
198
|
+
const parentCmd = execSync(`ps -p ${ppid} -o comm=`, { encoding: 'utf8' }).trim();
|
|
199
|
+
if (parentCmd.includes('trae')) return 'trae';
|
|
200
|
+
if (parentCmd.includes('cursor')) return 'cursor';
|
|
201
|
+
if (parentCmd.includes('claude')) return 'claude-desktop';
|
|
202
|
+
if (parentCmd.includes('windsurf')) return 'windsurf';
|
|
203
|
+
} catch (e) {
|
|
204
|
+
// 忽略错误
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
// 忽略错误
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 默认返回 unknown
|
|
212
|
+
return 'unknown';
|
|
213
|
+
}
|
|
214
|
+
|
|
159
215
|
module.exports = {
|
|
160
216
|
getMachineId,
|
|
161
217
|
getDeviceFingerprint,
|
|
162
218
|
getAgentType,
|
|
163
219
|
saveAgentType,
|
|
164
|
-
getMacAddresses
|
|
220
|
+
getMacAddresses,
|
|
221
|
+
detectAgentType
|
|
165
222
|
};
|