chattercatcher 0.1.3 → 0.1.6

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,155 +1,9 @@
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 fs5 from "fs/promises";
46
- import path8 from "path";
47
- function getLanceDbPath(config) {
48
- return path8.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 fs5.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
- });
148
2
 
149
3
  // src/cli.ts
150
4
  import { input, password, select, confirm, number } from "@inquirer/prompts";
151
5
  import { Command } from "commander";
152
- import fs13 from "fs/promises";
6
+ import fs12 from "fs/promises";
153
7
 
154
8
  // src/config/store.ts
155
9
  import fs from "fs/promises";
@@ -218,8 +72,29 @@ function createDefaultSecrets() {
218
72
  });
219
73
  }
220
74
 
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
+
221
97
  // src/config/store.ts
222
- init_paths();
223
98
  async function readJsonFile(filePath, fallback) {
224
99
  try {
225
100
  const raw = await fs.readFile(filePath, "utf8");
@@ -282,11 +157,7 @@ function resolveEmbeddingApiKey(input2) {
282
157
  return explicit || input2.llmApiKey;
283
158
  }
284
159
 
285
- // src/cli.ts
286
- init_paths();
287
-
288
160
  // src/data/deletion.ts
289
- init_paths();
290
161
  import fs2 from "fs/promises";
291
162
  import path4 from "path";
292
163
  function emptyResult(targetType, targetId) {
@@ -412,7 +283,6 @@ async function deleteLocalData(input2) {
412
283
  }
413
284
 
414
285
  // src/db/database.ts
415
- init_paths();
416
286
  import Database from "better-sqlite3";
417
287
  import fs3 from "fs";
418
288
  import path5 from "path";
@@ -473,6 +343,13 @@ function migrateDatabase(database) {
473
343
  tokenize = 'unicode61'
474
344
  );
475
345
 
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
+
476
353
  CREATE TABLE IF NOT EXISTS file_jobs (
477
354
  id TEXT PRIMARY KEY,
478
355
  source_path TEXT NOT NULL,
@@ -492,8 +369,7 @@ function migrateDatabase(database) {
492
369
  }
493
370
 
494
371
  // src/doctor/checks.ts
495
- init_paths();
496
- import fs6 from "fs/promises";
372
+ import fs5 from "fs/promises";
497
373
 
498
374
  // src/files/jobs.ts
499
375
  import crypto from "crypto";
@@ -635,7 +511,6 @@ var FileJobRepository = class {
635
511
  };
636
512
 
637
513
  // src/gateway/runtime.ts
638
- init_paths();
639
514
  import fs4 from "fs";
640
515
  import path7 from "path";
641
516
  function getGatewayPidPath() {
@@ -1265,6 +1140,82 @@ var MessageFtsRetriever = class {
1265
1140
  }
1266
1141
  };
1267
1142
 
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
+
1268
1219
  // src/rag/vector-retriever.ts
1269
1220
  var VectorRetriever = class {
1270
1221
  constructor(embedding, store, limit = 8) {
@@ -1289,8 +1240,7 @@ async function createHybridRetriever(input2) {
1289
1240
  const retrievers = [new MessageFtsRetriever(input2.messages, { excludeMessageIds: input2.excludeMessageIds })];
1290
1241
  const closers = [];
1291
1242
  if (hasEmbeddingConfig(input2.config, input2.secrets)) {
1292
- const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
1293
- const vectorStore = await LanceDbVectorStore2.connectFromConfig(input2.config);
1243
+ const vectorStore = new SQLiteVectorStore(input2.messages.database);
1294
1244
  retrievers.push(new VectorRetriever(createEmbeddingModel(input2.config, input2.secrets), vectorStore));
1295
1245
  closers.push(() => vectorStore.close());
1296
1246
  }
@@ -1322,7 +1272,7 @@ async function runDoctor(config, secrets, options = {}) {
1322
1272
  checks.push(checkEmbeddingConfig(config, secrets));
1323
1273
  checks.push(await checkSqlite(config));
1324
1274
  checks.push(await checkFilePipeline(config));
1325
- checks.push(await checkLanceDb(config));
1275
+ checks.push(await checkSqliteVector(config));
1326
1276
  checks.push(checkRagPolicy());
1327
1277
  if (options.online) {
1328
1278
  checks.push(await checkChatModel(config, secrets));
@@ -1333,8 +1283,8 @@ async function runDoctor(config, secrets, options = {}) {
1333
1283
  async function checkHomeDirectory() {
1334
1284
  const home = getChatterCatcherHome();
1335
1285
  try {
1336
- await fs6.mkdir(home, { recursive: true });
1337
- await fs6.access(home);
1286
+ await fs5.mkdir(home, { recursive: true });
1287
+ await fs5.access(home);
1338
1288
  return pass("\u914D\u7F6E\u76EE\u5F55", home);
1339
1289
  } catch (error) {
1340
1290
  return fail("\u914D\u7F6E\u76EE\u5F55", error instanceof Error ? error.message : String(error));
@@ -1355,7 +1305,7 @@ function checkLlmConfig(config, secrets) {
1355
1305
  }
1356
1306
  function checkEmbeddingConfig(config, secrets) {
1357
1307
  if (!hasEmbeddingConfig(config, secrets)) {
1358
- 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");
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");
1359
1309
  }
1360
1310
  return pass("Embedding \u914D\u7F6E", `${config.embedding.model} @ ${config.embedding.baseUrl || config.llm.baseUrl}`);
1361
1311
  }
@@ -1389,17 +1339,17 @@ async function checkFilePipeline(config) {
1389
1339
  database?.close();
1390
1340
  }
1391
1341
  }
1392
- async function checkLanceDb(config) {
1393
- let store = null;
1342
+ async function checkSqliteVector(config) {
1343
+ let database = null;
1394
1344
  try {
1395
- const { getLanceDbPath: getLanceDbPath2, LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
1396
- store = await LanceDbVectorStore2.connectFromConfig(config);
1345
+ database = openDatabase(config);
1346
+ const store = new SQLiteVectorStore(database);
1397
1347
  const count = await store.count();
1398
- return pass("LanceDB", `${getLanceDbPath2(config)}\uFF1Bvectors=${count}`);
1348
+ return pass("SQLite Vector", `${getDatabasePath(config)}\uFF1Bvectors=${count}`);
1399
1349
  } catch (error) {
1400
- return fail("LanceDB", error instanceof Error ? error.message : String(error));
1350
+ return fail("SQLite Vector", error instanceof Error ? error.message : String(error));
1401
1351
  } finally {
1402
- store?.close();
1352
+ database?.close();
1403
1353
  }
1404
1354
  }
1405
1355
  function checkRagPolicy() {
@@ -1440,9 +1390,8 @@ function formatDoctorChecks(checks) {
1440
1390
  }
1441
1391
 
1442
1392
  // src/export/data-export.ts
1443
- init_paths();
1444
- import fs7 from "fs/promises";
1445
- import path9 from "path";
1393
+ import fs6 from "fs/promises";
1394
+ import path8 from "path";
1446
1395
  function parseJsonObject(value) {
1447
1396
  try {
1448
1397
  const parsed = JSON.parse(value);
@@ -1461,11 +1410,11 @@ function parseJsonArray(value) {
1461
1410
  }
1462
1411
  function defaultExportPath(config, exportedAt) {
1463
1412
  const fileName = `chattercatcher-export-${exportedAt.replace(/[:.]/g, "-")}.json`;
1464
- return path9.join(resolveHomePath(config.storage.dataDir), "exports", fileName);
1413
+ return path8.join(resolveHomePath(config.storage.dataDir), "exports", fileName);
1465
1414
  }
1466
1415
  async function exportLocalData(input2) {
1467
1416
  const exportedAt = input2.exportedAt ?? (/* @__PURE__ */ new Date()).toISOString();
1468
- const outputPath = path9.resolve(input2.outputPath ?? defaultExportPath(input2.config, exportedAt));
1417
+ const outputPath = path8.resolve(input2.outputPath ?? defaultExportPath(input2.config, exportedAt));
1469
1418
  const chats = input2.database.prepare(
1470
1419
  `
1471
1420
  SELECT
@@ -1552,8 +1501,8 @@ async function exportLocalData(input2) {
1552
1501
  fileJobs
1553
1502
  }
1554
1503
  };
1555
- await fs7.mkdir(path9.dirname(outputPath), { recursive: true });
1556
- await fs7.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}
1504
+ await fs6.mkdir(path8.dirname(outputPath), { recursive: true });
1505
+ await fs6.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}
1557
1506
  `, "utf8");
1558
1507
  return {
1559
1508
  outputPath,
@@ -1565,8 +1514,8 @@ async function exportLocalData(input2) {
1565
1514
  }
1566
1515
 
1567
1516
  // src/export/data-restore.ts
1568
- import fs8 from "fs/promises";
1569
- import path10 from "path";
1517
+ import fs7 from "fs/promises";
1518
+ import path9 from "path";
1570
1519
  function asObject(value) {
1571
1520
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
1572
1521
  }
@@ -1614,8 +1563,8 @@ function clearDatabase(database) {
1614
1563
  database.prepare("DELETE FROM chats").run();
1615
1564
  }
1616
1565
  async function restoreLocalData(input2) {
1617
- const inputPath = path10.resolve(input2.inputPath);
1618
- const payload = parsePayload(await fs8.readFile(inputPath, "utf8"));
1566
+ const inputPath = path9.resolve(input2.inputPath);
1567
+ const payload = parsePayload(await fs7.readFile(inputPath, "utf8"));
1619
1568
  const mode = input2.replace ? "replace" : "merge";
1620
1569
  const restore = input2.database.transaction(() => {
1621
1570
  if (input2.replace) {
@@ -2207,10 +2156,9 @@ function createFeishuGateway(options) {
2207
2156
  }
2208
2157
 
2209
2158
  // src/feishu/resource-downloader.ts
2210
- init_paths();
2211
2159
  import * as lark3 from "@larksuiteoapi/node-sdk";
2212
- import fs9 from "fs/promises";
2213
- import path11 from "path";
2160
+ import fs8 from "fs/promises";
2161
+ import path10 from "path";
2214
2162
  var RESOURCE_TYPE_BY_KIND = {
2215
2163
  file: "file",
2216
2164
  image: "image",
@@ -2248,10 +2196,10 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
2248
2196
  }
2249
2197
  async download(input2) {
2250
2198
  const resourceType = RESOURCE_TYPE_BY_KIND[input2.attachment.kind];
2251
- const targetDir = path11.join(this.dataDir, "files", "feishu");
2252
- await fs9.mkdir(targetDir, { recursive: true });
2199
+ const targetDir = path10.join(this.dataDir, "files", "feishu");
2200
+ await fs8.mkdir(targetDir, { recursive: true });
2253
2201
  const fileName = buildStoredFileName(input2);
2254
- const storedPath = path11.join(targetDir, fileName);
2202
+ const storedPath = path10.join(targetDir, fileName);
2255
2203
  const payload = {
2256
2204
  params: { type: resourceType },
2257
2205
  path: { message_id: input2.messageId, file_key: input2.attachment.fileKey }
@@ -2273,31 +2221,30 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
2273
2221
  };
2274
2222
 
2275
2223
  // src/files/ingest.ts
2276
- init_paths();
2277
2224
  import crypto3 from "crypto";
2278
- import fs11 from "fs/promises";
2279
- import path13 from "path";
2280
-
2281
- // src/files/parser.ts
2282
2225
  import fs10 from "fs/promises";
2283
2226
  import path12 from "path";
2227
+
2228
+ // src/files/parser.ts
2229
+ import fs9 from "fs/promises";
2230
+ import path11 from "path";
2284
2231
  import mammoth from "mammoth";
2285
2232
  import { PDFParse } from "pdf-parse";
2286
2233
  var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".markdown", ".json", ".csv", ".tsv", ".log"]);
2287
2234
  var DOCX_EXTENSIONS = /* @__PURE__ */ new Set([".docx"]);
2288
2235
  var PDF_EXTENSIONS = /* @__PURE__ */ new Set([".pdf"]);
2289
2236
  function isSupportedParseFile(filePath) {
2290
- const extension = path12.extname(filePath).toLowerCase();
2237
+ const extension = path11.extname(filePath).toLowerCase();
2291
2238
  return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);
2292
2239
  }
2293
2240
  function describeSupportedParseTypes() {
2294
2241
  return "txt\u3001md\u3001json\u3001csv\u3001tsv\u3001log\u3001docx\u3001pdf";
2295
2242
  }
2296
2243
  async function parseFileToText(filePath) {
2297
- const extension = path12.extname(filePath).toLowerCase();
2244
+ const extension = path11.extname(filePath).toLowerCase();
2298
2245
  if (TEXT_EXTENSIONS.has(extension)) {
2299
2246
  return {
2300
- text: await fs10.readFile(filePath, "utf8"),
2247
+ text: await fs9.readFile(filePath, "utf8"),
2301
2248
  parser: "text",
2302
2249
  warnings: []
2303
2250
  };
@@ -2311,7 +2258,7 @@ async function parseFileToText(filePath) {
2311
2258
  };
2312
2259
  }
2313
2260
  if (PDF_EXTENSIONS.has(extension)) {
2314
- const buffer = await fs10.readFile(filePath);
2261
+ const buffer = await fs9.readFile(filePath);
2315
2262
  const parser = new PDFParse({ data: buffer });
2316
2263
  try {
2317
2264
  const result = await parser.getText();
@@ -2333,7 +2280,7 @@ function isSupportedTextFile(filePath) {
2333
2280
  }
2334
2281
  function ensureSupportedTextFile(filePath) {
2335
2282
  if (!isSupportedTextFile(filePath)) {
2336
- const extension = path13.extname(filePath).toLowerCase();
2283
+ const extension = path12.extname(filePath).toLowerCase();
2337
2284
  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`);
2338
2285
  }
2339
2286
  }
@@ -2342,12 +2289,12 @@ function stableStoredName(sourcePath, fileName) {
2342
2289
  return `${digest}-${fileName}`;
2343
2290
  }
2344
2291
  async function ingestLocalFile(input2) {
2345
- const sourcePath = path13.resolve(input2.filePath);
2346
- const fileName = path13.basename(sourcePath);
2292
+ const sourcePath = path12.resolve(input2.filePath);
2293
+ const fileName = path12.basename(sourcePath);
2347
2294
  const jobId = input2.jobs?.start({ sourcePath, fileName });
2348
2295
  try {
2349
2296
  ensureSupportedTextFile(sourcePath);
2350
- const stat = await fs11.stat(sourcePath);
2297
+ const stat = await fs10.stat(sourcePath);
2351
2298
  if (!stat.isFile()) {
2352
2299
  throw new Error(`\u4E0D\u662F\u6587\u4EF6\uFF1A${sourcePath}`);
2353
2300
  }
@@ -2356,10 +2303,10 @@ async function ingestLocalFile(input2) {
2356
2303
  if (!text) {
2357
2304
  throw new Error(`\u6587\u4EF6\u6CA1\u6709\u53EF\u7D22\u5F15\u6587\u672C\uFF1A${sourcePath}`);
2358
2305
  }
2359
- const fileDir = path13.join(resolveHomePath(input2.config.storage.dataDir), "files");
2360
- await fs11.mkdir(fileDir, { recursive: true });
2361
- const storedPath = path13.join(fileDir, stableStoredName(sourcePath, fileName));
2362
- await fs11.copyFile(sourcePath, storedPath);
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);
2363
2310
  const messageId = input2.messages.ingest({
2364
2311
  platform: "local-file",
2365
2312
  platformChatId: "local-files",
@@ -2641,15 +2588,14 @@ var GatewayIngestor = class {
2641
2588
  };
2642
2589
 
2643
2590
  // src/logs/reader.ts
2644
- init_paths();
2645
- import fs12 from "fs/promises";
2591
+ import fs11 from "fs/promises";
2646
2592
  import { watch } from "fs";
2647
- import path14 from "path";
2593
+ import path13 from "path";
2648
2594
  function getLogsDirectory() {
2649
- return path14.join(getChatterCatcherHome(), "logs");
2595
+ return path13.join(getChatterCatcherHome(), "logs");
2650
2596
  }
2651
2597
  function resolveLogPath(fileName, logsDir = getLogsDirectory()) {
2652
- return path14.isAbsolute(fileName) ? fileName : path14.join(logsDir, fileName);
2598
+ return path13.isAbsolute(fileName) ? fileName : path13.join(logsDir, fileName);
2653
2599
  }
2654
2600
  function normalizeLineCount(value, fallback = 200) {
2655
2601
  const parsed = Number(value ?? fallback);
@@ -2658,7 +2604,7 @@ function normalizeLineCount(value, fallback = 200) {
2658
2604
  async function listLogFiles(logsDir = getLogsDirectory()) {
2659
2605
  let entries;
2660
2606
  try {
2661
- entries = await fs12.readdir(logsDir, { withFileTypes: true });
2607
+ entries = await fs11.readdir(logsDir, { withFileTypes: true });
2662
2608
  } catch (error) {
2663
2609
  if (error.code === "ENOENT") {
2664
2610
  return [];
@@ -2667,8 +2613,8 @@ async function listLogFiles(logsDir = getLogsDirectory()) {
2667
2613
  }
2668
2614
  const files2 = await Promise.all(
2669
2615
  entries.filter((entry) => entry.isFile() && entry.name.endsWith(".log")).map(async (entry) => {
2670
- const filePath = path14.join(logsDir, entry.name);
2671
- const stats = await fs12.stat(filePath);
2616
+ const filePath = path13.join(logsDir, entry.name);
2617
+ const stats = await fs11.stat(filePath);
2672
2618
  return {
2673
2619
  name: entry.name,
2674
2620
  path: filePath,
@@ -2685,11 +2631,11 @@ function tailLines(content, lines) {
2685
2631
  return parts.slice(-lines).join("\n");
2686
2632
  }
2687
2633
  async function readLogTail(input2) {
2688
- const stats = await fs12.stat(input2.filePath);
2689
- const content = await fs12.readFile(input2.filePath, "utf8");
2634
+ const stats = await fs11.stat(input2.filePath);
2635
+ const content = await fs11.readFile(input2.filePath, "utf8");
2690
2636
  return {
2691
2637
  file: {
2692
- name: path14.basename(input2.filePath),
2638
+ name: path13.basename(input2.filePath),
2693
2639
  path: input2.filePath,
2694
2640
  updatedAt: stats.mtime,
2695
2641
  bytes: stats.size
@@ -2711,18 +2657,18 @@ async function readLatestLogTail(input2 = {}) {
2711
2657
  return readLogTail({ filePath: latest.path, lines: input2.lines });
2712
2658
  }
2713
2659
  async function followLogFile(input2) {
2714
- let offset = (await fs12.stat(input2.filePath)).size;
2715
- const directory = path14.dirname(input2.filePath);
2716
- const fileName = path14.basename(input2.filePath);
2660
+ let offset = (await fs11.stat(input2.filePath)).size;
2661
+ const directory = path13.dirname(input2.filePath);
2662
+ const fileName = path13.basename(input2.filePath);
2717
2663
  async function readAppended() {
2718
- const stats = await fs12.stat(input2.filePath);
2664
+ const stats = await fs11.stat(input2.filePath);
2719
2665
  if (stats.size < offset) {
2720
2666
  offset = 0;
2721
2667
  }
2722
2668
  if (stats.size === offset) {
2723
2669
  return;
2724
2670
  }
2725
- const handle = await fs12.open(input2.filePath, "r");
2671
+ const handle = await fs11.open(input2.filePath, "r");
2726
2672
  try {
2727
2673
  const length = stats.size - offset;
2728
2674
  const buffer = Buffer.alloc(length);
@@ -2803,8 +2749,7 @@ async function processMessagesNow(input2) {
2803
2749
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
2804
2750
  };
2805
2751
  }
2806
- const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
2807
- const vectorStore = await LanceDbVectorStore2.connectFromConfig(input2.config);
2752
+ const vectorStore = new SQLiteVectorStore(input2.database);
2808
2753
  try {
2809
2754
  const stats = await indexMessageChunks({
2810
2755
  messages: new MessageRepository(input2.database),
@@ -3221,7 +3166,7 @@ function createWebApp(config) {
3221
3166
  note: "\u95EE\u7B54\u5FC5\u987B\u5148\u68C0\u7D22\u8BC1\u636E\uFF0C\u7981\u6B62\u5168\u91CF\u4E0A\u4E0B\u6587\u5806\u53E0\u3002",
3222
3167
  retrieval: {
3223
3168
  keyword: "SQLite FTS5",
3224
- vector: "LanceDB",
3169
+ vector: "SQLite Vector",
3225
3170
  hybrid: true
3226
3171
  }
3227
3172
  },
@@ -3341,7 +3286,7 @@ function printSettings(config, secrets) {
3341
3286
  2
3342
3287
  ));
3343
3288
  }
3344
- program.name("chattercatcher").description("\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u673A\u5668\u4EBA").version("0.1.3");
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");
3345
3290
  program.command("setup").description("\u4EA4\u4E92\u5F0F\u521D\u59CB\u5316\u914D\u7F6E").action(async () => {
3346
3291
  const { config, secrets } = await ensureConfigFiles();
3347
3292
  await promptForConfiguration(config, secrets);
@@ -3389,8 +3334,7 @@ async function startGatewayCommand() {
3389
3334
  return;
3390
3335
  }
3391
3336
  const database = openDatabase(config);
3392
- const { LanceDbVectorStore: LanceDbVectorStore2 } = hasEmbeddingConfig(config, secrets) ? await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports)) : { LanceDbVectorStore: null };
3393
- const vectorStore = LanceDbVectorStore2 ? await LanceDbVectorStore2.connectFromConfig(config) : null;
3337
+ const vectorStore = hasEmbeddingConfig(config, secrets) ? new SQLiteVectorStore(database) : null;
3394
3338
  const gatewayRuntime = createFeishuGateway({
3395
3339
  config,
3396
3340
  secrets,
@@ -3475,7 +3419,7 @@ async function deleteDataCommand(targetType, targetId, options) {
3475
3419
  if (result.skippedStoredFiles.length > 0) {
3476
3420
  console.log(`\u8DF3\u8FC7\u975E\u6570\u636E\u76EE\u5F55\u6587\u4EF6\uFF1A${result.skippedStoredFiles.join("\uFF1B")}`);
3477
3421
  }
3478
- 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");
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");
3479
3423
  } finally {
3480
3424
  database.close();
3481
3425
  }
@@ -3490,20 +3434,19 @@ index.command("status").description("\u67E5\u770B\u7D22\u5F15\u72B6\u6001").acti
3490
3434
  const secrets = await loadSecrets();
3491
3435
  const database = openDatabase(config);
3492
3436
  const messages = new MessageRepository(database);
3493
- const { getLanceDbPath: getLanceDbPath2, LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
3494
- const vectorStore = await LanceDbVectorStore2.connectFromConfig(config);
3437
+ const vectorStore = new SQLiteVectorStore(database);
3495
3438
  const vectors = await vectorStore.count();
3496
3439
  console.log(JSON.stringify(
3497
3440
  {
3498
3441
  database: getDatabasePath(config),
3499
- vectorDatabase: getLanceDbPath2(config),
3442
+ vectorDatabase: getDatabasePath(config),
3500
3443
  chats: messages.getChatCount(),
3501
3444
  messages: messages.getMessageCount(),
3502
3445
  vectors,
3503
3446
  retrieval: {
3504
3447
  keyword: "SQLite FTS5",
3505
- 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",
3506
- hybrid: "\u542F\u7528\uFF1ASQLite FTS + LanceDB Vector",
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",
3507
3450
  rag: "\u5F3A\u5236\u5148\u68C0\u7D22\u8BC1\u636E\u518D\u56DE\u7B54\uFF0C\u7981\u6B62\u5168\u91CF\u4E0A\u4E0B\u6587\u5806\u53E0"
3508
3451
  }
3509
3452
  },
@@ -3513,7 +3456,7 @@ index.command("status").description("\u67E5\u770B\u7D22\u5F15\u72B6\u6001").acti
3513
3456
  vectorStore.close();
3514
3457
  database.close();
3515
3458
  });
3516
- 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) => {
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) => {
3517
3460
  const config = await loadConfig();
3518
3461
  const secrets = await loadSecrets();
3519
3462
  if (!hasEmbeddingConfig(config, secrets)) {
@@ -3521,8 +3464,7 @@ index.command("rebuild").description("\u91CD\u5EFA LanceDB \u5411\u91CF\u7D22\u5
3521
3464
  return;
3522
3465
  }
3523
3466
  const database = openDatabase(config);
3524
- const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
3525
- const vectorStore = await LanceDbVectorStore2.connectFromConfig(config);
3467
+ const vectorStore = new SQLiteVectorStore(database);
3526
3468
  try {
3527
3469
  const stats = await indexMessageChunks({
3528
3470
  messages: new MessageRepository(database),
@@ -3537,7 +3479,7 @@ index.command("rebuild").description("\u91CD\u5EFA LanceDB \u5411\u91CF\u7D22\u5
3537
3479
  }
3538
3480
  });
3539
3481
  var processCommand = program.command("process").description("\u7ACB\u5373\u5904\u7406\u540E\u53F0\u4EFB\u52A1");
3540
- 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) => {
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) => {
3541
3483
  const config = await loadConfig();
3542
3484
  const secrets = await loadSecrets();
3543
3485
  const database = openDatabase(config);
@@ -3571,7 +3513,7 @@ files.command("add").description("\u628A\u672C\u5730\u6587\u4EF6\u89E3\u6790\u30
3571
3513
  `\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}`
3572
3514
  );
3573
3515
  }
3574
- 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");
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");
3575
3517
  } finally {
3576
3518
  database.close();
3577
3519
  }
@@ -3700,7 +3642,7 @@ program.command("restore").description("\u4ECE ChatterCatcher \u5BFC\u51FA\u6587
3700
3642
  console.log(`\u6062\u590D\u5B8C\u6210\uFF1A${result.inputPath}`);
3701
3643
  console.log(`\u6A21\u5F0F\uFF1A${result.mode === "replace" ? "\u66FF\u6362" : "\u5408\u5E76"}`);
3702
3644
  console.log(`\u5305\u542B\uFF1A\u7FA4\u804A=${result.chats}\uFF0C\u6D88\u606F=${result.messages}\uFF0Cchunks=${result.chunks}\uFF0C\u6587\u4EF6\u4EFB\u52A1=${result.fileJobs}`);
3703
- console.log("SQLite FTS \u5DF2\u91CD\u5EFA\uFF1B\u5982\u4F7F\u7528 LanceDB \u8BED\u4E49\u68C0\u7D22\uFF0C\u8BF7\u8FD0\u884C chattercatcher index rebuild\u3002");
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");
3704
3646
  } finally {
3705
3647
  database.close();
3706
3648
  }
@@ -3730,7 +3672,7 @@ dev.command("ingest-feishu-event").description("\u4ECE JSON \u6587\u4EF6\u6A21\u
3730
3672
  const config = await loadConfig();
3731
3673
  const database = openDatabase(config);
3732
3674
  try {
3733
- const raw = await fs13.readFile(options.file, "utf8");
3675
+ const raw = await fs12.readFile(options.file, "utf8");
3734
3676
  const payload = JSON.parse(raw);
3735
3677
  const result = new GatewayIngestor(database).ingestFeishuEvent(payload);
3736
3678
  if (!result.accepted) {