chaimi-keep-mcp 3.3.3-beta.6 → 3.3.3-beta.8

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
@@ -89,6 +89,16 @@ export MCP_PROMPT_URL="你的Prompt服务地址"
89
89
 
90
90
  ## Changelog
91
91
 
92
+ ### v3.3.3-beta.8 (2026-04-30)
93
+ - **变更** 配置存储路径迁移 - 从 `~/.mcporter/` 迁移到 `~/.chaimi-keep/`,避免与其他 MCP 工具冲突
94
+ - **优化** 安全权限 - 新增目录权限控制(0700),敏感文件权限控制(0600)
95
+
96
+ ### v3.3.3-beta.7 (2026-04-29)
97
+ - **优化** 回复模板 - 新增"请确认信息"提示,时间移到核心数据区,添加类型变量
98
+ - **优化** 空值处理 - 商家为空时不显示,timeNote 仅异常时显示
99
+ - **修复** save_income - 添加 date 兜底逻辑,不传时间时使用当前时间
100
+ - **修复** save_expense - 优化 timeNote 显示逻辑
101
+
92
102
  ### v3.3.3-beta.6 (2026-04-29)
93
103
  - **优化** 品牌统一 - 所有"柴米记账"改为"柴米AI记账"
94
104
  - **优化** 工具描述 - 重写三个记账工具描述,包含完整调用示例和格式要求
package/SKILL.md CHANGED
@@ -154,6 +154,11 @@ chaimi-keep-mcp ⭐ 本Skill
154
154
  - ✅ 返回 `_templateHint` 提示使用哪个模板
155
155
  - ✅ 返回 `_templateVariables` 提供模板变量数据
156
156
 
157
+ **空值处理原则**:
158
+ - 模板变量中,如果某个维度的值为空(null/undefined/空字符串),Agent 应跳过该行不显示
159
+ - 必填字段(金额、类型、商品、分类)必须显示
160
+ - 可选字段(商家、时间、洞察)为空时不显示
161
+
157
162
  ### 7.2 模板文件位置
158
163
 
159
164
  **主模板文件**: `references/response-templates.md`
@@ -191,6 +196,8 @@ references/
191
196
  【第1层:成功标识】✅ 「Agent名称」已帮您记账成功
192
197
  【第2层:分隔强调】═══════════════
193
198
  【第3层:核心数据】💰 金额:¥xx.xx
199
+ 📊 类型:支出/收入
200
+ ═══════════════
194
201
  【第4层:详细信息】
195
202
  📦 商品:xxx
196
203
  🏷️ 分类:xxx
@@ -211,6 +218,7 @@ references/
211
218
  ✅ 「{agentName}」已帮您记账成功
212
219
  ═══════════════
213
220
  💰 金额:¥{金额}
221
+ 📊 类型:{类型}
214
222
  ═══════════════
215
223
 
216
224
  📦 商品:{商品名}
@@ -234,6 +242,7 @@ references/
234
242
  | {agentName} | Agent显示名称 | 「你的小可爱」 |
235
243
  | {商品名} | 商品或商家名称 | 午餐、麦当劳 |
236
244
  | {金额} | 金额(带¥符号) | ¥35.00 |
245
+ | {类型} | 支出/收入类型 | 支出、收入 |
237
246
  | {分类} | 消费分类 | 餐饮 |
238
247
  | {商家} | 商家名称(可选) | 麦当劳 |
239
248
  | {日期时间} | 格式化时间 | 2026-04-24 12:30 |
@@ -20,8 +20,9 @@ const MCP_OAUTH_URL = process.env.MCP_OAUTH_URL || 'https://cloud1-2gfe5jhjef06b
20
20
 
21
21
  // 检查是否已有有效 Token
