chatlab-cli 0.24.0 → 0.24.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.
Files changed (61) hide show
  1. package/README.md +5 -2
  2. package/dist/{chunk-KCBIBRHL.mjs → chunk-43XWYC3M.mjs} +2 -2
  3. package/dist/{chunk-A6JCL4PT.mjs → chunk-CIIQ3S2X.mjs} +24 -10
  4. package/dist/chunk-CIIQ3S2X.mjs.map +1 -0
  5. package/dist/{chunk-XJJXSOLO.mjs → chunk-N5DGZQB4.mjs} +172 -41
  6. package/dist/chunk-N5DGZQB4.mjs.map +1 -0
  7. package/dist/{chunk-RLCFXTTS.mjs → chunk-QJI6AOAT.mjs} +2 -2
  8. package/dist/{chunk-36W2HWQN.mjs → chunk-QY6P2YS7.mjs} +2 -2
  9. package/dist/{chunk-NE4A6PND.mjs → chunk-ZL322NVL.mjs} +23 -26
  10. package/dist/chunk-ZL322NVL.mjs.map +1 -0
  11. package/dist/cli.mjs +2 -2
  12. package/dist/{http-OPQ352FZ.mjs → http-TPX74BLK.mjs} +6 -6
  13. package/dist/{import-GZMDLK2E.mjs → import-B745DRHD.mjs} +4 -4
  14. package/dist/index.mjs +6 -6
  15. package/dist/{mcp-SHZ2CVXY.mjs → mcp-YWAVG5XW.mjs} +3 -3
  16. package/dist/{stream-import-RDDB3N7B.mjs → stream-import-OPALPVWF.mjs} +3 -3
  17. package/dist/{update-checker-JG5K67O2.mjs → update-checker-JIBA7T4U.mjs} +24 -21
  18. package/dist/update-checker-JIBA7T4U.mjs.map +1 -0
  19. package/dist-web/assets/{DebugToolsPanel.vue_vue_type_script_setup_true_lang-_gbTn1ha.js → DebugToolsPanel.vue_vue_type_script_setup_true_lang-BBDrsJfe.js} +9 -9
  20. package/dist-web/assets/MessageExportModal-CSfaY25B.js +1 -0
  21. package/dist-web/assets/{electron-BIWBW8k_.js → electron-BUeXfvxT.js} +1 -1
  22. package/dist-web/assets/electron-BynNUuEA.js +1 -0
  23. package/dist-web/assets/{fetch-BhsrPRzn.js → fetch-B2q6973s.js} +1 -1
  24. package/dist-web/assets/{fetch-CnyupWWc.js → fetch-BGMRmQx0.js} +2 -2
  25. package/dist-web/assets/{fetch-ClDGP9DK.js → fetch-BneHn5qk.js} +1 -1
  26. package/dist-web/assets/{fetch-DwPB8NOH.js → fetch-BohUj3CQ.js} +1 -1
  27. package/dist-web/assets/{fetch-BOevuRK_.js → fetch-BtHwWs8A.js} +1 -1
  28. package/dist-web/assets/{fetch-DFWZ48Xw.js → fetch-CARsBVl0.js} +1 -1
  29. package/dist-web/assets/{fetch-CvaXKzA-.js → fetch-Ck3tjoLa.js} +1 -1
  30. package/dist-web/assets/{fetch-DKD1bKt2.js → fetch-DypPduyQ.js} +1 -1
  31. package/dist-web/assets/{fetch-ChECfwh7.js → fetch-DzNuFdFM.js} +1 -1
  32. package/dist-web/assets/{fetch-D1_jL6PO.js → fetch-RDFt3hmp.js} +1 -1
  33. package/dist-web/assets/index-BRwEubUe.js +173 -0
  34. package/dist-web/assets/{index-Bzg8BftZ.js → index-BvaWRM6_.js} +1 -1
  35. package/dist-web/assets/{index-BVQsKKwg.js → index-Bvlj3dMn.js} +1 -1
  36. package/dist-web/assets/{index-D6ga3hbF.js → index-C5LrQG3N.js} +3 -3
  37. package/dist-web/assets/index-DB-iqXzV.css +1 -0
  38. package/dist-web/assets/index-DCWvD-4L.js +2 -0
  39. package/dist-web/assets/index-DS1rzk1I.js +8 -0
  40. package/dist-web/assets/{vendor-nuxt-ui-BLMzqeB8.js → vendor-nuxt-ui-BYU7dz8t.js} +1 -1
  41. package/dist-web/assets/{web-DtlveK3q.js → web-CXPkmhis.js} +1 -1
  42. package/dist-web/index.html +3 -3
  43. package/package.json +1 -1
  44. package/dist/chunk-A6JCL4PT.mjs.map +0 -1
  45. package/dist/chunk-NE4A6PND.mjs.map +0 -1
  46. package/dist/chunk-XJJXSOLO.mjs.map +0 -1
  47. package/dist/update-checker-JG5K67O2.mjs.map +0 -1
  48. package/dist-web/assets/MessageExportModal-n4JgNg_F.js +0 -1
  49. package/dist-web/assets/electron-Bt9dK4Sr.js +0 -1
  50. package/dist-web/assets/index-BN79SJSr.js +0 -173
  51. package/dist-web/assets/index-C-5bP7_E.js +0 -2
  52. package/dist-web/assets/index-ESCx9pRR.js +0 -8
  53. package/dist-web/assets/index-JbR9Q-bb.css +0 -1
  54. package/dist-web/assets/model-catalog-DUuKjIVU.js +0 -1
  55. /package/dist/{chunk-KCBIBRHL.mjs.map → chunk-43XWYC3M.mjs.map} +0 -0
  56. /package/dist/{chunk-RLCFXTTS.mjs.map → chunk-QJI6AOAT.mjs.map} +0 -0
  57. /package/dist/{chunk-36W2HWQN.mjs.map → chunk-QY6P2YS7.mjs.map} +0 -0
  58. /package/dist/{http-OPQ352FZ.mjs.map → http-TPX74BLK.mjs.map} +0 -0
  59. /package/dist/{import-GZMDLK2E.mjs.map → import-B745DRHD.mjs.map} +0 -0
  60. /package/dist/{mcp-SHZ2CVXY.mjs.map → mcp-YWAVG5XW.mjs.map} +0 -0
  61. /package/dist/{stream-import-RDDB3N7B.mjs.map → stream-import-OPALPVWF.mjs.map} +0 -0
