migrate-legacy-account.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. #!/usr/bin/env node
  2. 'use strict';
  3. const fs = require('fs');
  4. const path = require('path');
  5. const crypto = require('crypto');
  6. const { WORKSPACE_ROOT, outputsRoot } = require('../mcp/src/core/output-paths');
  7. const { readQiweiUid, readQiweiAccountMetadata, safeAccountKey } = require('../mcp/src/core/credentials');
  8. const execute = process.argv.includes('--execute');
  9. const force = process.argv.includes('--force');
  10. const legacyKey = safeAccountKey(readQiweiAccountMetadata().legacyAccountKey);
  11. const outputs = outputsRoot();
  12. const messages = process.env.QIWEI_MESSAGES_DIR
  13. ? path.resolve(process.env.QIWEI_MESSAGES_DIR)
  14. : path.join(WORKSPACE_ROOT, 'messages');
  15. const manifestPath = path.join(outputs, 'dashboard', legacyKey, 'legacy-migration.json');
  16. const uid = readQiweiUid();
  17. const oldWorkbenchKey = uid ? crypto.createHash('sha256').update(uid).digest('hex').slice(0, 16) : '';
  18. const actions = [];
  19. const DATE_DIR_RE = /^\d{4}-\d{2}-\d{2}$/;
  20. function addCopy(source, target) {
  21. if (fs.existsSync(source)) actions.push({ source, target });
  22. }
  23. function addDirectoryContents(source, target, include = () => true) {
  24. if (!fs.existsSync(source)) return;
  25. for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
  26. if (entry.name === legacyKey || !include(entry)) continue;
  27. if (entry.isDirectory() && /^[a-f0-9]{16}$/i.test(entry.name)) continue;
  28. addCopy(path.join(source, entry.name), path.join(target, entry.name));
  29. }
  30. }
  31. const oldDb = oldWorkbenchKey
  32. ? path.join(outputs, 'messages', `agent-workbench-${oldWorkbenchKey}.db`)
  33. : path.join(outputs, 'messages', 'agent-workbench.db');
  34. const newDb = path.join(outputs, 'messages', `agent-workbench-${legacyKey}.db`);
  35. addCopy(oldDb, newDb);
  36. addCopy(`${oldDb}-wal`, `${newDb}-wal`);
  37. addCopy(`${oldDb}-shm`, `${newDb}-shm`);
  38. if (oldWorkbenchKey) {
  39. addCopy(
  40. path.join(outputs, 'messages', `claude-code-sessions-${oldWorkbenchKey}.json`),
  41. path.join(outputs, 'messages', `claude-code-sessions-${legacyKey}.json`)
  42. );
  43. }
  44. for (const category of ['customers', 'portraits', 'tags', 'groups']) {
  45. addDirectoryContents(
  46. path.join(outputs, category),
  47. path.join(outputs, category, legacyKey),
  48. entry => !DATE_DIR_RE.test(entry.name)
  49. && (category !== 'groups' || !/^agent-replies-.*\.json$/.test(entry.name))
  50. );
  51. }
  52. for (const category of ['chat', 'memory', 'group']) {
  53. addDirectoryContents(path.join(messages, category), path.join(messages, category, legacyKey));
  54. }
  55. addCopy(
  56. path.join(outputs, 'dashboard', 'dashboard-state.json'),
  57. path.join(outputs, 'dashboard', legacyKey, 'dashboard-state.json')
  58. );
  59. if (!execute) {
  60. console.log(JSON.stringify({ mode: 'dry-run', legacyKey, uid: uid || null, actions }, null, 2));
  61. console.log('No files changed. Re-run with --execute after reviewing the plan.');
  62. process.exit(0);
  63. }
  64. if (fs.existsSync(manifestPath) && !force) {
  65. console.error(`Migration already completed: ${manifestPath}. Use --force only after reviewing existing data.`);
  66. process.exit(2);
  67. }
  68. const results = actions.map(action => {
  69. if (fs.existsSync(action.target) && !force) return { ...action, result: 'skipped_target_exists' };
  70. fs.mkdirSync(path.dirname(action.target), { recursive: true });
  71. fs.cpSync(action.source, action.target, { recursive: true, force });
  72. return { ...action, result: 'copied' };
  73. });
  74. fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
  75. fs.writeFileSync(manifestPath, JSON.stringify({
  76. version: 1,
  77. migratedAt: new Date().toISOString(),
  78. legacyKey,
  79. uid: uid || null,
  80. results,
  81. }, null, 2), 'utf8');
  82. console.log(JSON.stringify({ mode: 'execute', legacyKey, manifestPath, results }, null, 2));