@tkpdx01/ccc 1.2.7 → 1.3.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.
@@ -1,19 +1,19 @@
1
- import chalk from 'chalk';
2
- import { profileExists, setDefaultProfile } from '../profiles.js';
3
-
4
- export function useCommand(program) {
5
- program
6
- .command('use <profile>')
7
- .description('设置默认 profile')
8
- .action((profile) => {
9
- if (!profileExists(profile)) {
10
- console.log(chalk.red(`Profile "${profile}" 不存在`));
11
- console.log(chalk.yellow(`使用 "ccc list" 查看可用的 profiles`));
12
- process.exit(1);
13
- }
14
-
15
- setDefaultProfile(profile);
16
- console.log(chalk.green(`✓ 默认 profile 已设置为 "${profile}"`));
17
- });
18
- }
19
-
1
+ import chalk from 'chalk';
2
+ import { profileExists, setDefaultProfile } from '../profiles.js';
3
+
4
+ export function useCommand(program) {
5
+ program
6
+ .command('use <profile>')
7
+ .description('设置默认 profile')
8
+ .action((profile) => {
9
+ if (!profileExists(profile)) {
10
+ console.log(chalk.red(`Profile "${profile}" 不存在`));
11
+ console.log(chalk.yellow(`使用 "ccc list" 查看可用的 profiles`));
12
+ process.exit(1);
13
+ }
14
+
15
+ setDefaultProfile(profile);
16
+ console.log(chalk.green(`✓ 默认 profile 已设置为 "${profile}"`));
17
+ });
18
+ }
19
+
@@ -0,0 +1,477 @@
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
3
+ import { getProfiles } from '../profiles.js';
4
+ import {
5
+ saveSyncPassword,
6
+ loadSyncPassword,
7
+ hasSyncPassword
8
+ } from '../crypto.js';
9
+ import {
10
+ getWebDAVConfig,
11
+ saveWebDAVConfig,
12
+ createWebDAVClient,
13
+ uploadProfiles,
14
+ downloadProfiles,
15
+ getRemoteInfo,
16
+ compareProfiles,
17
+ mergePull,
18
+ mergePush
19
+ } from '../webdav.js';
20
+
21
+ // 获取或请求同步密码
22
+ async function getSyncPassword(forcePrompt = false) {
23
+ if (!forcePrompt) {
24
+ const cached = loadSyncPassword();
25
+ if (cached) {
26
+ return cached;
27
+ }
28
+ }
29
+
30
+ const { password } = await inquirer.prompt([
31
+ {
32
+ type: 'password',
33
+ name: 'password',
34
+ message: '请输入同步密码:',
35
+ mask: '*',
36
+ validate: (input) => input.length >= 6 || '密码至少需要 6 个字符'
37
+ }
38
+ ]);
39
+
40
+ return password;
41
+ }
42
+
43
+ // setup 子命令
44
+ async function setupAction() {
45
+ console.log(chalk.cyan('\n配置 WebDAV 同步\n'));
46
+
47
+ const existingConfig = getWebDAVConfig();
48
+
49
+ const answers = await inquirer.prompt([
50
+ {
51
+ type: 'input',
52
+ name: 'url',
53
+ message: 'WebDAV 服务器地址:',
54
+ default: existingConfig?.url || '',
55
+ validate: (input) => {
56
+ if (!input) return '请输入 WebDAV 地址';
57
+ if (!input.startsWith('http://') && !input.startsWith('https://')) {
58
+ return '地址必须以 http:// 或 https:// 开头';
59
+ }
60
+ return true;
61
+ }
62
+ },
63
+ {
64
+ type: 'input',
65
+ name: 'username',
66
+ message: 'WebDAV 用户名:',
67
+ default: existingConfig?.username || ''
68
+ },
69
+ {
70
+ type: 'password',
71
+ name: 'password',
72
+ message: 'WebDAV 密码:',
73
+ mask: '*'
74
+ },
75
+ {
76
+ type: 'input',
77
+ name: 'path',
78
+ message: '远程存储路径:',
79
+ default: existingConfig?.path || '/ccc-sync',
80
+ validate: (input) => input.startsWith('/') || '路径必须以 / 开头'
81
+ }
82
+ ]);
83
+
84
+ // 测试连接
85
+ console.log(chalk.gray('\n测试连接...'));
86
+ try {
87
+ const client = createWebDAVClient(answers);
88
+ await client.getDirectoryContents('/');
89
+ console.log(chalk.green('✓ 连接成功'));
90
+ } catch (err) {
91
+ console.log(chalk.red(`✗ 连接失败: ${err.message}`));
92
+ const { continueAnyway } = await inquirer.prompt([
93
+ {
94
+ type: 'confirm',
95
+ name: 'continueAnyway',
96
+ message: '是否仍要保存配置?',
97
+ default: false
98
+ }
99
+ ]);
100
+ if (!continueAnyway) {
101
+ process.exit(1);
102
+ }
103
+ }
104
+
105
+ saveWebDAVConfig(answers);
106
+ console.log(chalk.green('✓ WebDAV 配置已保存'));
107
+
108
+ // 设置同步密码
109
+ const hasPassword = hasSyncPassword();
110
+ const { setupPassword } = await inquirer.prompt([
111
+ {
112
+ type: 'confirm',
113
+ name: 'setupPassword',
114
+ message: hasPassword ? '是否重新设置同步密码?' : '是否设置同步密码?',
115
+ default: !hasPassword
116
+ }
117
+ ]);
118
+
119
+ if (setupPassword) {
120
+ const { newPassword, confirmPassword } = await inquirer.prompt([
121
+ {
122
+ type: 'password',
123
+ name: 'newPassword',
124
+ message: '设置同步密码 (用于加密云端数据):',
125
+ mask: '*',
126
+ validate: (input) => input.length >= 6 || '密码至少需要 6 个字符'
127
+ },
128
+ {
129
+ type: 'password',
130
+ name: 'confirmPassword',
131
+ message: '确认同步密码:',
132
+ mask: '*'
133
+ }
134
+ ]);
135
+
136
+ if (newPassword !== confirmPassword) {
137
+ console.log(chalk.red('✗ 两次输入的密码不一致'));
138
+ process.exit(1);
139
+ }
140
+
141
+ saveSyncPassword(newPassword);
142
+ console.log(chalk.green('✓ 同步密码已保存(本机免密)'));
143
+ }
144
+
145
+ console.log(chalk.cyan('\n配置完成!使用以下命令同步:'));
146
+ console.log(chalk.gray(' ccc webdav push - 推送到云端'));
147
+ console.log(chalk.gray(' ccc webdav pull - 从云端拉取'));
148
+ console.log(chalk.gray(' ccc webdav status - 查看同步状态'));
149
+ }
150
+
151
+ // push 子命令
152
+ async function pushAction(options) {
153
+ const config = getWebDAVConfig();
154
+ if (!config) {
155
+ console.log(chalk.red('请先运行 "ccc webdav setup" 配置 WebDAV'));
156
+ process.exit(1);
157
+ }
158
+
159
+ const localProfiles = getProfiles();
160
+ if (localProfiles.length === 0) {
161
+ console.log(chalk.yellow('没有可同步的 profiles'));
162
+ process.exit(0);
163
+ }
164
+
165
+ const syncPassword = await getSyncPassword();
166
+ const client = createWebDAVClient(config);
167
+
168
+ console.log(chalk.gray('\n检查远程状态...'));
169
+
170
+ let remoteData = null;
171
+ try {
172
+ remoteData = await downloadProfiles(client, config, syncPassword);
173
+ } catch (err) {
174
+ if (err.message.includes('密码错误')) {
175
+ console.log(chalk.red(`✗ ${err.message}`));
176
+ process.exit(1);
177
+ }
178
+ // 远程可能不存在,继续
179
+ }
180
+
181
+ const diff = compareProfiles(localProfiles, remoteData);
182
+
183
+ // 显示差异
184
+ console.log(chalk.cyan('\n同步预览:'));
185
+
186
+ if (diff.localOnly.length > 0) {
187
+ console.log(chalk.green(` ↑ 新增: ${diff.localOnly.join(', ')}`));
188
+ }
189
+
190
+ if (diff.remoteOnly.length > 0) {
191
+ console.log(chalk.gray(` ○ 远程独有 (保留): ${diff.remoteOnly.join(', ')}`));
192
+ }
193
+
194
+ const unchanged = diff.both.filter(n => !diff.conflicts.find(c => c.name === n));
195
+ if (unchanged.length > 0) {
196
+ console.log(chalk.gray(` = 无变化: ${unchanged.join(', ')}`));
197
+ }
198
+
199
+ // 处理冲突
200
+ const resolutions = {};
201
+
202
+ if (diff.conflicts.length > 0 && !options.force) {
203
+ console.log(chalk.yellow(`\n ⚠️ 发现 ${diff.conflicts.length} 个冲突:`));
204
+
205
+ for (const conflict of diff.conflicts) {
206
+ const remoteTime = new Date(conflict.remoteUpdatedAt).toLocaleString();
207
+ console.log(chalk.yellow(`\n "${conflict.name}" - 云端修改于 ${remoteTime}`));
208
+
209
+ const { resolution } = await inquirer.prompt([
210
+ {
211
+ type: 'list',
212
+ name: 'resolution',
213
+ message: `如何处理 "${conflict.name}"?`,
214
+ choices: [
215
+ { name: '保留两者 (本地上传为 *_local)', value: 'keep_both' },
216
+ { name: '使用本地版本覆盖云端', value: 'use_local' },
217
+ { name: '保留云端版本', value: 'keep_remote' }
218
+ ],
219
+ default: 'keep_both'
220
+ }
221
+ ]);
222
+
223
+ resolutions[conflict.name] = resolution;
224
+ }
225
+ } else if (diff.conflicts.length > 0 && options.force) {
226
+ console.log(chalk.yellow(` ⚠️ ${diff.conflicts.length} 个冲突将使用本地版本覆盖`));
227
+ for (const conflict of diff.conflicts) {
228
+ resolutions[conflict.name] = 'use_local';
229
+ }
230
+ }
231
+
232
+ // 确认
233
+ if (!options.force) {
234
+ const { confirm } = await inquirer.prompt([
235
+ {
236
+ type: 'confirm',
237
+ name: 'confirm',
238
+ message: '确认推送?',
239
+ default: true
240
+ }
241
+ ]);
242
+
243
+ if (!confirm) {
244
+ console.log(chalk.yellow('已取消'));
245
+ process.exit(0);
246
+ }
247
+ }
248
+
249
+ // 执行推送
250
+ console.log(chalk.gray('\n正在推送...'));
251
+ try {
252
+ const result = await mergePush(client, config, syncPassword, diff.conflicts, resolutions);
253
+ console.log(chalk.green(`\n✓ 已推送 ${result.count} 个 profiles 到云端`));
254
+ } catch (err) {
255
+ console.log(chalk.red(`\n✗ 推送失败: ${err.message}`));
256
+ process.exit(1);
257
+ }
258
+ }
259
+
260
+ // pull 子命令
261
+ async function pullAction(options) {
262
+ const config = getWebDAVConfig();
263
+ if (!config) {
264
+ console.log(chalk.red('请先运行 "ccc webdav setup" 配置 WebDAV'));
265
+ process.exit(1);
266
+ }
267
+
268
+ const syncPassword = await getSyncPassword();
269
+ const client = createWebDAVClient(config);
270
+
271
+ console.log(chalk.gray('\n正在获取云端数据...'));
272
+
273
+ let remoteData = null;
274
+ try {
275
+ remoteData = await downloadProfiles(client, config, syncPassword);
276
+ } catch (err) {
277
+ console.log(chalk.red(`✗ ${err.message}`));
278
+ process.exit(1);
279
+ }
280
+
281
+ if (!remoteData) {
282
+ console.log(chalk.yellow('云端没有同步数据'));
283
+ console.log(chalk.gray('使用 "ccc webdav push" 推送本地配置'));
284
+ process.exit(0);
285
+ }
286
+
287
+ const localProfiles = getProfiles();
288
+ const diff = compareProfiles(localProfiles, remoteData);
289
+
290
+ // 显示差异
291
+ console.log(chalk.cyan('\n同步预览:'));
292
+
293
+ if (diff.remoteOnly.length > 0) {
294
+ console.log(chalk.green(` ↓ 新增: ${diff.remoteOnly.join(', ')}`));
295
+ }
296
+
297
+ if (diff.localOnly.length > 0) {
298
+ console.log(chalk.gray(` ○ 本地独有 (保留): ${diff.localOnly.join(', ')}`));
299
+ }
300
+
301
+ const unchanged = diff.both.filter(n => !diff.conflicts.find(c => c.name === n));
302
+ if (unchanged.length > 0) {
303
+ console.log(chalk.gray(` = 无变化: ${unchanged.join(', ')}`));
304
+ }
305
+
306
+ // 处理冲突
307
+ const resolutions = {};
308
+
309
+ if (diff.conflicts.length > 0 && !options.force) {
310
+ console.log(chalk.yellow(`\n ⚠️ 发现 ${diff.conflicts.length} 个冲突:`));
311
+
312
+ for (const conflict of diff.conflicts) {
313
+ const remoteTime = new Date(conflict.remoteUpdatedAt).toLocaleString();
314
+ console.log(chalk.yellow(`\n "${conflict.name}" - 云端修改于 ${remoteTime}`));
315
+
316
+ const { resolution } = await inquirer.prompt([
317
+ {
318
+ type: 'list',
319
+ name: 'resolution',
320
+ message: `如何处理 "${conflict.name}"?`,
321
+ choices: [
322
+ { name: '保留两者 (云端保存为 *_cloud)', value: 'keep_both' },
323
+ { name: '使用云端版本覆盖本地', value: 'use_remote' },
324
+ { name: '保留本地版本', value: 'keep_local' }
325
+ ],
326
+ default: 'keep_both'
327
+ }
328
+ ]);
329
+
330
+ resolutions[conflict.name] = resolution;
331
+ }
332
+ } else if (diff.conflicts.length > 0 && options.force) {
333
+ console.log(chalk.yellow(` ⚠️ ${diff.conflicts.length} 个冲突将使用云端版本覆盖`));
334
+ for (const conflict of diff.conflicts) {
335
+ resolutions[conflict.name] = 'use_remote';
336
+ }
337
+ }
338
+
339
+ // 如果没有任何变化
340
+ if (diff.remoteOnly.length === 0 && diff.conflicts.length === 0) {
341
+ console.log(chalk.green('\n✓ 本地已是最新'));
342
+ process.exit(0);
343
+ }
344
+
345
+ // 确认
346
+ if (!options.force) {
347
+ const { confirm } = await inquirer.prompt([
348
+ {
349
+ type: 'confirm',
350
+ name: 'confirm',
351
+ message: '确认拉取?',
352
+ default: true
353
+ }
354
+ ]);
355
+
356
+ if (!confirm) {
357
+ console.log(chalk.yellow('已取消'));
358
+ process.exit(0);
359
+ }
360
+ }
361
+
362
+ // 执行拉取
363
+ console.log(chalk.gray('\n正在拉取...'));
364
+ const result = mergePull(remoteData, diff.conflicts, resolutions);
365
+
366
+ if (result.imported.length > 0) {
367
+ console.log(chalk.green(` ✓ 导入: ${result.imported.join(', ')}`));
368
+ }
369
+ if (result.renamed.length > 0) {
370
+ for (const r of result.renamed) {
371
+ console.log(chalk.cyan(` ✓ ${r.original} → ${r.renamed}`));
372
+ }
373
+ }
374
+ if (result.skipped.length > 0) {
375
+ console.log(chalk.gray(` ○ 跳过: ${result.skipped.join(', ')}`));
376
+ }
377
+
378
+ const total = result.imported.length + result.renamed.length;
379
+ console.log(chalk.green(`\n✓ 已拉取 ${total} 个 profiles`));
380
+ }
381
+
382
+ // status 子命令
383
+ async function statusAction() {
384
+ const config = getWebDAVConfig();
385
+ if (!config) {
386
+ console.log(chalk.red('请先运行 "ccc webdav setup" 配置 WebDAV'));
387
+ process.exit(1);
388
+ }
389
+
390
+ console.log(chalk.cyan('\nWebDAV 同步状态\n'));
391
+ console.log(chalk.gray(`服务器: ${config.url}`));
392
+ console.log(chalk.gray(`路径: ${config.path}`));
393
+ console.log(chalk.gray(`本机免密: ${hasSyncPassword() ? '是' : '否'}`));
394
+
395
+ const syncPassword = await getSyncPassword();
396
+ const client = createWebDAVClient(config);
397
+
398
+ console.log(chalk.gray('\n检查远程状态...'));
399
+
400
+ const remoteInfo = await getRemoteInfo(client, config, syncPassword);
401
+ const localProfiles = getProfiles();
402
+
403
+ console.log(chalk.cyan('\n本地:'));
404
+ console.log(` Profiles: ${localProfiles.length}`);
405
+ if (localProfiles.length > 0) {
406
+ console.log(chalk.gray(` ${localProfiles.join(', ')}`));
407
+ }
408
+
409
+ console.log(chalk.cyan('\n云端:'));
410
+ if (remoteInfo.exists) {
411
+ console.log(` Profiles: ${remoteInfo.profileCount}`);
412
+ console.log(` 最后更新: ${new Date(remoteInfo.updatedAt).toLocaleString()}`);
413
+ if (remoteInfo.profileNames.length > 0) {
414
+ console.log(chalk.gray(` ${remoteInfo.profileNames.join(', ')}`));
415
+ }
416
+ } else if (remoteInfo.error) {
417
+ console.log(chalk.red(` 错误: ${remoteInfo.error}`));
418
+ } else {
419
+ console.log(chalk.gray(' (无数据)'));
420
+ }
421
+
422
+ // 比较差异
423
+ if (remoteInfo.exists) {
424
+ let remoteData = null;
425
+ try {
426
+ remoteData = await downloadProfiles(client, config, syncPassword);
427
+ } catch {
428
+ // ignore
429
+ }
430
+
431
+ if (remoteData) {
432
+ const diff = compareProfiles(localProfiles, remoteData);
433
+
434
+ console.log(chalk.cyan('\n差异:'));
435
+ if (diff.localOnly.length > 0) {
436
+ console.log(chalk.green(` 本地新增: ${diff.localOnly.join(', ')}`));
437
+ }
438
+ if (diff.remoteOnly.length > 0) {
439
+ console.log(chalk.blue(` 云端新增: ${diff.remoteOnly.join(', ')}`));
440
+ }
441
+ if (diff.conflicts.length > 0) {
442
+ console.log(chalk.yellow(` 有冲突: ${diff.conflicts.map(c => c.name).join(', ')}`));
443
+ }
444
+ if (diff.localOnly.length === 0 && diff.remoteOnly.length === 0 && diff.conflicts.length === 0) {
445
+ console.log(chalk.green(' ✓ 已同步'));
446
+ }
447
+ }
448
+ }
449
+ }
450
+
451
+ export function webdavCommand(program) {
452
+ const webdav = program
453
+ .command('webdav')
454
+ .description('WebDAV 云同步');
455
+
456
+ webdav
457
+ .command('setup')
458
+ .description('配置 WebDAV 连接和同步密码')
459
+ .action(setupAction);
460
+
461
+ webdav
462
+ .command('push')
463
+ .description('推送本地 profiles 到云端')
464
+ .option('-f, --force', '强制覆盖,跳过冲突确认')
465
+ .action(pushAction);
466
+
467
+ webdav
468
+ .command('pull')
469
+ .description('从云端拉取 profiles 到本地')
470
+ .option('-f, --force', '强制覆盖,跳过冲突确认')
471
+ .action(pullAction);
472
+
473
+ webdav
474
+ .command('status')
475
+ .description('查看同步状态')
476
+ .action(statusAction);
477
+ }
package/src/config.js CHANGED
@@ -1,9 +1,9 @@
1
- import path from 'path';
2
- import os from 'os';
3
-
4
- // 配置文件存储目录
5
- export const CONFIG_DIR = path.join(os.homedir(), '.ccc');
6
- export const PROFILES_DIR = path.join(CONFIG_DIR, 'profiles');
7
- export const DEFAULT_FILE = path.join(CONFIG_DIR, 'default');
8
- export const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
9
-
1
+ import path from 'path';
2
+ import os from 'os';
3
+
4
+ // 配置文件存储目录
5
+ export const CONFIG_DIR = path.join(os.homedir(), '.ccc');
6
+ export const PROFILES_DIR = path.join(CONFIG_DIR, 'profiles');
7
+ export const DEFAULT_FILE = path.join(CONFIG_DIR, 'default');
8
+ export const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
9
+
package/src/crypto.js ADDED
@@ -0,0 +1,147 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { CONFIG_DIR } from './config.js';
6
+
7
+ const ALGORITHM = 'aes-256-gcm';
8
+ const KEY_LENGTH = 32;
9
+ const IV_LENGTH = 16;
10
+ const SALT_LENGTH = 32;
11
+ const AUTH_TAG_LENGTH = 16;
12
+ const PBKDF2_ITERATIONS = 100000;
13
+ const FILE_MAGIC = 'CCC_V1';
14
+ const SYNC_KEY_FILE = path.join(CONFIG_DIR, '.sync_key');
15
+
16
+ // 获取机器指纹(用于本地密码缓存加密)
17
+ function getMachineFingerprint() {
18
+ const hostname = os.hostname();
19
+ const username = os.userInfo().username;
20
+ const platform = os.platform();
21
+ const arch = os.arch();
22
+ // 组合多个机器特征生成指纹
23
+ const fingerprint = `${hostname}:${username}:${platform}:${arch}:ccc-sync`;
24
+ return crypto.createHash('sha256').update(fingerprint).digest();
25
+ }
26
+
27
+ // 从密码派生加密密钥
28
+ function deriveKey(password, salt) {
29
+ return crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha256');
30
+ }
31
+
32
+ // 加密数据
33
+ export function encrypt(plaintext, password) {
34
+ const salt = crypto.randomBytes(SALT_LENGTH);
35
+ const iv = crypto.randomBytes(IV_LENGTH);
36
+ const key = deriveKey(password, salt);
37
+
38
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
39
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
40
+ const authTag = cipher.getAuthTag();
41
+
42
+ // 格式: MAGIC + salt + iv + authTag + encrypted
43
+ const magic = Buffer.from(FILE_MAGIC, 'utf8');
44
+ return Buffer.concat([magic, salt, iv, authTag, encrypted]);
45
+ }
46
+
47
+ // 解密数据
48
+ export function decrypt(encryptedBuffer, password) {
49
+ const magic = Buffer.from(FILE_MAGIC, 'utf8');
50
+ const magicLength = magic.length;
51
+
52
+ // 验证 magic header
53
+ if (encryptedBuffer.length < magicLength + SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH) {
54
+ throw new Error('无效的加密文件格式');
55
+ }
56
+
57
+ const fileMagic = encryptedBuffer.subarray(0, magicLength);
58
+ if (!fileMagic.equals(magic)) {
59
+ throw new Error('无效的加密文件格式');
60
+ }
61
+
62
+ let offset = magicLength;
63
+ const salt = encryptedBuffer.subarray(offset, offset + SALT_LENGTH);
64
+ offset += SALT_LENGTH;
65
+ const iv = encryptedBuffer.subarray(offset, offset + IV_LENGTH);
66
+ offset += IV_LENGTH;
67
+ const authTag = encryptedBuffer.subarray(offset, offset + AUTH_TAG_LENGTH);
68
+ offset += AUTH_TAG_LENGTH;
69
+ const encrypted = encryptedBuffer.subarray(offset);
70
+
71
+ const key = deriveKey(password, salt);
72
+
73
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
74
+ decipher.setAuthTag(authTag);
75
+
76
+ try {
77
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
78
+ return decrypted.toString('utf8');
79
+ } catch (err) {
80
+ throw new Error('解密失败:密码错误或数据损坏');
81
+ }
82
+ }
83
+
84
+ // 使用机器指纹加密本地密码缓存
85
+ export function encryptLocalPassword(password) {
86
+ const key = getMachineFingerprint();
87
+ const iv = crypto.randomBytes(IV_LENGTH);
88
+
89
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
90
+ const encrypted = Buffer.concat([cipher.update(password, 'utf8'), cipher.final()]);
91
+ const authTag = cipher.getAuthTag();
92
+
93
+ return Buffer.concat([iv, authTag, encrypted]);
94
+ }
95
+
96
+ // 使用机器指纹解密本地密码缓存
97
+ export function decryptLocalPassword(encryptedBuffer) {
98
+ if (encryptedBuffer.length < IV_LENGTH + AUTH_TAG_LENGTH) {
99
+ throw new Error('无效的本地密码缓存');
100
+ }
101
+
102
+ const key = getMachineFingerprint();
103
+ const iv = encryptedBuffer.subarray(0, IV_LENGTH);
104
+ const authTag = encryptedBuffer.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
105
+ const encrypted = encryptedBuffer.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
106
+
107
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
108
+ decipher.setAuthTag(authTag);
109
+
110
+ try {
111
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
112
+ return decrypted.toString('utf8');
113
+ } catch (err) {
114
+ throw new Error('本地密码缓存解密失败');
115
+ }
116
+ }
117
+
118
+ // 保存同步密码到本地(加密存储)
119
+ export function saveSyncPassword(password) {
120
+ const encrypted = encryptLocalPassword(password);
121
+ fs.writeFileSync(SYNC_KEY_FILE, encrypted);
122
+ }
123
+
124
+ // 读取本地缓存的同步密码
125
+ export function loadSyncPassword() {
126
+ if (!fs.existsSync(SYNC_KEY_FILE)) {
127
+ return null;
128
+ }
129
+ try {
130
+ const encrypted = fs.readFileSync(SYNC_KEY_FILE);
131
+ return decryptLocalPassword(encrypted);
132
+ } catch (err) {
133
+ return null;
134
+ }
135
+ }
136
+
137
+ // 检查是否有本地缓存的密码
138
+ export function hasSyncPassword() {
139
+ return fs.existsSync(SYNC_KEY_FILE);
140
+ }
141
+
142
+ // 清除本地缓存的密码
143
+ export function clearSyncPassword() {
144
+ if (fs.existsSync(SYNC_KEY_FILE)) {
145
+ fs.unlinkSync(SYNC_KEY_FILE);
146
+ }
147
+ }