chattercatcher 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,9 +1,155 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/config/paths.ts
13
+ import os2 from "os";
14
+ import path2 from "path";
15
+ function getChatterCatcherHome() {
16
+ return process.env.CHATTERCATCHER_HOME || path2.join(os2.homedir(), ".chattercatcher");
17
+ }
18
+ function resolveHomePath(value) {
19
+ if (value === "~") {
20
+ return os2.homedir();
21
+ }
22
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
23
+ return path2.join(os2.homedir(), value.slice(2));
24
+ }
25
+ return path2.resolve(value);
26
+ }
27
+ function getConfigPath() {
28
+ return path2.join(getChatterCatcherHome(), "config.json");
29
+ }
30
+ function getSecretsPath() {
31
+ return path2.join(getChatterCatcherHome(), "secrets.json");
32
+ }
33
+ var init_paths = __esm({
34
+ "src/config/paths.ts"() {
35
+ "use strict";
36
+ }
37
+ });
38
+
39
+ // src/rag/lancedb-store.ts
40
+ var lancedb_store_exports = {};
41
+ __export(lancedb_store_exports, {
42
+ LanceDbVectorStore: () => LanceDbVectorStore,
43
+ getLanceDbPath: () => getLanceDbPath
44
+ });
45
+ import fs6 from "fs/promises";
46
+ import path9 from "path";
47
+ function getLanceDbPath(config) {
48
+ return path9.join(resolveHomePath(config.storage.dataDir), "vector", "lancedb");
49
+ }
50
+ function toRow(record) {
51
+ return {
52
+ id: record.id,
53
+ vector: record.vector,
54
+ text: record.evidence.text,
55
+ source_json: JSON.stringify(record.evidence.source)
56
+ };
57
+ }
58
+ function toLanceData(rows) {
59
+ return rows.map((row) => ({
60
+ id: row.id,
61
+ vector: row.vector,
62
+ text: row.text,
63
+ source_json: row.source_json
64
+ }));
65
+ }
66
+ function escapeSqlString(value) {
67
+ return value.replace(/'/g, "''");
68
+ }
69
+ function toEvidence(row) {
70
+ const distance = row._distance ?? 0;
71
+ const vectorScore = 1 / (1 + Math.max(0, distance));
72
+ return {
73
+ id: row.id,
74
+ text: row.text,
75
+ score: vectorScore,
76
+ vectorScore,
77
+ source: JSON.parse(row.source_json)
78
+ };
79
+ }
80
+ var DEFAULT_TABLE_NAME, LanceDbVectorStore;
81
+ var init_lancedb_store = __esm({
82
+ "src/rag/lancedb-store.ts"() {
83
+ "use strict";
84
+ init_paths();
85
+ DEFAULT_TABLE_NAME = "message_chunks";
86
+ LanceDbVectorStore = class _LanceDbVectorStore {
87
+ constructor(connection, tableName) {
88
+ this.connection = connection;
89
+ this.tableName = tableName;
90
+ }
91
+ connection;
92
+ tableName;
93
+ static async connect(uri, tableName = DEFAULT_TABLE_NAME) {
94
+ await fs6.mkdir(uri, { recursive: true });
95
+ const lancedb = await import("@lancedb/lancedb");
96
+ const connection = await lancedb.connect(uri);
97
+ return new _LanceDbVectorStore(connection, tableName);
98
+ }
99
+ static async connectFromConfig(config, tableName = DEFAULT_TABLE_NAME) {
100
+ return _LanceDbVectorStore.connect(getLanceDbPath(config), tableName);
101
+ }
102
+ close() {
103
+ this.connection.close();
104
+ }
105
+ async upsert(records) {
106
+ if (records.length === 0) {
107
+ return;
108
+ }
109
+ const rows = records.map(toRow);
110
+ const data2 = toLanceData(rows);
111
+ const table = await this.ensureTable(data2);
112
+ const ids = rows.map((row) => `'${escapeSqlString(row.id)}'`).join(", ");
113
+ await table.delete(`id IN (${ids})`);
114
+ await table.add(data2);
115
+ }
116
+ async search(vector, limit) {
117
+ const table = await this.openTableIfExists();
118
+ if (!table) {
119
+ return [];
120
+ }
121
+ const rows = await table.vectorSearch(vector).limit(limit).toArray();
122
+ return rows.map(toEvidence);
123
+ }
124
+ async count() {
125
+ const table = await this.openTableIfExists();
126
+ if (!table) {
127
+ return 0;
128
+ }
129
+ return table.countRows();
130
+ }
131
+ async ensureTable(initialRows) {
132
+ const table = await this.openTableIfExists();
133
+ if (table) {
134
+ return table;
135
+ }
136
+ return this.connection.createTable(this.tableName, initialRows);
137
+ }
138
+ async openTableIfExists() {
139
+ const tableNames = await this.connection.tableNames();
140
+ if (!tableNames.includes(this.tableName)) {
141
+ return null;
142
+ }
143
+ return this.connection.openTable(this.tableName);
144
+ }
145
+ };
146
+ }
147
+ });
2
148
 
3
149
  // src/cli.ts
4
150
  import { input, password, select, confirm, number } from "@inquirer/prompts";
5
151
  import { Command } from "commander";
6
- import fs12 from "fs/promises";
152
+ import fs14 from "fs/promises";
7
153
 
8
154
  // src/config/store.ts
9
155
  import fs from "fs/promises";
@@ -72,29 +218,8 @@ function createDefaultSecrets() {
72
218
  });
73
219
  }
74
220
 
75
- // src/config/paths.ts
76
- import os2 from "os";
77
- import path2 from "path";
78
- function getChatterCatcherHome() {
79
- return process.env.CHATTERCATCHER_HOME || path2.join(os2.homedir(), ".chattercatcher");
80
- }
81
- function resolveHomePath(value) {
82
- if (value === "~") {
83
- return os2.homedir();
84
- }
85
- if (value.startsWith("~/") || value.startsWith("~\\")) {
86
- return path2.join(os2.homedir(), value.slice(2));
87
- }
88
- return path2.resolve(value);
89
- }
90
- function getConfigPath() {
91
- return path2.join(getChatterCatcherHome(), "config.json");
92
- }
93
- function getSecretsPath() {
94
- return path2.join(getChatterCatcherHome(), "secrets.json");
95
- }
96
-
97
221
  // src/config/store.ts
