chaimi-keep-mcp 3.2.1-beta.7 → 3.3.0-beta.1
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 +56 -58
- 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
|
|
|
@@ -469,7 +466,35 @@ const TOKEN_REFRESH_INTERVAL = 2 * 60 * 60 * 1000;
|
|
|
469
466
|
let lastRefreshTime = 0;
|
|
470
467
|
|
|
471
468
|
async function getToken() {
|
|
472
|
-
//
|
|
469
|
+
// 【优化1】优先检查内存中的 token 是否有效
|
|
470
|
+
if (cachedToken && tokenExpireTime > Date.now() + 5 * 60 * 1000) {
|
|
471
|
+
if (Date.now() - lastRefreshTime < TOKEN_REFRESH_INTERVAL) {
|
|
472
|
+
return cachedToken;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// 【优化2】尝试从文件加载 token(优先级高于授权状态检查)
|
|
477
|
+
try {
|
|
478
|
+
const existingToken = await oauthManager.tokenStorage.load();
|
|
479
|
+
if (existingToken && existingToken.accessToken && existingToken.expiresAt) {
|
|
480
|
+
const expiresAt = new Date(existingToken.expiresAt).getTime();
|
|
481
|
+
if (expiresAt > Date.now() + 5 * 60 * 1000) {
|
|
482
|
+
// Token 有效,直接使用
|
|
483
|
+
cachedToken = existingToken.accessToken;
|
|
484
|
+
tokenExpireTime = expiresAt;
|
|
485
|
+
authState.isAuthorized = true;
|
|
486
|
+
lastRefreshTime = Date.now();
|
|
487
|
+
console.error('✅ 已从文件加载有效 Token,无需重新授权');
|
|
488
|
+
return cachedToken;
|
|
489
|
+
} else {
|
|
490
|
+
console.error('⏰ 保存的 Token 已过期,需要重新授权');
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
} catch (err) {
|
|
494
|
+
console.error('⚠️ 加载保存的 Token 失败:', err.message);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// 【优化3】Token 不存在或已过期,检查是否需要授权
|
|
473
498
|
if (!authState.isAuthorized) {
|
|
474
499
|
// 先检查是否有保存的授权状态(从文件加载)
|
|
475
500
|
const savedAuthState = await oauthManager.loadAuthState();
|
|
@@ -482,8 +507,7 @@ async function getToken() {
|
|
|
482
507
|
} else {
|
|
483
508
|
// 验证码未过期,尝试直接获取token(用户可能已授权)
|
|
484
509
|
try {
|
|
485
|
-
|
|
486
|
-
const token = await oauthManager.pollForTokenOnce(savedAuthState.deviceCode, savedAuthState.userCode);
|
|
510
|
+
const token = await oauthManager.pollForTokenOnce(savedAuthState.deviceCode);
|
|
487
511
|
if (token) {
|
|
488
512
|
// 授权成功
|
|
489
513
|
cachedToken = token.accessToken;
|
|
@@ -491,7 +515,6 @@ async function getToken() {
|
|
|
491
515
|
authState.isAuthorized = true;
|
|
492
516
|
await oauthManager.tokenStorage.save(token);
|
|
493
517
|
// 【新增】保存默认 Agent 名称和 deviceCode
|
|
494
|
-
// 【修复】使用实际授权的 deviceCode
|
|
495
518
|
await oauthManager.tokenStorage.saveAgentName('柴米AI助手');
|
|
496
519
|
await oauthManager.tokenStorage.saveDeviceCode(savedAuthState.deviceCode);
|
|
497
520
|
await oauthManager.clearAuthState();
|
|
@@ -512,20 +535,15 @@ async function getToken() {
|
|
|
512
535
|
}
|
|
513
536
|
}
|
|
514
537
|
}
|
|
515
|
-
|
|
538
|
+
|
|
516
539
|
// 没有保存的授权状态或已过期,启动新的授权流程
|
|
517
540
|
await startAuthFlow();
|
|
518
|
-
|
|
541
|
+
|
|
519
542
|
// 返回验证码给Agent
|
|
520
543
|
throw new Error(`NEED_AUTH:${authState.userCode || '等待生成验证码'}`);
|
|
521
544
|
}
|
|
522
545
|
|
|
523
|
-
|
|
524
|
-
if (Date.now() - lastRefreshTime < TOKEN_REFRESH_INTERVAL) {
|
|
525
|
-
return cachedToken;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
546
|
+
// 【优化4】已授权,尝试刷新 token
|
|
529
547
|
if (!oauthManager) {
|
|
530
548
|
throw new Error('OAuth 管理器未初始化,请检查 MCP_OAUTH_URL 配置');
|
|
531
549
|
}
|
|
@@ -981,7 +999,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
981
999
|
// 按 SKILL 规范格式:新的回复模板
|
|
982
1000
|
const displayStoreText = displayStore ? `【${displayStore}】` : '';
|
|
983
1001
|
const dateStr = result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN');
|
|
984
|
-
userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n
|
|
1002
|
+
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
1003
|
|
|
986
1004
|
if (!processedArgs.agentType || !processedArgs.apiProvider) {
|
|
987
1005
|
userMessage += '\n\n💡 提示:传递agentType和apiProvider参数可解锁小程序记录来源统计功能,示例:agentType="openclaw", apiProvider="doubao"';
|
|
@@ -1225,7 +1243,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1225
1243
|
|
|
1226
1244
|
// 按 SKILL 规范格式:新的回复模板
|
|
1227
1245
|
const dateStr = result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN');
|
|
1228
|
-
userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n
|
|
1246
|
+
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
1247
|
|
|
1230
1248
|
if (!processedArgs.agentType || !processedArgs.apiProvider) {
|
|
1231
1249
|
userMessage += '\n\n💡 提示:传递agentType和apiProvider参数可解锁小程序记录来源统计功能,示例:agentType="openclaw", apiProvider="doubao"';
|
|
@@ -1455,7 +1473,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1455
1473
|
|
|
1456
1474
|
// 按 SKILL 规范格式:新的回复模板
|
|
1457
1475
|
const dateStr = result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN');
|
|
1458
|
-
userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n
|
|
1476
|
+
userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n · 商品/店名:${displayName}${displayStoreText} 💰¥${displayAmount}\n · 收支类型: 收入\n · 分类:${result.data?.categoryName || displayCategory}\n · 时间:${dateStr}\n✅ 入账顺利!💰\n-------------\nchaimi-keep-mcp: v${MCP_VERSION}`;
|
|
1459
1477
|
}
|
|
1460
1478
|
break;
|
|
1461
1479
|
}
|
|
@@ -2111,33 +2129,13 @@ async function startAuthFlow() {
|
|
|
2111
2129
|
authState.isWaiting = true;
|
|
2112
2130
|
console.error(`🔑 使用已保存的验证码:${savedAuthState.userCode}`);
|
|
2113
2131
|
// 启动后台轮询
|
|
2114
|
-
|
|
2115
|
-
pollForAuthInBackground(savedAuthState.deviceCode, savedAuthState.userCode, 5000);
|
|
2132
|
+
pollForAuthInBackground(savedAuthState.deviceCode, 5000);
|
|
2116
2133
|
return;
|
|
2117
2134
|
}
|
|
2118
2135
|
}
|
|
2119
2136
|
|
|
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
|
-
|
|
2137
|
+
// 获取新的设备码
|
|
2138
|
+
const deviceCodeRes = await oauthManager.requestDeviceCode(false);
|
|
2141
2139
|
authState.deviceCode = deviceCodeRes.deviceCode;
|
|
2142
2140
|
authState.userCode = deviceCodeRes.userCode;
|
|
2143
2141
|
authState.isWaiting = true;
|
|
@@ -2153,8 +2151,7 @@ async function startAuthFlow() {
|
|
|
2153
2151
|
console.error('⏳ 请在小程序中完成授权,然后再次调用');
|
|
2154
2152
|
|
|
2155
2153
|
// 启动后台轮询(3分钟)
|
|
2156
|
-
|
|
2157
|
-
pollForAuthInBackground(deviceCodeRes.deviceCode, deviceCodeRes.userCode, 5000);
|
|
2154
|
+
pollForAuthInBackground(deviceCodeRes.deviceCode, 5000);
|
|
2158
2155
|
|
|
2159
2156
|
} catch (err) {
|
|
2160
2157
|
authState.error = err.message;
|
|
@@ -2164,7 +2161,7 @@ async function startAuthFlow() {
|
|
|
2164
2161
|
}
|
|
2165
2162
|
|
|
2166
2163
|
// 后台轮询授权状态(3分钟)
|
|
2167
|
-
async function pollForAuthInBackground(deviceCode,
|
|
2164
|
+
async function pollForAuthInBackground(deviceCode, interval) {
|
|
2168
2165
|
const maxAttempts = 36; // 3分钟(36次 × 5秒)
|
|
2169
2166
|
let attempts = 0;
|
|
2170
2167
|
|
|
@@ -2176,8 +2173,7 @@ async function pollForAuthInBackground(deviceCode, userCode, interval) {
|
|
|
2176
2173
|
|
|
2177
2174
|
try {
|
|
2178
2175
|
// 调用云函数检查授权状态
|
|
2179
|
-
|
|
2180
|
-
const token = await oauthManager.pollForTokenOnce(deviceCode, userCode);
|
|
2176
|
+
const token = await oauthManager.pollForTokenOnce(deviceCode);
|
|
2181
2177
|
|
|
2182
2178
|
if (token) {
|
|
2183
2179
|
// 授权成功
|
|
@@ -2185,22 +2181,23 @@ async function pollForAuthInBackground(deviceCode, userCode, interval) {
|
|
|
2185
2181
|
tokenExpireTime = token.expiresAt;
|
|
2186
2182
|
authState.isAuthorized = true;
|
|
2187
2183
|
authState.isWaiting = false;
|
|
2188
|
-
|
|
2184
|
+
|
|
2189
2185
|
// 保存token到文件
|
|
2190
2186
|
await oauthManager.tokenStorage.save(token);
|
|
2191
|
-
|
|
2187
|
+
|
|
2192
2188
|
// 【新增】保存默认 Agent 名称和 deviceCode(首次记账时会获取真实名称)
|
|
2193
|
-
// 【修复】使用传入的 deviceCode,而不是 authState.deviceCode(可能不一致)
|
|
2194
2189
|
await oauthManager.tokenStorage.saveAgentName('柴米AI助手');
|
|
2195
|
-
await oauthManager.tokenStorage.saveDeviceCode(deviceCode);
|
|
2196
|
-
|
|
2190
|
+
await oauthManager.tokenStorage.saveDeviceCode(authState.deviceCode);
|
|
2191
|
+
|
|
2197
2192
|
// 清除授权状态
|
|
2198
2193
|
await oauthManager.clearAuthState();
|
|
2199
|
-
|
|
2200
|
-
console.error('✅
|
|
2201
|
-
|
|
2202
|
-
//
|
|
2203
|
-
|
|
2194
|
+
|
|
2195
|
+
console.error('✅ 授权成功!可以继续使用记账功能');
|
|
2196
|
+
|
|
2197
|
+
// 【修复】不退出进程,让 Agent 可以在同一会话中继续执行记账操作
|
|
2198
|
+
// 这样 Agent 调用 get_skill 授权后,可以立即调用 save_expense 记账
|
|
2199
|
+
// process.exit(0); // 注释掉,避免授权成功后进程退出
|
|
2200
|
+
return; // 直接返回,停止轮询
|
|
2204
2201
|
}
|
|
2205
2202
|
} catch (err) {
|
|
2206
2203
|
if (err.message.includes('expired') || err.message.includes('invalid')) {
|
|
@@ -2215,8 +2212,9 @@ async function pollForAuthInBackground(deviceCode, userCode, interval) {
|
|
|
2215
2212
|
|
|
2216
2213
|
// 3分钟超时
|
|
2217
2214
|
authState.isWaiting = false;
|
|
2218
|
-
console.error('⏰
|
|
2219
|
-
|
|
2215
|
+
console.error('⏰ 授权超时(3分钟),请重新调用获取新验证码');
|
|
2216
|
+
// 【修复】不退出进程,让用户可以重新尝试
|
|
2217
|
+
// process.exit(0);
|
|
2220
2218
|
}
|
|
2221
2219
|
|
|
2222
2220
|
// 延迟函数
|
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
|