chattercatcher 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -34,6 +34,24 @@
34
34
 
35
35
  ---
36
36
 
37
+ ## 加入交流群
38
+
39
+ <table>
40
+ <tr>
41
+ <td width="280" valign="top">
42
+ <img src="./assets/readme/feishu-community-qr.png" alt="ChatterCatcher 飞书交流群二维码" width="260" />
43
+ </td>
44
+ <td valign="top">
45
+ <h3>反馈、测试和共建</h3>
46
+ <p>扫描左侧二维码加入 ChatterCatcher 飞书交流群。</p>
47
+ <p>欢迎提交使用反馈、Bug、部署经验、飞书配置问题、RAG 行为建议和真实家庭群使用案例。</p>
48
+ <p><strong>GitHub 仓库:</strong><a href="https://github.com/FlashingChen2024/chattercatcher">https://github.com/FlashingChen2024/chattercatcher</a></p>
49
+ </td>
50
+ </tr>
51
+ </table>
52
+
53
+ ---
54
+
37
55
  ## 项目状态
38
56
 
39
57
  ChatterCatcher 是一个早期 MVP。它已经具备飞书长连接接入、本地消息存储、SQLite FTS、LanceDB 向量检索、OpenAI-compatible LLM/Embedding、CLI、本地 Web UI 和带引用回答。
@@ -316,6 +334,26 @@ npm run dev -- --help
316
334
 
317
335
  可以保留 SQLite FTS 关键词检索,但语义检索需要配置 embedding。建议运行 `chattercatcher doctor --online` 确认维度和连通性。
318
336
 
337
+ ### macOS 上提示 `Cannot find native binding` 怎么办?
338
+
339
+ 这是 LanceDB 的 native optional dependency 没有被 npm 装完整,常见于 npm 全局安装时漏装可选依赖。
340
+
341
+ ChatterCatcher 从 `0.1.1` 起锁定到仍发布 `darwin-x64` 和 `darwin-arm64` native 包的 LanceDB 版本,以覆盖 Intel Mac 和 Apple Silicon Mac。先升级:
342
+
343
+ ```bash
344
+ npm uninstall -g chattercatcher
345
+ npm install -g chattercatcher --include=optional
346
+ ```
347
+
348
+ 如果仍然失败,清理 npm cache 后再装:
349
+
350
+ ```bash
351
+ npm cache clean --force
352
+ npm install -g chattercatcher --include=optional
353
+ ```
354
+
355
+ `chattercatcher --version` 和 `chattercatcher --help` 从 `0.1.1` 起不会加载 LanceDB native binding;只有 `index`、`process messages`、配置了 embedding 的问答等向量检索路径才需要 LanceDB native 包。
356
+
319
357
  ### 为什么要用 RAG?
320
358
 
321
359
  家庭聊天是长期知识库,不应该靠把全部历史消息塞进上下文。RAG 可以控制证据范围、保留来源、降低幻觉,并让回答可追溯。