222
+ init_paths();
98
223
  async function readJsonFile(filePath, fallback) {
99
224
  try {
100
225
  const raw = await fs.readFile(filePath, "utf8");
@@ -157,7 +282,11 @@ function resolveEmbeddingApiKey(input2) {
157
282
  return explicit || input2.llmApiKey;
158
283
  }
159
284
 
285
+ // src/cli.ts
286
+ init_paths();
287
+
160
288
  // src/data/deletion.ts
289
+ init_paths();
161
290
  import fs2 from "fs/promises";
162
291
  import path4 from "path";
163
292
  function emptyResult(targetType, targetId) {
@@ -283,6 +412,7 @@ async function deleteLocalData(input2) {
283
412
  }
284
413
 
285
414
  // src/db/database.ts
415
+ init_paths();
286
416
  import Database from "better-sqlite3";
287
417
  import fs3 from "fs";
288
418
  import path5 from "path";
@@ -343,13 +473,6 @@ function migrateDatabase(database) {
343
473
  tokenize = 'unicode61'
344
474
  );
345
475
 
346
- CREATE TABLE IF NOT EXISTS message_chunk_vectors (
347
- chunk_id TEXT PRIMARY KEY REFERENCES message_chunks(id) ON DELETE CASCADE,
348
- vector_json TEXT NOT NULL,
349
- evidence_json TEXT NOT NULL,
350
- updated_at TEXT NOT NULL
351
- );
352
-
353
476
  CREATE TABLE IF NOT EXISTS file_jobs (
354
477
  id TEXT PRIMARY KEY,
355
478
  source_path TEXT NOT NULL,
@@ -369,7 +492,8 @@ function migrateDatabase(database) {
369
492
  }
370
493
 
371
494
  // src/doctor/checks.ts
372
- import fs5 from "fs/promises";
495
+ init_paths();
496
+ import fs7 from "fs/promises";
373
497
 
374
498
  // src/files/jobs.ts
375
499
  import crypto from "crypto";
@@ -511,10 +635,120 @@ var FileJobRepository = class {
511
635
  };
512
636
 
513
637
  // src/gateway/runtime.ts
514
- import fs4 from "fs";
638
+ init_paths();
639
+ import fs5 from "fs";
640
+ import path8 from "path";
641
+
642
+ // src/logs/reader.ts
643
+ init_paths();
644
+ import fs4 from "fs/promises";
645
+ import { watch } from "fs";
515
646
  import path7 from "path";
647
+ function getLogsDirectory() {
648
+ return path7.join(getChatterCatcherHome(), "logs");
649
+ }
650
+ function resolveLogPath(fileName, logsDir = getLogsDirectory()) {
651
+ return path7.isAbsolute(fileName) ? fileName : path7.join(logsDir, fileName);
652
+ }
653
+ function normalizeLineCount(value, fallback = 200) {
654
+ const parsed = Number(value ?? fallback);
655
+ return Number.isFinite(parsed) ? Math.min(Math.max(Math.trunc(parsed), 1), 1e4) : fallback;
656
+ }
657
+ async function listLogFiles(logsDir = getLogsDirectory()) {
658
+ let entries;
659
+ try {
660
+ entries = await fs4.readdir(logsDir, { withFileTypes: true });
661
+ } catch (error) {
662
+ if (error.code === "ENOENT") {
663
+ return [];
664
+ }
665
+ throw error;
666
+ }
667
+ const files2 = await Promise.all(
668
+ entries.filter((entry) => entry.isFile() && entry.name.endsWith(".log")).map(async (entry) => {
669
+ const filePath = path7.join(logsDir, entry.name);
670
+ const stats = await fs4.stat(filePath);
671
+ return {
672
+ name: entry.name,
673
+ path: filePath,
674
+ updatedAt: stats.mtime,
675
+ bytes: stats.size
676
+ };
677
+ })
678
+ );
679
+ return files2.sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());
680
+ }
681
+ function tailLines(content, lines) {
682
+ const normalized = content.replace(/\r\n/g, "\n");
683
+ const parts = normalized.endsWith("\n") ? normalized.slice(0, -1).split("\n") : normalized.split("\n");
684
+ return parts.slice(-lines).join("\n");
685
+ }
686
+ async function readLogTail(input2) {
687
+ const stats = await fs4.stat(input2.filePath);
688
+ const content = await fs4.readFile(input2.filePath, "utf8");
689
+ return {
690
+ file: {
691
+ name: path7.basename(input2.filePath),
692
+ path: input2.filePath,
693
+ updatedAt: stats.mtime,
694
+ bytes: stats.size
695
+ },
696
+ content: tailLines(content, normalizeLineCount(input2.lines))
697
+ };
698
+ }
699
+ async function readLatestLogTail(input2 = {}) {
700
+ if (input2.fileName) {
701
+ return readLogTail({
702
+ filePath: resolveLogPath(input2.fileName, input2.logsDir),
703
+ lines: input2.lines
704
+ });
705
+ }
706
+ const [latest] = await listLogFiles(input2.logsDir);
707
+ if (!latest) {
708
+ return null;
709
+ }
710
+ return readLogTail({ filePath: latest.path, lines: input2.lines });
711
+ }
712
+ async function followLogFile(input2) {
713
+ let offset = (await fs4.stat(input2.filePath)).size;
714
+ const directory = path7.dirname(input2.filePath);
715
+ const fileName = path7.basename(input2.filePath);
716
+ async function readAppended() {
717
+ const stats = await fs4.stat(input2.filePath);
718
+ if (stats.size < offset) {
719
+ offset = 0;
720
+ }
721
+ if (stats.size === offset) {
722
+ return;
723
+ }
724
+ const handle = await fs4.open(input2.filePath, "r");
725
+ try {
726
+ const length = stats.size - offset;
727
+ const buffer = Buffer.alloc(length);
728
+ await handle.read(buffer, 0, length, offset);
729
+ offset = stats.size;
730
+ input2.onChunk(buffer.toString("utf8"));
731
+ } finally {
732
+ await handle.close();
733
+ }
734
+ }
735
+ const watcher = watch(directory, (eventType, changedFileName) => {
736
+ if (eventType !== "change" || changedFileName?.toString() !== fileName) {
737
+ return;
738
+ }
739
+ void readAppended().catch((error) => {
740
+ input2.onError?.(error instanceof Error ? error : new Error(String(error)));
741
+ });
742
+ });
743
+ return () => watcher.close();
744
+ }
745
+
746
+ // src/gateway/runtime.ts
516
747
  function getGatewayPidPath() {
517
- return path7.join(getChatterCatcherHome(), "gateway.pid");
748
+ return path8.join(getChatterCatcherHome(), "gateway.pid");
749
+ }
750
+ function getGatewayLogPath() {
751
+ return path8.join(getLogsDirectory(), "gateway.log");
518
752
  }
519
753
  function isProcessRunning(pid) {
520
754
  if (!Number.isInteger(pid) || pid <= 0) {
@@ -529,7 +763,7 @@ function isProcessRunning(pid) {
529
763
  }
530
764
  function readGatewayPidRecord(pidFile = getGatewayPidPath()) {
531
765
  try {
532
- const raw = fs4.readFileSync(pidFile, "utf8");
766
+ const raw = fs5.readFileSync(pidFile, "utf8");
533
767
  const parsed = JSON.parse(raw);
534
768
  if (!Number.isInteger(parsed.pid) || typeof parsed.startedAt !== "string" || typeof parsed.command !== "string") {
535
769
  return null;
@@ -541,7 +775,9 @@ function readGatewayPidRecord(pidFile = getGatewayPidPath()) {
541
775
  return {
542
776
  pid,
543
777
  startedAt: parsed.startedAt,
544
- command: parsed.command
778
+ command: parsed.command,
779
+ ...typeof parsed.logFile === "string" ? { logFile: parsed.logFile } : {},
780
+ ...parsed.mode === "gateway" || parsed.mode === "web" ? { mode: parsed.mode } : {}
545
781
  };
546
782
  } catch (error) {
547
783
  if (error.code === "ENOENT") {
@@ -555,13 +791,13 @@ function writeGatewayPidRecord(pidFile = getGatewayPidPath(), record = {
555
791
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
556
792
  command: process.argv.join(" ")
557
793
  }) {
558
- fs4.mkdirSync(path7.dirname(pidFile), { recursive: true });
559
- fs4.writeFileSync(pidFile, `${JSON.stringify(record, null, 2)}
794
+ fs5.mkdirSync(path8.dirname(pidFile), { recursive: true });
795
+ fs5.writeFileSync(pidFile, `${JSON.stringify(record, null, 2)}
560
796
  `, "utf8");
561
797
  }
562
798
  function removeGatewayPidRecord(pidFile = getGatewayPidPath()) {
563
799
  try {
564
- fs4.rmSync(pidFile, { force: true });
800
+ fs5.rmSync(pidFile, { force: true });
565
801
  } catch {
566
802
  }
567
803
  }
@@ -613,6 +849,28 @@ function stopGatewayProcess(pidFile = getGatewayPidPath()) {
613
849
 
614
850
  // src/gateway/index.ts
615
851
  function getGatewayStatus(config, secrets) {
852
+ const runtime = getGatewayRuntimeState();
853
+ const configured = Boolean(config.feishu.appId && (!secrets || secrets.feishu.appSecret));
854
+ if (runtime.running && runtime.record) {
855
+ if (runtime.record.mode === "web" && !configured) {
856
+ return {
857
+ configured,
858
+ connection: "running",
859
+ message: `\u672C\u5730 Web UI \u8FDB\u7A0B\u6B63\u5728\u8FD0\u884C\uFF1Apid=${runtime.record.pid}\uFF0CstartedAt=${runtime.record.startedAt}\uFF1B\u98DE\u4E66\u914D\u7F6E\u5C1A\u672A\u5B8C\u6210\u3002`,
860
+ pid: runtime.record.pid,
861
+ pidFile: runtime.pidFile,
862
+ logFile: runtime.record.logFile
863
+ };
864
+ }
865
+ return {
866
+ configured: true,
867
+ connection: "running",
868
+ message: `\u98DE\u4E66 Gateway \u6B63\u5728\u8FD0\u884C\uFF1Apid=${runtime.record.pid}\uFF0CstartedAt=${runtime.record.startedAt}`,
869
+ pid: runtime.record.pid,
870
+ pidFile: runtime.pidFile,
871
+ logFile: runtime.record.logFile
872
+ };
873
+ }
616
874
  if (!config.feishu.appId) {
617
875
  return {
618
876
  configured: false,
@@ -627,23 +885,14 @@ function getGatewayStatus(config, secrets) {
627
885
  message: "\u5C1A\u672A\u914D\u7F6E\u98DE\u4E66 App Secret\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002"
628
886
  };
629
887
  }
630
- const runtime = getGatewayRuntimeState();
631
- if (runtime.running && runtime.record) {
632
- return {
633
- configured: true,
634
- connection: "running",
635
- message: `\u98DE\u4E66 Gateway \u6B63\u5728\u8FD0\u884C\uFF1Apid=${runtime.record.pid}\uFF0CstartedAt=${runtime.record.startedAt}`,
636
- pid: runtime.record.pid,
637
- pidFile: runtime.pidFile
638
- };
639
- }
640
888
  if (runtime.stale && runtime.record) {
641
889
  return {
642
890
  configured: true,
643
891
  connection: "ready_for_start",
644
892
  message: `\u98DE\u4E66\u957F\u8FDE\u63A5\u914D\u7F6E\u5DF2\u5C31\u7EEA\uFF1B\u53D1\u73B0\u8FC7\u671F PID \u6587\u4EF6\uFF1Apid=${runtime.record.pid}\u3002\u8FD0\u884C chattercatcher gateway start \u4F1A\u8986\u76D6\u8FD0\u884C\u8BB0\u5F55\u3002`,
645
893
  pid: runtime.record.pid,
646
- pidFile: runtime.pidFile
894
+ pidFile: runtime.pidFile,
895
+ logFile: runtime.record.logFile
647
896
  };
648
897
  }
649
898
  return {
@@ -1140,82 +1389,6 @@ var MessageFtsRetriever = class {
1140
1389
  }
1141
1390
  };
1142
1391
 
1143
- // src/rag/embedding.ts
1144
- function cosineSimilarity(left, right) {
1145
- if (left.length === 0 || right.length === 0 || left.length !== right.length) {
1146
- return 0;
1147
- }
1148
- let dot = 0;
1149
- let leftNorm = 0;
1150
- let rightNorm = 0;
1151
- for (let index2 = 0; index2 < left.length; index2 += 1) {
1152
- const leftValue = left[index2] ?? 0;
1153
- const rightValue = right[index2] ?? 0;
1154
- dot += leftValue * rightValue;
1155
- leftNorm += leftValue * leftValue;
1156
- rightNorm += rightValue * rightValue;
1157
- }
1158
- if (leftNorm === 0 || rightNorm === 0) {
1159
- return 0;
1160
- }
1161
- return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
1162
- }
1163
-
1164
- // src/rag/sqlite-vector-store.ts
1165
- var SQLiteVectorStore = class {
1166
- constructor(database) {
1167
- this.database = database;
1168
- }
1169
- database;
1170
- async upsert(records) {
1171
- if (records.length === 0) {
1172
- return;
1173
- }
1174
- const statement = this.database.prepare(`
1175
- INSERT INTO message_chunk_vectors (chunk_id, vector_json, evidence_json, updated_at)
1176
- VALUES (@chunkId, @vectorJson, @evidenceJson, @updatedAt)
1177
- ON CONFLICT(chunk_id) DO UPDATE SET
1178
- vector_json = excluded.vector_json,
1179
- evidence_json = excluded.evidence_json,
1180
- updated_at = excluded.updated_at
1181
- `);
1182
- const upsertMany = this.database.transaction((items) => {
1183
- const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1184
- for (const item of items) {
1185
- statement.run({
1186
- chunkId: item.id,
1187
- vectorJson: JSON.stringify(item.vector),
1188
- evidenceJson: JSON.stringify(item.evidence),
1189
- updatedAt
1190
- });
1191
- }
1192
- });
1193
- upsertMany(records);
1194
- }
1195
- async search(vector, limit) {
1196
- if (limit <= 0) {
1197
- return [];
1198
- }
1199
- const rows = this.database.prepare("SELECT chunk_id, vector_json, evidence_json FROM message_chunk_vectors").all();
1200
- return rows.map((row) => {
1201
- const storedVector = JSON.parse(row.vector_json);
1202
- const evidence = JSON.parse(row.evidence_json);
1203
- const vectorScore = cosineSimilarity(vector, storedVector);
1204
- return {
1205
- ...evidence,
1206
- score: vectorScore,
1207
- vectorScore
1208
- };
1209
- }).sort((left, right) => right.vectorScore - left.vectorScore).slice(0, limit);
1210
- }
1211
- async count() {
1212
- const row = this.database.prepare("SELECT COUNT(*) AS count FROM message_chunk_vectors").get();
1213
- return row.count;
1214
- }
1215
- close() {
1216
- }
1217
- };
1218
-
1219
1392
  // src/rag/vector-retriever.ts
1220
1393
  var VectorRetriever = class {
1221
1394
  constructor(embedding, store, limit = 8) {
@@ -1240,7 +1413,8 @@ async function createHybridRetriever(input2) {
1240
1413
  const retrievers = [new MessageFtsRetriever(input2.messages, { excludeMessageIds: input2.excludeMessageIds })];
1241
1414
  const closers = [];
1242
1415
  if (hasEmbeddingConfig(input2.config, input2.secrets)) {
1243
- const vectorStore = new SQLiteVectorStore(input2.messages.database);
1416
+ const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
1417
+ const vectorStore = await LanceDbVectorStore2.connectFromConfig(input2.config);
1244
1418
  retrievers.push(new VectorRetriever(createEmbeddingModel(input2.config, input2.secrets), vectorStore));
1245
1419
  closers.push(() => vectorStore.close());
1246
1420
  }
@@ -1272,7 +1446,7 @@ async function runDoctor(config, secrets, options = {}) {
1272
1446
  checks.push(checkEmbeddingConfig(config, secrets));
1273
1447
  checks.push(await checkSqlite(config));
1274
1448
  checks.push(await checkFilePipeline(config));
1275
- checks.push(await checkSqliteVector(config));
1449
+ checks.push(await checkLanceDb(config));
1276
1450
  checks.push(checkRagPolicy());
1277
1451
  if (options.online) {
1278
1452
  checks.push(await checkChatModel(config, secrets));
@@ -1283,8 +1457,8 @@ async function runDoctor(config, secrets, options = {}) {
1283
1457
  async function checkHomeDirectory() {
1284
1458
  const home = getChatterCatcherHome();
1285
1459
  try {
1286
- await fs5.mkdir(home, { recursive: true });
1287
- await fs5.access(home);
1460
+ await fs7.mkdir(home, { recursive: true });
1461
+ await fs7.access(home);
1288
1462
  return pass("\u914D\u7F6E\u76EE\u5F55", home);
1289
1463
  } catch (error) {
1290
1464
  return fail("\u914D\u7F6E\u76EE\u5F55", error instanceof Error ? error.message : String(error));
@@ -1305,7 +1479,7 @@ function checkLlmConfig(config, secrets) {
1305
1479
  }
1306
1480
  function checkEmbeddingConfig(config, secrets) {
1307
1481
  if (!hasEmbeddingConfig(config, secrets)) {
1308
- return warn("Embedding \u914D\u7F6E", "\u672A\u914D\u7F6E\u5B8C\u6574\uFF1BRAG \u4F1A\u4F7F\u7528 SQLite FTS\uFF0C\u65E0\u6CD5\u4F7F\u7528 SQLite Vector \u8BED\u4E49\u68C0\u7D22\u3002");
1482
+ return warn("Embedding \u914D\u7F6E", "\u672A\u914D\u7F6E\u5B8C\u6574\uFF1BRAG \u4F1A\u4F7F\u7528 SQLite FTS\uFF0C\u65E0\u6CD5\u4F7F\u7528 LanceDB \u8BED\u4E49\u68C0\u7D22\u3002");
1309
1483
  }
1310
1484
  return pass("Embedding \u914D\u7F6E", `${config.embedding.model} @ ${config.embedding.baseUrl || config.llm.baseUrl}`);
1311
1485
  }
@@ -1339,17 +1513,17 @@ async function checkFilePipeline(config) {
1339
1513
  database?.close();
1340
1514
  }
1341
1515
  }
1342
- async function checkSqliteVector(config) {
1343
- let database = null;
1516
+ async function checkLanceDb(config) {
1517
+ let store = null;
1344
1518
  try {
1345
- database = openDatabase(config);
1346
- const store = new SQLiteVectorStore(database);
1519
+ const { getLanceDbPath: getLanceDbPath2, LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
1520
+ store = await LanceDbVectorStore2.connectFromConfig(config);
1347
1521
  const count = await store.count();
1348
- return pass("SQLite Vector", `${getDatabasePath(config)}\uFF1Bvectors=${count}`);
1522
+ return pass("LanceDB", `${getLanceDbPath2(config)}\uFF1Bvectors=${count}`);
1349
1523
  } catch (error) {
1350
- return fail("SQLite Vector", error instanceof Error ? error.message : String(error));
1524
+ return fail("LanceDB", error instanceof Error ? error.message : String(error));
1351
1525
  } finally {
1352
- database?.close();
1526
+ store?.close();
1353
1527
  }
1354
1528
  }
1355
1529
  function checkRagPolicy() {
@@ -1390,8 +1564,9 @@ function formatDoctorChecks(checks) {
1390
1564
  }
1391
1565
 
1392
1566
  // src/export/data-export.ts
1393
- import fs6 from "fs/promises";
1394
- import path8 from "path";
1567
+ init_paths();
1568
+ import fs8 from "fs/promises";
1569
+ import path10 from "path";
1395
1570
  function parseJsonObject(value) {
1396
1571
  try {
1397
1572
  const parsed = JSON.parse(value);
@@ -1410,11 +1585,11 @@ function parseJsonArray(value) {
1410
1585
  }
1411
1586
  function defaultExportPath(config, exportedAt) {
1412
1587
  const fileName = `chattercatcher-export-${exportedAt.replace(/[:.]/g, "-")}.json`;
1413
- return path8.join(resolveHomePath(config.storage.dataDir), "exports", fileName);
1588
+ return path10.join(resolveHomePath(config.storage.dataDir), "exports", fileName);
1414
1589
  }
1415
1590
  async function exportLocalData(input2) {
1416
1591
  const exportedAt = input2.exportedAt ?? (/* @__PURE__ */ new Date()).toISOString();
1417
- const outputPath = path8.resolve(input2.outputPath ?? defaultExportPath(input2.config, exportedAt));
1592
+ const outputPath = path10.resolve(input2.outputPath ?? defaultExportPath(input2.config, exportedAt));
1418
1593
  const chats = input2.database.prepare(
1419
1594
  `
1420
1595
  SELECT
@@ -1501,8 +1676,8 @@ async function exportLocalData(input2) {
1501
1676
  fileJobs
1502
1677
  }
1503
1678
  };
1504
- await fs6.mkdir(path8.dirname(outputPath), { recursive: true });
1505
- await fs6.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}
1679
+ await fs8.mkdir(path10.dirname(outputPath), { recursive: true });
1680
+ await fs8.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}
1506
1681
  `, "utf8");
1507
1682
  return {
1508
1683
  outputPath,
@@ -1514,8 +1689,8 @@ async function exportLocalData(input2) {
1514
1689
  }
1515
1690
 
1516
1691
  // src/export/data-restore.ts
1517
- import fs7 from "fs/promises";
1518
- import path9 from "path";
1692
+ import fs9 from "fs/promises";
1693
+ import path11 from "path";
1519
1694
  function asObject(value) {
1520
1695
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
1521
1696
  }
@@ -1563,8 +1738,8 @@ function clearDatabase(database) {
1563
1738
  database.prepare("DELETE FROM chats").run();
1564
1739
  }
1565
1740
  async function restoreLocalData(input2) {
1566
- const inputPath = path9.resolve(input2.inputPath);
1567
- const payload = parsePayload(await fs7.readFile(inputPath, "utf8"));
1741
+ const inputPath = path11.resolve(input2.inputPath);
1742
+ const payload = parsePayload(await fs9.readFile(inputPath, "utf8"));
1568
1743
  const mode = input2.replace ? "replace" : "merge";
1569
1744
  const restore = input2.database.transaction(() => {
1570
1745
  if (input2.replace) {
@@ -2156,9 +2331,10 @@ function createFeishuGateway(options) {
2156
2331
  }
2157
2332
 
2158
2333
  // src/feishu/resource-downloader.ts
2334
+ init_paths();
2159
2335
  import * as lark3 from "@larksuiteoapi/node-sdk";
2160
- import fs8 from "fs/promises";
2161
- import path10 from "path";
2336
+ import fs10 from "fs/promises";
2337
+ import path12 from "path";
2162
2338
  var RESOURCE_TYPE_BY_KIND = {
2163
2339
  file: "file",
2164
2340
  image: "image",
@@ -2196,10 +2372,10 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
2196
2372
  }
2197
2373
  async download(input2) {
2198
2374
  const resourceType = RESOURCE_TYPE_BY_KIND[input2.attachment.kind];
2199
- const targetDir = path10.join(this.dataDir, "files", "feishu");
2200
- await fs8.mkdir(targetDir, { recursive: true });
2375
+ const targetDir = path12.join(this.dataDir, "files", "feishu");
2376
+ await fs10.mkdir(targetDir, { recursive: true });
2201
2377
  const fileName = buildStoredFileName(input2);
2202
- const storedPath = path10.join(targetDir, fileName);
2378
+ const storedPath = path12.join(targetDir, fileName);
2203
2379
  const payload = {
2204
2380
  params: { type: resourceType },
2205
2381
  path: { message_id: input2.messageId, file_key: input2.attachment.fileKey }
@@ -2221,30 +2397,31 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
2221
2397
  };
2222
2398
 
2223
2399
  // src/files/ingest.ts
2400
+ init_paths();
2224
2401
  import crypto3 from "crypto";
2225
- import fs10 from "fs/promises";
2226
- import path12 from "path";
2402
+ import fs12 from "fs/promises";
2403
+ import path14 from "path";
2227
2404
 
2228
2405
  // src/files/parser.ts
2229
- import fs9 from "fs/promises";
2230
- import path11 from "path";
2406
+ import fs11 from "fs/promises";
2407
+ import path13 from "path";
2231
2408
  import mammoth from "mammoth";
2232
2409
  import { PDFParse } from "pdf-parse";
2233
2410
  var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".markdown", ".json", ".csv", ".tsv", ".log"]);
2234
2411
  var DOCX_EXTENSIONS = /* @__PURE__ */ new Set([".docx"]);
2235
2412
  var PDF_EXTENSIONS = /* @__PURE__ */ new Set([".pdf"]);
2236
2413
  function isSupportedParseFile(filePath) {
2237
- const extension = path11.extname(filePath).toLowerCase();
2414
+ const extension = path13.extname(filePath).toLowerCase();
2238
2415
  return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);
2239
2416
  }
2240
2417
  function describeSupportedParseTypes() {
2241
2418
  return "txt\u3001md\u3001json\u3001csv\u3001tsv\u3001log\u3001docx\u3001pdf";
2242
2419
  }
2243
2420
  async function parseFileToText(filePath) {
2244
- const extension = path11.extname(filePath).toLowerCase();
2421
+ const extension = path13.extname(filePath).toLowerCase();
2245
2422
  if (TEXT_EXTENSIONS.has(extension)) {
2246
2423
  return {
2247
- text: await fs9.readFile(filePath, "utf8"),
2424
+ text: await fs11.readFile(filePath, "utf8"),
2248
2425
  parser: "text",
2249
2426
  warnings: []
2250
2427
  };
@@ -2258,7 +2435,7 @@ async function parseFileToText(filePath) {
2258
2435
  };
2259
2436
  }
2260
2437
  if (PDF_EXTENSIONS.has(extension)) {
2261
- const buffer = await fs9.readFile(filePath);
2438
+ const buffer = await fs11.readFile(filePath);
2262
2439
  const parser = new PDFParse({ data: buffer });
2263
2440
  try {
2264
2441
  const result = await parser.getText();
@@ -2280,7 +2457,7 @@ function isSupportedTextFile(filePath) {
2280
2457
  }
2281
2458
  function ensureSupportedTextFile(filePath) {
2282
2459
  if (!isSupportedTextFile(filePath)) {
2283
- const extension = path12.extname(filePath).toLowerCase();
2460
+ const extension = path14.extname(filePath).toLowerCase();
2284
2461
  throw new Error(`\u6682\u4E0D\u652F\u6301\u8BE5\u6587\u4EF6\u7C7B\u578B\uFF1A${extension || "\u65E0\u6269\u5C55\u540D"}\u3002\u5F53\u524D\u652F\u6301 ${describeSupportedParseTypes()}\u3002`);
2285
2462
  }
2286
2463
  }
@@ -2289,12 +2466,12 @@ function stableStoredName(sourcePath, fileName) {
2289
2466
  return `${digest}-${fileName}`;
2290
2467
  }
2291
2468
  async function ingestLocalFile(input2) {
2292
- const sourcePath = path12.resolve(input2.filePath);
2293
- const fileName = path12.basename(sourcePath);
2469
+ const sourcePath = path14.resolve(input2.filePath);
2470
+ const fileName = path14.basename(sourcePath);
2294
2471
  const jobId = input2.jobs?.start({ sourcePath, fileName });
2295
2472
  try {
2296
2473
  ensureSupportedTextFile(sourcePath);
2297
- const stat = await fs10.stat(sourcePath);
2474
+ const stat = await fs12.stat(sourcePath);
2298
2475
  if (!stat.isFile()) {
2299
2476
  throw new Error(`\u4E0D\u662F\u6587\u4EF6\uFF1A${sourcePath}`);
2300
2477
  }
@@ -2303,10 +2480,10 @@ async function ingestLocalFile(input2) {
2303
2480
  if (!text) {
2304
2481
  throw new Error(`\u6587\u4EF6\u6CA1\u6709\u53EF\u7D22\u5F15\u6587\u672C\uFF1A${sourcePath}`);
2305
2482
  }
2306
- const fileDir = path12.join(resolveHomePath(input2.config.storage.dataDir), "files");
2307
- await fs10.mkdir(fileDir, { recursive: true });
2308
- const storedPath = path12.join(fileDir, stableStoredName(sourcePath, fileName));
2309
- await fs10.copyFile(sourcePath, storedPath);
2483
+ const fileDir = path14.join(resolveHomePath(input2.config.storage.dataDir), "files");
2484
+ await fs12.mkdir(fileDir, { recursive: true });
2485
+ const storedPath = path14.join(fileDir, stableStoredName(sourcePath, fileName));
2486
+ await fs12.copyFile(sourcePath, storedPath);
2310
2487
  const messageId = input2.messages.ingest({
2311
2488
  platform: "local-file",
2312
2489
  platformChatId: "local-files",
@@ -2587,107 +2764,72 @@ var GatewayIngestor = class {
2587
2764
  }
2588
2765
  };
2589
2766
 
2590
- // src/logs/reader.ts
2591
- import fs11 from "fs/promises";
2592
- import { watch } from "fs";
2593
- import path13 from "path";
2594
- function getLogsDirectory() {
2595
- return path13.join(getChatterCatcherHome(), "logs");
2596
- }
2597
- function resolveLogPath(fileName, logsDir = getLogsDirectory()) {
2598
- return path13.isAbsolute(fileName) ? fileName : path13.join(logsDir, fileName);
2599
- }
2600
- function normalizeLineCount(value, fallback = 200) {
2601
- const parsed = Number(value ?? fallback);
2602
- return Number.isFinite(parsed) ? Math.min(Math.max(Math.trunc(parsed), 1), 1e4) : fallback;
2603
- }
2604
- async function listLogFiles(logsDir = getLogsDirectory()) {
2605
- let entries;
2606
- try {
2607
- entries = await fs11.readdir(logsDir, { withFileTypes: true });
2608
- } catch (error) {
2609
- if (error.code === "ENOENT") {
2610
- return [];
2611
- }
2612
- throw error;
2767
+ // src/gateway/detached.ts
2768
+ import { spawn } from "child_process";
2769
+ import fs13 from "fs";
2770
+ import path15 from "path";
2771
+ function buildGatewayForegroundSpawnCommand(argv = process.argv) {
2772
+ const [command = process.execPath, ...rawArgs] = argv;
2773
+ const args = [...rawArgs];
2774
+ while (args.at(-1) === "--foreground") {
2775
+ args.pop();
2776
+ }
2777
+ if (args.at(-1) === "start" && args.at(-2) === "gateway") {
2778
+ args.splice(-2, 2);
2613
2779
  }
2614
- const files2 = await Promise.all(
2615
- entries.filter((entry) => entry.isFile() && entry.name.endsWith(".log")).map(async (entry) => {
2616
- const filePath = path13.join(logsDir, entry.name);
2617
- const stats = await fs11.stat(filePath);
2618
- return {
2619
- name: entry.name,
2620
- path: filePath,
2621
- updatedAt: stats.mtime,
2622
- bytes: stats.size
2623
- };
2624
- })
2625
- );
2626
- return files2.sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());
2627
- }
2628
- function tailLines(content, lines) {
2629
- const normalized = content.replace(/\r\n/g, "\n");
2630
- const parts = normalized.endsWith("\n") ? normalized.slice(0, -1).split("\n") : normalized.split("\n");
2631
- return parts.slice(-lines).join("\n");
2632
- }
2633
- async function readLogTail(input2) {
2634
- const stats = await fs11.stat(input2.filePath);
2635
- const content = await fs11.readFile(input2.filePath, "utf8");
2636
2780
  return {
2637
- file: {
2638
- name: path13.basename(input2.filePath),
2639
- path: input2.filePath,
2640
- updatedAt: stats.mtime,
2641
- bytes: stats.size
2642
- },
2643
- content: tailLines(content, normalizeLineCount(input2.lines))
2781
+ command,
2782
+ args: [...args, "gateway", "start", "--foreground"]
2644
2783
  };
2645
2784
  }
2646
- async function readLatestLogTail(input2 = {}) {
2647
- if (input2.fileName) {
2648
- return readLogTail({
2649
- filePath: resolveLogPath(input2.fileName, input2.logsDir),
2650
- lines: input2.lines
2651
- });
2652
- }
2653
- const [latest] = await listLogFiles(input2.logsDir);
2654
- if (!latest) {
2655
- return null;
2785
+ function startDetachedGateway(input2) {
2786
+ const status = getGatewayStatus(input2.config, input2.secrets);
2787
+ const logFile = getGatewayLogPath();
2788
+ if (status.connection === "running") {
2789
+ return {
2790
+ started: false,
2791
+ message: `\u98DE\u4E66 Gateway \u5DF2\u7ECF\u6B63\u5728\u8FD0\u884C\uFF1Apid=${status.pid ?? "unknown"}`,
2792
+ logFile,
2793
+ ...status.pid ? { pid: status.pid } : {}
2794
+ };
2656
2795
  }
2657
- return readLogTail({ filePath: latest.path, lines: input2.lines });
2658
- }
2659
- async function followLogFile(input2) {
2660
- let offset = (await fs11.stat(input2.filePath)).size;
2661
- const directory = path13.dirname(input2.filePath);
2662
- const fileName = path13.basename(input2.filePath);
2663
- async function readAppended() {
2664
- const stats = await fs11.stat(input2.filePath);
2665
- if (stats.size < offset) {
2666
- offset = 0;
2667
- }
2668
- if (stats.size === offset) {
2796
+ fs13.mkdirSync(path15.dirname(logFile), { recursive: true });
2797
+ let out;
2798
+ let err;
2799
+ let stdioClosed = false;
2800
+ const closeStdio = () => {
2801
+ if (stdioClosed) {
2669
2802
  return;
2670
2803
  }
2671
- const handle = await fs11.open(input2.filePath, "r");
2672
- try {
2673
- const length = stats.size - offset;
2674
- const buffer = Buffer.alloc(length);
2675
- await handle.read(buffer, 0, length, offset);
2676
- offset = stats.size;
2677
- input2.onChunk(buffer.toString("utf8"));
2678
- } finally {
2679
- await handle.close();
2804
+ stdioClosed = true;
2805
+ if (typeof out === "number") {
2806
+ fs13.closeSync(out);
2680
2807
  }
2681
- }
2682
- const watcher = watch(directory, (eventType, changedFileName) => {
2683
- if (eventType !== "change" || changedFileName?.toString() !== fileName) {
2684
- return;
2808
+ if (typeof err === "number") {
2809
+ fs13.closeSync(err);
2685
2810
  }
2686
- void readAppended().catch((error) => {
2687
- input2.onError?.(error instanceof Error ? error : new Error(String(error)));
2811
+ };
2812
+ try {
2813
+ out = fs13.openSync(logFile, "a");
2814
+ err = fs13.openSync(logFile, "a");
2815
+ const foreground = buildGatewayForegroundSpawnCommand(input2.argv);
2816
+ const child = spawn(foreground.command, foreground.args, {
2817
+ detached: true,
2818
+ stdio: ["ignore", out, err],
2819
+ windowsHide: true
2688
2820
  });
2689
- });
2690
- return () => watcher.close();
2821
+ closeStdio();
2822
+ child.unref();
2823
+ return {
2824
+ started: true,
2825
+ message: `\u5DF2\u5728\u540E\u53F0\u542F\u52A8\u98DE\u4E66 Gateway\uFF1Apid=${child.pid}`,
2826
+ pid: child.pid,
2827
+ logFile
2828
+ };
2829
+ } catch (error) {
2830
+ closeStdio();
2831
+ throw error;
2832
+ }
2691
2833
  }
2692
2834
 
2693
2835
  // src/rag/indexer.ts
@@ -2749,7 +2891,8 @@ async function processMessagesNow(input2) {
2749
2891
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
2750
2892
  };
2751
2893
  }
2752
- const vectorStore = new SQLiteVectorStore(input2.database);
2894
+ const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
2895
+ const vectorStore = await LanceDbVectorStore2.connectFromConfig(input2.config);
2753
2896
  try {
2754
2897
  const stats = await indexMessageChunks({
2755
2898
  messages: new MessageRepository(input2.database),
@@ -3166,7 +3309,7 @@ function createWebApp(config) {
3166
3309
  note: "\u95EE\u7B54\u5FC5\u987B\u5148\u68C0\u7D22\u8BC1\u636E\uFF0C\u7981\u6B62\u5168\u91CF\u4E0A\u4E0B\u6587\u5806\u53E0\u3002",
3167
3310
  retrieval: {
3168
3311
  keyword: "SQLite FTS5",
3169
- vector: "SQLite Vector",
3312
+ vector: "LanceDB",
3170
3313
  hybrid: true
3171
3314
  }
3172
3315
  },
@@ -3286,7 +3429,7 @@ function printSettings(config, secrets) {
3286
3429
  2
3287
3430
  ));
3288
3431
  }
3289
- program.name("chattercatcher").description("\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u673A\u5668\u4EBA").version("0.1.6");
3432
+ program.name("chattercatcher").description("\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u673A\u5668\u4EBA").version("0.1.5");
3290
3433
  program.command("setup").description("\u4EA4\u4E92\u5F0F\u521D\u59CB\u5316\u914D\u7F6E").action(async () => {
3291
3434
  const { config, secrets } = await ensureConfigFiles();
3292
3435
  await promptForConfiguration(config, secrets);
@@ -3323,18 +3466,33 @@ program.command("doctor").description("\u68C0\u67E5\u672C\u5730\u914D\u7F6E\u300
3323
3466
  console.log(formatDoctorChecks(checks));
3324
3467
  });
3325
3468
  var gateway = program.command("gateway").description("\u7BA1\u7406\u672C\u5730\u98DE\u4E66 Gateway");
3326
- async function startGatewayCommand() {
3469
+ async function startGatewayForegroundCommand() {
3327
3470
  const config = await loadConfig();
3328
3471
  const secrets = await loadSecrets();
3329
3472
  const status = getGatewayStatus(config, secrets);
3473
+ const pidRecordBase = {
3474
+ pid: process.pid,
3475
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
3476
+ command: process.argv.join(" "),
3477
+ logFile: getGatewayLogPath()
3478
+ };
3330
3479
  if (!status.configured) {
3480
+ writeGatewayPidRecord(void 0, {
3481
+ ...pidRecordBase,
3482
+ mode: "web"
3483
+ });
3331
3484
  console.log(status.message);
3332
3485
  console.log("\u672C\u5730 Web UI \u4ECD\u4F1A\u542F\u52A8\uFF0C\u65B9\u4FBF\u7EE7\u7EED\u914D\u7F6E\u3002");
3333
3486
  await startWebServer(config);
3334
3487
  return;
3335
3488
  }
3489
+ writeGatewayPidRecord(void 0, {
3490
+ ...pidRecordBase,
3491
+ mode: "gateway"
3492
+ });
3336
3493
  const database = openDatabase(config);
3337
- const vectorStore = hasEmbeddingConfig(config, secrets) ? new SQLiteVectorStore(database) : null;
3494
+ const { LanceDbVectorStore: LanceDbVectorStore2 } = hasEmbeddingConfig(config, secrets) ? await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports)) : { LanceDbVectorStore: null };
3495
+ const vectorStore = LanceDbVectorStore2 ? await LanceDbVectorStore2.connectFromConfig(config) : null;
3338
3496
  const gatewayRuntime = createFeishuGateway({
3339
3497
  config,
3340
3498
  secrets,
@@ -3369,7 +3527,6 @@ async function startGatewayCommand() {
3369
3527
  process.exit(0);
3370
3528
  });
3371
3529
  console.log(status.message);
3372
- writeGatewayPidRecord();
3373
3530
  try {
3374
3531
  await gatewayRuntime.start();
3375
3532
  await startWebServer(config);
@@ -3378,12 +3535,28 @@ async function startGatewayCommand() {
3378
3535
  throw error;
3379
3536
  }
3380
3537
  }
3538
+ async function startGatewayCommand(options = {}) {
3539
+ if (options.foreground) {
3540
+ await startGatewayForegroundCommand();
3541
+ return;
3542
+ }
3543
+ const config = await loadConfig();
3544
+ const secrets = await loadSecrets();
3545
+ const result = startDetachedGateway({ config, secrets });
3546
+ console.log(result.message);
3547
+ if (result.pid) {
3548
+ console.log(`PID\uFF1A${result.pid}`);
3549
+ }
3550
+ console.log(`\u65E5\u5FD7\u6587\u4EF6\uFF1A${result.logFile}`);
3551
+ console.log("\u67E5\u770B\u65E5\u5FD7\uFF1Achattercatcher logs --follow --file gateway.log");
3552
+ console.log("\u505C\u6B62 Gateway\uFF1Achattercatcher gateway stop");
3553
+ }
3381
3554
  gateway.command("status").description("\u67E5\u770B Gateway \u72B6\u6001").action(async () => {
3382
3555
  const config = await loadConfig();
3383
3556
  const secrets = await loadSecrets();
3384
3557
  console.log(JSON.stringify(getGatewayStatus(config, secrets), null, 2));
3385
3558
  });
3386
- gateway.command("start").description("\u542F\u52A8\u98DE\u4E66\u957F\u8FDE\u63A5 Gateway \u548C\u672C\u5730 Web UI").action(startGatewayCommand);
3559
+ gateway.command("start").description("\u542F\u52A8\u98DE\u4E66\u957F\u8FDE\u63A5 Gateway \u548C\u672C\u5730 Web UI").option("--foreground", "\u5728\u5F53\u524D\u7EC8\u7AEF\u4EE5\u524D\u53F0\u6A21\u5F0F\u8FD0\u884C").action(startGatewayCommand);
3387
3560
  gateway.command("stop").description("\u505C\u6B62 Gateway").action(() => {
3388
3561
  console.log(stopGatewayProcess().message);
3389
3562
  });
@@ -3419,7 +3592,7 @@ async function deleteDataCommand(targetType, targetId, options) {
3419
3592
  if (result.skippedStoredFiles.length > 0) {
3420
3593
  console.log(`\u8DF3\u8FC7\u975E\u6570\u636E\u76EE\u5F55\u6587\u4EF6\uFF1A${result.skippedStoredFiles.join("\uFF1B")}`);
3421
3594
  }
3422
- console.log("SQLite FTS \u5DF2\u540C\u6B65\u5220\u9664\uFF1B\u5982\u4F7F\u7528\u8BED\u4E49\u68C0\u7D22\uFF0C\u8BF7\u8FD0\u884C chattercatcher index rebuild \u91CD\u5EFA SQLite Vector\u3002");
3595
+ console.log("SQLite FTS \u5DF2\u540C\u6B65\u5220\u9664\uFF1B\u5982\u4F7F\u7528 LanceDB \u8BED\u4E49\u68C0\u7D22\uFF0C\u8BF7\u8FD0\u884C chattercatcher index rebuild\u3002");
3423
3596
  } finally {
3424
3597
  database.close();
3425
3598
  }
@@ -3434,19 +3607,20 @@ index.command("status").description("\u67E5\u770B\u7D22\u5F15\u72B6\u6001").acti
3434
3607
  const secrets = await loadSecrets();
3435
3608
  const database = openDatabase(config);
3436
3609
  const messages = new MessageRepository(database);
3437
- const vectorStore = new SQLiteVectorStore(database);
3610
+ const { getLanceDbPath: getLanceDbPath2, LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
3611
+ const vectorStore = await LanceDbVectorStore2.connectFromConfig(config);
3438
3612
  const vectors = await vectorStore.count();
3439
3613
  console.log(JSON.stringify(
3440
3614
  {
3441
3615
  database: getDatabasePath(config),
3442
- vectorDatabase: getDatabasePath(config),
3616
+ vectorDatabase: getLanceDbPath2(config),
3443
3617
  chats: messages.getChatCount(),
3444
3618
  messages: messages.getMessageCount(),
3445
3619
  vectors,
3446
3620
  retrieval: {
3447
3621
  keyword: "SQLite FTS5",
3448
- vector: hasEmbeddingConfig(config, secrets) ? "SQLite Vector \u5DF2\u53EF\u7528\u4E8E\u8BED\u4E49\u68C0\u7D22" : "SQLite Vector \u5DF2\u63A5\u5165\uFF1B\u9700\u914D\u7F6E embedding \u540E\u542F\u7528\u8BED\u4E49\u68C0\u7D22",
3449
- hybrid: "\u542F\u7528\uFF1ASQLite FTS + SQLite Vector",
3622
+ vector: hasEmbeddingConfig(config, secrets) ? "LanceDB \u5DF2\u53EF\u7528\u4E8E\u8BED\u4E49\u68C0\u7D22" : "LanceDB \u5DF2\u63A5\u5165\uFF1B\u9700\u914D\u7F6E embedding \u540E\u542F\u7528\u8BED\u4E49\u68C0\u7D22",
3623
+ hybrid: "\u542F\u7528\uFF1ASQLite FTS + LanceDB Vector",
3450
3624
  rag: "\u5F3A\u5236\u5148\u68C0\u7D22\u8BC1\u636E\u518D\u56DE\u7B54\uFF0C\u7981\u6B62\u5168\u91CF\u4E0A\u4E0B\u6587\u5806\u53E0"
3451
3625
  }
3452
3626
  },
@@ -3456,7 +3630,7 @@ index.command("status").description("\u67E5\u770B\u7D22\u5F15\u72B6\u6001").acti
3456
3630
  vectorStore.close();
3457
3631
  database.close();
3458
3632
  });
3459
- index.command("rebuild").description("\u91CD\u5EFA SQLite \u5411\u91CF\u7D22\u5F15").option("--limit <number>", "\u6700\u591A\u7D22\u5F15\u7684 chunk \u6570", "10000").action(async (options) => {
3633
+ index.command("rebuild").description("\u91CD\u5EFA LanceDB \u5411\u91CF\u7D22\u5F15").option("--limit <number>", "\u6700\u591A\u7D22\u5F15\u7684 chunk \u6570", "10000").action(async (options) => {
3460
3634
  const config = await loadConfig();
3461
3635
  const secrets = await loadSecrets();
3462
3636
  if (!hasEmbeddingConfig(config, secrets)) {
@@ -3464,7 +3638,8 @@ index.command("rebuild").description("\u91CD\u5EFA SQLite \u5411\u91CF\u7D22\u5F
3464
3638
  return;
3465
3639
  }
3466
3640
  const database = openDatabase(config);
3467
- const vectorStore = new SQLiteVectorStore(database);
3641
+ const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
3642
+ const vectorStore = await LanceDbVectorStore2.connectFromConfig(config);
3468
3643
  try {
3469
3644
  const stats = await indexMessageChunks({
3470
3645
  messages: new MessageRepository(database),
@@ -3479,7 +3654,7 @@ index.command("rebuild").description("\u91CD\u5EFA SQLite \u5411\u91CF\u7D22\u5F
3479
3654
  }
3480
3655
  });
3481
3656
  var processCommand = program.command("process").description("\u7ACB\u5373\u5904\u7406\u540E\u53F0\u4EFB\u52A1");
3482
- processCommand.command("messages").description("\u7ACB\u5373\u5904\u7406\u6D88\u606F\u7D22\u5F15\u4EFB\u52A1\uFF0C\u628A\u6D88\u606F chunks \u5199\u5165 SQLite \u5411\u91CF\u7D22\u5F15").option("--limit <number>", "\u6700\u591A\u5904\u7406\u7684 chunk \u6570", "10000").action(async (options) => {
3657
+ processCommand.command("messages").description("\u7ACB\u5373\u5904\u7406\u6D88\u606F\u7D22\u5F15\u4EFB\u52A1\uFF0C\u628A\u6D88\u606F chunks \u5199\u5165 LanceDB \u5411\u91CF\u7D22\u5F15").option("--limit <number>", "\u6700\u591A\u5904\u7406\u7684 chunk \u6570", "10000").action(async (options) => {
3483
3658
  const config = await loadConfig();
3484
3659
  const secrets = await loadSecrets();
3485
3660
  const database = openDatabase(config);
@@ -3513,7 +3688,7 @@ files.command("add").description("\u628A\u672C\u5730\u6587\u4EF6\u89E3\u6790\u30
3513
3688
  `\u5DF2\u5BFC\u5165\u6587\u4EF6\uFF1A${result.fileName}\uFF0C\u89E3\u6790\u5668=${result.parser}\uFF0C\u5B57\u7B26\u6570=${result.characters}\uFF0C\u6D88\u606FID=${result.messageId}`
3514
3689
  );
3515
3690
  }
3516
- console.log("\u6587\u4EF6\u5DF2\u8FDB\u5165 SQLite FTS \u68C0\u7D22\uFF1B\u5982\u5DF2\u914D\u7F6E embedding\uFF0C\u53EF\u8FD0\u884C chattercatcher index rebuild \u66F4\u65B0 SQLite \u5411\u91CF\u7D22\u5F15\u3002");
3691
+ console.log("\u6587\u4EF6\u5DF2\u8FDB\u5165 SQLite FTS \u68C0\u7D22\uFF1B\u5982\u5DF2\u914D\u7F6E embedding\uFF0C\u53EF\u8FD0\u884C chattercatcher index rebuild \u66F4\u65B0 LanceDB \u5411\u91CF\u7D22\u5F15\u3002");
3517
3692
  } finally {
3518
3693
  database.close();
3519
3694
  }
@@ -3642,7 +3817,7 @@ program.command("restore").description("\u4ECE ChatterCatcher \u5BFC\u51FA\u6587
3642
3817
  console.log(`\u6062\u590D\u5B8C\u6210\uFF1A${result.inputPath}`);
3643
3818
  console.log(`\u6A21\u5F0F\uFF1A${result.mode === "replace" ? "\u66FF\u6362" : "\u5408\u5E76"}`);
3644
3819
  console.log(`\u5305\u542B\uFF1A\u7FA4\u804A=${result.chats}\uFF0C\u6D88\u606F=${result.messages}\uFF0Cchunks=${result.chunks}\uFF0C\u6587\u4EF6\u4EFB\u52A1=${result.fileJobs}`);
3645
- console.log("SQLite FTS \u5DF2\u91CD\u5EFA\uFF1B\u5982\u4F7F\u7528\u8BED\u4E49\u68C0\u7D22\uFF0C\u8BF7\u8FD0\u884C chattercatcher index rebuild \u91CD\u5EFA SQLite Vector\u3002");
3820
+ console.log("SQLite FTS \u5DF2\u91CD\u5EFA\uFF1B\u5982\u4F7F\u7528 LanceDB \u8BED\u4E49\u68C0\u7D22\uFF0C\u8BF7\u8FD0\u884C chattercatcher index rebuild\u3002");
3646
3821
  } finally {
3647
3822
  database.close();
3648
3823
  }
@@ -3672,7 +3847,7 @@ dev.command("ingest-feishu-event").description("\u4ECE JSON \u6587\u4EF6\u6A21\u
3672
3847
  const config = await loadConfig();
3673
3848
  const database = openDatabase(config);
3674
3849
  try {
3675
- const raw = await fs12.readFile(options.file, "utf8");
3850
+ const raw = await fs14.readFile(options.file, "utf8");
3676
3851
  const payload = JSON.parse(raw);
3677
3852
  const result = new GatewayIngestor(database).ingestFeishuEvent(payload);
3678
3853
  if (!result.accepted) {