package/README.md CHANGED
@@ -92,10 +92,12 @@ For a deep dive, see the [architecture documentation](https://docs.chatlab.fun/i
92
92
 
93
93
  ## Local Development
94
94
 
95
+ For complete contributor instructions, see the [Development Guide](https://docs.chatlab.fun/contributing/development).
96
+
95
97
  ### Requirements
96
98
 
97
- - Node.js >= 20
98
- - pnpm
99
+ - Node.js >= 24 < 25
100
+ - pnpm >= 9 < 10
99
101
 
100
102
  ### Setup
101
103
 
@@ -128,6 +130,7 @@ Please follow these principles before submitting a Pull Request:
128
130
  - Obvious bug fixes can be submitted directly.
129
131
  - For new features, please submit an Issue for discussion first; **PRs submitted without prior discussion will be closed**.
130
132
  - Keep one PR focused on one task; if changes are extensive, consider splitting them into multiple independent PRs.
133
+ - For local setup, repository structure, checks, and AI collaboration notes, see the [Development Guide](https://docs.chatlab.fun/contributing/development).
131
134
 
132
135
  Thanks to all contributors:
133
136
 
@@ -26,7 +26,7 @@ import {
26
26
  getWeekdayActivity,
27
27
  searchMessagesLike,
28
28
  searchSessions
29
- } from "./chunk-XJJXSOLO.mjs";
29
+ } from "./chunk-N5DGZQB4.mjs";
30
30
 
31
31
  // ../../packages/tools/src/definitions/sql-query.ts
32
32
  var inputSchema = {
@@ -1341,4 +1341,4 @@ export {
1341
1341
  MCP_TOOL_REGISTRY,
1342
1342
  CoreDataProvider
1343
1343
  };
1344
- //# sourceMappingURL=chunk-KCBIBRHL.mjs.map
1344
+ //# sourceMappingURL=chunk-43XWYC3M.mjs.map
@@ -22,7 +22,7 @@ import {
22
22
  loadConfig,
23
23
  searchMessagesLike,
24
24
  verifyCliDataPath
25
- } from "./chunk-XJJXSOLO.mjs";
25
+ } from "./chunk-N5DGZQB4.mjs";
26
26
  import {
27
27
  resolveCliPath
28
28
  } from "./chunk-FJ35V3TE.mjs";
@@ -63,10 +63,24 @@ function formatPortInUseError(port) {
63
63
  // src/cli.ts
64
64
  var program = new Command();
65
65
  program.name("chatlab").description("ChatLab - Chat history analysis tool").version(getVersion(), "-v, --version");
66
- program.hook("preAction", async () => {
67
- const { checkForUpdatesInteractive } = await import("./update-checker-JG5K67O2.mjs");
66
+ program.hook("preAction", async (_thisCommand, actionCommand) => {
67
+ if (actionCommand.name() === "update") return;
68
+ const { checkForUpdatesInteractive } = await import("./update-checker-JIBA7T4U.mjs");
68
69
  await checkForUpdatesInteractive();
69
70
  });
71
+ program.command("update").description("Update ChatLab CLI to the latest version").action(async () => {
72
+ const { performCliSelfUpdate } = await import("./update-checker-JIBA7T4U.mjs");
73
+ const result = await performCliSelfUpdate({
74
+ write: (text) => process.stderr.write(text)
75
+ });
76
+ if (result.success) {
77
+ console.error(" Updated successfully. Please restart chatlab to use the new version.\n");
78
+ return;
79
+ }
80
+ console.error(` Update failed: ${result.error || "unknown error"}
81
+ `);
82
+ process.exitCode = 1;
83
+ });
70
84
  program.command("sessions").description("List all imported chat sessions").option("--format <format>", "Output format (table|json)", "table").action((options) => {
71
85
  const { dbManager } = initRuntime();
72
86
  const sessionIds = dbManager.listSessionIds();
@@ -205,7 +219,7 @@ program.command("import <file>").description("Import a chat history file (14+ fo
205
219
  console.error(`File not found: ${file}`);
206
220
  process.exit(1);
207
221
  }
208
- const { streamImport, detectFormat } = await import("./import-GZMDLK2E.mjs");
222
+ const { streamImport, detectFormat } = await import("./import-B745DRHD.mjs");
209
223
  const { dbManager } = initRuntime();
210
224
  const nativeBinding = resolveNativeBinding();
211
225
  const format = detectFormat(file);
@@ -247,7 +261,7 @@ Import error: ${err instanceof Error ? err.message : err}`);
247
261
  }
248
262
  });
249
263
  program.command("formats").description("List all supported chat history formats").action(async () => {
250
- const { getSupportedFormats } = await import("./import-GZMDLK2E.mjs");
264
+ const { getSupportedFormats } = await import("./import-B745DRHD.mjs");
251
265
  const formats = getSupportedFormats();
252
266
  console.log(`${formats.length} supported format(s):
253
267
  `);
@@ -256,7 +270,7 @@ program.command("formats").description("List all supported chat history formats"
256
270
  }
257
271
  });
258
272
  program.command("mcp").description("Start MCP Server (stdio transport, for ClaudeCode / Cursor / AI agents)").action(async () => {
259
- const { startCliMcpServer } = await import("./mcp-SHZ2CVXY.mjs");
273
+ const { startCliMcpServer } = await import("./mcp-YWAVG5XW.mjs");
260
274
  await startCliMcpServer();
261
275
  });
262
276
  program.command("start").description("Start ChatLab (HTTP API + Web UI)").option("--port <port>", "Server port", String(DEFAULT_API_PORT)).option("--host <host>", "Listen address", "127.0.0.1").option("--token <token>", "Custom Bearer Token (reads from config or auto-generates if omitted)").option("--headless", "API-only mode, do not serve the Web UI").option("--require-auth", "Require Bearer token for all routes including /_web/*").option("--no-open", "Do not auto-open the browser").option("--daemon", "Run as a resident system service (auto-start on login, macOS/Linux)").action(async (options) => {
@@ -271,7 +285,7 @@ program.command("start").description("Start ChatLab (HTTP API + Web UI)").option
271
285
  });
272
286
  return;
273
287
  }
274
- const { startHttpServer } = await import("./http-OPQ352FZ.mjs");
288
+ const { startHttpServer } = await import("./http-TPX74BLK.mjs");
275
289
  const port = parseInt(options.port, 10);
276
290
  let webRoot;
277
291
  if (!options.headless) {
@@ -294,7 +308,7 @@ program.command("start").description("Start ChatLab (HTTP API + Web UI)").option
294
308
  webRoot,
295
309
  requireAuth: options.requireAuth || void 0
296
310
  });
297
- const { startPeriodicUpdateCheck } = await import("./update-checker-JG5K67O2.mjs");
311
+ const { startPeriodicUpdateCheck } = await import("./update-checker-JIBA7T4U.mjs");
298
312
  startPeriodicUpdateCheck();
299
313
  const displayHost = info.host === "0.0.0.0" ? "127.0.0.1" : info.host;
300
314
  const url = `http://${displayHost}:${info.port}`;
@@ -318,7 +332,7 @@ Press Ctrl+C to stop.
318
332
  }
319
333
  const shutdown = async () => {
320
334
  console.log("\nShutting down...");
321
- const { stopHttpServer } = await import("./http-OPQ352FZ.mjs");
335
+ const { stopHttpServer } = await import("./http-TPX74BLK.mjs");
322
336
  await stopHttpServer();
323
337
  process.exit(0);
324
338
  };
@@ -461,4 +475,4 @@ if (isDirectRun) {
461
475
  export {
462
476
  run
463
477
  };
464
- //# sourceMappingURL=chunk-A6JCL4PT.mjs.map
478
+ //# sourceMappingURL=chunk-CIIQ3S2X.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/http/port.ts"],"sourcesContent":["/**\n * ChatLab CLI entry point\n *\n * Dev: pnpm --filter chatlab-cli run cli -- sessions\n */\n\nimport * as fs from 'fs'\nimport { execSync } from 'child_process'\nimport { Command } from 'commander'\nimport { DEFAULT_API_PORT, loadConfig, getConfigPath } from '@openchatlab/config'\nimport {\n NodePathProvider,\n DatabaseManager,\n applyPendingNodeDataDirMigrationIfNeeded,\n hasPendingElectronDataWarning,\n verifyCliDataPath,\n} from '@openchatlab/node-runtime'\nimport {\n getSessionMeta,\n getSessionOverview,\n getMemberActivity,\n searchMessagesLike,\n getMembers,\n executeReadonlySql,\n} from '@openchatlab/core'\nimport { getVersion } from './version'\nimport { resolveCliPath } from './paths'\nimport { isPortAvailable, formatPortInUseError } from './http/port'\n\nconst program = new Command()\n\nprogram.name('chatlab').description('ChatLab - Chat history analysis tool').version(getVersion(), '-v, --version')\n\nprogram.hook('preAction', async (_thisCommand, actionCommand) => {\n if (actionCommand.name() === 'update') return\n const { checkForUpdatesInteractive } = await import('./update-checker')\n await checkForUpdatesInteractive()\n})\n\nprogram\n .command('update')\n .description('Update ChatLab CLI to the latest version')\n .action(async () => {\n const { performCliSelfUpdate } = await import('./update-checker')\n const result = await performCliSelfUpdate({\n write: (text) => process.stderr.write(text),\n })\n\n if (result.success) {\n console.error(' Updated successfully. Please restart chatlab to use the new version.\\n')\n return\n }\n\n console.error(` Update failed: ${result.error || 'unknown error'}\\n`)\n process.exitCode = 1\n })\n\nprogram\n .command('sessions')\n .description('List all imported chat sessions')\n .option('--format <format>', 'Output format (table|json)', 'table')\n .action((options) => {\n const { dbManager } = initRuntime()\n const sessionIds = dbManager.listSessionIds()\n\n if (sessionIds.length === 0) {\n console.log('No chat sessions found.')\n console.log(`Data directory: ${dbManager['pathProvider'].getUserDataDir()}`)\n dbManager.closeAll()\n return\n }\n\n const sessions = sessionIds\n .map((id) => {\n const db = dbManager.open(id)\n if (!db) return null\n const meta = getSessionMeta(db)\n if (!meta) return null\n const overview = getSessionOverview(db)\n return { id, ...meta, ...overview }\n })\n .filter(Boolean)\n\n if (options.format === 'json') {\n console.log(JSON.stringify(sessions, null, 2))\n } else {\n console.log(`${sessions.length} session(s) found:\\n`)\n for (const s of sessions) {\n if (!s) continue\n const timeRange = formatTimeRange(s.firstMessageTs, s.lastMessageTs)\n console.log(` ${s.name}`)\n console.log(` ID: ${s.id}`)\n console.log(\n ` Platform: ${s.platform} | Type: ${s.type} | Members: ${s.totalMembers} | Messages: ${s.totalMessages}`\n )\n if (timeRange) console.log(` Range: ${timeRange}`)\n console.log()\n }\n }\n\n dbManager.closeAll()\n })\n\nprogram\n .command('stats <session-id>')\n .description('Show session statistics overview')\n .option('--format <format>', 'Output format (table|json)', 'table')\n .option('--top <n>', 'Show top N active members', '10')\n .action((sessionId, options) => {\n const { dbManager } = initRuntime()\n const db = dbManager.open(sessionId)\n if (!db) {\n console.error(`Session ${sessionId} not found`)\n process.exit(1)\n }\n\n const meta = getSessionMeta(db)\n const overview = getSessionOverview(db)\n const topMembers = getMemberActivity(db).slice(0, parseInt(options.top))\n\n if (options.format === 'json') {\n console.log(JSON.stringify({ meta, overview, topMembers }, null, 2))\n } else {\n console.log(`\\nSession: ${meta?.name}`)\n console.log(`Platform: ${meta?.platform} | Type: ${meta?.type}`)\n console.log(`Total messages: ${overview.totalMessages}`)\n console.log(`Total members: ${overview.totalMembers}`)\n console.log(`Time range: ${formatTimeRange(overview.firstMessageTs, overview.lastMessageTs)}`)\n\n if (topMembers.length > 0) {\n console.log(`\\nActivity ranking (Top ${options.top}):`)\n for (const [i, m] of topMembers.entries()) {\n console.log(` ${i + 1}. ${m.name} - ${m.messageCount} messages (${m.percentage}%)`)\n }\n }\n }\n\n dbManager.closeAll()\n })\n\nprogram\n .command('search <session-id> <keyword>')\n .description('Search messages by keyword')\n .option('--limit <n>', 'Max results to return', '20')\n .option('--format <format>', 'Output format (table|json)', 'table')\n .action((sessionId, keyword, options) => {\n const { dbManager } = initRuntime()\n const db = dbManager.open(sessionId)\n if (!db) {\n console.error(`Session ${sessionId} not found`)\n process.exit(1)\n }\n\n const limit = parseInt(options.limit)\n const result = searchMessagesLike(db, keyword, { limit })\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2))\n } else {\n console.log(\n `Search \"${keyword}\" - ${result.total} result(s)${result.hasMore ? ' (showing first ' + limit + ')' : ''}:\\n`\n )\n for (const msg of result.messages) {\n const time = new Date(msg.timestamp * 1000).toLocaleString()\n console.log(` [${time}] ${msg.senderName}: ${msg.content}`)\n }\n }\n\n dbManager.closeAll()\n })\n\nprogram\n .command('members <session-id>')\n .description('List session members')\n .option('--format <format>', 'Output format (table|json)', 'table')\n .action((sessionId, options) => {\n const { dbManager } = initRuntime()\n const db = dbManager.open(sessionId)\n if (!db) {\n console.error(`Session ${sessionId} not found`)\n process.exit(1)\n }\n\n const members = getMembers(db)\n\n if (options.format === 'json') {\n console.log(JSON.stringify(members, null, 2))\n } else {\n console.log(`${members.length} member(s):\\n`)\n for (const [i, m] of members.entries()) {\n console.log(` ${i + 1}. ${m.name} (${m.platformId}) - ${m.messageCount} messages`)\n }\n }\n\n dbManager.closeAll()\n })\n\nprogram\n .command('query <session-id>')\n .description('Execute a read-only SQL query on a session database')\n .requiredOption('--sql <sql>', 'SQL query statement')\n .option('--format <format>', 'Output format (table|json)', 'table')\n .action((sessionId, options) => {\n const { dbManager } = initRuntime()\n const db = dbManager.open(sessionId)\n if (!db) {\n console.error(`Session ${sessionId} not found`)\n process.exit(1)\n }\n\n try {\n const result = executeReadonlySql(db, options.sql)\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2))\n } else {\n if (result.rows.length === 0) {\n console.log('No results.')\n } else {\n printTable(result.columns, result.rows)\n console.log(`\\n${result.rowCount} row(s)${result.truncated ? ' (truncated)' : ''}`)\n }\n }\n } catch (err) {\n console.error(`SQL error: ${err instanceof Error ? err.message : err}`)\n process.exit(1)\n }\n\n dbManager.closeAll()\n })\n\nprogram\n .command('import <file>')\n .description('Import a chat history file (14+ formats: QQ/WeChat/Telegram/WhatsApp/LINE/Discord/Instagram, etc.)')\n .option('--session-id <id>', 'Specify session ID (auto-generated if omitted)')\n .option('--format <id>', 'Specify format ID (skip auto-detection)')\n .action(async (file, options) => {\n if (!fs.existsSync(file)) {\n console.error(`File not found: ${file}`)\n process.exit(1)\n }\n\n const { streamImport, detectFormat } = await import('./import')\n const { dbManager } = initRuntime()\n const nativeBinding = resolveNativeBinding()\n\n const format = detectFormat(file)\n if (!format && !options.format) {\n console.error(`Unrecognized file format: ${file}`)\n console.error('Use --format <id> to specify manually, or run \"chatlab formats\" to see supported formats')\n process.exit(1)\n }\n\n console.log(`Importing: ${file}`)\n if (format) console.log(` Format: ${format.name} (${format.platform})`)\n\n try {\n const result = await streamImport(dbManager, file, {\n formatId: options.format,\n nativeBinding,\n onProgress: (p) => {\n process.stdout.write(`\\r ${p.stage}: ${p.progress}%`)\n },\n })\n\n if (result.success) {\n console.log(`\\n\\nImport succeeded!`)\n console.log(` Session ID: ${result.sessionId}`)\n console.log(` Messages written: ${result.diagnostics?.messagesWritten ?? 0}`)\n console.log(` Messages skipped: ${result.diagnostics?.messagesSkipped ?? 0}`)\n } else {\n console.error(`\\n\\nImport failed: ${result.error}`)\n process.exit(1)\n }\n } catch (err) {\n console.error(`\\n\\nImport error: ${err instanceof Error ? err.message : err}`)\n process.exit(1)\n } finally {\n dbManager.closeAll()\n }\n })\n\nprogram\n .command('formats')\n .description('List all supported chat history formats')\n .action(async () => {\n const { getSupportedFormats } = await import('./import')\n const formats = getSupportedFormats()\n console.log(`${formats.length} supported format(s):\\n`)\n for (const f of formats) {\n console.log(` ${f.id.padEnd(30)} ${f.name} (${f.platform}) [${f.extensions.join(', ')}]`)\n }\n })\n\nprogram\n .command('mcp')\n .description('Start MCP Server (stdio transport, for ClaudeCode / Cursor / AI agents)')\n .action(async () => {\n const { startCliMcpServer } = await import('./mcp')\n await startCliMcpServer()\n })\n\nprogram\n .command('start')\n .description('Start ChatLab (HTTP API + Web UI)')\n .option('--port <port>', 'Server port', String(DEFAULT_API_PORT))\n .option('--host <host>', 'Listen address', '127.0.0.1')\n .option('--token <token>', 'Custom Bearer Token (reads from config or auto-generates if omitted)')\n .option('--headless', 'API-only mode, do not serve the Web UI')\n .option('--require-auth', 'Require Bearer token for all routes including /_web/*')\n .option('--no-open', 'Do not auto-open the browser')\n .option('--daemon', 'Run as a resident system service (auto-start on login, macOS/Linux)')\n .action(async (options) => {\n // --daemon: install as system service and exit\n if (options.daemon) {\n const { serviceInstall } = await import('./daemon/service')\n serviceInstall({\n port: parseInt(options.port, 10),\n host: options.host,\n token: options.token || undefined,\n headless: options.headless,\n requireAuth: options.requireAuth || undefined,\n })\n return\n }\n\n const { startHttpServer } = await import('./http')\n const port = parseInt(options.port, 10)\n\n let webRoot: string | undefined\n if (!options.headless) {\n const webDir = resolveCliPath('dist-web')\n if (fs.existsSync(webDir)) {\n webRoot = webDir\n } else {\n console.warn('Warning: dist-web/ not found, starting in API-only mode')\n }\n }\n\n try {\n // 启动前预检端口,快速失败,避免无谓的初始化后再报 EADDRINUSE;\n // 置于 try 内确保非 EADDRINUSE 错误(EACCES/EADDRNOTAVAIL 等)\n // 也能走到统一的 Startup failed 错误处理路径。\n if (!(await isPortAvailable(port, options.host))) {\n console.error(formatPortInUseError(port))\n process.exit(1)\n }\n\n const info = await startHttpServer({\n port,\n host: options.host,\n token: options.token || undefined,\n webRoot,\n requireAuth: options.requireAuth || undefined,\n })\n\n const { startPeriodicUpdateCheck } = await import('./update-checker')\n startPeriodicUpdateCheck()\n\n const displayHost = info.host === '0.0.0.0' ? '127.0.0.1' : info.host\n const url = `http://${displayHost}:${info.port}`\n\n console.log(`\\nChatLab v${getVersion()}`)\n if (webRoot) console.log(` Web UI: ${url}/`)\n console.log(` API: ${url}`)\n console.log(` Token: ${info.token}`)\n console.log(`\\nExample:`)\n console.log(` curl -H \"Authorization: Bearer ${info.token}\" ${url}/api/v1/status`)\n\n if (webRoot && options.open) {\n openBrowser(url)\n console.log(`\\nBrowser opened. Press Ctrl+C to stop.\\n`)\n } else {\n console.log(`\\nPress Ctrl+C to stop.\\n`)\n }\n\n const shutdown = async () => {\n console.log('\\nShutting down...')\n const { stopHttpServer } = await import('./http')\n await stopHttpServer()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err)\n if (message.includes('EADDRINUSE')) {\n // 极低概率的 TOCTOU 竞态(预检通过后端口被占),复用统一文案\n console.error(formatPortInUseError(port))\n } else {\n console.error(`Startup failed: ${message}`)\n }\n process.exit(1)\n }\n })\n\nconst configCmd = program.command('config').description('Configuration management')\n\nconfigCmd\n .command('path')\n .description('Show config file path')\n .action(() => {\n console.log(getConfigPath())\n })\n\nconfigCmd\n .command('show')\n .description('Show current configuration')\n .action(() => {\n const config = loadConfig()\n console.log(JSON.stringify(config, null, 2))\n })\n\nprogram\n .command('stop')\n .description('Stop the resident service and remove auto-start (reverse of start --daemon)')\n .action(async () => {\n const { serviceUninstall } = await import('./daemon/service')\n serviceUninstall()\n })\n\nprogram\n .command('status')\n .description('Show resident service status')\n .action(async () => {\n const { getServiceStatus } = await import('./daemon/service')\n const svc = getServiceStatus()\n\n console.log('\\nChatLab Status')\n console.log('─'.repeat(36))\n\n if (svc.installed) {\n const portStr = svc.port ? `http://${svc.host ?? '127.0.0.1'}:${svc.port}` : ''\n console.log(` Service: ${svc.running ? 'running' : 'installed (not running)'}`)\n if (portStr) console.log(` Address: ${portStr}`)\n console.log(` Auto-start: enabled`)\n console.log(`\\n Use \\`chatlab stop\\` to remove the service.\\n`)\n } else {\n console.log(` Service: not installed`)\n console.log(` Auto-start: disabled`)\n console.log(`\\n Use \\`chatlab start --daemon\\` to install as a system service.\\n`)\n }\n })\n\n// --- 工具函数 ---\n\nfunction openBrowser(url: string): void {\n try {\n const cmd =\n process.platform === 'darwin'\n ? `open \"${url}\"`\n : process.platform === 'win32'\n ? `start \"\" \"${url}\"`\n : `xdg-open \"${url}\"`\n execSync(cmd, { stdio: 'ignore' })\n } catch {\n console.log(` Open manually: ${url}`)\n }\n}\n\n/**\n * Resolve standalone better-sqlite3 native module path.\n * Used in non-Electron environments to avoid electron-rebuild conflicts.\n */\nfunction resolveNativeBinding(): string | undefined {\n if (process.versions.electron) return undefined\n const nativePath = resolveCliPath('native/better_sqlite3.node')\n if (fs.existsSync(nativePath)) return nativePath\n return undefined\n}\n\nfunction initRuntime() {\n let config = loadConfig()\n const pendingMigration = applyPendingNodeDataDirMigrationIfNeeded()\n if (!pendingMigration.skipped) {\n if (pendingMigration.success) {\n console.log('[Migration] Pending data directory migration completed')\n config = loadConfig()\n } else {\n console.error('[Migration] Pending data directory migration failed:', pendingMigration.error)\n }\n }\n const userDataDir = config.data.user_data_dir || undefined\n const pathProvider = new NodePathProvider(userDataDir)\n pathProvider.ensureAllDirs()\n\n if (hasPendingElectronDataWarning() || !verifyCliDataPath(pathProvider.getDatabaseDir())) {\n printElectronDataError()\n process.exit(1)\n }\n\n const nativeBinding = resolveNativeBinding()\n const dbManager = new DatabaseManager(pathProvider, { nativeBinding })\n return { config, pathProvider, dbManager }\n}\n\nfunction printElectronDataError(): void {\n console.error('\\n' + '='.repeat(68))\n console.error(' ChatLab: Electron desktop data not found')\n console.error('='.repeat(68))\n console.error('')\n console.error(' Detected that ChatLab desktop app was installed on this machine,')\n console.error(' but could not locate your chat databases.')\n console.error('')\n console.error(' This usually means you changed the data directory in desktop settings.')\n console.error('')\n console.error(' To fix this, choose one of:')\n console.error('')\n console.error(' 1. Open ChatLab desktop app — it will auto-migrate your data')\n console.error(' 2. Set the data directory manually:')\n console.error(' export CHATLAB_DATA_DIR=\"/path/to/your/data\"')\n console.error(' 3. Edit ~/.chatlab/config.toml:')\n console.error(' [data]')\n console.error(' user_data_dir = \"/path/to/your/data\"')\n console.error('')\n console.error('='.repeat(68) + '\\n')\n}\n\nfunction formatTimeRange(first: number | null, last: number | null): string {\n if (!first || !last) return 'Unknown'\n const from = new Date(first * 1000).toLocaleDateString()\n const to = new Date(last * 1000).toLocaleDateString()\n return `${from} ~ ${to}`\n}\n\nfunction printTable(columns: string[], rows: Record<string, unknown>[]): void {\n const widths = columns.map((col) => {\n const maxData = rows.reduce((max, row) => {\n const val = String(row[col] ?? '')\n return Math.max(max, val.length)\n }, 0)\n return Math.max(col.length, Math.min(maxData, 40))\n })\n\n const header = columns.map((col, i) => col.padEnd(widths[i])).join(' | ')\n const separator = widths.map((w) => '-'.repeat(w)).join('-+-')\n console.log(header)\n console.log(separator)\n\n for (const row of rows) {\n const line = columns\n .map((col, i) => {\n const val = String(row[col] ?? '')\n return val.length > 40 ? val.slice(0, 37) + '...' : val.padEnd(widths[i])\n })\n .join(' | ')\n console.log(line)\n }\n}\n\n/** CLI entry function */\nexport function run(argv?: string[]): void {\n program.parse(argv)\n}\n\n// Auto-execute when run directly as a script\nconst isDirectRun = process.argv[1]?.endsWith('cli.ts') || process.argv[1]?.endsWith('cli.js')\nif (isDirectRun) {\n run()\n}\n","/**\n * 端口可用性检测与错误文案\n */\n\nimport * as net from 'net'\n\n/**\n * 检测指定端口在给定 host 上是否可用。\n * 使用 net.createServer() 试探性绑定,无副作用(成功后立即释放)。\n */\nexport function isPortAvailable(port: number, host: string): Promise<boolean> {\n return new Promise((resolve, reject) => {\n const server = net.createServer()\n server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n resolve(false)\n } else {\n reject(err)\n }\n })\n server.once('listening', () => server.close(() => resolve(true)))\n server.listen(port, host)\n })\n}\n\n/**\n * 返回端口被占用时统一的友好报错文案。\n */\nexport function formatPortInUseError(port: number): string {\n return [\n ``,\n ` ✖ Error: port ${port} is already in use.`,\n ``,\n ` Another process (possibly a ChatLab instance) is using this port. You can:`,\n ` • Use another port: chatlab start --port <port>`,\n ` • Find the process: lsof -iTCP:${port} -sTCP:LISTEN`,\n ``,\n ].join('\\n')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,YAAY,QAAQ;AACpB,SAAS,gBAAgB;AACzB,SAAS,eAAe;;;ACJxB,YAAY,SAAS;AAMd,SAAS,gBAAgB,MAAc,MAAgC;AAC5E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAa,iBAAa;AAChC,WAAO,KAAK,SAAS,CAAC,QAA+B;AACnD,UAAI,IAAI,SAAS,cAAc;AAC7B,gBAAQ,KAAK;AAAA,MACf,OAAO;AACL,eAAO,GAAG;AAAA,MACZ;AAAA,IACF,CAAC;AACD,WAAO,KAAK,aAAa,MAAM,OAAO,MAAM,MAAM,QAAQ,IAAI,CAAC,CAAC;AAChE,WAAO,OAAO,MAAM,IAAI;AAAA,EAC1B,CAAC;AACH;AAKO,SAAS,qBAAqB,MAAsB;AACzD,SAAO;AAAA,IACL;AAAA,IACA,wBAAmB,IAAI;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,6CAAwC,IAAI;AAAA,IAC5C;AAAA,EACF,EAAE,KAAK,IAAI;AACb;;;ADTA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QAAQ,KAAK,SAAS,EAAE,YAAY,sCAAsC,EAAE,QAAQ,WAAW,GAAG,eAAe;AAEjH,QAAQ,KAAK,aAAa,OAAO,cAAc,kBAAkB;AAC/D,MAAI,cAAc,KAAK,MAAM,SAAU;AACvC,QAAM,EAAE,2BAA2B,IAAI,MAAM,OAAO,+BAAkB;AACtE,QAAM,2BAA2B;AACnC,CAAC;AAED,QACG,QAAQ,QAAQ,EAChB,YAAY,0CAA0C,EACtD,OAAO,YAAY;AAClB,QAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,+BAAkB;AAChE,QAAM,SAAS,MAAM,qBAAqB;AAAA,IACxC,OAAO,CAAC,SAAS,QAAQ,OAAO,MAAM,IAAI;AAAA,EAC5C,CAAC;AAED,MAAI,OAAO,SAAS;AAClB,YAAQ,MAAM,0EAA0E;AACxF;AAAA,EACF;AAEA,UAAQ,MAAM,oBAAoB,OAAO,SAAS,eAAe;AAAA,CAAI;AACrE,UAAQ,WAAW;AACrB,CAAC;AAEH,QACG,QAAQ,UAAU,EAClB,YAAY,iCAAiC,EAC7C,OAAO,qBAAqB,8BAA8B,OAAO,EACjE,OAAO,CAAC,YAAY;AACnB,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,aAAa,UAAU,eAAe;AAE5C,MAAI,WAAW,WAAW,GAAG;AAC3B,YAAQ,IAAI,yBAAyB;AACrC,YAAQ,IAAI,mBAAmB,UAAU,cAAc,EAAE,eAAe,CAAC,EAAE;AAC3E,cAAU,SAAS;AACnB;AAAA,EACF;AAEA,QAAM,WAAW,WACd,IAAI,CAAC,OAAO;AACX,UAAM,KAAK,UAAU,KAAK,EAAE;AAC5B,QAAI,CAAC,GAAI,QAAO;AAChB,UAAM,OAAO,eAAe,EAAE;AAC9B,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,WAAW,mBAAmB,EAAE;AACtC,WAAO,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS;AAAA,EACpC,CAAC,EACA,OAAO,OAAO;AAEjB,MAAI,QAAQ,WAAW,QAAQ;AAC7B,YAAQ,IAAI,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,EAC/C,OAAO;AACL,YAAQ,IAAI,GAAG,SAAS,MAAM;AAAA,CAAsB;AACpD,eAAW,KAAK,UAAU;AACxB,UAAI,CAAC,EAAG;AACR,YAAM,YAAY,gBAAgB,EAAE,gBAAgB,EAAE,aAAa;AACnE,cAAQ,IAAI,KAAK,EAAE,IAAI,EAAE;AACzB,cAAQ,IAAI,WAAW,EAAE,EAAE,EAAE;AAC7B,cAAQ;AAAA,QACN,iBAAiB,EAAE,QAAQ,YAAY,EAAE,IAAI,eAAe,EAAE,YAAY,gBAAgB,EAAE,aAAa;AAAA,MAC3G;AACA,UAAI,UAAW,SAAQ,IAAI,cAAc,SAAS,EAAE;AACpD,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,YAAU,SAAS;AACrB,CAAC;AAEH,QACG,QAAQ,oBAAoB,EAC5B,YAAY,kCAAkC,EAC9C,OAAO,qBAAqB,8BAA8B,OAAO,EACjE,OAAO,aAAa,6BAA6B,IAAI,EACrD,OAAO,CAAC,WAAW,YAAY;AAC9B,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,KAAK,UAAU,KAAK,SAAS;AACnC,MAAI,CAAC,IAAI;AACP,YAAQ,MAAM,WAAW,SAAS,YAAY;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAO,eAAe,EAAE;AAC9B,QAAM,WAAW,mBAAmB,EAAE;AACtC,QAAM,aAAa,kBAAkB,EAAE,EAAE,MAAM,GAAG,SAAS,QAAQ,GAAG,CAAC;AAEvE,MAAI,QAAQ,WAAW,QAAQ;AAC7B,YAAQ,IAAI,KAAK,UAAU,EAAE,MAAM,UAAU,WAAW,GAAG,MAAM,CAAC,CAAC;AAAA,EACrE,OAAO;AACL,YAAQ,IAAI;AAAA,WAAc,MAAM,IAAI,EAAE;AACtC,YAAQ,IAAI,aAAa,MAAM,QAAQ,YAAY,MAAM,IAAI,EAAE;AAC/D,YAAQ,IAAI,mBAAmB,SAAS,aAAa,EAAE;AACvD,YAAQ,IAAI,kBAAkB,SAAS,YAAY,EAAE;AACrD,YAAQ,IAAI,eAAe,gBAAgB,SAAS,gBAAgB,SAAS,aAAa,CAAC,EAAE;AAE7F,QAAI,WAAW,SAAS,GAAG;AACzB,cAAQ,IAAI;AAAA,wBAA2B,QAAQ,GAAG,IAAI;AACtD,iBAAW,CAAC,GAAG,CAAC,KAAK,WAAW,QAAQ,GAAG;AACzC,gBAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,EAAE,IAAI,MAAM,EAAE,YAAY,cAAc,EAAE,UAAU,IAAI;AAAA,MACrF;AAAA,IACF;AAAA,EACF;AAEA,YAAU,SAAS;AACrB,CAAC;AAEH,QACG,QAAQ,+BAA+B,EACvC,YAAY,4BAA4B,EACxC,OAAO,eAAe,yBAAyB,IAAI,EACnD,OAAO,qBAAqB,8BAA8B,OAAO,EACjE,OAAO,CAAC,WAAW,SAAS,YAAY;AACvC,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,KAAK,UAAU,KAAK,SAAS;AACnC,MAAI,CAAC,IAAI;AACP,YAAQ,MAAM,WAAW,SAAS,YAAY;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,SAAS,QAAQ,KAAK;AACpC,QAAM,SAAS,mBAAmB,IAAI,SAAS,EAAE,MAAM,CAAC;AAExD,MAAI,QAAQ,WAAW,QAAQ;AAC7B,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,EAC7C,OAAO;AACL,YAAQ;AAAA,MACN,WAAW,OAAO,OAAO,OAAO,KAAK,aAAa,OAAO,UAAU,qBAAqB,QAAQ,MAAM,EAAE;AAAA;AAAA,IAC1G;AACA,eAAW,OAAO,OAAO,UAAU;AACjC,YAAM,OAAO,IAAI,KAAK,IAAI,YAAY,GAAI,EAAE,eAAe;AAC3D,cAAQ,IAAI,MAAM,IAAI,KAAK,IAAI,UAAU,KAAK,IAAI,OAAO,EAAE;AAAA,IAC7D;AAAA,EACF;AAEA,YAAU,SAAS;AACrB,CAAC;AAEH,QACG,QAAQ,sBAAsB,EAC9B,YAAY,sBAAsB,EAClC,OAAO,qBAAqB,8BAA8B,OAAO,EACjE,OAAO,CAAC,WAAW,YAAY;AAC9B,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,KAAK,UAAU,KAAK,SAAS;AACnC,MAAI,CAAC,IAAI;AACP,YAAQ,MAAM,WAAW,SAAS,YAAY;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,UAAU,WAAW,EAAE;AAE7B,MAAI,QAAQ,WAAW,QAAQ;AAC7B,YAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,EAC9C,OAAO;AACL,YAAQ,IAAI,GAAG,QAAQ,MAAM;AAAA,CAAe;AAC5C,eAAW,CAAC,GAAG,CAAC,KAAK,QAAQ,QAAQ,GAAG;AACtC,cAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,EAAE,UAAU,OAAO,EAAE,YAAY,WAAW;AAAA,IACpF;AAAA,EACF;AAEA,YAAU,SAAS;AACrB,CAAC;AAEH,QACG,QAAQ,oBAAoB,EAC5B,YAAY,qDAAqD,EACjE,eAAe,eAAe,qBAAqB,EACnD,OAAO,qBAAqB,8BAA8B,OAAO,EACjE,OAAO,CAAC,WAAW,YAAY;AAC9B,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,KAAK,UAAU,KAAK,SAAS;AACnC,MAAI,CAAC,IAAI;AACP,YAAQ,MAAM,WAAW,SAAS,YAAY;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI;AACF,UAAM,SAAS,mBAAmB,IAAI,QAAQ,GAAG;AACjD,QAAI,QAAQ,WAAW,QAAQ;AAC7B,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC7C,OAAO;AACL,UAAI,OAAO,KAAK,WAAW,GAAG;AAC5B,gBAAQ,IAAI,aAAa;AAAA,MAC3B,OAAO;AACL,mBAAW,OAAO,SAAS,OAAO,IAAI;AACtC,gBAAQ,IAAI;AAAA,EAAK,OAAO,QAAQ,UAAU,OAAO,YAAY,iBAAiB,EAAE,EAAE;AAAA,MACpF;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM,cAAc,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACtE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,YAAU,SAAS;AACrB,CAAC;AAEH,QACG,QAAQ,eAAe,EACvB,YAAY,oGAAoG,EAChH,OAAO,qBAAqB,gDAAgD,EAC5E,OAAO,iBAAiB,yCAAyC,EACjE,OAAO,OAAO,MAAM,YAAY;AAC/B,MAAI,CAAI,cAAW,IAAI,GAAG;AACxB,YAAQ,MAAM,mBAAmB,IAAI,EAAE;AACvC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,cAAc,aAAa,IAAI,MAAM,OAAO,uBAAU;AAC9D,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,gBAAgB,qBAAqB;AAE3C,QAAM,SAAS,aAAa,IAAI;AAChC,MAAI,CAAC,UAAU,CAAC,QAAQ,QAAQ;AAC9B,YAAQ,MAAM,6BAA6B,IAAI,EAAE;AACjD,YAAQ,MAAM,0FAA0F;AACxG,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,cAAc,IAAI,EAAE;AAChC,MAAI,OAAQ,SAAQ,IAAI,aAAa,OAAO,IAAI,KAAK,OAAO,QAAQ,GAAG;AAEvE,MAAI;AACF,UAAM,SAAS,MAAM,aAAa,WAAW,MAAM;AAAA,MACjD,UAAU,QAAQ;AAAA,MAClB;AAAA,MACA,YAAY,CAAC,MAAM;AACjB,gBAAQ,OAAO,MAAM,OAAO,EAAE,KAAK,KAAK,EAAE,QAAQ,GAAG;AAAA,MACvD;AAAA,IACF,CAAC;AAED,QAAI,OAAO,SAAS;AAClB,cAAQ,IAAI;AAAA;AAAA,kBAAuB;AACnC,cAAQ,IAAI,iBAAiB,OAAO,SAAS,EAAE;AAC/C,cAAQ,IAAI,uBAAuB,OAAO,aAAa,mBAAmB,CAAC,EAAE;AAC7E,cAAQ,IAAI,uBAAuB,OAAO,aAAa,mBAAmB,CAAC,EAAE;AAAA,IAC/E,OAAO;AACL,cAAQ,MAAM;AAAA;AAAA,iBAAsB,OAAO,KAAK,EAAE;AAClD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM;AAAA;AAAA,gBAAqB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAC7E,YAAQ,KAAK,CAAC;AAAA,EAChB,UAAE;AACA,cAAU,SAAS;AAAA,EACrB;AACF,CAAC;AAEH,QACG,QAAQ,SAAS,EACjB,YAAY,yCAAyC,EACrD,OAAO,YAAY;AAClB,QAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,uBAAU;AACvD,QAAM,UAAU,oBAAoB;AACpC,UAAQ,IAAI,GAAG,QAAQ,MAAM;AAAA,CAAyB;AACtD,aAAW,KAAK,SAAS;AACvB,YAAQ,IAAI,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,EAAE,QAAQ,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG;AAAA,EAC3F;AACF,CAAC;AAEH,QACG,QAAQ,KAAK,EACb,YAAY,yEAAyE,EACrF,OAAO,YAAY;AAClB,QAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,oBAAO;AAClD,QAAM,kBAAkB;AAC1B,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,mCAAmC,EAC/C,OAAO,iBAAiB,eAAe,OAAO,gBAAgB,CAAC,EAC/D,OAAO,iBAAiB,kBAAkB,WAAW,EACrD,OAAO,mBAAmB,sEAAsE,EAChG,OAAO,cAAc,wCAAwC,EAC7D,OAAO,kBAAkB,uDAAuD,EAChF,OAAO,aAAa,8BAA8B,EAClD,OAAO,YAAY,qEAAqE,EACxF,OAAO,OAAO,YAAY;AAEzB,MAAI,QAAQ,QAAQ;AAClB,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,wBAAkB;AAC1D,mBAAe;AAAA,MACb,MAAM,SAAS,QAAQ,MAAM,EAAE;AAAA,MAC/B,MAAM,QAAQ;AAAA,MACd,OAAO,QAAQ,SAAS;AAAA,MACxB,UAAU,QAAQ;AAAA,MAClB,aAAa,QAAQ,eAAe;AAAA,IACtC,CAAC;AACD;AAAA,EACF;AAEA,QAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,qBAAQ;AACjD,QAAM,OAAO,SAAS,QAAQ,MAAM,EAAE;AAEtC,MAAI;AACJ,MAAI,CAAC,QAAQ,UAAU;AACrB,UAAM,SAAS,eAAe,UAAU;AACxC,QAAO,cAAW,MAAM,GAAG;AACzB,gBAAU;AAAA,IACZ,OAAO;AACL,cAAQ,KAAK,yDAAyD;AAAA,IACxE;AAAA,EACF;AAEA,MAAI;AAIF,QAAI,CAAE,MAAM,gBAAgB,MAAM,QAAQ,IAAI,GAAI;AAChD,cAAQ,MAAM,qBAAqB,IAAI,CAAC;AACxC,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,OAAO,MAAM,gBAAgB;AAAA,MACjC;AAAA,MACA,MAAM,QAAQ;AAAA,MACd,OAAO,QAAQ,SAAS;AAAA,MACxB;AAAA,MACA,aAAa,QAAQ,eAAe;AAAA,IACtC,CAAC;AAED,UAAM,EAAE,yBAAyB,IAAI,MAAM,OAAO,+BAAkB;AACpE,6BAAyB;AAEzB,UAAM,cAAc,KAAK,SAAS,YAAY,cAAc,KAAK;AACjE,UAAM,MAAM,UAAU,WAAW,IAAI,KAAK,IAAI;AAE9C,YAAQ,IAAI;AAAA,WAAc,WAAW,CAAC,EAAE;AACxC,QAAI,QAAS,SAAQ,IAAI,aAAa,GAAG,GAAG;AAC5C,YAAQ,IAAI,aAAa,GAAG,EAAE;AAC9B,YAAQ,IAAI,aAAa,KAAK,KAAK,EAAE;AACrC,YAAQ,IAAI;AAAA,SAAY;AACxB,YAAQ,IAAI,oCAAoC,KAAK,KAAK,KAAK,GAAG,gBAAgB;AAElF,QAAI,WAAW,QAAQ,MAAM;AAC3B,kBAAY,GAAG;AACf,cAAQ,IAAI;AAAA;AAAA,CAA2C;AAAA,IACzD,OAAO;AACL,cAAQ,IAAI;AAAA;AAAA,CAA2B;AAAA,IACzC;AAEA,UAAM,WAAW,YAAY;AAC3B,cAAQ,IAAI,oBAAoB;AAChC,YAAM,EAAE,eAAe,IAAI,MAAM,OAAO,qBAAQ;AAChD,YAAM,eAAe;AACrB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,GAAG,UAAU,QAAQ;AAC7B,YAAQ,GAAG,WAAW,QAAQ;AAAA,EAChC,SAAS,KAAc;AACrB,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,QAAI,QAAQ,SAAS,YAAY,GAAG;AAElC,cAAQ,MAAM,qBAAqB,IAAI,CAAC;AAAA,IAC1C,OAAO;AACL,cAAQ,MAAM,mBAAmB,OAAO,EAAE;AAAA,IAC5C;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,IAAM,YAAY,QAAQ,QAAQ,QAAQ,EAAE,YAAY,0BAA0B;AAElF,UACG,QAAQ,MAAM,EACd,YAAY,uBAAuB,EACnC,OAAO,MAAM;AACZ,UAAQ,IAAI,cAAc,CAAC;AAC7B,CAAC;AAEH,UACG,QAAQ,MAAM,EACd,YAAY,4BAA4B,EACxC,OAAO,MAAM;AACZ,QAAM,SAAS,WAAW;AAC1B,UAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC7C,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,6EAA6E,EACzF,OAAO,YAAY;AAClB,QAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,wBAAkB;AAC5D,mBAAiB;AACnB,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,8BAA8B,EAC1C,OAAO,YAAY;AAClB,QAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,wBAAkB;AAC5D,QAAM,MAAM,iBAAiB;AAE7B,UAAQ,IAAI,kBAAkB;AAC9B,UAAQ,IAAI,SAAI,OAAO,EAAE,CAAC;AAE1B,MAAI,IAAI,WAAW;AACjB,UAAM,UAAU,IAAI,OAAO,UAAU,IAAI,QAAQ,WAAW,IAAI,IAAI,IAAI,KAAK;AAC7E,YAAQ,IAAI,iBAAiB,IAAI,UAAU,YAAY,yBAAyB,EAAE;AAClF,QAAI,QAAS,SAAQ,IAAI,iBAAiB,OAAO,EAAE;AACnD,YAAQ,IAAI,uBAAuB;AACnC,YAAQ,IAAI;AAAA;AAAA,CAAmD;AAAA,EACjE,OAAO;AACL,YAAQ,IAAI,6BAA6B;AACzC,YAAQ,IAAI,wBAAwB;AACpC,YAAQ,IAAI;AAAA;AAAA,CAAsE;AAAA,EACpF;AACF,CAAC;AAIH,SAAS,YAAY,KAAmB;AACtC,MAAI;AACF,UAAM,MACJ,QAAQ,aAAa,WACjB,SAAS,GAAG,MACZ,QAAQ,aAAa,UACnB,aAAa,GAAG,MAChB,aAAa,GAAG;AACxB,aAAS,KAAK,EAAE,OAAO,SAAS,CAAC;AAAA,EACnC,QAAQ;AACN,YAAQ,IAAI,oBAAoB,GAAG,EAAE;AAAA,EACvC;AACF;AAMA,SAAS,uBAA2C;AAClD,MAAI,QAAQ,SAAS,SAAU,QAAO;AACtC,QAAM,aAAa,eAAe,4BAA4B;AAC9D,MAAO,cAAW,UAAU,EAAG,QAAO;AACtC,SAAO;AACT;AAEA,SAAS,cAAc;AACrB,MAAI,SAAS,WAAW;AACxB,QAAM,mBAAmB,yCAAyC;AAClE,MAAI,CAAC,iBAAiB,SAAS;AAC7B,QAAI,iBAAiB,SAAS;AAC5B,cAAQ,IAAI,wDAAwD;AACpE,eAAS,WAAW;AAAA,IACtB,OAAO;AACL,cAAQ,MAAM,wDAAwD,iBAAiB,KAAK;AAAA,IAC9F;AAAA,EACF;AACA,QAAM,cAAc,OAAO,KAAK,iBAAiB;AACjD,QAAM,eAAe,IAAI,iBAAiB,WAAW;AACrD,eAAa,cAAc;AAE3B,MAAI,8BAA8B,KAAK,CAAC,kBAAkB,aAAa,eAAe,CAAC,GAAG;AACxF,2BAAuB;AACvB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,gBAAgB,qBAAqB;AAC3C,QAAM,YAAY,IAAI,gBAAgB,cAAc,EAAE,cAAc,CAAC;AACrE,SAAO,EAAE,QAAQ,cAAc,UAAU;AAC3C;AAEA,SAAS,yBAA+B;AACtC,UAAQ,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;AACnC,UAAQ,MAAM,4CAA4C;AAC1D,UAAQ,MAAM,IAAI,OAAO,EAAE,CAAC;AAC5B,UAAQ,MAAM,EAAE;AAChB,UAAQ,MAAM,oEAAoE;AAClF,UAAQ,MAAM,6CAA6C;AAC3D,UAAQ,MAAM,EAAE;AAChB,UAAQ,MAAM,0EAA0E;AACxF,UAAQ,MAAM,EAAE;AAChB,UAAQ,MAAM,+BAA+B;AAC7C,UAAQ,MAAM,EAAE;AAChB,UAAQ,MAAM,qEAAgE;AAC9E,UAAQ,MAAM,uCAAuC;AACrD,UAAQ,MAAM,mDAAmD;AACjE,UAAQ,MAAM,mCAAmC;AACjD,UAAQ,MAAM,aAAa;AAC3B,UAAQ,MAAM,2CAA2C;AACzD,UAAQ,MAAM,EAAE;AAChB,UAAQ,MAAM,IAAI,OAAO,EAAE,IAAI,IAAI;AACrC;AAEA,SAAS,gBAAgB,OAAsB,MAA6B;AAC1E,MAAI,CAAC,SAAS,CAAC,KAAM,QAAO;AAC5B,QAAM,OAAO,IAAI,KAAK,QAAQ,GAAI,EAAE,mBAAmB;AACvD,QAAM,KAAK,IAAI,KAAK,OAAO,GAAI,EAAE,mBAAmB;AACpD,SAAO,GAAG,IAAI,MAAM,EAAE;AACxB;AAEA,SAAS,WAAW,SAAmB,MAAuC;AAC5E,QAAM,SAAS,QAAQ,IAAI,CAAC,QAAQ;AAClC,UAAM,UAAU,KAAK,OAAO,CAAC,KAAK,QAAQ;AACxC,YAAM,MAAM,OAAO,IAAI,GAAG,KAAK,EAAE;AACjC,aAAO,KAAK,IAAI,KAAK,IAAI,MAAM;AAAA,IACjC,GAAG,CAAC;AACJ,WAAO,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI,SAAS,EAAE,CAAC;AAAA,EACnD,CAAC;AAED,QAAM,SAAS,QAAQ,IAAI,CAAC,KAAK,MAAM,IAAI,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK;AACxE,QAAM,YAAY,OAAO,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,KAAK;AAC7D,UAAQ,IAAI,MAAM;AAClB,UAAQ,IAAI,SAAS;AAErB,aAAW,OAAO,MAAM;AACtB,UAAM,OAAO,QACV,IAAI,CAAC,KAAK,MAAM;AACf,YAAM,MAAM,OAAO,IAAI,GAAG,KAAK,EAAE;AACjC,aAAO,IAAI,SAAS,KAAK,IAAI,MAAM,GAAG,EAAE,IAAI,QAAQ,IAAI,OAAO,OAAO,CAAC,CAAC;AAAA,IAC1E,CAAC,EACA,KAAK,KAAK;AACb,YAAQ,IAAI,IAAI;AAAA,EAClB;AACF;AAGO,SAAS,IAAI,MAAuB;AACzC,UAAQ,MAAM,IAAI;AACpB;AAGA,IAAM,cAAc,QAAQ,KAAK,CAAC,GAAG,SAAS,QAAQ,KAAK,QAAQ,KAAK,CAAC,GAAG,SAAS,QAAQ;AAC7F,IAAI,aAAa;AACf,MAAI;AACN;","names":[]}
@@ -4377,6 +4377,39 @@ function runMigrations(db, migrationsList, forceRepair = false) {
4377
4377
  return true;
4378
4378
  }
4379
4379
 
4380
+ // ../../packages/core/src/version.ts
4381
+ function parseStableVersion(version) {
4382
+ const normalized = version.trim().replace(/^v/i, "");
4383
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(normalized);
4384
+ if (!match) return null;
4385
+ return buildParsedVersion(match);
4386
+ }
4387
+ function parseComparableCurrentVersion(version) {
4388
+ const normalized = version.trim().replace(/^v/i, "");
4389
+ const match = /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?$/.exec(normalized);
4390
+ if (!match) return null;
4391
+ return {
4392
+ ...buildParsedVersion(match),
4393
+ prerelease: Boolean(match[4])
4394
+ };
4395
+ }
4396
+ function buildParsedVersion(match) {
4397
+ return {
4398
+ major: Number(match[1]),
4399
+ minor: Number(match[2]),
4400
+ patch: Number(match[3])
4401
+ };
4402
+ }
4403
+ function isNewerStableVersion(latest, current) {
4404
+ const latestVersion = parseStableVersion(latest);
4405
+ const currentVersion = parseComparableCurrentVersion(current);
4406
+ if (!latestVersion || !currentVersion) return false;
4407
+ if (latestVersion.major !== currentVersion.major) return latestVersion.major > currentVersion.major;
4408
+ if (latestVersion.minor !== currentVersion.minor) return latestVersion.minor > currentVersion.minor;
4409
+ if (latestVersion.patch !== currentVersion.patch) return latestVersion.patch > currentVersion.patch;
4410
+ return currentVersion.prerelease;
4411
+ }
4412
+
4380
4413
  // ../../packages/core/src/nlp/pos-tags.ts
4381
4414
  var POS_TAG_DEFINITIONS = [
4382
4415
  { tag: "n", name: "\u540D\u8BCD", description: "\u666E\u901A\u540D\u8BCD", meaningful: true },
@@ -14132,6 +14165,7 @@ function compileRules(rules, logger) {
14132
14165
  }
14133
14166
 
14134
14167
  // ../../packages/node-runtime/src/ai/preprocessor/builtin-rules.ts
14168
+ var DESENSITIZE_RULES_SCHEMA_VERSION = 2;
14135
14169
  var BUILTIN_DESENSITIZE_RULES = [
14136
14170
  // ==================== 中国 (zh-CN) ====================
14137
14171
  {
@@ -14141,7 +14175,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14141
14175
  replacement: "[\u624B\u673A\u53F7]",
14142
14176
  enabled: true,
14143
14177
  builtin: true,
14144
- locales: ["zh-CN"]
14178
+ locales: ["zh-CN"],
14179
+ group: "region_cn"
14145
14180
  },
14146
14181
  {
14147
14182
  id: "cn_id_card",
@@ -14150,25 +14185,28 @@ var BUILTIN_DESENSITIZE_RULES = [
14150
14185
  replacement: "[\u8EAB\u4EFD\u8BC1]",
14151
14186
  enabled: true,
14152
14187
  builtin: true,
14153
- locales: ["zh-CN"]
14188
+ locales: ["zh-CN"],
14189
+ group: "region_cn"
14154
14190
  },
14155
14191
  {
14156
14192
  id: "cn_bank_card",
14157
14193
  label: "desensitize.rules.cn_bank_card",
14158
14194
  pattern: "(?<!\\d)\\d{16,19}(?!\\d)",
14159
14195
  replacement: "[\u94F6\u884C\u5361]",
14160
- enabled: false,
14196
+ enabled: true,
14161
14197
  builtin: true,
14162
- locales: ["zh-CN"]
14198
+ locales: ["zh-CN"],
14199
+ group: "region_cn"
14163
14200
  },
14164
14201
  {
14165
14202
  id: "cn_landline",
14166
14203
  label: "desensitize.rules.cn_landline",
14167
14204
  pattern: "(?<!\\d)0\\d{2,3}-?\\d{7,8}(?!\\d)",
14168
14205
  replacement: "[\u5EA7\u673A\u53F7]",
14169
- enabled: false,
14206
+ enabled: true,
14170
14207
  builtin: true,
14171
- locales: ["zh-CN"]
14208
+ locales: ["zh-CN"],
14209
+ group: "region_cn"
14172
14210
  },
14173
14211
  // ==================== 美国 (en-US) ====================
14174
14212
  {
@@ -14178,7 +14216,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14178
14216
  replacement: "[SSN]",
14179
14217
  enabled: true,
14180
14218
  builtin: true,
14181
- locales: ["en-US"]
14219
+ locales: ["en-US"],
14220
+ group: "region_us"
14182
14221
  },
14183
14222
  {
14184
14223
  id: "us_phone",
@@ -14187,16 +14226,18 @@ var BUILTIN_DESENSITIZE_RULES = [
14187
14226
  replacement: "[Phone]",
14188
14227
  enabled: true,
14189
14228
  builtin: true,
14190
- locales: ["en-US"]
14229
+ locales: ["en-US"],
14230
+ group: "region_us"
14191
14231
  },
14192
14232
  {
14193
14233
  id: "us_drivers_license",
14194
14234
  label: "desensitize.rules.us_drivers_license",
14195
14235
  pattern: "(?<![A-Z])\\b[A-Z]\\d{7,8}\\b",
14196
14236
  replacement: "[Driver's License]",
14197
- enabled: false,
14237
+ enabled: true,
14198
14238
  builtin: true,
14199
- locales: ["en-US"]
14239
+ locales: ["en-US"],
14240
+ group: "region_us"
14200
14241
  },
14201
14242
  // ==================== 日本 (ja-JP) ====================
14202
14243
  {
@@ -14206,16 +14247,18 @@ var BUILTIN_DESENSITIZE_RULES = [
14206
14247
  replacement: "[\u96FB\u8A71\u756A\u53F7]",
14207
14248
  enabled: true,
14208
14249
  builtin: true,
14209
- locales: ["ja-JP"]
14250
+ locales: ["ja-JP"],
14251
+ group: "region_jp"
14210
14252
  },
14211
14253
  {
14212
14254
  id: "jp_my_number",
14213
14255
  label: "desensitize.rules.jp_my_number",
14214
14256
  pattern: "(?<!\\d)\\d{4}\\s?\\d{4}\\s?\\d{4}(?!\\d)",
14215
14257
  replacement: "[\u30DE\u30A4\u30CA\u30F3\u30D0\u30FC]",
14216
- enabled: false,
14258
+ enabled: true,
14217
14259
  builtin: true,
14218
- locales: ["ja-JP"]
14260
+ locales: ["ja-JP"],
14261
+ group: "region_jp"
14219
14262
  },
14220
14263
  // ==================== 韩国 (ko-KR) ====================
14221
14264
  {
@@ -14225,7 +14268,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14225
14268
  replacement: "[\uC804\uD654\uBC88\uD638]",
14226
14269
  enabled: true,
14227
14270
  builtin: true,
14228
- locales: ["ko-KR"]
14271
+ locales: ["ko-KR"],
14272
+ group: "region_kr"
14229
14273
  },
14230
14274
  {
14231
14275
  id: "kr_rrn",
@@ -14234,7 +14278,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14234
14278
  replacement: "[\uC8FC\uBBFC\uBC88\uD638]",
14235
14279
  enabled: true,
14236
14280
  builtin: true,
14237
- locales: ["ko-KR"]
14281
+ locales: ["ko-KR"],
14282
+ group: "region_kr"
14238
14283
  },
14239
14284
  // ==================== 凭据 / Token (所有语言) ====================
14240
14285
  {
@@ -14244,7 +14289,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14244
14289
  replacement: "[API Key]",
14245
14290
  enabled: true,
14246
14291
  builtin: true,
14247
- locales: []
14292
+ locales: [],
14293
+ group: "credentials"
14248
14294
  },
14249
14295
  {
14250
14296
  id: "bearer_token",
@@ -14253,7 +14299,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14253
14299
  replacement: "Bearer [Token]",
14254
14300
  enabled: true,
14255
14301
  builtin: true,
14256
- locales: []
14302
+ locales: [],
14303
+ group: "credentials"
14257
14304
  },
14258
14305
  // ==================== 通用 (所有语言) ====================
14259
14306
  {
@@ -14263,34 +14310,38 @@ var BUILTIN_DESENSITIZE_RULES = [
14263
14310
  replacement: "[Email]",
14264
14311
  enabled: true,
14265
14312
  builtin: true,
14266
- locales: []
14313
+ locales: [],
14314
+ group: "global_contact"
14267
14315
  },
14268
14316
  {
14269
14317
  id: "credit_card",
14270
14318
  label: "desensitize.rules.credit_card",
14271
14319
  pattern: "(?<!\\d)(?:4\\d{3}|5[1-5]\\d{2}|3[47]\\d{2}|6(?:011|5\\d{2}))[-\\s.]?\\d{4}[-\\s.]?\\d{4}[-\\s.]?\\d{4}(?!\\d)",
14272
14320
  replacement: "[Credit Card]",
14273
- enabled: false,
14321
+ enabled: true,
14274
14322
  builtin: true,
14275
- locales: []
14323
+ locales: [],
14324
+ group: "global_financial"
14276
14325
  },
14277
14326
  {
14278
14327
  id: "ipv4",
14279
14328
  label: "desensitize.rules.ipv4",
14280
14329
  pattern: "(?<!\\d)(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)(?!\\d)",
14281
14330
  replacement: "[IP]",
14282
- enabled: false,
14331
+ enabled: true,
14283
14332
  builtin: true,
14284
- locales: []
14333
+ locales: [],
14334
+ group: "global_network"
14285
14335
  },
14286
14336
  {
14287
14337
  id: "url",
14288
14338
  label: "desensitize.rules.url",
14289
14339
  pattern: 'https?://[^\\s<>"]+',
14290
14340
  replacement: "[URL]",
14291
- enabled: false,
14341
+ enabled: true,
14292
14342
  builtin: true,
14293
- locales: []
14343
+ locales: [],
14344
+ group: "global_network"
14294
14345
  }
14295
14346
  ];
14296
14347
  function getDefaultRulesForLocale(locale) {
@@ -14298,12 +14349,16 @@ function getDefaultRulesForLocale(locale) {
14298
14349
  (rule) => ({ ...rule })
14299
14350
  );
14300
14351
  }
14301
- function mergeRulesForLocale(existing, locale) {
14302
- const existingIds = new Set(existing.map((r) => r.id));
14303
- const newRules = BUILTIN_DESENSITIZE_RULES.filter(
14304
- (rule) => !existingIds.has(rule.id) && (rule.locales.length === 0 || rule.locales.includes(locale))
14305
- ).map((rule) => ({ ...rule }));
14306
- return [...existing, ...newRules];
14352
+ function applyDesensitizeRuleOverrides(rules, overrides = {}) {
14353
+ return rules.map((rule) => ({
14354
+ ...rule,
14355
+ enabled: overrides[rule.id] ?? rule.enabled
14356
+ }));
14357
+ }
14358
+ function mergeRulesForLocale(existing, locale, overrides = {}) {
14359
+ const customRules = existing.filter((rule) => !rule.builtin);
14360
+ const builtinRules = applyDesensitizeRuleOverrides(getDefaultRulesForLocale(locale), overrides);
14361
+ return [...builtinRules, ...customRules];
14307
14362
  }
14308
14363
 
14309
14364
  // ../../packages/node-runtime/src/ai/preprocessor/format.ts
@@ -17904,11 +17959,13 @@ var DEFAULTS = {
17904
17959
  pinnedSessionIds: [],
17905
17960
  aiPreprocessConfig: {
17906
17961
  dataCleaning: true,
17907
- mergeConsecutive: false,
17962
+ mergeConsecutive: true,
17908
17963
  mergeWindowSeconds: 180,
17909
17964
  blacklistKeywords: [],
17910
- denoise: false,
17911
- desensitize: false,
17965
+ denoise: true,
17966
+ desensitize: true,
17967
+ desensitizeRulesSchemaVersion: DESENSITIZE_RULES_SCHEMA_VERSION,
17968
+ desensitizeBuiltinRuleOverrides: {},
17912
17969
  desensitizeRules: [],
17913
17970
  anonymizeNames: false
17914
17971
  },
@@ -17947,7 +18004,11 @@ var PreferencesManager = class {
17947
18004
  if (fs35.existsSync(this.filePath)) {
17948
18005
  const raw = fs35.readFileSync(this.filePath, "utf-8");
17949
18006
  const parsed = JSON.parse(raw);
17950
- this.cache = this.mergeDefaults(parsed);
18007
+ const migrated = this.migrateLegacyDesensitizeRules(parsed, raw);
18008
+ this.cache = this.mergeDefaults(migrated.preferences);
18009
+ if (migrated.changed) {
18010
+ this.writePreferences(this.cache);
18011
+ }
17951
18012
  return this.cache;
17952
18013
  }
17953
18014
  } catch (err) {
@@ -17963,12 +18024,12 @@ var PreferencesManager = class {
17963
18024
  current,
17964
18025
  partial
17965
18026
  );
17966
- this.cache = merged;
17967
- const dir = path32.dirname(this.filePath);
17968
- if (!fs35.existsSync(dir)) {
17969
- fs35.mkdirSync(dir, { recursive: true });
18027
+ if (Object.prototype.hasOwnProperty.call(partial.aiPreprocessConfig ?? {}, "desensitizeBuiltinRuleOverrides")) {
18028
+ const mergedAiPreprocessConfig = merged.aiPreprocessConfig;
18029
+ mergedAiPreprocessConfig.desensitizeBuiltinRuleOverrides = partial.aiPreprocessConfig?.desensitizeBuiltinRuleOverrides;
17970
18030
  }
17971
- fs35.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
18031
+ this.cache = this.mergeDefaults(merged);
18032
+ this.writePreferences(this.cache);
17972
18033
  return { success: true };
17973
18034
  } catch (err) {
17974
18035
  const msg = err instanceof Error ? err.message : String(err);
@@ -17985,7 +18046,7 @@ var PreferencesManager = class {
17985
18046
  mergeDefaults(partial) {
17986
18047
  return {
17987
18048
  pinnedSessionIds: partial.pinnedSessionIds ?? DEFAULTS.pinnedSessionIds,
17988
- aiPreprocessConfig: partial.aiPreprocessConfig ? { ...DEFAULTS.aiPreprocessConfig, ...partial.aiPreprocessConfig } : { ...DEFAULTS.aiPreprocessConfig },
18049
+ aiPreprocessConfig: partial.aiPreprocessConfig ? this.normalizeAiPreprocessConfig(partial.aiPreprocessConfig) : { ...DEFAULTS.aiPreprocessConfig },
17989
18050
  aiGlobalSettings: partial.aiGlobalSettings ? {
17990
18051
  ...DEFAULTS.aiGlobalSettings,
17991
18052
  ...partial.aiGlobalSettings,
@@ -18000,6 +18061,75 @@ var PreferencesManager = class {
18000
18061
  filterHistory: partial.filterHistory ?? DEFAULTS.filterHistory
18001
18062
  };
18002
18063
  }
18064
+ normalizeAiPreprocessConfig(partial) {
18065
+ const defaults = DEFAULTS.aiPreprocessConfig;
18066
+ const rules = Array.isArray(partial?.desensitizeRules) ? partial.desensitizeRules : [];
18067
+ const customRules = rules.filter((rule) => !rule.builtin);
18068
+ return {
18069
+ dataCleaning: partial?.dataCleaning ?? defaults.dataCleaning,
18070
+ mergeConsecutive: partial?.mergeConsecutive ?? defaults.mergeConsecutive,
18071
+ mergeWindowSeconds: partial?.mergeWindowSeconds ?? defaults.mergeWindowSeconds,
18072
+ blacklistKeywords: partial?.blacklistKeywords ?? defaults.blacklistKeywords,
18073
+ denoise: partial?.denoise ?? defaults.denoise,
18074
+ desensitize: partial?.desensitize ?? defaults.desensitize,
18075
+ desensitizeRulesSchemaVersion: DESENSITIZE_RULES_SCHEMA_VERSION,
18076
+ desensitizeBuiltinRuleOverrides: this.normalizeBooleanMap(partial?.desensitizeBuiltinRuleOverrides),
18077
+ desensitizeRules: customRules,
18078
+ anonymizeNames: partial?.anonymizeNames ?? defaults.anonymizeNames
18079
+ };
18080
+ }
18081
+ normalizeBooleanMap(value) {
18082
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
18083
+ const result = {};
18084
+ for (const [key, flag] of Object.entries(value)) {
18085
+ if (typeof flag === "boolean") result[key] = flag;
18086
+ }
18087
+ return result;
18088
+ }
18089
+ migrateLegacyDesensitizeRules(partial, rawContent) {
18090
+ const rules = partial.aiPreprocessConfig?.desensitizeRules;
18091
+ if (!Array.isArray(rules) || !rules.some((rule) => rule.builtin)) {
18092
+ return { preferences: partial, changed: false };
18093
+ }
18094
+ this.backupLegacyPreferences(rawContent);
18095
+ const legacyBuiltinRuleOverrides = rules.filter((rule) => rule.builtin && typeof rule.enabled === "boolean").reduce((overrides, rule) => {
18096
+ overrides[rule.id] = rule.enabled;
18097
+ return overrides;
18098
+ }, {});
18099
+ const existingOverrides = this.normalizeBooleanMap(partial.aiPreprocessConfig?.desensitizeBuiltinRuleOverrides);
18100
+ return {
18101
+ preferences: {
18102
+ ...partial,
18103
+ aiPreprocessConfig: {
18104
+ ...partial.aiPreprocessConfig,
18105
+ desensitizeRulesSchemaVersion: DESENSITIZE_RULES_SCHEMA_VERSION,
18106
+ desensitizeBuiltinRuleOverrides: {
18107
+ ...legacyBuiltinRuleOverrides,
18108
+ ...existingOverrides
18109
+ },
18110
+ desensitizeRules: rules.filter((rule) => !rule.builtin)
18111
+ }
18112
+ },
18113
+ changed: true
18114
+ };
18115
+ }
18116
+ backupLegacyPreferences(rawContent) {
18117
+ const backupDir = path32.join(path32.dirname(this.filePath), "backups");
18118
+ if (!fs35.existsSync(backupDir)) {
18119
+ fs35.mkdirSync(backupDir, { recursive: true });
18120
+ }
18121
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
18122
+ const backupPath = path32.join(backupDir, `preferences-pre-desensitize-groups-${timestamp}.json`);
18123
+ fs35.writeFileSync(backupPath, rawContent, "utf-8");
18124
+ console.info(`[Preferences] Backed up legacy desensitize rules to ${backupPath}`);
18125
+ }
18126
+ writePreferences(preferences) {
18127
+ const dir = path32.dirname(this.filePath);
18128
+ if (!fs35.existsSync(dir)) {
18129
+ fs35.mkdirSync(dir, { recursive: true });
18130
+ }
18131
+ fs35.writeFileSync(this.filePath, JSON.stringify(preferences, null, 2), "utf-8");
18132
+ }
18003
18133
  /**
18004
18134
  * Deep merge: arrays are replaced (not concatenated), objects are merged.
18005
18135
  */
@@ -18794,6 +18924,7 @@ export {
18794
18924
  getClusterGraph,
18795
18925
  getRelationshipStats,
18796
18926
  getLanguagePreferenceAnalysis,
18927
+ isNewerStableVersion,
18797
18928
  POS_TAG_DEFINITIONS,
18798
18929
  BUILTIN_TOOL_CATALOG,
18799
18930
  BUILTIN_PROVIDERS,
@@ -18874,4 +19005,4 @@ export {
18874
19005
  createDatabaseManagerAdapter,
18875
19006
  MergeSessionCache
18876
19007
  };
18877
- //# sourceMappingURL=chunk-XJJXSOLO.mjs.map
19008
+ //# sourceMappingURL=chunk-N5DGZQB4.mjs.map