package/dist/cli.js CHANGED
@@ -1,4 +1,150 @@
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 * as lancedb from "@lancedb/lancedb";
46
+ import fs5 from "fs/promises";
47
+ import path8 from "path";
48
+ function getLanceDbPath(config) {
49
+ return path8.join(resolveHomePath(config.storage.dataDir), "vector", "lancedb");
50
+ }
51
+ function toRow(record) {
52
+ return {
53
+ id: record.id,
54
+ vector: record.vector,
55
+ text: record.evidence.text,
56
+ source_json: JSON.stringify(record.evidence.source)
57
+ };
58
+ }
59
+ function toLanceData(rows) {
60
+ return rows.map((row) => ({
61
+ id: row.id,
62
+ vector: row.vector,
63
+ text: row.text,
64
+ source_json: row.source_json
65
+ }));
66
+ }
67
+ function escapeSqlString(value) {
68
+ return value.replace(/'/g, "''");
69
+ }
70
+ function toEvidence(row) {
71
+ const distance = row._distance ?? 0;
72
+ const vectorScore = 1 / (1 + Math.max(0, distance));
73
+ return {
74
+ id: row.id,
75
+ text: row.text,
76
+ score: vectorScore,
77
+ vectorScore,
78
+ source: JSON.parse(row.source_json)
79
+ };
80
+ }
81
+ var DEFAULT_TABLE_NAME, LanceDbVectorStore;
82
+ var init_lancedb_store = __esm({
83
+ "src/rag/lancedb-store.ts"() {
84
+ "use strict";
85
+ init_paths();
86
+ DEFAULT_TABLE_NAME = "message_chunks";
87
+ LanceDbVectorStore = class _LanceDbVectorStore {
88
+ constructor(connection, tableName) {
89
+ this.connection = connection;
90
+ this.tableName = tableName;
91
+ }
92
+ connection;
93
+ tableName;
94
+ static async connect(uri, tableName = DEFAULT_TABLE_NAME) {
95
+ await fs5.mkdir(uri, { recursive: true });
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";
@@ -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";
@@ -362,6 +492,7 @@ function migrateDatabase(database) {
362
492
  }
363
493
 
364
494
  // src/doctor/checks.ts
495
+ init_paths();
365
496
  import fs6 from "fs/promises";
366
497
 
367
498
  // src/files/jobs.ts
@@ -504,6 +635,7 @@ var FileJobRepository = class {
504
635
  };
505
636
 
506
637
  // src/gateway/runtime.ts
638
+ init_paths();
507
639
  import fs4 from "fs";
508
640
  import path7 from "path";
509
641
  function getGatewayPidPath() {
@@ -1097,104 +1229,6 @@ var HybridRetriever = class {
1097
1229
  }
1098
1230
  };
1099
1231
 
1100
- // src/rag/lancedb-store.ts
1101
- import * as lancedb from "@lancedb/lancedb";
1102
- import fs5 from "fs/promises";
1103
- import path8 from "path";
1104
- var DEFAULT_TABLE_NAME = "message_chunks";
1105
- function getLanceDbPath(config) {
1106
- return path8.join(resolveHomePath(config.storage.dataDir), "vector", "lancedb");
1107
- }
1108
- function toRow(record) {
1109
- return {
1110
- id: record.id,
1111
- vector: record.vector,
1112
- text: record.evidence.text,
1113
- source_json: JSON.stringify(record.evidence.source)
1114
- };
1115
- }
1116
- function toLanceData(rows) {
1117
- return rows.map((row) => ({
1118
- id: row.id,
1119
- vector: row.vector,
1120
- text: row.text,
1121
- source_json: row.source_json
1122
- }));
1123
- }
1124
- function escapeSqlString(value) {
1125
- return value.replace(/'/g, "''");
1126
- }
1127
- function toEvidence(row) {
1128
- const distance = row._distance ?? 0;
1129
- const vectorScore = 1 / (1 + Math.max(0, distance));
1130
- return {
1131
- id: row.id,
1132
- text: row.text,
1133
- score: vectorScore,
1134
- vectorScore,
1135
- source: JSON.parse(row.source_json)
1136
- };
1137
- }
1138
- var LanceDbVectorStore = class _LanceDbVectorStore {
1139
- constructor(connection, tableName) {
1140
- this.connection = connection;
1141
- this.tableName = tableName;
1142
- }
1143
- connection;
1144
- tableName;
1145
- static async connect(uri, tableName = DEFAULT_TABLE_NAME) {
1146
- await fs5.mkdir(uri, { recursive: true });
1147
- const connection = await lancedb.connect(uri);
1148
- return new _LanceDbVectorStore(connection, tableName);
1149
- }
1150
- static async connectFromConfig(config, tableName = DEFAULT_TABLE_NAME) {
1151
- return _LanceDbVectorStore.connect(getLanceDbPath(config), tableName);
1152
- }
1153
- close() {
1154
- this.connection.close();
1155
- }
1156
- async upsert(records) {
1157
- if (records.length === 0) {
1158
- return;
1159
- }
1160
- const rows = records.map(toRow);
1161
- const data2 = toLanceData(rows);
1162
- const table = await this.ensureTable(data2);
1163
- const ids = rows.map((row) => `'${escapeSqlString(row.id)}'`).join(", ");
1164
- await table.delete(`id IN (${ids})`);
1165
- await table.add(data2);
1166
- }
1167
- async search(vector, limit) {
1168
- const table = await this.openTableIfExists();
1169
- if (!table) {
1170
- return [];
1171
- }
1172
- const rows = await table.vectorSearch(vector).limit(limit).toArray();
1173
- return rows.map(toEvidence);
1174
- }
1175
- async count() {
1176
- const table = await this.openTableIfExists();
1177
- if (!table) {
1178
- return 0;
1179
- }
1180
- return table.countRows();
1181
- }
1182
- async ensureTable(initialRows) {
1183
- const table = await this.openTableIfExists();
1184
- if (table) {
1185
- return table;
1186
- }
1187
- return this.connection.createTable(this.tableName, initialRows);
1188
- }
1189
- async openTableIfExists() {
1190
- const tableNames = await this.connection.tableNames();
1191
- if (!tableNames.includes(this.tableName)) {
1192
- return null;
1193
- }
1194
- return this.connection.openTable(this.tableName);
1195
- }
1196
- };
1197
-
1198
1232
  // src/rag/message-retriever.ts
1199
1233
  function toEvidenceSource(result) {
1200
1234
  if (result.messageType === "file") {
@@ -1255,7 +1289,8 @@ async function createHybridRetriever(input2) {
1255
1289
  const retrievers = [new MessageFtsRetriever(input2.messages, { excludeMessageIds: input2.excludeMessageIds })];
1256
1290
  const closers = [];
1257
1291
  if (hasEmbeddingConfig(input2.config, input2.secrets)) {
1258
- const vectorStore = await LanceDbVectorStore.connectFromConfig(input2.config);
1292
+ const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
1293
+ const vectorStore = await LanceDbVectorStore2.connectFromConfig(input2.config);
1259
1294
  retrievers.push(new VectorRetriever(createEmbeddingModel(input2.config, input2.secrets), vectorStore));
1260
1295
  closers.push(() => vectorStore.close());
1261
1296
  }
@@ -1357,9 +1392,10 @@ async function checkFilePipeline(config) {
1357
1392
  async function checkLanceDb(config) {
1358
1393
  let store = null;
1359
1394
  try {
1360
- store = await LanceDbVectorStore.connectFromConfig(config);
1395
+ const { getLanceDbPath: getLanceDbPath2, LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
1396
+ store = await LanceDbVectorStore2.connectFromConfig(config);
1361
1397
  const count = await store.count();
1362
- return pass("LanceDB", `${getLanceDbPath(config)}\uFF1Bvectors=${count}`);
1398
+ return pass("LanceDB", `${getLanceDbPath2(config)}\uFF1Bvectors=${count}`);
1363
1399
  } catch (error) {
1364
1400
  return fail("LanceDB", error instanceof Error ? error.message : String(error));
1365
1401
  } finally {
@@ -1404,6 +1440,7 @@ function formatDoctorChecks(checks) {
1404
1440
  }
1405
1441
 
1406
1442
  // src/export/data-export.ts
1443
+ init_paths();
1407
1444
  import fs7 from "fs/promises";
1408
1445
  import path9 from "path";
1409
1446
  function parseJsonObject(value) {
@@ -2170,6 +2207,7 @@ function createFeishuGateway(options) {
2170
2207
  }
2171
2208
 
2172
2209
  // src/feishu/resource-downloader.ts
2210
+ init_paths();
2173
2211
  import * as lark3 from "@larksuiteoapi/node-sdk";
2174
2212
  import fs9 from "fs/promises";
2175
2213
  import path11 from "path";
@@ -2235,6 +2273,7 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
2235
2273
  };
2236
2274
 
2237
2275
  // src/files/ingest.ts
2276
+ init_paths();
2238
2277
  import crypto3 from "crypto";
2239
2278
  import fs11 from "fs/promises";
2240
2279
  import path13 from "path";
@@ -2602,6 +2641,7 @@ var GatewayIngestor = class {
2602
2641
  };
2603
2642
 
2604
2643
  // src/logs/reader.ts
2644
+ init_paths();
2605
2645
  import fs12 from "fs/promises";
2606
2646
  import { watch } from "fs";
2607
2647
  import path14 from "path";
@@ -2763,7 +2803,8 @@ async function processMessagesNow(input2) {
2763
2803
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
2764
2804
  };
2765
2805
  }
2766
- const vectorStore = await LanceDbVectorStore.connectFromConfig(input2.config);
2806
+ const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
2807
+ const vectorStore = await LanceDbVectorStore2.connectFromConfig(input2.config);
2767
2808
  try {
2768
2809
  const stats = await indexMessageChunks({
2769
2810
  messages: new MessageRepository(input2.database),
@@ -3300,7 +3341,7 @@ function printSettings(config, secrets) {
3300
3341
  2
3301
3342
  ));
3302
3343
  }
3303
- program.name("chattercatcher").description("\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u673A\u5668\u4EBA").version("0.1.0");
3344
+ program.name("chattercatcher").description("\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u673A\u5668\u4EBA").version("0.1.2");
3304
3345
  program.command("setup").description("\u4EA4\u4E92\u5F0F\u521D\u59CB\u5316\u914D\u7F6E").action(async () => {
3305
3346
  const { config, secrets } = await ensureConfigFiles();
3306
3347
  await promptForConfiguration(config, secrets);
@@ -3348,7 +3389,8 @@ async function startGatewayCommand() {
3348
3389
  return;
3349
3390
  }
3350
3391
  const database = openDatabase(config);
3351
- const vectorStore = hasEmbeddingConfig(config, secrets) ? await LanceDbVectorStore.connectFromConfig(config) : null;
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;
3352
3394
  const gatewayRuntime = createFeishuGateway({
3353
3395
  config,
3354
3396
  secrets,
@@ -3448,12 +3490,13 @@ index.command("status").description("\u67E5\u770B\u7D22\u5F15\u72B6\u6001").acti
3448
3490
  const secrets = await loadSecrets();
3449
3491
  const database = openDatabase(config);
3450
3492
  const messages = new MessageRepository(database);
3451
- const vectorStore = await LanceDbVectorStore.connectFromConfig(config);
3493
+ const { getLanceDbPath: getLanceDbPath2, LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
3494
+ const vectorStore = await LanceDbVectorStore2.connectFromConfig(config);
3452
3495
  const vectors = await vectorStore.count();
3453
3496
  console.log(JSON.stringify(
3454
3497
  {
3455
3498
  database: getDatabasePath(config),
3456
- vectorDatabase: getLanceDbPath(config),
3499
+ vectorDatabase: getLanceDbPath2(config),
3457
3500
  chats: messages.getChatCount(),
3458
3501
  messages: messages.getMessageCount(),
3459
3502
  vectors,
@@ -3478,7 +3521,8 @@ index.command("rebuild").description("\u91CD\u5EFA LanceDB \u5411\u91CF\u7D22\u5
3478
3521
  return;
3479
3522
  }
3480
3523
  const database = openDatabase(config);
3481
- const vectorStore = await LanceDbVectorStore.connectFromConfig(config);
3524
+ const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
3525
+ const vectorStore = await LanceDbVectorStore2.connectFromConfig(config);
3482
3526
  try {
3483
3527
  const stats = await indexMessageChunks({
3484
3528
  messages: new MessageRepository(database),