22
22
  async function checkExistingToken() {
23
+ // 使用新的配置路径 ~/.chaimi-keep/
23
24
  const tokenStorage = new FileTokenStorage(
24
- path.join(os.homedir(), '.mcporter', 'oauth-token.json')
25
+ path.join(os.homedir(), '.chaimi-keep', 'oauth-token.json')
25
26
  );
26
27
 
27
28
  try {
@@ -37,8 +38,9 @@ async function checkExistingToken() {
37
38
 
38
39
  // 获取新的授权码
39
40
  async function getAuthCode() {
41
+ // 使用新的配置路径 ~/.chaimi-keep/
40
42
  const tokenStorage = new FileTokenStorage(
41
- path.join(os.homedir(), '.mcporter', 'oauth-token.json')
43
+ path.join(os.homedir(), '.chaimi-keep', 'oauth-token.json')
42
44
  );
43
45
 
44
46
  const oauthManager = new OAuthManager({
package/oauth.js CHANGED
@@ -311,10 +311,14 @@ class OAuthManager {
311
311
  try {
312
312
  const fs = require('fs').promises;
313
313
  const path = require('path');
314
- const stateFile = path.join(require('os').homedir(), '.mcporter', 'auth-state.json');
314
+ const os = require('os');
315
+ // 使用新的配置路径 ~/.chaimi-keep/
316
+ const stateFile = path.join(os.homedir(), '.chaimi-keep', 'auth-state.json');
315
317
 
316
318
  const dir = path.dirname(stateFile);
317
319
  await fs.mkdir(dir, { recursive: true });
320
+ // 设置目录权限 0700
321
+ await fs.chmod(dir, 0o700).catch(() => {});
318
322
 
319
323
  await fs.writeFile(
320
324
  stateFile,
@@ -329,7 +333,9 @@ class OAuthManager {
329
333
  try {
330
334
  const fs = require('fs').promises;
331
335
  const path = require('path');
332
- const stateFile = path.join(require('os').homedir(), '.mcporter', 'auth-state.json');
336
+ const os = require('os');
337
+ // 使用新的配置路径 ~/.chaimi-keep/
338
+ const stateFile = path.join(os.homedir(), '.chaimi-keep', 'auth-state.json');
333
339
 
334
340
  const data = await fs.readFile(stateFile, 'utf8');
335
341
  return JSON.parse(data);
@@ -345,7 +351,9 @@ class OAuthManager {
345
351
  try {
346
352
  const fs = require('fs').promises;
347
353
  const path = require('path');
348
- const stateFile = path.join(require('os').homedir(), '.mcporter', 'auth-state.json');
354
+ const os = require('os');
355
+ // 使用新的配置路径 ~/.chaimi-keep/
356
+ const stateFile = path.join(os.homedir(), '.chaimi-keep', 'auth-state.json');
349
357
  await fs.unlink(stateFile);
350
358
  } catch (err) {
351
359
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaimi-keep-mcp",
3
- "version": "3.3.3-beta.6",
3
+ "version": "3.3.3-beta.8",
4
4
  "description": "柴米AI记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: "chaimi-keep-authentication"
3
3
  description: 柴米AI记账授权流程详解
4
- version: "1.0.0"
5
- updated: "2026-04-25"
4
+ version: "1.0.1"
5
+ updated: "2026-04-30"
6
6
  ---
7
7
 
8
8
  # 柴米AI记账授权流程
@@ -134,12 +134,12 @@ mcporter auth 柴米AI记账 --reset
134
134
 
135
135
  **macOS/Linux:**
136
136
  ```bash
137
- rm ~/.mcporter/oauth-token.json
137
+ rm ~/.chaimi-keep/oauth-token.json
138
138
  ```
139
139
 
140
140
  **Windows:**
141
141
  ```cmd
142
- del %USERPROFILE%\.mcporter\oauth-token.json
142
+ del %USERPROFILE%\.chaimi-keep\oauth-token.json
143
143
  ```
144
144
 
145
145
  然后完全退出并重新启动 Claude/Cursor
@@ -157,7 +157,7 @@ del %USERPROFILE%\.mcporter\oauth-token.json
157
157
 
158
158
  ```bash
159
159
  # 查看 Token 文件是否存在
160
- cat ~/.mcporter/oauth-token.json
160
+ cat ~/.chaimi-keep/oauth-token.json
161
161
 
162
162
  # 或尝试调用工具测试
163
163
  mcporter call 柴米AI记账.get_skill
@@ -165,7 +165,7 @@ mcporter call 柴米AI记账.get_skill
165
165
 
166
166
  **Token 文件位置:**
167
167
  ```
168
- ~/.mcporter/oauth-token.json
168
+ ~/.chaimi-keep/oauth-token.json
169
169
  ```
170
170
 
171
171
  ### 3.2 Token刷新
@@ -47,6 +47,7 @@ updated: "2026-04-25"
47
47
  |:-----|:------|:-----|
48
48
  | 成功标识 | ✅ | 操作成功 |
49
49
  | 金额 | 💰 | 核心数据 |
50
+ | 类型 | 📊 | 支出/收入 |
50
51
  | 商品 | 📦 | 物品 |
51
52
  | 分类 | 🏷️ | 标签分类 |
52
53
  | 商家 | 🏪 | 商店 |
@@ -54,6 +55,63 @@ updated: "2026-04-25"
54
55
  | 祝福 | 🎉 | 庆祝 |
55
56
  | 警示 | ⚠️ | 需要关注 |
56
57
 
58
+ ### 1.4 空值处理规范(重要)
59
+
60
+ **规则**:详细信息区(第4层)的维度,如果值为空,则不展示该行。
61
+
62
+ **处理逻辑**:
63
+ ```
64
+ 渲染前检查每个字段:
65
+ - 如果字段值存在且不为空 → 显示该行
66
+ - 如果字段值为空/null/undefined → 跳过该行
67
+ ```
68
+
69
+ **示例**:
70
+ ```markdown
71
+ // 完整数据
72
+ 📦 商品:午餐
73
+ 🏷️ 分类:餐饮
74
+ 🏪 商家:麦当劳
75
+ 🕐 时间:2026-04-24 12:30
76
+
77
+ // 商家为空时
78
+ 📦 商品:午餐
79
+ 🏷️ 分类:餐饮
80
+ 🕐 时间:2026-04-24 12:30
81
+ (🏪 商家:这一行不显示)
82
+
83
+ // 只有商品和金额时
84
+ 📦 商品:午餐
85
+ (其他维度为空,都不显示)
86
+ ```
87
+
88
+ **必填 vs 可选字段**:
89
+ | 字段 | 是否必填 | 为空时处理 |
90
+ |:-----|:---------|:-----------|
91
+ | 商品名 | ✅ 必填 | 必须显示 |
92
+ | 分类 | ✅ 必填 | 必须显示 |
93
+ | 类型 | ✅ 必填 | 必须显示 |
94
+ | 金额 | ✅ 必填 | 必须显示 |
95
+ | 商家 | ❌ 可选 | 为空不显示 |
96
+ | 时间 | ❌ 可选 | 为空不显示 |
97
+ | 洞察 | ❌ 可选 | 为空不显示 |
98
+
99
+ **规则2:避免重复显示**
100
+ - 如果商品名和分类相同 → 只显示分类行(避免重复)
101
+ - 如果商品名和分类不同 → 两行都显示
102
+
103
+ **示例**:
104
+ ```markdown
105
+ // 用户说"交通 12"(商品名=分类)
106
+ 🏷️ 分类:交通
107
+ (📦 商品:交通 不显示,避免重复)
108
+
109
+ // 用户说"午餐 35"(商品名=午餐,分类=餐饮)
110
+ 📦 商品:午餐
111
+ 🏷️ 分类:餐饮
112
+ (两行都显示)
113
+ ```
114
+
57
115
  ---
58
116
 
59
117
  ## 二、标准模板
@@ -65,18 +123,20 @@ updated: "2026-04-25"
65
123
  ```markdown
66
124
  ✅ 「{agentName}」已帮您记账成功
67
125
  ═══════════════
126
+ 🔴🔴 请确认以下信息是否正确
68
127
  💰 金额:¥{金额}
128
+ 📊 类型:{类型}
129
+ 🕐 时间:{日期时间}
69
130
  ═══════════════
70
131
 
71
132
  📦 商品:{商品名}
72
133
  🏷️ 分类:{分类}
73
134
  🏪 商家:{商家}
74
- 🕐 时间:{日期时间}
75
-
76
- 💡 消费洞察:{洞察内容}(可选)
135
+ 💡 消费洞察:{洞察内容}
136
+ ⏰ 时间说明:{timeNote}
77
137
 
78
138
  ───────────────
79
- 🎉 {正能量祝福语}!
139
+ 🎉 {正能量情绪词}!
80
140
  柴米AI记账
81
141
  chaimi-keep-mcp v{版本号}
82
142
  ───────────────
@@ -87,14 +147,15 @@ updated: "2026-04-25"
87
147
  ```markdown
88
148
  ✅ 「你的小可爱」已帮您记账成功
89
149
  ═══════════════
150
+ 🔴🔴 请确认以下信息是否正确
90
151
  💰 金额:¥35.00
152
+ 📊 类型:支出
153
+ 🕐 时间:2026-04-24 12:30
91
154
  ═══════════════
92
155
 
93
156
  📦 商品:午餐
94
157
  🏷️ 分类:餐饮
95
158
  🏪 商家:麦当劳
96
- 🕐 时间:2026-04-24 12:30
97
-
98
159
  💡 消费洞察:本月餐饮支出占比30%,建议控制
99
160
 
100
161
  ───────────────
package/server.js CHANGED
@@ -39,6 +39,9 @@ const MCP_VERSION = getVersion();
39
39
  // 导入 OAuth 模块
40
40
  const { OAuthManager, FileTokenStorage } = require('./oauth.js');
41
41
 
42
+ // 导入配置管理工具
43
+ const { configManager } = require('./utils/config.js');
44
+
42
45
  // 导入校验工具
43
46
  const { validateDate } = require('./utils/validators.js');
44
47
 
@@ -167,9 +170,9 @@ let oauthManager = null;
167
170
 
168
171
  // 初始化 OAuth 管理器
169
172
  async function initOAuthManager() {
170
- const tokenStorage = new FileTokenStorage(
171
- path.join(os.homedir(), '.mcporter', 'oauth-token.json')
172
- );
173
+ // 使用新的配置路径 ~/.chaimi-keep/
174
+ const tokenPath = configManager.getPath('oauth-token.json');
175
+ const tokenStorage = new FileTokenStorage(tokenPath);
173
176
 
174
177
  oauthManager = new OAuthManager({
175
178
  mcpOAuthUrl: MCP_OAUTH_URL,
@@ -1000,11 +1003,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1000
1003
  }
1001
1004
 
1002
1005
  // 【新增】处理 time_description(文本记账新时间格式)
1003
- let timeNote = '';
1006
+ let timeNote = null;
1004
1007
  if (processedArgs.time_description) {
1005
1008
  const parsedDate = parseTimeDescription(processedArgs.time_description, Date.now());
1006
1009
  processedArgs.date = parsedDate;
1007
- timeNote = getTimeNote(processedArgs.time_description);
1010
+ // 正常解析时不显示 timeNote,只有兜底或异常时才显示
1008
1011
  console.log(`[save_expense] 时间描述解析: ${processedArgs.time_description} → ${parsedDate} (${new Date(parsedDate).toLocaleString('zh-CN')})`);
1009
1012
  }
1010
1013
 
@@ -1071,15 +1074,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1071
1074
  result._templateHint = '请使用references/response-templates.md中的2.1标准成功模板渲染回复';
1072
1075
  result._templateVariables = {
1073
1076
  agentName: await getAgentName(),
1077
+ 类型: '支出',
1074
1078
  商品名: displayName,
1075
- 商家: displayStore || '',
1079
+ 商家: displayStore || null,
1076
1080
  金额: displayAmount,
1077
1081
  分类: result.data?.categoryName || displayCategory,
1078
- 日期: result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN'),
1082
+ 日期: result.data?.date ? new Date(result.data.date).toLocaleString('zh-CN') : new Date().toLocaleString('zh-CN'),
1079
1083
  正能量祝福语: friendlyEnding,
1080
1084
  insightsText,
1081
1085
  achievementsText,
1082
- timeNote // 【新增】时间说明
1086
+ timeNote: timeNote || null
1083
1087
  };
1084
1088
  }
1085
1089
  break;
@@ -1519,13 +1523,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1519
1523
  }
1520
1524
 
1521
1525
  // 【新增】处理 time_description(收入记账新时间格式)
1522
- let timeNote = '';
1526
+ let timeNote = null;
1523
1527
  if (processedArgs.time_description) {
1524
1528
  const parsedDate = parseTimeDescription(processedArgs.time_description, Date.now());
1525
1529
  processedArgs.date = parsedDate;
1526
- timeNote = getTimeNote(processedArgs.time_description);
1530
+ // 正常解析时不显示 timeNote,只有兜底或异常时才显示
1527
1531
  console.log(`[save_income] 时间描述解析: ${processedArgs.time_description} → ${parsedDate} (${new Date(parsedDate).toLocaleString('zh-CN')})`);
1528
1532
  }
1533
+ // 【新增】兜底:如果既没传 time_description 也没传 date,使用当前时间
1534
+ else if (!processedArgs.date) {
1535
+ processedArgs.date = Date.now();
1536
+ timeNote = '⏰ 时间说明:未指定时间,使用当前时间';
1537
+ console.log(`[save_income] 未传时间参数,使用当前时间: ${processedArgs.date}`);
1538
+ }
1529
1539
 
1530
1540
  // P1: 日期合理性检查
1531
1541
  if (processedArgs.date) {
@@ -1567,13 +1577,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1567
1577
  result._templateHint = '请使用references/response-templates.md中的2.1标准成功模板渲染回复';
1568
1578
  result._templateVariables = {
1569
1579
  agentName: await getAgentName(),
1580
+ 类型: '收入',
1570
1581
  商品名: displayName,
1571
- 商家: displayStore,
1582
+ 商家: displayStore || null,
1572
1583
  金额: displayAmount,
1573
1584
  分类: result.data?.categoryName || displayCategory,
1574
- 日期: result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN'),
1585
+ 日期: result.data?.date ? new Date(result.data.date).toLocaleString('zh-CN') : new Date().toLocaleString('zh-CN'),
1575
1586
  正能量祝福语: '入账顺利!💰',
1576
- timeNote // 【新增】时间说明
1587
+ timeNote: timeNote || null
1577
1588
  };
1578
1589
  }
1579
1590
  break;
@@ -0,0 +1,229 @@
1
+ /**
2
+ * 柴米记账 MCP 配置管理工具
3
+ * 统一管理配置文件存储路径,支持从旧路径迁移
4
+ *
5
+ * 存储位置:~/.chaimi-keep/
6
+ * 旧位置:~/.mcporter/(兼容迁移)
7
+ */
8
+
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const fs = require('fs').promises;
12
+
13
+ // 配置目录名称
14
+ const CONFIG_DIR_NAME = '.chaimi-keep';
15
+ const LEGACY_DIR_NAME = '.mcporter';
16
+
17
+ class ConfigManager {
18
+ constructor() {
19
+ this.configDir = path.join(os.homedir(), CONFIG_DIR_NAME);
20
+ this.legacyDir = path.join(os.homedir(), LEGACY_DIR_NAME);
21
+ this.migrationChecked = false;
22
+ }
23
+
24
+ /**
25
+ * 获取配置文件路径
26
+ * @param {string} filename - 文件名
27
+ * @returns {string} 完整路径
28
+ */
29
+ getPath(filename) {
30
+ return path.join(this.configDir, filename);
31
+ }
32
+
33
+ /**
34
+ * 获取旧配置文件路径
35
+ * @param {string} filename - 文件名
36
+ * @returns {string} 完整路径
37
+ */
38
+ getLegacyPath(filename) {
39
+ return path.join(this.legacyDir, filename);
40
+ }
41
+
42
+ /**
43
+ * 确保配置目录存在,并设置正确权限
44
+ */
45
+ async ensureDir() {
46
+ try {
47
+ await fs.mkdir(this.configDir, { recursive: true });
48
+ // 设置目录权限 0700(仅用户可读写执行)
49
+ await fs.chmod(this.configDir, 0o700);
50
+ } catch (err) {
51
+ // 如果权限设置失败,继续执行
52
+ if (err.code !== 'EEXIST') {
53
+ console.error('创建配置目录失败:', err.message);
54
+ }
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 检查文件是否存在于新位置
60
+ * @param {string} filename - 文件名
61
+ * @returns {boolean}
62
+ */
63
+ async exists(filename) {
64
+ try {
65
+ await fs.access(this.getPath(filename));
66
+ return true;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 检查文件是否存在于旧位置
74
+ * @param {string} filename - 文件名
75
+ * @returns {boolean}
76
+ */
77
+ async existsInLegacy(filename) {
78
+ try {
79
+ await fs.access(this.getLegacyPath(filename));
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 从旧位置迁移文件到新位置
88
+ * @param {string} filename - 文件名
89
+ * @returns {boolean} 是否成功迁移
90
+ */
91
+ async migrateFile(filename) {
92
+ const legacyPath = this.getLegacyPath(filename);
93
+ const newPath = this.getPath(filename);
94
+
95
+ try {
96
+ // 检查旧文件是否存在
97
+ await fs.access(legacyPath);
98
+
99
+ // 确保新目录存在
100
+ await this.ensureDir();
101
+
102
+ // 读取旧文件
103
+ const data = await fs.readFile(legacyPath, 'utf8');
104
+
105
+ // 写入新位置,设置权限 0600
106
+ await fs.writeFile(newPath, data, { mode: 0o600 });
107
+
108
+ // 删除旧文件
109
+ await fs.unlink(legacyPath);
110
+
111
+ console.error(`✅ 已迁移 ${filename} 到新位置`);
112
+ return true;
113
+ } catch (err) {
114
+ if (err.code !== 'ENOENT') {
115
+ console.error(`迁移 ${filename} 失败:`, err.message);
116
+ }
117
+ return false;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * 读取配置文件(自动处理迁移)
123
+ * @param {string} filename - 文件名
124
+ * @returns {any} 文件内容(JSON 解析后)或 null
125
+ */
126
+ async read(filename) {
127
+ // 确保目录存在
128
+ await this.ensureDir();
129
+
130
+ // 优先从新位置读取
131
+ const newPath = this.getPath(filename);
132
+ try {
133
+ const data = await fs.readFile(newPath, 'utf8');
134
+ return JSON.parse(data);
135
+ } catch (err) {
136
+ if (err.code !== 'ENOENT') {
137
+ console.error(`读取 ${filename} 失败:`, err.message);
138
+ }
139
+ }
140
+
141
+ // 新位置不存在,尝试从旧位置迁移
142
+ const migrated = await this.migrateFile(filename);
143
+ if (migrated) {
144
+ try {
145
+ const data = await fs.readFile(newPath, 'utf8');
146
+ return JSON.parse(data);
147
+ } catch (err) {
148
+ console.error(`读取迁移后的 ${filename} 失败:`, err.message);
149
+ }
150
+ }
151
+
152
+ return null;
153
+ }
154
+
155
+ /**
156
+ * 写入配置文件
157
+ * @param {string} filename - 文件名
158
+ * @param {any} data - 数据(会被 JSON 序列化)
159
+ * @param {boolean} isSensitive - 是否为敏感文件(设置 0600 权限)
160
+ */
161
+ async write(filename, data, isSensitive = true) {
162
+ await this.ensureDir();
163
+ const filePath = this.getPath(filename);
164
+
165
+ const options = isSensitive ? { mode: 0o600 } : {};
166
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2), options);
167
+ }
168
+
169
+ /**
170
+ * 删除配置文件
171
+ * @param {string} filename - 文件名
172
+ */
173
+ async delete(filename) {
174
+ const filePath = this.getPath(filename);
175
+ try {
176
+ await fs.unlink(filePath);
177
+ } catch (err) {
178
+ if (err.code !== 'ENOENT') {
179
+ throw err;
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * 删除旧位置的文件(清理用)
186
+ * @param {string} filename - 文件名
187
+ */
188
+ async deleteLegacy(filename) {
189
+ const legacyPath = this.getLegacyPath(filename);
190
+ try {
191
+ await fs.unlink(legacyPath);
192
+ } catch (err) {
193
+ // 忽略不存在的错误
194
+ }
195
+ }
196
+
197
+ /**
198
+ * 获取所有相关文件的路径信息
199
+ * @returns {Object} 路径映射
200
+ */
201
+ getPaths() {
202
+ const files = [
203
+ 'oauth-token.json',
204
+ 'auth-state.json',
205
+ 'agent-name.json',
206
+ 'device-code.json',
207
+ 'machine-id'
208
+ ];
209
+
210
+ const result = {};
211
+ for (const file of files) {
212
+ result[file] = {
213
+ new: this.getPath(file),
214
+ legacy: this.getLegacyPath(file)
215
+ };
216
+ }
217
+ return result;
218
+ }
219
+ }
220
+
221
+ // 导出单例实例
222
+ const configManager = new ConfigManager();
223
+
224
+ module.exports = {
225
+ ConfigManager,
226
+ configManager,
227
+ CONFIG_DIR_NAME,
228
+ LEGACY_DIR_NAME
229
+ };
@@ -2,7 +2,7 @@
2
2
  * 机器ID和设备指纹管理模块
3
3
  *
4
4
  * 功能:
5
- * 1. 生成并管理机器唯一ID(存储在 ~/.mcporter/machine-id)
5
+ * 1. 生成并管理机器唯一ID(存储在 ~/.chaimi-keep/machine-id)
6
6
  * 2. 生成设备指纹(hash(machineId + agentType))
7
7
  *
8
8
  * 特点:
@@ -16,9 +16,16 @@ const path = require('path');
16
16
  const os = require('os');
17
17
  const crypto = require('crypto');
18
18
 
19
- // 机器ID文件路径
19
+ // 机器ID文件路径(新位置 ~/.chaimi-keep/)
20
20
  const MACHINE_ID_FILE = path.join(
21
- process.env.HOME || process.env.USERPROFILE,
21
+ os.homedir(),
22
+ '.chaimi-keep',
23
+ 'machine-id'
24
+ );
25
+
26
+ // 旧机器ID文件路径(用于迁移)
27
+ const LEGACY_MACHINE_ID_FILE = path.join(
28
+ os.homedir(),
22
29
  '.mcporter',
23
30
  'machine-id'
24
31
  );