chatlab-cli 0.21.0 → 0.22.0

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 (69) hide show
  1. package/README.md +5 -5
  2. package/dist/{chunk-XUHPCRVT.mjs → chunk-3UBWLWXB.mjs} +2 -2
  3. package/dist/{chunk-EWCSGYVT.mjs → chunk-4U45E7TO.mjs} +2 -2
  4. package/dist/{chunk-NB4BIAYM.mjs → chunk-6GLMVWGA.mjs} +24 -15
  5. package/dist/chunk-6GLMVWGA.mjs.map +1 -0
  6. package/dist/{chunk-2AIOU3PW.mjs → chunk-6NB475AA.mjs} +12 -3
  7. package/dist/chunk-6NB475AA.mjs.map +1 -0
  8. package/dist/{chunk-FBTHZU55.mjs → chunk-AMMSVJG4.mjs} +507 -115
  9. package/dist/chunk-AMMSVJG4.mjs.map +1 -0
  10. package/dist/{chunk-3DIWXYMJ.mjs → chunk-D2XJ4Z7G.mjs} +2 -2
  11. package/dist/{chunk-EIRLEPXP.mjs → chunk-QJQJIUUS.mjs} +107 -96
  12. package/dist/chunk-QJQJIUUS.mjs.map +1 -0
  13. package/dist/chunk-VSOWNKPA.mjs +49 -0
  14. package/dist/cli.mjs +4 -3
  15. package/dist/{http-QVYJ5HER.mjs → http-R5UECMPJ.mjs} +8 -7
  16. package/dist/{import-AJKL6JPR.mjs → import-3LOWRXWA.mjs} +5 -4
  17. package/dist/index.mjs +8 -7
  18. package/dist/{mcp-FRF3YAN5.mjs → mcp-WBHNLED2.mjs} +8 -7
  19. package/dist/mcp-WBHNLED2.mjs.map +1 -0
  20. package/dist/{src-KOIGBZJX.mjs → src-UHM2UDRD.mjs} +3 -2
  21. package/dist/{stream-import-URGOCXUH.mjs → stream-import-V6Z7DXSW.mjs} +4 -3
  22. package/dist/stream-import-V6Z7DXSW.mjs.map +1 -0
  23. package/dist/update-checker-MQMQ6A4B.mjs +191 -0
  24. package/dist/update-checker-MQMQ6A4B.mjs.map +1 -0
  25. package/dist-web/assets/{DebugToolsPanel-dUV6pVqD.css → DebugToolsPanel-DkbsB6lr.css} +1 -1
  26. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-CxYyne-Q.js +147 -0
  27. package/dist-web/assets/{EChartWordcloud-BYLFxPAX.js → EChartWordcloud-Ubal_aYg.js} +1 -1
  28. package/dist-web/assets/{MessageExportModal-BAf68-wB.js → MessageExportModal-De5_tX6a.js} +1 -1
  29. package/dist-web/assets/{electron-CTK4NJag.js → electron-CRSL7qPM.js} +1 -1
  30. package/dist-web/assets/electron-H65bUnhW.js +1 -0
  31. package/dist-web/assets/{fetch-Ayp337Ng.js → fetch-Q-PqsiMb.js} +1 -1
  32. package/dist-web/assets/fetch-cFjLAPG2.js +1 -0
  33. package/dist-web/assets/{index-CmU6x-uJ.css → index-BE64tUWq.css} +1 -1
  34. package/dist-web/assets/index-BJUGnYn5.js +24 -0
  35. package/dist-web/assets/index-B_31fXeB.js +2 -0
  36. package/dist-web/assets/index-CbOwHYzO.css +1 -0
  37. package/dist-web/assets/index-CbQMfheg.css +1 -0
  38. package/dist-web/assets/index-DKMUn8F5.js +173 -0
  39. package/dist-web/assets/{index-Bd5r2oFG.js → index-D_ksezTw.js} +4 -4
  40. package/dist-web/assets/index-Dhg3SDb6.js +7 -0
  41. package/dist-web/assets/logo-DXIuG_WE.svg +17 -0
  42. package/dist-web/assets/{vendor-echarts-C13vDgZW.js → vendor-echarts-C5u62NnM.js} +1 -1
  43. package/dist-web/assets/{vendor-echarts-wordcloud-uVdwYsYa.js → vendor-echarts-wordcloud-CZ9gsX8l.js} +1 -1
  44. package/dist-web/assets/{vendor-nuxt-ui-CYMNNdeD.js → vendor-nuxt-ui-7OAuiz_i.js} +1 -1
  45. package/dist-web/assets/{vendor-zrender-5DcYjSZk.js → vendor-zrender-DFl4U2Oe.js} +1 -1
  46. package/dist-web/assets/{web-C5yfcwjn.js → web-CZe0Ismu.js} +1 -1
  47. package/dist-web/index.html +3 -3
  48. package/package.json +4 -9
  49. package/dist/chunk-2AIOU3PW.mjs.map +0 -1
  50. package/dist/chunk-EIRLEPXP.mjs.map +0 -1
  51. package/dist/chunk-FBTHZU55.mjs.map +0 -1
  52. package/dist/chunk-NB4BIAYM.mjs.map +0 -1
  53. package/dist/mcp-FRF3YAN5.mjs.map +0 -1
  54. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-DFN1Co1H.js +0 -155
  55. package/dist-web/assets/electron-BHjOx6B6.js +0 -1
  56. package/dist-web/assets/fetch-BWtpQwBZ.js +0 -1
  57. package/dist-web/assets/index-BfZM-q0T.js +0 -7
  58. package/dist-web/assets/index-CLGv8Den.css +0 -1
  59. package/dist-web/assets/index-CLYHD_f4.js +0 -2
  60. package/dist-web/assets/index-CaTRpNBK.js +0 -16
  61. package/dist-web/assets/index-CzR1sElx.css +0 -1
  62. package/dist-web/assets/index-Ksa--o23.js +0 -173
  63. /package/dist/{chunk-XUHPCRVT.mjs.map → chunk-3UBWLWXB.mjs.map} +0 -0
  64. /package/dist/{chunk-EWCSGYVT.mjs.map → chunk-4U45E7TO.mjs.map} +0 -0
  65. /package/dist/{chunk-3DIWXYMJ.mjs.map → chunk-D2XJ4Z7G.mjs.map} +0 -0
  66. /package/dist/{http-QVYJ5HER.mjs.map → chunk-VSOWNKPA.mjs.map} +0 -0
  67. /package/dist/{import-AJKL6JPR.mjs.map → http-R5UECMPJ.mjs.map} +0 -0
  68. /package/dist/{src-KOIGBZJX.mjs.map → import-3LOWRXWA.mjs.map} +0 -0
  69. /package/dist/{stream-import-URGOCXUH.mjs.map → src-UHM2UDRD.mjs.map} +0 -0
@@ -7,20 +7,21 @@ const __dirname = __pathDirname(__filename);
7
7
  import {
8
8
  importData,
9
9
  parseFile
10
- } from "./chunk-XUHPCRVT.mjs";
10
+ } from "./chunk-3UBWLWXB.mjs";
11
11
  import {
12
12
  analyzeIncrementalImport,
13
13
  analyzeNewImport,
14
14
  incrementalImport,
15
15
  streamImport
16
- } from "./chunk-EWCSGYVT.mjs";
16
+ } from "./chunk-4U45E7TO.mjs";
17
17
  import {
18
18
  AGENT_TOOL_REGISTRY,
19
19
  CoreDataProvider
20
- } from "./chunk-3DIWXYMJ.mjs";
20
+ } from "./chunk-D2XJ4Z7G.mjs";
21
21
  import {
22
- getVersion
23
- } from "./chunk-2AIOU3PW.mjs";
22
+ getVersion,
23
+ resolveCliPath
24
+ } from "./chunk-6NB475AA.mjs";
24
25
  import {
25
26
  AIConversationManager,
26
27
  ALL_MIGRATIONS,
@@ -114,11 +115,10 @@ import {
114
115
  verifyCliDataPath,
115
116
  writeAuthProfile,
116
117
  writeConfigField
117
- } from "./chunk-EIRLEPXP.mjs";
118
+ } from "./chunk-QJQJIUUS.mjs";
118
119
 
119
120
  // src/http/index.ts
120
121
  import * as fs14 from "fs";
121
- import * as path15 from "path";
122
122
  import * as crypto5 from "crypto";
123
123
 
124
124
  // src/http/server.ts
