chaimi-keep-mcp 3.2.1-beta.6 → 3.3.0-beta.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 +0 -32
- package/bin/cli.js +1 -26
- package/oauth.js +9 -16
- package/package.json +1 -1
- package/server.js +11 -39
- package/utils/machine-id.js +0 -197
- package/.env.example +0 -26
- package/references/response-templates-test.md +0 -243
package/README.md
CHANGED
|
@@ -148,38 +148,6 @@ 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
|
-
|
|
168
|
-
### v3.2.1-beta.4 (2026-04-26)
|
|
169
|
-
- **修复** deviceCode 保存不一致问题
|
|
170
|
-
- 小程序端使用授权返回的实际 deviceCode,解决复用授权时 deviceCode 不匹配
|
|
171
|
-
- **优化** 回复模板格式
|
|
172
|
-
- 统一使用15字符短分隔线格式
|
|
173
|
-
- 添加消费洞察变量 `{洞察内容}`
|
|
174
|
-
- **更新** 正能量情绪词库
|
|
175
|
-
- 41条分类祝福语,更积极向上的结束语
|
|
176
|
-
|
|
177
|
-
### v3.2.1-beta.0 (2026-04-26)
|
|
178
|
-
- **新增** 设备指纹机制,防止同一设备重复授权
|
|
179
|
-
- 基于机器ID + Agent类型生成唯一设备指纹
|
|
180
|
-
- 同一设备重复授权时复用已有记录,避免生成多个agentName
|
|
181
|
-
- 支持设备指纹查重,自动识别已授权设备
|
|
182
|
-
|
|
183
151
|
### v3.1.49-beta.2 (2026-04-24)
|
|
184
152
|
- **新增** period 参数支持
|
|
185
153
|
- getStatistics: 支持 this_week/last_week/this_month/last_month 周期筛选
|
package/bin/cli.js
CHANGED
|
@@ -125,24 +125,6 @@ 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
|
-
|
|
146
128
|
function installToAgent(agent) {
|
|
147
129
|
try {
|
|
148
130
|
// 平台检查
|
|
@@ -178,14 +160,7 @@ function installToAgent(agent) {
|
|
|
178
160
|
}
|
|
179
161
|
|
|
180
162
|
// 添加柴米记账配置
|
|
181
|
-
|
|
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;
|
|
163
|
+
config.mcpServers['柴米记账'] = JSON.parse(JSON.stringify(DEFAULT_CHAIMI_CONFIG));
|
|
189
164
|
|
|
190
165
|
// 写入配置
|
|
191
166
|
writeJSON(agent.configPath, config);
|
package/oauth.js
CHANGED
|
@@ -64,10 +64,8 @@ class OAuthManager {
|
|
|
64
64
|
console.error('⏳ 等待用户授权,请勿关闭窗口...');
|
|
65
65
|
console.error(' (请在手机微信中完成授权操作)');
|
|
66
66
|
console.error('');
|
|
67
|
-
// 【新增】同时传入 userCode,用于复用授权场景
|
|
68
67
|
const token = await this.pollForToken(
|
|
69
68
|
deviceCodeRes.deviceCode,
|
|
70
|
-
deviceCodeRes.userCode,
|
|
71
69
|
deviceCodeRes.interval * 1000 || 5000
|
|
72
70
|
);
|
|
73
71
|
|
|
@@ -148,7 +146,7 @@ class OAuthManager {
|
|
|
148
146
|
await execPromise(command);
|
|
149
147
|
}
|
|
150
148
|
|
|
151
|
-
async requestDeviceCode(useUrlScheme = false
|
|
149
|
+
async requestDeviceCode(useUrlScheme = false) {
|
|
152
150
|
const response = await fetch(this.mcpOAuthUrl, {
|
|
153
151
|
method: 'POST',
|
|
154
152
|
headers: {
|
|
@@ -158,8 +156,7 @@ class OAuthManager {
|
|
|
158
156
|
tool: 'deviceCode',
|
|
159
157
|
params: {
|
|
160
158
|
clientId: 'chaihuo-mcp-client',
|
|
161
|
-
useUrlScheme: useUrlScheme
|
|
162
|
-
...extraParams
|
|
159
|
+
useUrlScheme: useUrlScheme
|
|
163
160
|
}
|
|
164
161
|
})
|
|
165
162
|
});
|
|
@@ -174,14 +171,8 @@ class OAuthManager {
|
|
|
174
171
|
}
|
|
175
172
|
|
|
176
173
|
// 单次轮询(用于后台轮询)
|
|
177
|
-
async pollForTokenOnce(deviceCode
|
|
174
|
+
async pollForTokenOnce(deviceCode) {
|
|
178
175
|
try {
|
|
179
|
-
const params = { deviceCode };
|
|
180
|
-
// 【新增】如果有 userCode,一并传入(用于复用授权场景)
|
|
181
|
-
if (userCode) {
|
|
182
|
-
params.userCode = userCode;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
176
|
const response = await fetch(this.mcpOAuthUrl, {
|
|
186
177
|
method: 'POST',
|
|
187
178
|
headers: {
|
|
@@ -189,7 +180,9 @@ class OAuthManager {
|
|
|
189
180
|
},
|
|
190
181
|
body: JSON.stringify({
|
|
191
182
|
tool: 'deviceToken',
|
|
192
|
-
params
|
|
183
|
+
params: {
|
|
184
|
+
deviceCode
|
|
185
|
+
}
|
|
193
186
|
})
|
|
194
187
|
});
|
|
195
188
|
|
|
@@ -223,17 +216,17 @@ class OAuthManager {
|
|
|
223
216
|
}
|
|
224
217
|
}
|
|
225
218
|
|
|
226
|
-
async pollForToken(deviceCode,
|
|
219
|
+
async pollForToken(deviceCode, interval) {
|
|
227
220
|
const maxAttempts = 60;
|
|
228
221
|
let attempts = 0;
|
|
229
222
|
|
|
230
223
|
while (attempts < maxAttempts) {
|
|
231
224
|
attempts++;
|
|
225
|
+
|
|
232
226
|
await this.delay(interval);
|
|
233
227
|
|
|
234
228
|
try {
|
|
235
|
-
|
|
236
|
-
const token = await this.pollForTokenOnce(deviceCode, userCode);
|
|
229
|
+
const token = await this.pollForTokenOnce(deviceCode);
|
|
237
230
|
if (token) {
|
|
238
231
|
return token;
|
|
239
232
|
}
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -33,9 +33,6 @@ const { OAuthManager, FileTokenStorage } = require('./oauth.js');
|
|
|
33
33
|
// 导入校验工具
|
|
34
34
|
const { validateDate } = require('./utils/validators.js');
|
|
35
35
|
|
|
36
|
-
// 导入设备指纹模块
|
|
37
|
-
const { getDeviceFingerprint, getAgentType, saveAgentType, getMacAddresses } = require('./utils/machine-id.js');
|
|
38
|
-
|
|
39
36
|
// API 签名密钥(用于验证请求来自合法 MCP Server)
|
|
40
37
|
const CHAIMI_API_SECRET = 'chaimi-mcp-secret-2024';
|
|
41
38
|
|
|
@@ -482,8 +479,7 @@ async function getToken() {
|
|
|
482
479
|
} else {
|
|
483
480
|
// 验证码未过期,尝试直接获取token(用户可能已授权)
|
|
484
481
|
try {
|
|
485
|
-
|
|
486
|
-
const token = await oauthManager.pollForTokenOnce(savedAuthState.deviceCode, savedAuthState.userCode);
|
|
482
|
+
const token = await oauthManager.pollForTokenOnce(savedAuthState.deviceCode);
|
|
487
483
|
if (token) {
|
|
488
484
|
// 授权成功
|
|
489
485
|
cachedToken = token.accessToken;
|
|
@@ -491,7 +487,6 @@ async function getToken() {
|
|
|
491
487
|
authState.isAuthorized = true;
|
|
492
488
|
await oauthManager.tokenStorage.save(token);
|
|
493
489
|
// 【新增】保存默认 Agent 名称和 deviceCode
|
|
494
|
-
// 【修复】使用实际授权的 deviceCode
|
|
495
490
|
await oauthManager.tokenStorage.saveAgentName('柴米AI助手');
|
|
496
491
|
await oauthManager.tokenStorage.saveDeviceCode(savedAuthState.deviceCode);
|
|
497
492
|
await oauthManager.clearAuthState();
|
|
@@ -981,7 +976,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
981
976
|
// 按 SKILL 规范格式:新的回复模板
|
|
982
977
|
const displayStoreText = displayStore ? `【${displayStore}】` : '';
|
|
983
978
|
const dateStr = result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN');
|
|
984
|
-
userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n
|
|
979
|
+
userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n · 商品/店名:${displayName}${displayStoreText} 💰¥${displayAmount}\n · 收支类型: 支出\n · 分类:${result.data?.categoryName || displayCategory}\n · 时间:${dateStr}\n✅ ${friendlyEnding}${insightsText}${achievementsText}\n-------------\nchaimi-keep-mcp: v${MCP_VERSION}`;
|
|
985
980
|
|
|
986
981
|
if (!processedArgs.agentType || !processedArgs.apiProvider) {
|
|
987
982
|
userMessage += '\n\n💡 提示:传递agentType和apiProvider参数可解锁小程序记录来源统计功能,示例:agentType="openclaw", apiProvider="doubao"';
|
|
@@ -1225,7 +1220,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1225
1220
|
|
|
1226
1221
|
// 按 SKILL 规范格式:新的回复模板
|
|
1227
1222
|
const dateStr = result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN');
|
|
1228
|
-
userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n
|
|
1223
|
+
userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n · 商品/店名:${storeName} 💰¥${totalAmount}\n · 收支类型: 支出\n · 分类:${result.data?.storeCategory || category}\n · 商品数量:${itemCount}件\n · 时间:${dateStr}\n✅ ${friendlyEnding}${insightsText}${achievementsText}\n-------------\nchaimi-keep-mcp: v${MCP_VERSION}`;
|
|
1229
1224
|
|
|
1230
1225
|
if (!processedArgs.agentType || !processedArgs.apiProvider) {
|
|
1231
1226
|
userMessage += '\n\n💡 提示:传递agentType和apiProvider参数可解锁小程序记录来源统计功能,示例:agentType="openclaw", apiProvider="doubao"';
|
|
@@ -1455,7 +1450,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1455
1450
|
|
|
1456
1451
|
// 按 SKILL 规范格式:新的回复模板
|
|
1457
1452
|
const dateStr = result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN');
|
|
1458
|
-
userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n
|
|
1453
|
+
userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n · 商品/店名:${displayName}${displayStoreText} 💰¥${displayAmount}\n · 收支类型: 收入\n · 分类:${result.data?.categoryName || displayCategory}\n · 时间:${dateStr}\n✅ 入账顺利!💰\n-------------\nchaimi-keep-mcp: v${MCP_VERSION}`;
|
|
1459
1454
|
}
|
|
1460
1455
|
break;
|
|
1461
1456
|
}
|
|
@@ -2111,33 +2106,13 @@ async function startAuthFlow() {
|
|
|
2111
2106
|
authState.isWaiting = true;
|
|
2112
2107
|
console.error(`🔑 使用已保存的验证码:${savedAuthState.userCode}`);
|
|
2113
2108
|
// 启动后台轮询
|
|
2114
|
-
|
|
2115
|
-
pollForAuthInBackground(savedAuthState.deviceCode, savedAuthState.userCode, 5000);
|
|
2109
|
+
pollForAuthInBackground(savedAuthState.deviceCode, 5000);
|
|
2116
2110
|
return;
|
|
2117
2111
|
}
|
|
2118
2112
|
}
|
|
2119
2113
|
|
|
2120
|
-
//
|
|
2121
|
-
|
|
2122
|
-
if (!agentType) {
|
|
2123
|
-
agentType = process.env.AGENT_TYPE || 'unknown';
|
|
2124
|
-
await saveAgentType(agentType);
|
|
2125
|
-
console.error(`[Auth] 首次使用,设置Agent类型: ${agentType}`);
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
// 【新增】生成设备指纹
|
|
2129
|
-
const deviceFingerprint = await getDeviceFingerprint(agentType);
|
|
2130
|
-
const macAddresses = await getMacAddresses();
|
|
2131
|
-
console.error(`[Auth] 设备指纹: ${deviceFingerprint}`);
|
|
2132
|
-
console.error(`[Auth] MAC地址: ${macAddresses.join(', ') || 'unknown'}`);
|
|
2133
|
-
|
|
2134
|
-
// 获取新的设备码(带上设备指纹)
|
|
2135
|
-
const deviceCodeRes = await oauthManager.requestDeviceCode(false, {
|
|
2136
|
-
deviceFingerprint,
|
|
2137
|
-
agentType,
|
|
2138
|
-
macAddress: macAddresses[0] || 'unknown'
|
|
2139
|
-
});
|
|
2140
|
-
|
|
2114
|
+
// 获取新的设备码
|
|
2115
|
+
const deviceCodeRes = await oauthManager.requestDeviceCode(false);
|
|
2141
2116
|
authState.deviceCode = deviceCodeRes.deviceCode;
|
|
2142
2117
|
authState.userCode = deviceCodeRes.userCode;
|
|
2143
2118
|
authState.isWaiting = true;
|
|
@@ -2153,8 +2128,7 @@ async function startAuthFlow() {
|
|
|
2153
2128
|
console.error('⏳ 请在小程序中完成授权,然后再次调用');
|
|
2154
2129
|
|
|
2155
2130
|
// 启动后台轮询(3分钟)
|
|
2156
|
-
|
|
2157
|
-
pollForAuthInBackground(deviceCodeRes.deviceCode, deviceCodeRes.userCode, 5000);
|
|
2131
|
+
pollForAuthInBackground(deviceCodeRes.deviceCode, 5000);
|
|
2158
2132
|
|
|
2159
2133
|
} catch (err) {
|
|
2160
2134
|
authState.error = err.message;
|
|
@@ -2164,7 +2138,7 @@ async function startAuthFlow() {
|
|
|
2164
2138
|
}
|
|
2165
2139
|
|
|
2166
2140
|
// 后台轮询授权状态(3分钟)
|
|
2167
|
-
async function pollForAuthInBackground(deviceCode,
|
|
2141
|
+
async function pollForAuthInBackground(deviceCode, interval) {
|
|
2168
2142
|
const maxAttempts = 36; // 3分钟(36次 × 5秒)
|
|
2169
2143
|
let attempts = 0;
|
|
2170
2144
|
|
|
@@ -2176,8 +2150,7 @@ async function pollForAuthInBackground(deviceCode, userCode, interval) {
|
|
|
2176
2150
|
|
|
2177
2151
|
try {
|
|
2178
2152
|
// 调用云函数检查授权状态
|
|
2179
|
-
|
|
2180
|
-
const token = await oauthManager.pollForTokenOnce(deviceCode, userCode);
|
|
2153
|
+
const token = await oauthManager.pollForTokenOnce(deviceCode);
|
|
2181
2154
|
|
|
2182
2155
|
if (token) {
|
|
2183
2156
|
// 授权成功
|
|
@@ -2190,9 +2163,8 @@ async function pollForAuthInBackground(deviceCode, userCode, interval) {
|
|
|
2190
2163
|
await oauthManager.tokenStorage.save(token);
|
|
2191
2164
|
|
|
2192
2165
|
// 【新增】保存默认 Agent 名称和 deviceCode(首次记账时会获取真实名称)
|
|
2193
|
-
// 【修复】使用传入的 deviceCode,而不是 authState.deviceCode(可能不一致)
|
|
2194
2166
|
await oauthManager.tokenStorage.saveAgentName('柴米AI助手');
|
|
2195
|
-
await oauthManager.tokenStorage.saveDeviceCode(deviceCode);
|
|
2167
|
+
await oauthManager.tokenStorage.saveDeviceCode(authState.deviceCode);
|
|
2196
2168
|
|
|
2197
2169
|
// 清除授权状态
|
|
2198
2170
|
await oauthManager.clearAuthState();
|
package/utils/machine-id.js
CHANGED
|
@@ -23,200 +23,3 @@ const MACHINE_ID_FILE = path.join(
|
|
|
23
23
|
'machine-id'
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
-
// Agent类型文件路径
|
|
27
|
-
const AGENT_TYPE_FILE = path.join(
|
|
28
|
-
process.env.HOME || process.env.USERPROFILE,
|
|
29
|
-
'.mcporter',
|
|
30
|
-
'agent-type.json'
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 获取或生成机器ID
|
|
35
|
-
* @returns {Promise<string>} 16位机器ID
|
|
36
|
-
*/
|
|
37
|
-
async function getMachineId() {
|
|
38
|
-
try {
|
|
39
|
-
// 1. 尝试读取已有ID
|
|
40
|
-
const existing = await fs.readFile(MACHINE_ID_FILE, 'utf8');
|
|
41
|
-
if (existing.trim()) {
|
|
42
|
-
console.error('[MachineId] 使用已有机器ID:', existing.trim());
|
|
43
|
-
return existing.trim();
|
|
44
|
-
}
|
|
45
|
-
} catch (err) {
|
|
46
|
-
// 文件不存在,继续生成
|
|
47
|
-
console.error('[MachineId] 首次使用,生成新机器ID');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 2. 生成新ID
|
|
51
|
-
const networkInterfaces = os.networkInterfaces();
|
|
52
|
-
const macAddresses = [];
|
|
53
|
-
|
|
54
|
-
for (const name in networkInterfaces) {
|
|
55
|
-
for (const iface of networkInterfaces[name]) {
|
|
56
|
-
if (!iface.internal && iface.mac && iface.mac !== '00:00:00:00:00:00') {
|
|
57
|
-
macAddresses.push(iface.mac);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 排序后合并(避免网卡顺序影响)
|
|
63
|
-
macAddresses.sort();
|
|
64
|
-
const machineId = crypto
|
|
65
|
-
.createHash('sha256')
|
|
66
|
-
.update(macAddresses.join(':') + os.hostname())
|
|
67
|
-
.digest('hex')
|
|
68
|
-
.substring(0, 16);
|
|
69
|
-
|
|
70
|
-
console.error('[MachineId] 生成新机器ID:', machineId);
|
|
71
|
-
|
|
72
|
-
// 3. 保存到文件
|
|
73
|
-
try {
|
|
74
|
-
await fs.mkdir(path.dirname(MACHINE_ID_FILE), { recursive: true });
|
|
75
|
-
await fs.writeFile(MACHINE_ID_FILE, machineId, { mode: 0o600 });
|
|
76
|
-
console.error('[MachineId] 机器ID已保存到:', MACHINE_ID_FILE);
|
|
77
|
-
} catch (err) {
|
|
78
|
-
console.error('[MachineId] 保存机器ID失败:', err.message);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return machineId;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* 获取设备指纹
|
|
86
|
-
* @param {string} agentType - Agent类型(claude/cursor等)
|
|
87
|
-
* @returns {Promise<string>} 16位设备指纹
|
|
88
|
-
*/
|
|
89
|
-
async function getDeviceFingerprint(agentType) {
|
|
90
|
-
const machineId = await getMachineId();
|
|
91
|
-
const fingerprint = crypto
|
|
92
|
-
.createHash('sha256')
|
|
93
|
-
.update(`${machineId}:${agentType}`)
|
|
94
|
-
.digest('hex')
|
|
95
|
-
.substring(0, 16);
|
|
96
|
-
|
|
97
|
-
console.error('[MachineId] 设备指纹:', fingerprint, '(agentType:', agentType + ')');
|
|
98
|
-
return fingerprint;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* 获取Agent类型
|
|
103
|
-
* @returns {Promise<string|null>} Agent类型,首次返回null
|
|
104
|
-
*/
|
|
105
|
-
async function getAgentType() {
|
|
106
|
-
try {
|
|
107
|
-
const data = await fs.readFile(AGENT_TYPE_FILE, 'utf8');
|
|
108
|
-
const parsed = JSON.parse(data);
|
|
109
|
-
if (parsed.agentType) {
|
|
110
|
-
console.error('[MachineId] 使用已有Agent类型:', parsed.agentType);
|
|
111
|
-
return parsed.agentType;
|
|
112
|
-
}
|
|
113
|
-
} catch (err) {
|
|
114
|
-
console.error('[MachineId] 首次使用,需要设置Agent类型');
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 保存Agent类型
|
|
121
|
-
* @param {string} agentType - Agent类型
|
|
122
|
-
*/
|
|
123
|
-
async function saveAgentType(agentType) {
|
|
124
|
-
try {
|
|
125
|
-
await fs.mkdir(path.dirname(AGENT_TYPE_FILE), { recursive: true });
|
|
126
|
-
await fs.writeFile(
|
|
127
|
-
AGENT_TYPE_FILE,
|
|
128
|
-
JSON.stringify({
|
|
129
|
-
agentType,
|
|
130
|
-
updatedAt: new Date().toISOString()
|
|
131
|
-
}),
|
|
132
|
-
{ mode: 0o600 }
|
|
133
|
-
);
|
|
134
|
-
console.error('[MachineId] Agent类型已保存:', agentType);
|
|
135
|
-
} catch (err) {
|
|
136
|
-
console.error('[MachineId] 保存Agent类型失败:', err.message);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* 获取MAC地址列表(用于调试)
|
|
142
|
-
* @returns {Promise<string[]>} MAC地址列表
|
|
143
|
-
*/
|
|
144
|
-
async function getMacAddresses() {
|
|
145
|
-
const networkInterfaces = os.networkInterfaces();
|
|
146
|
-
const macAddresses = [];
|
|
147
|
-
|
|
148
|
-
for (const name in networkInterfaces) {
|
|
149
|
-
for (const iface of networkInterfaces[name]) {
|
|
150
|
-
if (!iface.internal && iface.mac && iface.mac !== '00:00:00:00:00:00') {
|
|
151
|
-
macAddresses.push(iface.mac);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return macAddresses.sort();
|
|
157
|
-
}
|
|
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
|
-
|
|
215
|
-
module.exports = {
|
|
216
|
-
getMachineId,
|
|
217
|
-
getDeviceFingerprint,
|
|
218
|
-
getAgentType,
|
|
219
|
-
saveAgentType,
|
|
220
|
-
getMacAddresses,
|
|
221
|
-
detectAgentType
|
|
222
|
-
};
|
package/.env.example
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# 柴米记账 MCP Server 环境变量配置
|
|
2
|
-
# OAuth 2.0 Device Flow 认证(行业标准方案)
|
|
3
|
-
|
|
4
|
-
# ========================================
|
|
5
|
-
# 必需配置
|
|
6
|
-
# ========================================
|
|
7
|
-
|
|
8
|
-
# OAuth 云函数地址
|
|
9
|
-
# 首次启动会自动引导完成授权,Token 存储在 ~/.mcporter/oauth-token.json
|
|
10
|
-
MCP_OAUTH_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpOAuth
|
|
11
|
-
|
|
12
|
-
# mcpHub 云函数地址
|
|
13
|
-
MCP_HUB_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpHub-mcp
|
|
14
|
-
|
|
15
|
-
# mcpPrompt 云函数地址
|
|
16
|
-
MCP_PROMPT_URL=https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpPrompt
|
|
17
|
-
|
|
18
|
-
# ========================================
|
|
19
|
-
# 可选配置
|
|
20
|
-
# ========================================
|
|
21
|
-
|
|
22
|
-
# AI API Key(用于本地解析)
|
|
23
|
-
AI_API_KEY=
|
|
24
|
-
|
|
25
|
-
# AI 提供商(openai/anthropic/deepseek)
|
|
26
|
-
AI_PROVIDER=deepseek
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "chaimi-keep-response-templates-test"
|
|
3
|
-
description: 记账回复模板测试方案 - 供Agent测试不同格式的显示效果
|
|
4
|
-
version: "1.1.0"
|
|
5
|
-
updated: "2026-04-26"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# 记账回复模板测试方案
|
|
9
|
-
|
|
10
|
-
> 用于测试不同分隔符和排版在手机/PC端的显示效果
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## 测试说明
|
|
15
|
-
|
|
16
|
-
将以下各方案的模板复制到Agent的skill中使用,观察:
|
|
17
|
-
1. 换行是否正常
|
|
18
|
-
2. 分隔线显示效果
|
|
19
|
-
3. 底部三行对齐效果
|
|
20
|
-
4. 整体美观度
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## 方案1:双分隔线短版(15字符)
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
✅ 「{agentName}」已帮您记账成功
|
|
28
|
-
═══════════════
|
|
29
|
-
💰 金额:¥{金额}
|
|
30
|
-
═══════════════
|
|
31
|
-
|
|
32
|
-
📦 商品:{商品名}
|
|
33
|
-
🏷️ 分类:{分类}
|
|
34
|
-
🏪 商家:{商家}
|
|
35
|
-
🕐 时间:{日期时间}
|
|
36
|
-
|
|
37
|
-
───────────────
|
|
38
|
-
🎉 {正能量祝福语}!
|
|
39
|
-
柴米AI记账
|
|
40
|
-
chaimi-keep-mcp v{版本号}
|
|
41
|
-
───────────────
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
**填充示例:**
|
|
45
|
-
```
|
|
46
|
-
✅ 「你的小可爱」已帮您记账成功
|
|
47
|
-
═══════════════
|
|
48
|
-
💰 金额:¥35.00
|
|
49
|
-
═══════════════
|
|
50
|
-
|
|
51
|
-
📦 商品:午餐
|
|
52
|
-
🏷️ 分类:餐饮
|
|
53
|
-
🏪 商家:麦当劳
|
|
54
|
-
🕐 时间:2026-04-24 12:30
|
|
55
|
-
|
|
56
|
-
───────────────
|
|
57
|
-
🎉 美食为梦想充电,继续向前冲!🍚
|
|
58
|
-
柴米AI记账
|
|
59
|
-
chaimi-keep-mcp v3.2.0
|
|
60
|
-
───────────────
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## 方案2:单分隔线极简版
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
✅ 「{agentName}」已帮您记账成功
|
|
69
|
-
|
|
70
|
-
💰 金额:¥{金额}
|
|
71
|
-
|
|
72
|
-
📦 商品:{商品名}
|
|
73
|
-
🏷️ 分类:{分类}
|
|
74
|
-
🏪 商家:{商家}
|
|
75
|
-
🕐 时间:{日期时间}
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
🎉 {正能量祝福语}!
|
|
79
|
-
柴米AI记账
|
|
80
|
-
chaimi-keep-mcp v{版本号}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**填充示例:**
|
|
84
|
-
```
|
|
85
|
-
✅ 「你的小可爱」已帮您记账成功
|
|
86
|
-
|
|
87
|
-
💰 金额:¥35.00
|
|
88
|
-
|
|
89
|
-
📦 商品:午餐
|
|
90
|
-
🏷️ 分类:餐饮
|
|
91
|
-
🏪 商家:麦当劳
|
|
92
|
-
🕐 时间:2026-04-24 12:30
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
🎉 美食为梦想充电,继续向前冲!🍚
|
|
96
|
-
柴米AI记账
|
|
97
|
-
chaimi-keep-mcp v3.2.0
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## 方案3:信息紧凑单行版(已优化)
|
|
103
|
-
|
|
104
|
-
```
|
|
105
|
-
✅ 「{agentName}」已帮您记账成功 💰¥{金额}
|
|
106
|
-
|
|
107
|
-
📦{商品名} · 🏷️{分类} · 🏪{商家} · 🕐{日期时间}
|
|
108
|
-
|
|
109
|
-
═══════════════
|
|
110
|
-
🎉 {正能量祝福语}!
|
|
111
|
-
柴米AI记账
|
|
112
|
-
chaimi-keep-mcp v{版本号}
|
|
113
|
-
═══════════════
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
**填充示例:**
|
|
117
|
-
```
|
|
118
|
-
✅ 「你的小可爱」已帮您记账成功 💰¥35.00
|
|
119
|
-
|
|
120
|
-
📦午餐 · 🏷️餐饮 · 🏪麦当劳 · 🕐12:30
|
|
121
|
-
|
|
122
|
-
═══════════════
|
|
123
|
-
🎉 祝您用餐愉快!
|
|
124
|
-
柴米AI记账
|
|
125
|
-
chaimi-keep-mcp v3.2.0
|
|
126
|
-
═══════════════
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
---
|
|
130
|
-
|
|
131
|
-
## 测试记录表
|
|
132
|
-
|
|
133
|
-
| 方案 | 手机端效果 | PC端效果 | 推荐场景 |
|
|
134
|
-
|:-----|:-----------|:---------|:---------|
|
|
135
|
-
| 方案1:双分隔线短版 | 待测试 | 待测试 | 默认推荐 |
|
|
136
|
-
| 方案2:单分隔线极简版 | 待测试 | 待测试 | 快速记账 |
|
|
137
|
-
| 方案3:信息紧凑单行版 | 待测试 | 待测试 | 空间受限 |
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
## 关键检查点
|
|
142
|
-
|
|
143
|
-
### 1. 分隔线显示
|
|
144
|
-
- [ ] 方案1/5的 `═` 符号是否正常显示
|
|
145
|
-
- [ ] 方案2的 `---` 是否被识别为分隔线
|
|
146
|
-
|
|
147
|
-
### 2. 底部对齐
|
|
148
|
-
- [ ] 底部三行文字是否左对齐(🎉 不占对齐位)
|
|
149
|
-
- [ ] "祝"、"柴"、"c" 三字符是否在同一列
|
|
150
|
-
|
|
151
|
-
### 3. 换行效果
|
|
152
|
-
- [ ] 空行是否被正确保留
|
|
153
|
-
- [ ] 信息项之间是否有适当间距
|
|
154
|
-
|
|
155
|
-
### 4. Emoji显示
|
|
156
|
-
- [ ] ✅ 💰 📦 🏷️ 🏪 🕐 🎉 是否正常显示
|
|
157
|
-
- [ ] 是否有乱码或方框
|
|
158
|
-
|
|
159
|
-
### 5. 方案5特殊检查
|
|
160
|
-
- [ ] 单行信息是否可读
|
|
161
|
-
- [ ] 分隔符 `·` 是否正常显示
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## 正能量情绪词库
|
|
166
|
-
|
|
167
|
-
根据消费分类自动匹配正能量结束语:
|
|
168
|
-
|
|
169
|
-
### 餐饮类
|
|
170
|
-
| 场景 | 结束语 |
|
|
171
|
-
|:-----|:-------|
|
|
172
|
-
| 早餐 | 早餐吃得香,元气满满迎朝阳!☀️ |
|
|
173
|
-
| 午餐 | 美食为梦想充电,继续向前冲!🍚 |
|
|
174
|
-
| 晚餐 | 晚餐后放松身心,明天更精彩!🌙 |
|
|
175
|
-
| 零食 | 生活要有仪式感,小确幸也是幸福!😋 |
|
|
176
|
-
| 外卖 | 再忙也要爱自己,好好吃饭!💕 |
|
|
177
|
-
| 聚餐 | 朋友相聚是福气,友谊地久天长!🎉 |
|
|
178
|
-
| 咖啡 | 一杯咖啡提神醒脑,效率倍增!☕ |
|
|
179
|
-
| 奶茶 | 甜蜜相伴,工作生活都精彩!🧋 |
|
|
180
|
-
|
|
181
|
-
### 交通类
|
|
182
|
-
| 场景 | 结束语 |
|
|
183
|
-
|:-----|:-------|
|
|
184
|
-
| 地铁 | 城市动脉连接梦想,未来可期!🚇 |
|
|
185
|
-
| 打车 | 一路顺风,事事顺心!🚗 |
|
|
186
|
-
| 加油 | 加满油向梦想出发,勇往直前!⛽ |
|
|
187
|
-
| 停车 | 车位到手,好运相伴!🅿️ |
|
|
188
|
-
| 公交 | 绿色出行低碳环保,为地球助力!🚌 |
|
|
189
|
-
| 高铁 | 速度与梦想同行,前程似锦!🚄 |
|
|
190
|
-
|
|
191
|
-
### 购物类
|
|
192
|
-
| 场景 | 结束语 |
|
|
193
|
-
|:-----|:-------|
|
|
194
|
-
| 日用品 | 精明消费品质生活,越买越聪明!✨ |
|
|
195
|
-
| 服装 | 新衣新气象,自信每一天!👗 |
|
|
196
|
-
| 电子产品 | 科技赋能,效率翻倍!💻 |
|
|
197
|
-
| 超市 | 满载而归,生活美满!🛒 |
|
|
198
|
-
| 网购 | 好物即将到家,期待美好!📦 |
|
|
199
|
-
| 美妆 | 美丽自信,由内而外散发光彩!💄 |
|
|
200
|
-
|
|
201
|
-
### 居住类
|
|
202
|
-
| 场景 | 结束语 |
|
|
203
|
-
|:-----|:-------|
|
|
204
|
-
| 房租 | 投资未来,房子是温馨的港湾!🏠 |
|
|
205
|
-
| 水电 | 节约环保,绿色生活从我做起!💡 |
|
|
206
|
-
| 物业 | 安心居住,幸福生活有保障!🏢 |
|
|
207
|
-
| 装修 | 亲手打造温馨小窝,成就感满满!🔨 |
|
|
208
|
-
| 家具 | 新家具新生活,每一天都舒心!🛋️ |
|
|
209
|
-
|
|
210
|
-
### 娱乐类
|
|
211
|
-
| 场景 | 结束语 |
|
|
212
|
-
|:-----|:-------|
|
|
213
|
-
| 电影 | 光影世界丰富心灵,生活更精彩!🎬 |
|
|
214
|
-
| 游戏 | 适度娱乐放松身心,工作生活两不误!🎮 |
|
|
215
|
-
| 旅游 | 读万卷书行万里路,拓宽人生视野!✈️ |
|
|
216
|
-
| 健身 | 投资健康是最大的财富,活力满满!💪 |
|
|
217
|
-
| 书籍 | 知识改变命运,智慧点亮人生!📚 |
|
|
218
|
-
|
|
219
|
-
### 收入类
|
|
220
|
-
| 场景 | 结束语 |
|
|
221
|
-
|:-----|:-------|
|
|
222
|
-
| 工资 | 劳动创造价值,努力终有回报!🎉 |
|
|
223
|
-
| 奖金 | 努力被看见,未来更辉煌!💪 |
|
|
224
|
-
| 兼职 | 多元化发展,人生更多精彩!✨ |
|
|
225
|
-
| 理财 | 钱生钱利滚利,财富自由更进一步!📈 |
|
|
226
|
-
| 红包 | 好运连连,福气满满!🧧 |
|
|
227
|
-
|
|
228
|
-
### 通用类
|
|
229
|
-
| 情绪 | 结束语 |
|
|
230
|
-
|:-----|:-------|
|
|
231
|
-
| 鼓励 | 今天的你特别棒,继续发光发热!👍 |
|
|
232
|
-
| 温暖 | 记录生活点滴,美好永留存!📝 |
|
|
233
|
-
| 正能量 | 每一笔记录都是对生活的热爱!💕 |
|
|
234
|
-
| 轻松 | 记账完成,又是充实的一天!✨ |
|
|
235
|
-
| 感恩 | 感恩生活,感恩有你!🙏 |
|
|
236
|
-
| 期待 | 未来可期,一起加油!🌟 |
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
|
|
240
|
-
**测试完成后的最佳方案将更新到 response-templates.md 正式文档中**
|
|
241
|
-
|
|
242
|
-
文档版本:v1.2.0
|
|
243
|
-
最后更新:2026-04-26
|