@@ -207,6 +207,10 @@ async function authHook(request, reply) {
207
207
  if (!cachedToken) return;
208
208
  if (request.url.startsWith("/_web/")) return;
209
209
  if (webModeEnabled && !request.url.startsWith("/api/")) return;
210
+ return requireBearerToken(request, reply);
211
+ }
212
+ function requireBearerToken(request, reply) {
213
+ if (!cachedToken) return;
210
214
  const authHeader = request.headers.authorization;
211
215
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
212
216
  const err = unauthorized();
@@ -492,7 +496,7 @@ function registerSessionRoutes(server2, dbManager2) {
492
496
 
493
497
  // src/http/routes/web/index.ts
494
498
  import * as os3 from "os";
495
- import * as path7 from "path";
499
+ import * as path6 from "path";
496
500
 
497
501
  // src/merger/merge-cache.ts
498
502
  import * as fs from "fs";
@@ -653,7 +657,6 @@ function registerMemberRoutes(server2, adapter) {
653
657
 
654
658
  // src/http/routes/web/helpers.ts
655
659
  import * as fs2 from "fs";
656
- import * as path2 from "path";
657
660
  function parseTimeFilter(query) {
658
661
  const { startTs, endTs, memberId } = query;
659
662
  if (!startTs && !endTs && !memberId) return void 0;
@@ -665,7 +668,7 @@ function parseTimeFilter(query) {
665
668
  }
666
669
  function resolveNativeBinding() {
667
670
  if (process.versions.electron) return void 0;
668
- const nativePath = path2.resolve(__dirname, "../../../../native/better_sqlite3.node");
671
+ const nativePath = resolveCliPath("native/better_sqlite3.node");
669
672
  if (fs2.existsSync(nativePath)) return nativePath;
670
673
  return void 0;
671
674
  }
@@ -849,6 +852,11 @@ function registerSessionIndexRoutes(server2, adapter) {
849
852
  const sessionCount = session_index_service_exports.generateIndex(adapter, request.params.id, gapThreshold);
850
853
  return { sessionCount };
851
854
  });
855
+ server2.post("/_web/sessions/:id/generate-incremental-index", async (request) => {
856
+ const gapThreshold = request.body?.gapThreshold ?? 1800;
857
+ const sessionCount = session_index_service_exports.generateIncrementalIndex(adapter, request.params.id, gapThreshold);
858
+ return { sessionCount };
859
+ });
852
860
  server2.post("/_web/sessions/:id/clear-index", async (request) => {
853
861
  session_index_service_exports.clearIndex(adapter, request.params.id);
854
862
  return { success: true };
@@ -874,10 +882,10 @@ function registerSessionIndexRoutes(server2, adapter) {
874
882
 
875
883
  // src/ai/llm-config.ts
876
884
  import * as fs3 from "fs";
877
- import * as path3 from "path";
885
+ import * as path2 from "path";
878
886
  import { randomUUID as randomUUID2 } from "crypto";
879
887
  function loadLlmConfig(aiDataDir) {
880
- const configPath = path3.join(aiDataDir, "llm-config.json");
888
+ const configPath = path2.join(aiDataDir, "llm-config.json");
881
889
  if (!fs3.existsSync(configPath)) {
882
890
  return { configs: [], defaultAssistant: null, fastModel: null };
883
891
  }
@@ -919,7 +927,7 @@ function getDefaultAssistantConfig(aiDataDir) {
919
927
  }
920
928
  var MAX_CONFIG_COUNT = 99;
921
929
  function saveLlmConfig(aiDataDir, store) {
922
- const configPath = path3.join(aiDataDir, "llm-config.json");
930
+ const configPath = path2.join(aiDataDir, "llm-config.json");
923
931
  const toSave = {
924
932
  ...store,
925
933
  configs: store.configs.map((c) => {
@@ -1023,7 +1031,7 @@ function setFastModelSlot(aiDataDir, slot) {
1023
1031
  return { success: true };
1024
1032
  }
1025
1033
  function loadRawConfigStore(aiDataDir) {
1026
- const configPath = path3.join(aiDataDir, "llm-config.json");
1034
+ const configPath = path2.join(aiDataDir, "llm-config.json");
1027
1035
  if (!fs3.existsSync(configPath)) {
1028
1036
  return { configs: [], defaultAssistant: null, fastModel: null };
1029
1037
  }
@@ -1083,13 +1091,13 @@ function registerSummaryRoutes(server2, dbManager2, adapter) {
1083
1091
  // src/http/routes/web/import.ts
1084
1092
  import * as fs4 from "fs";
1085
1093
  import * as os from "os";
1086
- import * as path4 from "path";
1094
+ import * as path3 from "path";
1087
1095
  var DEMO_BASE_URL = "https://chatlab.fun/assets/demo";
1088
1096
  function cleanupTemp(...paths) {
1089
1097
  for (const p of paths) {
1090
1098
  try {
1091
- const stat = fs4.statSync(p);
1092
- if (stat.isDirectory()) {
1099
+ const stat2 = fs4.statSync(p);
1100
+ if (stat2.isDirectory()) {
1093
1101
  fs4.rmSync(p, { recursive: true, force: true });
1094
1102
  } else {
1095
1103
  fs4.unlinkSync(p);
@@ -1105,8 +1113,8 @@ function registerImportRoutes(server2, dbManager2) {
1105
1113
  server2.post("/_web/detect-format", async (request, reply) => {
1106
1114
  const data = await request.file();
1107
1115
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1108
- const tmpDir = fs4.mkdtempSync(path4.join(os.tmpdir(), "chatlab-detect-"));
1109
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
1116
+ const tmpDir = fs4.mkdtempSync(path3.join(os.tmpdir(), "chatlab-detect-"));
1117
+ const tmpPath = path3.join(tmpDir, data.filename || "upload");
1110
1118
  try {
1111
1119
  const chunks = [];
1112
1120
  for await (const chunk of data.file) {
@@ -1123,8 +1131,8 @@ function registerImportRoutes(server2, dbManager2) {
1123
1131
  server2.post("/_web/scan-multi-chat", async (request, reply) => {
1124
1132
  const data = await request.file();
1125
1133
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1126
- const tmpDir = fs4.mkdtempSync(path4.join(os.tmpdir(), "chatlab-scan-"));
1127
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
1134
+ const tmpDir = fs4.mkdtempSync(path3.join(os.tmpdir(), "chatlab-scan-"));
1135
+ const tmpPath = path3.join(tmpDir, data.filename || "upload");
1128
1136
  try {
1129
1137
  const chunks = [];
1130
1138
  for await (const chunk of data.file) {
@@ -1140,8 +1148,8 @@ function registerImportRoutes(server2, dbManager2) {
1140
1148
  server2.post("/_web/import", async (request, reply) => {
1141
1149
  const data = await request.file();
1142
1150
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1143
- const tmpDir = fs4.mkdtempSync(path4.join(os.tmpdir(), "chatlab-import-"));
1144
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
1151
+ const tmpDir = fs4.mkdtempSync(path3.join(os.tmpdir(), "chatlab-import-"));
1152
+ const tmpPath = path3.join(tmpDir, data.filename || "upload");
1145
1153
  const chunks = [];
1146
1154
  for await (const chunk of data.file) {
1147
1155
  chunks.push(chunk);
@@ -1189,7 +1197,7 @@ data: ${JSON.stringify(eventData)}
1189
1197
  server2.post("/_web/import-directory", async (request, reply) => {
1190
1198
  const parts = request.parts();
1191
1199
  if (!parts) return reply.code(400).send({ error: "No files uploaded" });
1192
- const tmpDir = fs4.mkdtempSync(path4.join(os.tmpdir(), "chatlab-dir-import-"));
1200
+ const tmpDir = fs4.mkdtempSync(path3.join(os.tmpdir(), "chatlab-dir-import-"));
1193
1201
  const relativePaths = [];
1194
1202
  const fileBuffers = [];
1195
1203
  try {
@@ -1217,9 +1225,9 @@ data: ${JSON.stringify(eventData)}
1217
1225
  if (segments.length > 1) {
1218
1226
  relPath = segments.slice(1).join("/");
1219
1227
  }
1220
- const targetPath = path4.resolve(tmpDir, relPath);
1221
- if (!targetPath.startsWith(tmpDir + path4.sep)) continue;
1222
- fs4.mkdirSync(path4.dirname(targetPath), { recursive: true });
1228
+ const targetPath = path3.resolve(tmpDir, relPath);
1229
+ if (!targetPath.startsWith(tmpDir + path3.sep)) continue;
1230
+ fs4.mkdirSync(path3.dirname(targetPath), { recursive: true });
1223
1231
  fs4.writeFileSync(targetPath, fileBuffers[i].data);
1224
1232
  }
1225
1233
  const entryPath = findEntryFileInDirectory(tmpDir);
@@ -1265,8 +1273,8 @@ data: ${JSON.stringify({ success: false, error: err instanceof Error ? err.messa
1265
1273
  const sessionId = request.params.id;
1266
1274
  const data = await request.file();
1267
1275
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1268
- const tmpDir = fs4.mkdtempSync(path4.join(os.tmpdir(), "chatlab-inc-"));
1269
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
1276
+ const tmpDir = fs4.mkdtempSync(path3.join(os.tmpdir(), "chatlab-inc-"));
1277
+ const tmpPath = path3.join(tmpDir, data.filename || "upload");
1270
1278
  const chunks = [];
1271
1279
  for await (const chunk of data.file) {
1272
1280
  chunks.push(chunk);
@@ -1303,8 +1311,8 @@ data: ${JSON.stringify(eventData)}
1303
1311
  const sessionId = request.params.id;
1304
1312
  const data = await request.file();
1305
1313
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1306
- const tmpDir = fs4.mkdtempSync(path4.join(os.tmpdir(), "chatlab-analyze-"));
1307
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
1314
+ const tmpDir = fs4.mkdtempSync(path3.join(os.tmpdir(), "chatlab-analyze-"));
1315
+ const tmpPath = path3.join(tmpDir, data.filename || "upload");
1308
1316
  const chunks = [];
1309
1317
  for await (const chunk of data.file) {
1310
1318
  chunks.push(chunk);
@@ -1321,8 +1329,8 @@ data: ${JSON.stringify(eventData)}
1321
1329
  server2.post("/_web/import/analyze", async (request, reply) => {
1322
1330
  const data = await request.file();
1323
1331
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1324
- const tmpDir = fs4.mkdtempSync(path4.join(os.tmpdir(), "chatlab-analyze-"));
1325
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
1332
+ const tmpDir = fs4.mkdtempSync(path3.join(os.tmpdir(), "chatlab-analyze-"));
1333
+ const tmpPath = path3.join(tmpDir, data.filename || "upload");
1326
1334
  const chunks = [];
1327
1335
  for await (const chunk of data.file) {
1328
1336
  chunks.push(chunk);
@@ -1350,9 +1358,9 @@ data: ${JSON.stringify(eventData)}
1350
1358
 
1351
1359
  `);
1352
1360
  }
1353
- const tmpDir = fs4.mkdtempSync(path4.join(os.tmpdir(), "chatlab-demo-"));
1354
- const groupPath = path4.join(tmpDir, "demo-group.json");
1355
- const privatePath = path4.join(tmpDir, "demo-private.json");
1361
+ const tmpDir = fs4.mkdtempSync(path3.join(os.tmpdir(), "chatlab-demo-"));
1362
+ const groupPath = path3.join(tmpDir, "demo-group.json");
1363
+ const privatePath = path3.join(tmpDir, "demo-private.json");
1356
1364
  try {
1357
1365
  sendEvent("progress", { stage: "downloading", current: 1, total: 2 });
1358
1366
  const groupResp = await fetch(`${DEMO_BASE_URL}/${locale}/demo-group.json`, {
@@ -1392,13 +1400,13 @@ data: ${JSON.stringify(eventData)}
1392
1400
  // src/http/routes/web/merge.ts
1393
1401
  import * as fs5 from "fs";
1394
1402
  import * as os2 from "os";
1395
- import * as path5 from "path";
1403
+ import * as path4 from "path";
1396
1404
  function registerMergeRoutes(server2, dbManager2, mergeCache) {
1397
1405
  server2.post("/_web/merge/parse", async (request, reply) => {
1398
1406
  const data = await request.file();
1399
1407
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1400
- const tmpDir = fs5.mkdtempSync(path5.join(os2.tmpdir(), "chatlab-merge-"));
1401
- const tmpPath = path5.join(tmpDir, data.filename || "upload");
1408
+ const tmpDir = fs5.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-merge-"));
1409
+ const tmpPath = path4.join(tmpDir, data.filename || "upload");
1402
1410
  const chunks = [];
1403
1411
  for await (const chunk of data.file) {
1404
1412
  chunks.push(chunk);
@@ -1407,7 +1415,7 @@ function registerMergeRoutes(server2, dbManager2, mergeCache) {
1407
1415
  try {
1408
1416
  const result = await streamParseFileInfo(tmpPath, {
1409
1417
  createTempDatabase(sourceFilePath) {
1410
- return mergeCache.createTempDatabase(path5.basename(sourceFilePath));
1418
+ return mergeCache.createTempDatabase(path4.basename(sourceFilePath));
1411
1419
  },
1412
1420
  onProgress() {
1413
1421
  }
@@ -1472,10 +1480,10 @@ function registerMergeRoutes(server2, dbManager2, mergeCache) {
1472
1480
  const merged = buildMergedOutput(dataSources, outputName);
1473
1481
  let sessionId;
1474
1482
  if (andImport) {
1475
- const { streamImport: serverStreamImport } = await import("./stream-import-URGOCXUH.mjs");
1483
+ const { streamImport: serverStreamImport } = await import("./stream-import-V6Z7DXSW.mjs");
1476
1484
  const jsonData = JSON.stringify(merged.chatLabData);
1477
- const tmpDir = fs5.mkdtempSync(path5.join(os2.tmpdir(), "chatlab-merged-"));
1478
- const tmpPath = path5.join(tmpDir, `${outputName}.json`);
1485
+ const tmpDir = fs5.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-merged-"));
1486
+ const tmpPath = path4.join(tmpDir, `${outputName}.json`);
1479
1487
  fs5.writeFileSync(tmpPath, jsonData, "utf-8");
1480
1488
  try {
1481
1489
  const importResult = await serverStreamImport(dbManager2, tmpPath);
@@ -1525,7 +1533,7 @@ function registerMergeRoutes(server2, dbManager2, mergeCache) {
1525
1533
  const db = ensureDb2(dbManager2, sid);
1526
1534
  const exported = exportSessionToJson(db);
1527
1535
  const { db: tempDb, tempDbPath } = mergeCache.createTempDatabase(exported.meta.name);
1528
- const { TempDbWriter } = await import("./src-KOIGBZJX.mjs");
1536
+ const { TempDbWriter } = await import("./src-UHM2UDRD.mjs");
1529
1537
  const writer = new TempDbWriter(tempDb);
1530
1538
  writer.writeMeta({
1531
1539
  name: exported.meta.name,
@@ -1587,8 +1595,192 @@ function registerExportRoutes(server2, adapter) {
1587
1595
 
1588
1596
  // src/http/routes/web/cache.ts
1589
1597
  import * as fs6 from "fs";
1590
- import * as path6 from "path";
1591
- function registerCacheRoutes(server2, downloadsDir) {
1598
+ import * as fsp from "fs/promises";
1599
+ import * as path5 from "path";
1600
+ import { execFile } from "child_process";
1601
+ function runExecFile(file, args, runner = execFile) {
1602
+ return new Promise((resolve2, reject) => {
1603
+ runner(file, args, (error) => {
1604
+ if (error) {
1605
+ reject(error);
1606
+ return;
1607
+ }
1608
+ resolve2();
1609
+ });
1610
+ });
1611
+ }
1612
+ async function openDirectoryPath(dirPath, platform = process.platform, runner) {
1613
+ if (platform === "darwin") {
1614
+ await runExecFile("open", [dirPath], runner);
1615
+ } else if (platform === "win32") {
1616
+ await runExecFile("explorer.exe", [dirPath], runner);
1617
+ } else {
1618
+ await runExecFile("xdg-open", [dirPath], runner);
1619
+ }
1620
+ }
1621
+ async function showPathInFolder(filePath, platform = process.platform, runner) {
1622
+ if (platform === "darwin") {
1623
+ await runExecFile("open", ["-R", filePath], runner);
1624
+ } else if (platform === "win32") {
1625
+ await runExecFile("explorer.exe", [`/select,${filePath}`], runner);
1626
+ } else {
1627
+ await runExecFile("xdg-open", [path5.dirname(filePath)], runner);
1628
+ }
1629
+ }
1630
+ async function getDirSize(dirPath) {
1631
+ let totalSize = 0;
1632
+ try {
1633
+ if (!fs6.existsSync(dirPath)) return 0;
1634
+ const files = await fsp.readdir(dirPath, { withFileTypes: true });
1635
+ for (const file of files) {
1636
+ const filePath = path5.join(dirPath, file.name);
1637
+ if (file.isDirectory()) {
1638
+ totalSize += await getDirSize(filePath);
1639
+ } else {
1640
+ const stat2 = await fsp.stat(filePath);
1641
+ totalSize += stat2.size;
1642
+ }
1643
+ }
1644
+ } catch {
1645
+ }
1646
+ return totalSize;
1647
+ }
1648
+ async function getFileCount(dirPath) {
1649
+ let count = 0;
1650
+ try {
1651
+ if (!fs6.existsSync(dirPath)) return 0;
1652
+ const files = await fsp.readdir(dirPath, { withFileTypes: true });
1653
+ for (const file of files) {
1654
+ const filePath = path5.join(dirPath, file.name);
1655
+ if (file.isDirectory()) {
1656
+ count += await getFileCount(filePath);
1657
+ } else {
1658
+ count++;
1659
+ }
1660
+ }
1661
+ } catch {
1662
+ }
1663
+ return count;
1664
+ }
1665
+ function registerCacheRoutes(server2, pathProvider) {
1666
+ const downloadsDir = pathProvider.getDownloadsDir();
1667
+ server2.get("/_web/cache/info", async () => {
1668
+ const directories = [
1669
+ {
1670
+ id: "databases",
1671
+ name: "settings.storage.cache.databases.name",
1672
+ description: "settings.storage.cache.databases.description",
1673
+ path: pathProvider.getDatabaseDir(),
1674
+ icon: "i-heroicons-circle-stack",
1675
+ canClear: false
1676
+ },
1677
+ {
1678
+ id: "ai",
1679
+ name: "settings.storage.cache.ai.name",
1680
+ description: "settings.storage.cache.ai.description",
1681
+ path: pathProvider.getAiDataDir(),
1682
+ icon: "i-heroicons-sparkles",
1683
+ canClear: false
1684
+ },
1685
+ {
1686
+ id: "cache",
1687
+ name: "settings.storage.cache.statsCache.name",
1688
+ description: "settings.storage.cache.statsCache.description",
1689
+ path: pathProvider.getCacheDir(),
1690
+ icon: "i-heroicons-bolt",
1691
+ canClear: true
1692
+ },
1693
+ {
1694
+ id: "logs",
1695
+ name: "settings.storage.cache.logs.name",
1696
+ description: "settings.storage.cache.logs.description",
1697
+ path: pathProvider.getLogsDir(),
1698
+ icon: "i-heroicons-document-text",
1699
+ canClear: true
1700
+ }
1701
+ ];
1702
+ const results = await Promise.all(
1703
+ directories.map(async (dir) => {
1704
+ const [size, fileCount] = await Promise.all([getDirSize(dir.path), getFileCount(dir.path)]);
1705
+ return { ...dir, size, fileCount, exists: fs6.existsSync(dir.path) };
1706
+ })
1707
+ );
1708
+ return {
1709
+ baseDir: pathProvider.getUserDataDir(),
1710
+ systemDir: pathProvider.getSystemDir(),
1711
+ directories: results,
1712
+ totalSize: results.reduce((sum, d) => sum + d.size, 0)
1713
+ };
1714
+ });
1715
+ server2.post("/_web/cache/clear", async (request) => {
1716
+ const { cacheId } = request.body;
1717
+ const allowedDirs = {
1718
+ cache: pathProvider.getCacheDir(),
1719
+ logs: pathProvider.getLogsDir()
1720
+ };
1721
+ const dirPath = allowedDirs[cacheId];
1722
+ if (!dirPath) return { success: false, error: "Not allowed to clear this directory" };
1723
+ if (!fs6.existsSync(dirPath)) return { success: true };
1724
+ const files = await fsp.readdir(dirPath);
1725
+ for (const file of files) {
1726
+ const filePath = path5.join(dirPath, file);
1727
+ const stat2 = await fsp.stat(filePath);
1728
+ if (stat2.isDirectory()) {
1729
+ await fsp.rm(filePath, { recursive: true });
1730
+ } else {
1731
+ await fsp.unlink(filePath);
1732
+ }
1733
+ }
1734
+ return { success: true };
1735
+ });
1736
+ server2.post("/_web/cache/open-dir", async (request) => {
1737
+ const { cacheId } = request.body;
1738
+ const dirPaths = {
1739
+ base: pathProvider.getUserDataDir(),
1740
+ system: pathProvider.getSystemDir(),
1741
+ databases: pathProvider.getDatabaseDir(),
1742
+ cache: pathProvider.getCacheDir(),
1743
+ ai: pathProvider.getAiDataDir(),
1744
+ logs: pathProvider.getLogsDir(),
1745
+ downloads: downloadsDir
1746
+ };
1747
+ const dirPath = dirPaths[cacheId];
1748
+ if (!dirPath) return { success: false, error: "Unknown directory" };
1749
+ if (!fs6.existsSync(dirPath)) {
1750
+ await fsp.mkdir(dirPath, { recursive: true });
1751
+ }
1752
+ try {
1753
+ await openDirectoryPath(dirPath);
1754
+ } catch (error) {
1755
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1756
+ }
1757
+ return { success: true };
1758
+ });
1759
+ server2.get("/_web/cache/data-dir", async () => {
1760
+ const config = loadConfig();
1761
+ const isCustom = Boolean(config.data.user_data_dir || process.env.CHATLAB_DATA_DIR);
1762
+ return { path: pathProvider.getUserDataDir(), isCustom };
1763
+ });
1764
+ server2.get("/_web/cache/latest-import-log", async () => {
1765
+ const importLogDir = path5.join(pathProvider.getLogsDir(), "import");
1766
+ if (!fs6.existsSync(importLogDir)) {
1767
+ return { success: false, error: "Log directory not found" };
1768
+ }
1769
+ const files = await fsp.readdir(importLogDir);
1770
+ const logFiles = files.filter((f) => f.startsWith("import_") && f.endsWith(".log"));
1771
+ if (logFiles.length === 0) {
1772
+ return { success: false, error: "No import logs found" };
1773
+ }
1774
+ const fileStats = await Promise.all(
1775
+ logFiles.map(async (f) => {
1776
+ const filePath = path5.join(importLogDir, f);
1777
+ const stat2 = await fsp.stat(filePath);
1778
+ return { name: f, path: filePath, mtime: stat2.mtime.getTime() };
1779
+ })
1780
+ );
1781
+ fileStats.sort((a, b) => b.mtime - a.mtime);
1782
+ return { success: true, path: fileStats[0].path, name: fileStats[0].name };
1783
+ });
1592
1784
  server2.post("/_web/cache/save-to-downloads", async (request) => {
1593
1785
  const { filename, dataUrl } = request.body;
1594
1786
  if (!filename || !dataUrl) {
@@ -1600,7 +1792,7 @@ function registerCacheRoutes(server2, downloadsDir) {
1600
1792
  if (!fs6.existsSync(downloadsDir)) {
1601
1793
  fs6.mkdirSync(downloadsDir, { recursive: true });
1602
1794
  }
1603
- const filePath = path6.join(downloadsDir, filename);
1795
+ const filePath = path5.join(downloadsDir, filename);
1604
1796
  fs6.writeFileSync(filePath, buffer);
1605
1797
  return { success: true, filePath };
1606
1798
  });
@@ -1609,26 +1801,46 @@ function registerCacheRoutes(server2, downloadsDir) {
1609
1801
  if (!filePath) {
1610
1802
  return { success: false, error: "filePath is required" };
1611
1803
  }
1612
- const { exec } = await import("child_process");
1613
- const platform = process.platform;
1614
- const dir = path6.dirname(filePath);
1615
- if (platform === "darwin") {
1616
- exec(`open -R "${filePath}"`);
1617
- } else if (platform === "win32") {
1618
- exec(`explorer /select,"${filePath}"`);
1619
- } else {
1620
- exec(`xdg-open "${dir}"`);
1804
+ try {
1805
+ await showPathInFolder(filePath);
1806
+ } catch (error) {
1807
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1621
1808
  }
1622
1809
  return { success: true };
1623
1810
  });
1624
1811
  }
1625
1812
 
1626
1813
  // src/http/routes/web/index.ts
1814
+ function isNewerVersion(latest, current) {
1815
+ const parse = (v) => {
1816
+ const [core, pre] = v.split("-", 2);
1817
+ const parts = core.split(".").map(Number);
1818
+ return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0, pre };
1819
+ };
1820
+ const l = parse(latest);
1821
+ const c = parse(current);
1822
+ if (l.major !== c.major) return l.major > c.major;
1823
+ if (l.minor !== c.minor) return l.minor > c.minor;
1824
+ if (l.patch !== c.patch) return l.patch > c.patch;
1825
+ if (c.pre && !l.pre) return true;
1826
+ return false;
1827
+ }
1627
1828
  function registerWebRoutes(server2, dbManager2, options) {
1628
1829
  const adapter = createDatabaseManagerAdapter(dbManager2);
1629
1830
  const mergeCache = options?.pathProvider ? new MergeSessionCache(options.pathProvider, { nativeBinding: options.nativeBinding }) : null;
1630
1831
  mergeCache?.cleanupOrphans();
1631
- const downloadsDir = options?.pathProvider?.getDownloadsDir() || path7.join(os3.homedir(), "Downloads");
1832
+ const fallbackPathProvider = {
1833
+ getSystemDir: () => path6.join(os3.homedir(), ".chatlab"),
1834
+ getUserDataDir: () => path6.join(os3.homedir(), ".chatlab", "data"),
1835
+ getDatabaseDir: () => path6.join(os3.homedir(), ".chatlab", "data", "databases"),
1836
+ getAiDataDir: () => path6.join(os3.homedir(), ".chatlab", "ai"),
1837
+ getSettingsDir: () => path6.join(os3.homedir(), ".chatlab", "settings"),
1838
+ getCacheDir: () => path6.join(os3.homedir(), ".chatlab", "cache"),
1839
+ getTempDir: () => path6.join(os3.homedir(), ".chatlab", "temp"),
1840
+ getLogsDir: () => path6.join(os3.homedir(), ".chatlab", "logs"),
1841
+ getDownloadsDir: () => path6.join(os3.homedir(), "Downloads")
1842
+ };
1843
+ const resolvedPathProvider = options?.pathProvider ?? fallbackPathProvider;
1632
1844
  registerSessionRoutes2(server2, adapter);
1633
1845
  registerMemberRoutes(server2, adapter);
1634
1846
  registerAnalyticsRoutes(server2, dbManager2, adapter);
@@ -1640,11 +1852,34 @@ function registerWebRoutes(server2, dbManager2, options) {
1640
1852
  registerMergeRoutes(server2, dbManager2, mergeCache);
1641
1853
  }
1642
1854
  registerExportRoutes(server2, adapter);
1643
- registerCacheRoutes(server2, downloadsDir);
1855
+ registerCacheRoutes(server2, resolvedPathProvider);
1856
+ server2.get("/_web/system/check-update", async () => {
1857
+ const currentVersion = getVersion();
1858
+ const packageName = "chatlab-cli";
1859
+ try {
1860
+ const resp = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
1861
+ headers: { Accept: "application/json" },
1862
+ signal: AbortSignal.timeout(1e4)
1863
+ });
1864
+ if (!resp.ok) {
1865
+ return { hasUpdate: false, currentVersion, error: `npm registry HTTP ${resp.status}` };
1866
+ }
1867
+ const data = await resp.json();
1868
+ const latestVersion = data.version || currentVersion;
1869
+ const hasUpdate = isNewerVersion(latestVersion, currentVersion);
1870
+ return { hasUpdate, currentVersion, latestVersion };
1871
+ } catch (err) {
1872
+ return {
1873
+ hasUpdate: false,
1874
+ currentVersion,
1875
+ error: err instanceof Error ? err.message : String(err)
1876
+ };
1877
+ }
1878
+ });
1644
1879
  }
1645
1880
 
1646
1881
  // src/http/routes/nlp.ts
1647
- import * as path8 from "path";
1882
+ import * as path7 from "path";
1648
1883
  function ensureDb3(dbManager2, sessionId) {
1649
1884
  const db = dbManager2.open(sessionId);
1650
1885
  if (!db) {
@@ -1655,7 +1890,7 @@ function ensureDb3(dbManager2, sessionId) {
1655
1890
  function registerNlpRoutes(server2, dbManager2) {
1656
1891
  const pathProvider = dbManager2["pathProvider"];
1657
1892
  if (pathProvider) {
1658
- const nlpDir = path8.join(pathProvider.getUserDataDir(), "nlp");
1893
+ const nlpDir = path7.join(pathProvider.getSystemDir(), "nlp");
1659
1894
  initNlpDir(nlpDir);
1660
1895
  ensureDefaultDict(nlpDir).catch((err) => console.warn("[NLP] Auto-download zh-CN dict failed:", err));
1661
1896
  }
@@ -1693,12 +1928,12 @@ function registerNlpRoutes(server2, dbManager2) {
1693
1928
  }
1694
1929
  function resolveNlpDir(dbManager2) {
1695
1930
  const pathProvider = dbManager2["pathProvider"];
1696
- return path8.join(pathProvider.getUserDataDir(), "nlp");
1931
+ return path7.join(pathProvider.getSystemDir(), "nlp");
1697
1932
  }
1698
1933
 
1699
1934
  // src/http/routes/ai.ts
1700
1935
  import * as fs10 from "fs";
1701
- import * as path12 from "path";
1936
+ import * as path11 from "path";
1702
1937
 
1703
1938
  // src/ai/logger.ts
1704
1939
  var logger = null;
@@ -1747,6 +1982,7 @@ function adaptToolsForAgent(tools, getContext, options) {
1747
1982
  description: tool.description,
1748
1983
  parameters: convertJsonSchemaToParameters(tool.inputSchema),
1749
1984
  async execute(_toolCallId, params) {
1985
+ const toolParams = params && typeof params === "object" ? params : {};
1750
1986
  const ctx = getContext();
1751
1987
  const execCtx = {
1752
1988
  db: ctx.db,
@@ -1756,7 +1992,7 @@ function adaptToolsForAgent(tools, getContext, options) {
1756
1992
  segmentText: (texts, locale, options2) => batchSegmentWithFrequency(texts, locale, options2)
1757
1993
  };
1758
1994
  try {
1759
- const result = await tool.handler(params, execCtx);
1995
+ const result = await tool.handler(toolParams, execCtx);
1760
1996
  if (result.rawMessages && result.rawMessages.length > 0) {
1761
1997
  const pipelineResult = applyPreprocessingPipeline({
1762
1998
  rawMessages: result.rawMessages,
@@ -1779,9 +2015,9 @@ function adaptToolsForAgent(tools, getContext, options) {
1779
2015
 
1780
2016
  // src/ai/assistant-loader.ts
1781
2017
  import * as fs7 from "fs";
1782
- import * as path9 from "path";
2018
+ import * as path8 from "path";
1783
2019
  function loadAssistantConfig(aiDataDir, assistantId) {
1784
- const filePath = path9.join(aiDataDir, "assistants", `${assistantId}.md`);
2020
+ const filePath = path8.join(aiDataDir, "assistants", `${assistantId}.md`);
1785
2021
  if (!fs7.existsSync(filePath)) return null;
1786
2022
  try {
1787
2023
  const content = fs7.readFileSync(filePath, "utf-8");
@@ -1793,7 +2029,7 @@ function loadAssistantConfig(aiDataDir, assistantId) {
1793
2029
 
1794
2030
  // src/ai/manager-factory.ts
1795
2031
  import * as fs8 from "fs";
1796
- import * as path10 from "path";
2032
+ import * as path9 from "path";
1797
2033
  import { createHash, randomUUID as randomUUID3 } from "crypto";
1798
2034
  var nodeFs = {
1799
2035
  ensureDir(dir) {
@@ -1816,7 +2052,7 @@ var nodeFs = {
1816
2052
  return fs8.existsSync(filePath);
1817
2053
  },
1818
2054
  joinPath(...parts) {
1819
- return path10.join(...parts);
2055
+ return path9.join(...parts);
1820
2056
  }
1821
2057
  };
1822
2058
  var managerCache = /* @__PURE__ */ new Map();
@@ -1826,13 +2062,13 @@ function getAssistantManager(aiDataDir) {
1826
2062
  cached = {
1827
2063
  assistant: new AssistantManager({
1828
2064
  fs: nodeFs,
1829
- assistantsDir: path10.join(aiDataDir, "assistants"),
2065
+ assistantsDir: path9.join(aiDataDir, "assistants"),
1830
2066
  builtinRawConfigs: [],
1831
2067
  generateId: () => `custom_${randomUUID3().replace(/-/g, "").slice(0, 12)}`
1832
2068
  }),
1833
2069
  skill: new SkillManagerCore({
1834
2070
  fs: nodeFs,
1835
- skillsDir: path10.join(aiDataDir, "skills"),
2071
+ skillsDir: path9.join(aiDataDir, "skills"),
1836
2072
  builtinRawSkills: [],
1837
2073
  contentHash: (content) => createHash("md5").update(content).digest("hex")
1838
2074
  })
@@ -1975,10 +2211,10 @@ async function runServerAgent(options) {
1975
2211
 
1976
2212
  // src/ai/custom-store.ts
1977
2213
  import * as fs9 from "fs";
1978
- import * as path11 from "path";
2214
+ import * as path10 from "path";
1979
2215
  import { randomUUID as randomUUID4 } from "crypto";
1980
2216
  function readProviders(aiDataDir) {
1981
- const storePath = path11.join(aiDataDir, "custom-providers.json");
2217
+ const storePath = path10.join(aiDataDir, "custom-providers.json");
1982
2218
  if (!fs9.existsSync(storePath)) return [];
1983
2219
  try {
1984
2220
  const content = fs9.readFileSync(storePath, "utf-8");
@@ -1988,8 +2224,8 @@ function readProviders(aiDataDir) {
1988
2224
  }
1989
2225
  }
1990
2226
  function writeProviders(aiDataDir, providers) {
1991
- const storePath = path11.join(aiDataDir, "custom-providers.json");
1992
- const dir = path11.dirname(storePath);
2227
+ const storePath = path10.join(aiDataDir, "custom-providers.json");
2228
+ const dir = path10.dirname(storePath);
1993
2229
  if (!fs9.existsSync(dir)) fs9.mkdirSync(dir, { recursive: true });
1994
2230
  fs9.writeFileSync(storePath, JSON.stringify(providers, null, 2), "utf-8");
1995
2231
  }
@@ -2022,7 +2258,7 @@ function deleteCustomProvider(aiDataDir, id) {
2022
2258
  return { success: true };
2023
2259
  }
2024
2260
  function readModels(aiDataDir) {
2025
- const storePath = path11.join(aiDataDir, "custom-models.json");
2261
+ const storePath = path10.join(aiDataDir, "custom-models.json");
2026
2262
  if (!fs9.existsSync(storePath)) return [];
2027
2263
  try {
2028
2264
  const content = fs9.readFileSync(storePath, "utf-8");
@@ -2032,8 +2268,8 @@ function readModels(aiDataDir) {
2032
2268
  }
2033
2269
  }
2034
2270
  function writeModels(aiDataDir, models) {
2035
- const storePath = path11.join(aiDataDir, "custom-models.json");
2036
- const dir = path11.dirname(storePath);
2271
+ const storePath = path10.join(aiDataDir, "custom-models.json");
2272
+ const dir = path10.dirname(storePath);
2037
2273
  if (!fs9.existsSync(dir)) fs9.mkdirSync(dir, { recursive: true });
2038
2274
  fs9.writeFileSync(storePath, JSON.stringify(models, null, 2), "utf-8");
2039
2275
  }
@@ -2149,7 +2385,7 @@ function registerAiRoutes(server2, dbManager2, convManager2) {
2149
2385
  return mgr.getBuiltinCatalog();
2150
2386
  });
2151
2387
  server2.get("/_web/ai/llm/has-config", async () => {
2152
- const configPath = path12.join(getAiDir(dbManager2), "llm-config.json");
2388
+ const configPath = path11.join(getAiDir(dbManager2), "llm-config.json");
2153
2389
  if (!fs10.existsSync(configPath)) return false;
2154
2390
  try {
2155
2391
  const data = JSON.parse(fs10.readFileSync(configPath, "utf-8"));
@@ -2160,7 +2396,7 @@ function registerAiRoutes(server2, dbManager2, convManager2) {
2160
2396
  }
2161
2397
  });
2162
2398
  server2.get("/_web/ai/llm/configs", async () => {
2163
- const configPath = path12.join(getAiDir(dbManager2), "llm-config.json");
2399
+ const configPath = path11.join(getAiDir(dbManager2), "llm-config.json");
2164
2400
  if (!fs10.existsSync(configPath)) return { configs: [], defaultAssistant: null, fastModel: null };
2165
2401
  try {
2166
2402
  const data = JSON.parse(fs10.readFileSync(configPath, "utf-8"));
@@ -2189,7 +2425,7 @@ function registerAiRoutes(server2, dbManager2, convManager2) {
2189
2425
  });
2190
2426
  });
2191
2427
  server2.get("/_web/ai/llm/provider-registry", async () => {
2192
- const customPath = path12.join(getAiDir(dbManager2), "custom-providers.json");
2428
+ const customPath = path11.join(getAiDir(dbManager2), "custom-providers.json");
2193
2429
  let custom = [];
2194
2430
  if (fs10.existsSync(customPath)) {
2195
2431
  try {
@@ -2202,7 +2438,7 @@ function registerAiRoutes(server2, dbManager2, convManager2) {
2202
2438
  return [...BUILTIN_PROVIDERS, ...custom];
2203
2439
  });
2204
2440
  server2.get("/_web/ai/llm/model-catalog", async () => {
2205
- const customPath = path12.join(getAiDir(dbManager2), "custom-models.json");
2441
+ const customPath = path11.join(getAiDir(dbManager2), "custom-models.json");
2206
2442
  let custom = [];
2207
2443
  if (fs10.existsSync(customPath)) {
2208
2444
  try {
@@ -2215,7 +2451,7 @@ function registerAiRoutes(server2, dbManager2, convManager2) {
2215
2451
  return [...BUILTIN_MODELS, ...custom];
2216
2452
  });
2217
2453
  server2.get("/_web/ai/llm/default-assistant-slot", async () => {
2218
- const configPath = path12.join(getAiDir(dbManager2), "llm-config.json");
2454
+ const configPath = path11.join(getAiDir(dbManager2), "llm-config.json");
2219
2455
  if (!fs10.existsSync(configPath)) return null;
2220
2456
  try {
2221
2457
  const data = JSON.parse(fs10.readFileSync(configPath, "utf-8"));
@@ -2225,7 +2461,7 @@ function registerAiRoutes(server2, dbManager2, convManager2) {
2225
2461
  }
2226
2462
  });
2227
2463
  server2.get("/_web/ai/llm/fast-model-slot", async () => {
2228
- const configPath = path12.join(getAiDir(dbManager2), "llm-config.json");
2464
+ const configPath = path11.join(getAiDir(dbManager2), "llm-config.json");
2229
2465
  if (!fs10.existsSync(configPath)) return null;
2230
2466
  try {
2231
2467
  const data = JSON.parse(fs10.readFileSync(configPath, "utf-8"));
@@ -2582,7 +2818,7 @@ var NOOP_LOGGER = { info: noop, warn: noop, error: noop };
2582
2818
 
2583
2819
  // ../../packages/sync/src/data-source-manager.ts
2584
2820
  import * as fs11 from "fs";
2585
- import * as path13 from "path";
2821
+ import * as path12 from "path";
2586
2822
  import * as crypto2 from "crypto";
2587
2823
  var CONFIG_FILE = "data-sources.json";
2588
2824
  function generateId(prefix = "ds") {
@@ -2610,7 +2846,7 @@ var DataSourceManager = class {
2610
2846
  this.logger = logger2 ?? NOOP_LOGGER;
2611
2847
  }
2612
2848
  getConfigPath() {
2613
- return path13.join(this.configDir, CONFIG_FILE);
2849
+ return path12.join(this.configDir, CONFIG_FILE);
2614
2850
  }
2615
2851
  ensureDir() {
2616
2852
  fs11.mkdirSync(this.configDir, { recursive: true });
@@ -2713,12 +2949,12 @@ var DataSourceManager = class {
2713
2949
  removeSession(sourceId, sessionId) {
2714
2950
  const sources = this.loadAll();
2715
2951
  const ds = sources.find((s) => s.id === sourceId);
2716
- if (!ds) return false;
2717
- const before = ds.sessions.length;
2952
+ if (!ds) return null;
2953
+ const removed = ds.sessions.find((s) => s.id === sessionId);
2954
+ if (!removed) return null;
2718
2955
  ds.sessions = ds.sessions.filter((s) => s.id !== sessionId);
2719
- if (ds.sessions.length === before) return false;
2720
2956
  this.saveAll(sources);
2721
- return true;
2957
+ return removed;
2722
2958
  }
2723
2959
  updateSession(sourceId, sessionId, updates) {
2724
2960
  const sources = this.loadAll();
@@ -2766,14 +3002,13 @@ function parseRemoteSessionsResponse(body) {
2766
3002
  // ../../packages/sync/src/pull-engine.ts
2767
3003
  import * as fs12 from "fs";
2768
3004
  import * as crypto3 from "crypto";
2769
- var MAX_PAGES_PER_PULL = 50;
3005
+ var MAX_PAGES_PER_PULL = 5e3;
2770
3006
  var PULL_OVERLAP_SECONDS = 60;
2771
3007
  function buildPullUrl(baseUrl, remoteSessionId, params) {
2772
3008
  const base = `${baseUrl}/sessions/${remoteSessionId}/messages`;
2773
3009
  const qs = ["format=chatlab"];
2774
3010
  if (params.since !== void 0 && params.since > 0) qs.push(`since=${params.since}`);
2775
3011
  if (params.offset !== void 0 && params.offset > 0) qs.push(`offset=${params.offset}`);
2776
- if (params.end !== void 0 && params.end > 0) qs.push(`end=${params.end}`);
2777
3012
  if (params.limit !== void 0 && params.limit > 0) qs.push(`limit=${params.limit}`);
2778
3013
  return base + "?" + qs.join("&");
2779
3014
  }
@@ -2815,6 +3050,29 @@ function parseSyncFromFile(filePath) {
2815
3050
  return null;
2816
3051
  }
2817
3052
  }
3053
+ function fileContainsMessages(filePath) {
3054
+ try {
3055
+ if (filePath.endsWith(".jsonl")) {
3056
+ const content = fs12.readFileSync(filePath, "utf-8");
3057
+ for (const line of content.split("\n")) {
3058
+ const trimmed = line.trim();
3059
+ if (!trimmed) continue;
3060
+ try {
3061
+ const obj = JSON.parse(trimmed);
3062
+ if (obj._type === "message") return true;
3063
+ } catch {
3064
+ continue;
3065
+ }
3066
+ }
3067
+ return false;
3068
+ }
3069
+ const raw = fs12.readFileSync(filePath, "utf-8");
3070
+ const parsed = JSON.parse(raw);
3071
+ return Array.isArray(parsed.messages) && parsed.messages.length > 0;
3072
+ } catch {
3073
+ return false;
3074
+ }
3075
+ }
2818
3076
  function cleanupTempFile(filePath) {
2819
3077
  try {
2820
3078
  if (fs12.existsSync(filePath)) fs12.unlinkSync(filePath);
@@ -2829,6 +3087,7 @@ var PullEngine = class {
2829
3087
  logger;
2830
3088
  isImporting;
2831
3089
  pullingSourceIds = /* @__PURE__ */ new Set();
3090
+ progressMap = /* @__PURE__ */ new Map();
2832
3091
  constructor(options) {
2833
3092
  this.fetcher = options.fetcher;
2834
3093
  this.importer = options.importer;
@@ -2837,6 +3096,9 @@ var PullEngine = class {
2837
3096
  this.logger = options.logger ?? NOOP_LOGGER;
2838
3097
  this.isImporting = options.isImporting ?? (() => false);
2839
3098
  }
3099
+ getProgress() {
3100
+ return Array.from(this.progressMap.values());
3101
+ }
2840
3102
  async importTempFile(baseUrl, sess, tempFile) {
2841
3103
  let targetId = sess.targetSessionId;
2842
3104
  if (!targetId) {
@@ -2850,6 +3112,11 @@ var PullEngine = class {
2850
3112
  return this.importer.importFile(tempFile, targetId || void 0, externalId);
2851
3113
  }
2852
3114
  async executePullSession(sourceId, ds, sess) {
3115
+ const currentDs = this.dsManager.get(sourceId);
3116
+ if (!currentDs || !currentDs.sessions.some((s) => s.id === sess.id)) {
3117
+ this.logger.info(`[Pull] Skipping "${sess.name}": session no longer exists`);
3118
+ return { success: true, newMessageCount: 0 };
3119
+ }
2853
3120
  if (this.isImporting()) {
2854
3121
  this.logger.info(`[Pull] Skipping "${sess.name}": import in progress`);
2855
3122
  return { success: false, newMessageCount: 0, error: "Import in progress" };
@@ -2857,34 +3124,114 @@ var PullEngine = class {
2857
3124
  this.logger.info(`[Pull] Pulling "${sess.name}" from ${ds.baseUrl}`);
2858
3125
  let totalNewMessages = 0;
2859
3126
  let since = sess.lastPullAt;
2860
- let offset = 0;
2861
- let end;
2862
3127
  let pageCount = 0;
2863
3128
  let resyncAttempted = false;
3129
+ this.progressMap.set(sess.id, { sessionId: sess.id, sessionName: sess.name, current: 0, pages: 0, done: false });
2864
3130
  try {
2865
3131
  while (pageCount < MAX_PAGES_PER_PULL) {
2866
3132
  pageCount++;
2867
3133
  const tempFile = await this.fetcher.fetchToTempFile(ds.baseUrl, sess.remoteSessionId, ds.token, {
2868
3134
  since,
2869
- offset,
2870
- end,
2871
3135
  limit: ds.pullLimit
2872
3136
  });
2873
3137
  try {
2874
- const stat = fs12.statSync(tempFile);
2875
- this.logger.info(`[Pull] "${sess.name}" page ${pageCount}: fetched ${stat.size} bytes`);
2876
- if (stat.size === 0) {
3138
+ const stat2 = fs12.statSync(tempFile);
3139
+ this.logger.info(`[Pull] "${sess.name}" page ${pageCount}: fetched ${stat2.size} bytes`);
3140
+ const sync0 = parseSyncFromFile(tempFile);
3141
+ if (stat2.size < 1024) {
3142
+ if (!fileContainsMessages(tempFile) && sync0?.hasMore === false) {
3143
+ cleanupTempFile(tempFile);
3144
+ const retryDelays = [2e3, 3e3, 5e3];
3145
+ let retrySuccess = false;
3146
+ let retryHasMore = false;
3147
+ for (let ri = 0; ri < retryDelays.length; ri++) {
3148
+ this.logger.info(
3149
+ `[Pull] "${sess.name}" page ${pageCount} got empty response, retry ${ri + 1}/${retryDelays.length} after ${retryDelays[ri]}ms`
3150
+ );
3151
+ await new Promise((r) => setTimeout(r, retryDelays[ri]));
3152
+ const retryFile = await this.fetcher.fetchToTempFile(ds.baseUrl, sess.remoteSessionId, ds.token, {
3153
+ since,
3154
+ limit: ds.pullLimit
3155
+ });
3156
+ const retryStat = fs12.statSync(retryFile);
3157
+ this.logger.info(`[Pull] "${sess.name}" retry ${ri + 1}: fetched ${retryStat.size} bytes`);
3158
+ const retrySync = parseSyncFromFile(retryFile);
3159
+ if (retryStat.size < 1024 && !fileContainsMessages(retryFile) && retrySync?.hasMore === false) {
3160
+ cleanupTempFile(retryFile);
3161
+ continue;
3162
+ }
3163
+ const retryResult = await this.importTempFile(ds.baseUrl, sess, retryFile);
3164
+ cleanupTempFile(retryFile);
3165
+ if (retryResult.needFullResync && !resyncAttempted) {
3166
+ resyncAttempted = true;
3167
+ this.logger.info(`[Pull] Resetting since=0 for "${sess.name}" full resync`);
3168
+ since = 0;
3169
+ pageCount = 0;
3170
+ sess.targetSessionId = "";
3171
+ sess.lastPullAt = 0;
3172
+ this.dsManager.updateSession(sourceId, sess.id, { targetSessionId: "", lastPullAt: 0 });
3173
+ retrySuccess = true;
3174
+ retryHasMore = true;
3175
+ break;
3176
+ }
3177
+ if (retryResult.needFullResync) {
3178
+ const errMsg = "Full resync failed";
3179
+ this.logger.error(`[Pull] Full resync already attempted for "${sess.name}", aborting`);
3180
+ this.dsManager.updateSession(sourceId, sess.id, {
3181
+ lastPullAt: Math.floor(Date.now() / 1e3),
3182
+ lastStatus: "error",
3183
+ lastError: errMsg
3184
+ });
3185
+ this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
3186
+ this.markProgressDone(sess.id);
3187
+ return { success: false, newMessageCount: 0, error: errMsg };
3188
+ }
3189
+ if (!retryResult.success) {
3190
+ const errMsg = retryResult.error || "Import failed";
3191
+ this.dsManager.updateSession(sourceId, sess.id, {
3192
+ lastPullAt: Math.floor(Date.now() / 1e3),
3193
+ lastStatus: "error",
3194
+ lastError: errMsg
3195
+ });
3196
+ this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
3197
+ this.markProgressDone(sess.id);
3198
+ return { success: false, newMessageCount: 0, error: errMsg };
3199
+ }
3200
+ if (retryResult.success && retryResult.sessionId && !sess.targetSessionId) {
3201
+ sess.targetSessionId = retryResult.sessionId;
3202
+ this.dsManager.updateSession(sourceId, sess.id, { targetSessionId: retryResult.sessionId });
3203
+ }
3204
+ totalNewMessages += retryResult.newMessageCount;
3205
+ this.progressMap.set(sess.id, {
3206
+ sessionId: sess.id,
3207
+ sessionName: sess.name,
3208
+ current: totalNewMessages,
3209
+ pages: pageCount,
3210
+ done: false
3211
+ });
3212
+ if (retrySync?.hasMore && retrySync.nextSince !== void 0) {
3213
+ since = retrySync.nextSince;
3214
+ }
3215
+ retryHasMore = !!retrySync?.hasMore;
3216
+ retrySuccess = true;
3217
+ break;
3218
+ }
3219
+ if (!retrySuccess) break;
3220
+ if (!retryHasMore) break;
3221
+ continue;
3222
+ }
3223
+ }
3224
+ if (stat2.size === 0) {
2877
3225
  cleanupTempFile(tempFile);
2878
3226
  break;
2879
3227
  }
2880
- const sync = parseSyncFromFile(tempFile);
3228
+ const sync = sync0;
2881
3229
  const result = await this.importTempFile(ds.baseUrl, sess, tempFile);
2882
3230
  cleanupTempFile(tempFile);
2883
3231
  if (result.needFullResync && !resyncAttempted) {
2884
3232
  resyncAttempted = true;
2885
3233
  this.logger.info(`[Pull] Resetting since=0 for "${sess.name}" full resync`);
2886
3234
  since = 0;
2887
- offset = 0;
2888
3235
  pageCount = 0;
2889
3236
  sess.targetSessionId = "";
2890
3237
  sess.lastPullAt = 0;
@@ -2900,6 +3247,7 @@ var PullEngine = class {
2900
3247
  lastError: errMsg
2901
3248
  });
2902
3249
  this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
3250
+ this.markProgressDone(sess.id);
2903
3251
  return { success: false, newMessageCount: 0, error: errMsg };
2904
3252
  }
2905
3253
  if (!result.success) {
@@ -2910,6 +3258,7 @@ var PullEngine = class {
2910
3258
  lastError: errMsg
2911
3259
  });
2912
3260
  this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
3261
+ this.markProgressDone(sess.id);
2913
3262
  return { success: false, newMessageCount: 0, error: errMsg };
2914
3263
  }
2915
3264
  if (!sess.targetSessionId && result.sessionId) {
@@ -2917,11 +3266,15 @@ var PullEngine = class {
2917
3266
  this.dsManager.updateSession(sourceId, sess.id, { targetSessionId: result.sessionId });
2918
3267
  }
2919
3268
  totalNewMessages += result.newMessageCount;
3269
+ this.progressMap.set(sess.id, {
3270
+ sessionId: sess.id,
3271
+ sessionName: sess.name,
3272
+ current: totalNewMessages,
3273
+ pages: pageCount,
3274
+ done: false
3275
+ });
2920
3276
  if (!sync || !sync.hasMore) break;
2921
3277
  if (sync.nextSince !== void 0) since = sync.nextSince;
2922
- if (sync.nextOffset !== void 0) offset = sync.nextOffset;
2923
- else offset = 0;
2924
- if (sync.watermark !== void 0 && !end) end = sync.watermark;
2925
3278
  } catch (importErr) {
2926
3279
  cleanupTempFile(tempFile);
2927
3280
  throw importErr;
@@ -2938,6 +3291,7 @@ var PullEngine = class {
2938
3291
  });
2939
3292
  if (totalNewMessages > 0) this.notifier.onSessionListChanged();
2940
3293
  this.notifier.onPullResult(sourceId, sess.id, "success", `+${totalNewMessages} messages`);
3294
+ this.markProgressDone(sess.id);
2941
3295
  return { success: true, newMessageCount: totalNewMessages };
2942
3296
  } catch (error) {
2943
3297
  const errMsg = error.message || "Pull failed";
@@ -2948,9 +3302,17 @@ var PullEngine = class {
2948
3302
  lastError: errMsg
2949
3303
  });
2950
3304
  this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
3305
+ this.markProgressDone(sess.id);
2951
3306
  return { success: false, newMessageCount: 0, error: errMsg };
2952
3307
  }
2953
3308
  }
3309
+ markProgressDone(sessionId) {
3310
+ const p = this.progressMap.get(sessionId);
3311
+ if (p) {
3312
+ p.done = true;
3313
+ setTimeout(() => this.progressMap.delete(sessionId), 5e3);
3314
+ }
3315
+ }
2954
3316
  async pullAllSessions(ds) {
2955
3317
  if (this.pullingSourceIds.has(ds.id)) {
2956
3318
  this.logger.info(`[Pull] Skipping pullAllSessions for "${ds.baseUrl}": pull already in progress`);
@@ -3067,17 +3429,17 @@ function reloadTimer(dsId, skipInitialPull = false) {
3067
3429
  // src/sync/adapters.ts
3068
3430
  import * as fs13 from "fs";
3069
3431
  import * as os4 from "os";
3070
- import * as path14 from "path";
3432
+ import * as path13 from "path";
3071
3433
  import * as crypto4 from "crypto";
3072
3434
  function getTempFilePath(ext) {
3073
3435
  const id = crypto4.randomBytes(8).toString("hex");
3074
- return path14.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3436
+ return path13.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3075
3437
  }
3076
3438
  var NodeFetcher = class {
3077
3439
  async fetchToTempFile(baseUrl, remoteSessionId, token, params) {
3078
3440
  const url = buildPullUrl(baseUrl, remoteSessionId, params);
3079
3441
  const headers = {
3080
- Accept: "application/json, application/x-ndjson"
3442
+ Accept: "application/json"
3081
3443
  };
3082
3444
  if (token) headers["Authorization"] = `Bearer ${token}`;
3083
3445
  const response = await fetch(url, { headers, signal: AbortSignal.timeout(12e4) });
@@ -3094,7 +3456,7 @@ var NodeFetcher = class {
3094
3456
  };
3095
3457
  function resolveNativeBinding2() {
3096
3458
  if (process.versions.electron) return void 0;
3097
- const nativePath = path14.resolve(__dirname, "../../native/better_sqlite3.node");
3459
+ const nativePath = resolveCliPath("native/better_sqlite3.node");
3098
3460
  if (fs13.existsSync(nativePath)) return nativePath;
3099
3461
  return void 0;
3100
3462
  }
@@ -3109,7 +3471,28 @@ var DirectImporter = class {
3109
3471
  }
3110
3472
  sessionExists(sessionId) {
3111
3473
  const dbPath = this.dbManager.getDbPath(sessionId);
3112
- return fs13.existsSync(dbPath);
3474
+ if (!fs13.existsSync(dbPath)) return false;
3475
+ try {
3476
+ const db = openBetterSqliteDatabase(dbPath, { readonly: true, nativeBinding: this.nativeBinding });
3477
+ const row = db.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name='message'").get();
3478
+ db.close();
3479
+ if (row.cnt === 0) {
3480
+ this.logger.warn(`[DirectImporter] DB file exists but has no message table: ${sessionId}, removing`);
3481
+ try {
3482
+ fs13.unlinkSync(dbPath);
3483
+ } catch {
3484
+ }
3485
+ return false;
3486
+ }
3487
+ return true;
3488
+ } catch {
3489
+ this.logger.warn(`[DirectImporter] Cannot validate DB file: ${sessionId}, removing`);
3490
+ try {
3491
+ fs13.unlinkSync(dbPath);
3492
+ } catch {
3493
+ }
3494
+ return false;
3495
+ }
3113
3496
  }
3114
3497
  async importFile(tempFile, targetSessionId, externalId) {
3115
3498
  if (targetSessionId && this.sessionExists(targetSessionId)) {
@@ -3139,7 +3522,7 @@ var DirectImporter = class {
3139
3522
  sessionId
3140
3523
  };
3141
3524
  }
3142
- if (result.error?.includes("not found") || result.error?.includes("session_not_found")) {
3525
+ if (result.error?.includes("not found") || result.error?.includes("session_not_found") || result.error?.includes("no such table")) {
3143
3526
  return { success: false, newMessageCount: 0, sessionId, needFullResync: true };
3144
3527
  }
3145
3528
  return { success: false, newMessageCount: 0, sessionId, error: result.error };
@@ -3177,7 +3560,7 @@ var NoopNotifier = class {
3177
3560
 
3178
3561
  // src/sync/routes.ts
3179
3562
  function registerAutomationRoutes(server2, ctx) {
3180
- const { dsManager: dsManager2, pullEngine: pullEngine2, serverInfo } = ctx;
3563
+ const { dsManager: dsManager2, pullEngine: pullEngine2, dbManager: dbManager2, serverInfo } = ctx;
3181
3564
  server2.get("/_web/automation/config", async () => {
3182
3565
  return {
3183
3566
  enabled: true,
@@ -3219,9 +3602,15 @@ function registerAutomationRoutes(server2, ctx) {
3219
3602
  return added;
3220
3603
  });
3221
3604
  server2.delete("/_web/automation/data-sources/:id/sessions/:sessId", async (request, reply) => {
3222
- const ok = dsManager2.removeSession(request.params.id, request.params.sessId);
3223
- if (!ok) return reply.code(404).send({ error: "Session not found" });
3605
+ const removed = dsManager2.removeSession(request.params.id, request.params.sessId);
3606
+ if (!removed) return reply.code(404).send({ error: "Session not found" });
3224
3607
  reloadTimer(request.params.id);
3608
+ if (request.query.deleteData === "true" && removed.targetSessionId) {
3609
+ dbManager2.close(removed.targetSessionId);
3610
+ const dbPath = dbManager2.getDbPath(removed.targetSessionId);
3611
+ const fs15 = await import("fs");
3612
+ if (fs15.existsSync(dbPath)) fs15.unlinkSync(dbPath);
3613
+ }
3225
3614
  return { success: true };
3226
3615
  });
3227
3616
  server2.post(
@@ -3233,6 +3622,9 @@ function registerAutomationRoutes(server2, ctx) {
3233
3622
  server2.post("/_web/automation/data-sources/:id/pull-all", async (request) => {
3234
3623
  return pullEngine2.triggerPullAll(request.params.id);
3235
3624
  });
3625
+ server2.get("/_web/automation/sync-progress", async () => {
3626
+ return pullEngine2.getProgress();
3627
+ });
3236
3628
  server2.get("/_web/automation/remote-sessions", async (request, reply) => {
3237
3629
  const { baseUrl, token, keyword, limit, cursor } = request.query;
3238
3630
  if (!baseUrl) return reply.code(400).send({ error: "baseUrl is required" });
@@ -3276,7 +3668,7 @@ function initSync(server2, dbManager2, pathProvider, serverInfo) {
3276
3668
  dsManager,
3277
3669
  logger: syncLogger
3278
3670
  });
3279
- registerAutomationRoutes(server2, { dsManager, pullEngine, serverInfo });
3671
+ registerAutomationRoutes(server2, { dsManager, pullEngine, dbManager: dbManager2, serverInfo });
3280
3672
  initScheduler({
3281
3673
  dsManager,
3282
3674
  pullEngine,
@@ -3295,7 +3687,7 @@ var dbManager = null;
3295
3687
  var convManager = null;
3296
3688
  function resolveNativeBinding3() {
3297
3689
  if (process.versions.electron) return void 0;
3298
- const nativePath = path15.resolve(__dirname, "../../native/better_sqlite3.node");
3690
+ const nativePath = resolveCliPath("native/better_sqlite3.node");
3299
3691
  if (fs14.existsSync(nativePath)) return nativePath;
3300
3692
  return void 0;
3301
3693
  }
@@ -3320,7 +3712,7 @@ async function startHttpServer(options) {
3320
3712
  const pathProvider = new NodePathProvider(userDataDir);
3321
3713
  pathProvider.ensureAllDirs();
3322
3714
  if (hasPendingElectronDataWarning() || !verifyCliDataPath(pathProvider.getDatabaseDir())) {
3323
- throw new Error(
3715
+ console.error(
3324
3716
  "\n" + "=".repeat(68) + "\n ChatLab: Electron desktop data not found\n" + "=".repeat(68) + '\n\n Detected that ChatLab desktop app was installed on this machine,\n but could not locate your chat databases.\n\n This usually means you changed the data directory in desktop settings.\n\n To fix this, choose one of:\n\n 1. Open ChatLab desktop app \u2014 it will auto-migrate your data\n 2. Set the data directory manually:\n export CHATLAB_DATA_DIR="/path/to/your/data"\n 3. Edit ~/.chatlab/config.toml:\n [data]\n user_data_dir = "/path/to/your/data"\n\n' + "=".repeat(68) + "\n"
3325
3717
  );
3326
3718
  }
@@ -3397,4 +3789,4 @@ export {
3397
3789
  startHttpServer,
3398
3790
  stopHttpServer
3399
3791
  };
3400
- //# sourceMappingURL=chunk-FBTHZU55.mjs.map
3792
+ //# sourceMappingURL=chunk-AMMSVJG4.mjs.map