brainbank 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1059 -0
  3. package/assets/architecture.png +0 -0
  4. package/bin/brainbank +11 -0
  5. package/dist/chunk-2P3EGY6S.js +37 -0
  6. package/dist/chunk-2P3EGY6S.js.map +1 -0
  7. package/dist/chunk-3GAIDXRW.js +105 -0
  8. package/dist/chunk-3GAIDXRW.js.map +1 -0
  9. package/dist/chunk-4ZKBQ33J.js +56 -0
  10. package/dist/chunk-4ZKBQ33J.js.map +1 -0
  11. package/dist/chunk-7QVYU63E.js +7 -0
  12. package/dist/chunk-7QVYU63E.js.map +1 -0
  13. package/dist/chunk-EDKSKLX4.js +490 -0
  14. package/dist/chunk-EDKSKLX4.js.map +1 -0
  15. package/dist/chunk-GOUBW7UA.js +373 -0
  16. package/dist/chunk-GOUBW7UA.js.map +1 -0
  17. package/dist/chunk-MJ3Y24H6.js +185 -0
  18. package/dist/chunk-MJ3Y24H6.js.map +1 -0
  19. package/dist/chunk-N6ZMBFDE.js +224 -0
  20. package/dist/chunk-N6ZMBFDE.js.map +1 -0
  21. package/dist/chunk-YGSEUWLV.js +2053 -0
  22. package/dist/chunk-YGSEUWLV.js.map +1 -0
  23. package/dist/chunk-Z5SU54HP.js +171 -0
  24. package/dist/chunk-Z5SU54HP.js.map +1 -0
  25. package/dist/cli.d.ts +1 -0
  26. package/dist/cli.js +731 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/code.d.ts +31 -0
  29. package/dist/code.js +8 -0
  30. package/dist/code.js.map +1 -0
  31. package/dist/docs.d.ts +19 -0
  32. package/dist/docs.js +8 -0
  33. package/dist/docs.js.map +1 -0
  34. package/dist/git.d.ts +31 -0
  35. package/dist/git.js +8 -0
  36. package/dist/git.js.map +1 -0
  37. package/dist/index.d.ts +845 -0
  38. package/dist/index.js +80 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/memory.d.ts +19 -0
  41. package/dist/memory.js +146 -0
  42. package/dist/memory.js.map +1 -0
  43. package/dist/notes.d.ts +19 -0
  44. package/dist/notes.js +57 -0
  45. package/dist/notes.js.map +1 -0
  46. package/dist/openai-PCTYLOWI.js +8 -0
  47. package/dist/openai-PCTYLOWI.js.map +1 -0
  48. package/dist/types-Da_zLLOl.d.ts +474 -0
  49. package/package.json +91 -0
package/dist/cli.js ADDED
@@ -0,0 +1,731 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ BrainBank
4
+ } from "./chunk-YGSEUWLV.js";
5
+ import {
6
+ code
7
+ } from "./chunk-EDKSKLX4.js";
8
+ import {
9
+ git
10
+ } from "./chunk-N6ZMBFDE.js";
11
+ import {
12
+ docs
13
+ } from "./chunk-GOUBW7UA.js";
14
+ import "./chunk-4ZKBQ33J.js";
15
+ import "./chunk-2P3EGY6S.js";
16
+ import {
17
+ __name
18
+ } from "./chunk-7QVYU63E.js";
19
+
20
+ // src/integrations/cli.ts
21
+ import * as path from "path";
22
+ import * as fs from "fs";
23
+ var c = {
24
+ green: /* @__PURE__ */ __name((s) => `\x1B[32m${s}\x1B[0m`, "green"),
25
+ red: /* @__PURE__ */ __name((s) => `\x1B[31m${s}\x1B[0m`, "red"),
26
+ yellow: /* @__PURE__ */ __name((s) => `\x1B[33m${s}\x1B[0m`, "yellow"),
27
+ cyan: /* @__PURE__ */ __name((s) => `\x1B[36m${s}\x1B[0m`, "cyan"),
28
+ dim: /* @__PURE__ */ __name((s) => `\x1B[2m${s}\x1B[0m`, "dim"),
29
+ bold: /* @__PURE__ */ __name((s) => `\x1B[1m${s}\x1B[0m`, "bold"),
30
+ magenta: /* @__PURE__ */ __name((s) => `\x1B[35m${s}\x1B[0m`, "magenta")
31
+ };
32
+ var args = process.argv.slice(2);
33
+ var command = args[0];
34
+ var subcommand = args[1];
35
+ function getFlag(name) {
36
+ const idx = args.indexOf(`--${name}`);
37
+ return idx >= 0 ? args[idx + 1] : void 0;
38
+ }
39
+ __name(getFlag, "getFlag");
40
+ function hasFlag(name) {
41
+ return args.includes(`--${name}`);
42
+ }
43
+ __name(hasFlag, "hasFlag");
44
+ var CONFIG_NAMES = [
45
+ "config.ts",
46
+ "config.js",
47
+ "config.mjs"
48
+ ];
49
+ var INDEXER_EXTENSIONS = [".ts", ".js", ".mjs"];
50
+ var _configCache = void 0;
51
+ var _folderIndexersCache = void 0;
52
+ async function loadConfig() {
53
+ if (_configCache !== void 0) return _configCache;
54
+ const repoPath = getFlag("repo") ?? ".";
55
+ const brainbankDir = path.resolve(repoPath, ".brainbank");
56
+ for (const name of CONFIG_NAMES) {
57
+ const configPath = path.join(brainbankDir, name);
58
+ if (fs.existsSync(configPath)) {
59
+ try {
60
+ const mod = await import(configPath);
61
+ _configCache = mod.default ?? mod;
62
+ return _configCache;
63
+ } catch (err) {
64
+ console.error(c.red(`Error loading .brainbank/${name}: ${err.message}`));
65
+ process.exit(1);
66
+ }
67
+ }
68
+ }
69
+ _configCache = null;
70
+ return null;
71
+ }
72
+ __name(loadConfig, "loadConfig");
73
+ async function discoverFolderIndexers() {
74
+ if (_folderIndexersCache !== void 0) return _folderIndexersCache;
75
+ const repoPath = getFlag("repo") ?? ".";
76
+ const indexersDir = path.resolve(repoPath, ".brainbank", "indexers");
77
+ if (!fs.existsSync(indexersDir)) {
78
+ _folderIndexersCache = [];
79
+ return [];
80
+ }
81
+ const files = fs.readdirSync(indexersDir).filter((f) => INDEXER_EXTENSIONS.some((ext) => f.endsWith(ext))).sort();
82
+ const indexers = [];
83
+ for (const file of files) {
84
+ const filePath = path.join(indexersDir, file);
85
+ try {
86
+ const mod = await import(filePath);
87
+ const indexer = mod.default ?? mod;
88
+ if (indexer && typeof indexer === "object" && indexer.name) {
89
+ indexers.push(indexer);
90
+ } else {
91
+ console.error(c.yellow(`\u26A0 ${file}: must export a default Indexer with a 'name' property, skipping`));
92
+ }
93
+ } catch (err) {
94
+ console.error(c.red(`Error loading indexer ${file}: ${err.message}`));
95
+ }
96
+ }
97
+ _folderIndexersCache = indexers;
98
+ return indexers;
99
+ }
100
+ __name(discoverFolderIndexers, "discoverFolderIndexers");
101
+ async function createBrain(repoPath) {
102
+ const rp = repoPath ?? getFlag("repo") ?? ".";
103
+ const config = await loadConfig();
104
+ const folderIndexers = await discoverFolderIndexers();
105
+ const brainOpts = { repoPath: rp, ...config?.brainbank ?? {} };
106
+ const rerankerFlag = getFlag("reranker");
107
+ if (rerankerFlag === "qwen3") {
108
+ const { Qwen3Reranker } = await import("@brainbank/reranker");
109
+ brainOpts.reranker = new Qwen3Reranker();
110
+ }
111
+ const embeddingEnv = process.env.BRAINBANK_EMBEDDING;
112
+ if (embeddingEnv === "openai") {
113
+ const { OpenAIEmbedding } = await import("./openai-PCTYLOWI.js");
114
+ const provider = new OpenAIEmbedding();
115
+ brainOpts.embeddingProvider = provider;
116
+ brainOpts.embeddingDims = provider.dims;
117
+ }
118
+ const brain = new BrainBank(brainOpts);
119
+ const builtins = config?.builtins ?? ["code", "git", "docs"];
120
+ const resolvedRp = path.resolve(rp);
121
+ const hasRootGit = fs.existsSync(path.join(resolvedRp, ".git"));
122
+ const gitSubdirs = !hasRootGit ? detectGitSubdirs(resolvedRp) : [];
123
+ if (gitSubdirs.length > 0 && (builtins.includes("code") || builtins.includes("git"))) {
124
+ console.log(c.cyan(` Multi-repo: found ${gitSubdirs.length} git repos: ${gitSubdirs.map((d) => d.name).join(", ")}`));
125
+ for (const sub of gitSubdirs) {
126
+ if (builtins.includes("code")) {
127
+ brain.use(code({ repoPath: sub.path, name: `code:${sub.name}` }));
128
+ }
129
+ if (builtins.includes("git")) {
130
+ brain.use(git({ repoPath: sub.path, name: `git:${sub.name}` }));
131
+ }
132
+ }
133
+ } else {
134
+ if (builtins.includes("code")) brain.use(code({ repoPath: rp }));
135
+ if (builtins.includes("git")) brain.use(git());
136
+ }
137
+ if (builtins.includes("docs")) brain.use(docs());
138
+ for (const indexer of folderIndexers) {
139
+ brain.use(indexer);
140
+ }
141
+ if (config?.indexers) {
142
+ for (const indexer of config.indexers) {
143
+ brain.use(indexer);
144
+ }
145
+ }
146
+ return brain;
147
+ }
148
+ __name(createBrain, "createBrain");
149
+ function detectGitSubdirs(parentPath) {
150
+ try {
151
+ const entries = fs.readdirSync(parentPath, { withFileTypes: true });
152
+ return entries.filter(
153
+ (e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("node_modules") && fs.existsSync(path.join(parentPath, e.name, ".git"))
154
+ ).map((e) => ({ name: e.name, path: path.join(parentPath, e.name) }));
155
+ } catch {
156
+ return [];
157
+ }
158
+ }
159
+ __name(detectGitSubdirs, "detectGitSubdirs");
160
+ async function cmdIndex() {
161
+ const repoPath = args[1] || ".";
162
+ const force = hasFlag("force");
163
+ const depth = parseInt(getFlag("depth") || "500", 10);
164
+ console.log(c.bold("\n\u2501\u2501\u2501 BrainBank Index \u2501\u2501\u2501"));
165
+ console.log(c.dim(` Repo: ${repoPath}`));
166
+ console.log(c.dim(` Force: ${force}`));
167
+ console.log(c.dim(` Git depth: ${depth}`));
168
+ const brain = await createBrain(repoPath);
169
+ const result = await brain.index({
170
+ forceReindex: force,
171
+ gitDepth: depth,
172
+ onProgress: /* @__PURE__ */ __name((stage, msg) => {
173
+ process.stdout.write(`\r ${c.cyan(stage.toUpperCase())} ${msg} `);
174
+ }, "onProgress")
175
+ });
176
+ console.log("\n");
177
+ if (result.code) {
178
+ console.log(` ${c.green("Code")}: ${result.code.indexed} indexed, ${result.code.skipped} skipped, ${result.code.chunks ?? 0} chunks`);
179
+ }
180
+ if (result.git) {
181
+ console.log(` ${c.green("Git")}: ${result.git.indexed} indexed, ${result.git.skipped} skipped`);
182
+ }
183
+ const stats = brain.stats();
184
+ console.log(`
185
+ ${c.bold("Totals")}:`);
186
+ if (stats.code) console.log(` Code chunks: ${stats.code.chunks}`);
187
+ if (stats.git) console.log(` Git commits: ${stats.git.commits}`);
188
+ if (stats.git) console.log(` Co-edit pairs: ${stats.git.coEdits}`);
189
+ brain.close();
190
+ }
191
+ __name(cmdIndex, "cmdIndex");
192
+ async function cmdCollection() {
193
+ const sub = args[1];
194
+ if (sub === "add") {
195
+ const path2 = args[2];
196
+ const name = getFlag("name");
197
+ const pattern = getFlag("pattern") ?? "**/*.md";
198
+ const context = getFlag("context");
199
+ const ignoreRaw = getFlag("ignore");
200
+ if (!path2 || !name) {
201
+ console.log(c.red('Usage: brainbank collection add <path> --name <name> [--pattern "**/*.md"] [--ignore "glob"] [--context "description"]'));
202
+ process.exit(1);
203
+ }
204
+ const brain = await createBrain();
205
+ await brain.addCollection({
206
+ name,
207
+ path: path2,
208
+ pattern,
209
+ ignore: ignoreRaw ? ignoreRaw.split(",") : [],
210
+ context: context ?? void 0
211
+ });
212
+ console.log(c.green(`\u2713 Collection '${name}' added: ${path2} (${pattern})`));
213
+ if (context) console.log(c.dim(` Context: ${context}`));
214
+ brain.close();
215
+ return;
216
+ }
217
+ if (sub === "list") {
218
+ const brain = await createBrain();
219
+ await brain.initialize();
220
+ const collections = brain.listCollections();
221
+ if (collections.length === 0) {
222
+ console.log(c.yellow(" No collections registered."));
223
+ } else {
224
+ console.log(c.bold("\n\u2501\u2501\u2501 Collections \u2501\u2501\u2501\n"));
225
+ for (const col of collections) {
226
+ console.log(` ${c.cyan(col.name)} ${c.dim("\u2192")} ${col.path}`);
227
+ console.log(` Pattern: ${col.pattern ?? "**/*.md"}`);
228
+ if (col.context) console.log(` Context: ${c.dim(col.context)}`);
229
+ }
230
+ }
231
+ brain.close();
232
+ return;
233
+ }
234
+ if (sub === "remove") {
235
+ const name = args[2];
236
+ if (!name) {
237
+ console.log(c.red("Usage: brainbank collection remove <name>"));
238
+ process.exit(1);
239
+ }
240
+ const brain = await createBrain();
241
+ await brain.removeCollection(name);
242
+ console.log(c.green(`\u2713 Collection '${name}' removed.`));
243
+ brain.close();
244
+ return;
245
+ }
246
+ console.log(c.red("Usage: brainbank collection <add|list|remove>"));
247
+ process.exit(1);
248
+ }
249
+ __name(cmdCollection, "cmdCollection");
250
+ async function cmdKv() {
251
+ const sub = args[1];
252
+ if (sub === "add") {
253
+ const collName = args[2];
254
+ const content = args.slice(3).filter((a) => !a.startsWith("--")).join(" ");
255
+ const metaRaw = getFlag("meta");
256
+ if (!collName || !content) {
257
+ console.log(c.red(`Usage: brainbank kv add <collection> <content> [--meta '{"key":"val"}']`));
258
+ process.exit(1);
259
+ }
260
+ const brain = await createBrain();
261
+ await brain.initialize();
262
+ const coll = brain.collection(collName);
263
+ const meta = metaRaw ? JSON.parse(metaRaw) : {};
264
+ const id = await coll.add(content, meta);
265
+ console.log(c.green(`\u2713 Added item #${id} to '${collName}'`));
266
+ brain.close();
267
+ return;
268
+ }
269
+ if (sub === "search") {
270
+ const collName = args[2];
271
+ const query = args.slice(3).filter((a) => !a.startsWith("--")).join(" ");
272
+ const k = parseInt(getFlag("k") || "5", 10);
273
+ const mode = getFlag("mode") || "hybrid";
274
+ if (!collName || !query) {
275
+ console.log(c.red("Usage: brainbank kv search <collection> <query> [--k 5] [--mode hybrid|keyword|vector]"));
276
+ process.exit(1);
277
+ }
278
+ const brain = await createBrain();
279
+ await brain.initialize();
280
+ const coll = brain.collection(collName);
281
+ const results = await coll.search(query, { k, mode });
282
+ if (results.length === 0) {
283
+ console.log(c.yellow(" No results found."));
284
+ } else {
285
+ console.log(c.bold(`
286
+ \u2501\u2501\u2501 ${collName}: "${query}" \u2501\u2501\u2501
287
+ `));
288
+ for (const r of results) {
289
+ const score = Math.round((r.score ?? 0) * 100);
290
+ console.log(` ${c.cyan(`[${score}%]`)} ${r.content}`);
291
+ if (Object.keys(r.metadata).length > 0) {
292
+ console.log(` ${c.dim(JSON.stringify(r.metadata))}`);
293
+ }
294
+ }
295
+ }
296
+ brain.close();
297
+ return;
298
+ }
299
+ if (sub === "list") {
300
+ const collName = args[2];
301
+ const limit = parseInt(getFlag("limit") || "20", 10);
302
+ if (!collName) {
303
+ const brain2 = await createBrain();
304
+ await brain2.initialize();
305
+ const names = brain2.listCollectionNames();
306
+ if (names.length === 0) {
307
+ console.log(c.yellow(" No KV collections found."));
308
+ } else {
309
+ console.log(c.bold("\n\u2501\u2501\u2501 KV Collections \u2501\u2501\u2501\n"));
310
+ for (const n of names) {
311
+ const coll2 = brain2.collection(n);
312
+ console.log(` ${c.cyan(n)} \u2014 ${coll2.count()} items`);
313
+ }
314
+ }
315
+ brain2.close();
316
+ return;
317
+ }
318
+ const brain = await createBrain();
319
+ await brain.initialize();
320
+ const coll = brain.collection(collName);
321
+ const items = coll.list({ limit });
322
+ if (items.length === 0) {
323
+ console.log(c.yellow(` Collection '${collName}' is empty.`));
324
+ } else {
325
+ console.log(c.bold(`
326
+ \u2501\u2501\u2501 ${collName} (${coll.count()} items) \u2501\u2501\u2501
327
+ `));
328
+ for (const item of items) {
329
+ const age = Math.round((Date.now() / 1e3 - item.createdAt) / 60);
330
+ console.log(` #${item.id} ${c.dim(`(${age}m ago)`)} ${item.content.slice(0, 80)}`);
331
+ }
332
+ }
333
+ brain.close();
334
+ return;
335
+ }
336
+ if (sub === "trim") {
337
+ const collName = args[2];
338
+ const keep = parseInt(getFlag("keep") || "0", 10);
339
+ if (!collName || keep <= 0) {
340
+ console.log(c.red("Usage: brainbank kv trim <collection> --keep <n>"));
341
+ process.exit(1);
342
+ }
343
+ const brain = await createBrain();
344
+ await brain.initialize();
345
+ const coll = brain.collection(collName);
346
+ const result = await coll.trim({ keep });
347
+ console.log(c.green(`\u2713 Trimmed ${result.removed} items from '${collName}' (kept ${keep})`));
348
+ brain.close();
349
+ return;
350
+ }
351
+ if (sub === "clear") {
352
+ const collName = args[2];
353
+ if (!collName) {
354
+ console.log(c.red("Usage: brainbank kv clear <collection>"));
355
+ process.exit(1);
356
+ }
357
+ const brain = await createBrain();
358
+ await brain.initialize();
359
+ const coll = brain.collection(collName);
360
+ const before = coll.count();
361
+ coll.clear();
362
+ console.log(c.green(`\u2713 Cleared ${before} items from '${collName}'`));
363
+ brain.close();
364
+ return;
365
+ }
366
+ console.log(c.red("Usage: brainbank kv <add|search|list|trim|clear>"));
367
+ process.exit(1);
368
+ }
369
+ __name(cmdKv, "cmdKv");
370
+ async function cmdDocs() {
371
+ const collection = getFlag("collection");
372
+ const brain = await createBrain();
373
+ console.log(c.bold("\n\u2501\u2501\u2501 BrainBank Docs Index \u2501\u2501\u2501\n"));
374
+ const opts = {};
375
+ if (collection) opts.collections = [collection];
376
+ opts.onProgress = (col, file, cur, total) => {
377
+ process.stdout.write(`\r ${c.cyan(col)} [${cur}/${total}] ${file} `);
378
+ };
379
+ const results = await brain.indexDocs(opts);
380
+ console.log("\n");
381
+ for (const [name, stat] of Object.entries(results)) {
382
+ console.log(` ${c.green(name)}: ${stat.indexed} indexed, ${stat.skipped} skipped, ${stat.chunks} chunks`);
383
+ }
384
+ brain.close();
385
+ }
386
+ __name(cmdDocs, "cmdDocs");
387
+ async function cmdDocSearch() {
388
+ const query = args.slice(1).filter((a) => !a.startsWith("--")).join(" ");
389
+ if (!query) {
390
+ console.log(c.red("Usage: brainbank dsearch <query>"));
391
+ process.exit(1);
392
+ }
393
+ const brain = await createBrain();
394
+ const collection = getFlag("collection");
395
+ const k = parseInt(getFlag("k") || "8", 10);
396
+ console.log(c.bold(`
397
+ \u2501\u2501\u2501 BrainBank Doc Search: "${query}" \u2501\u2501\u2501
398
+ `));
399
+ const results = await brain.searchDocs(query, { collection: collection ?? void 0, k });
400
+ if (results.length === 0) {
401
+ console.log(c.yellow(" No results found."));
402
+ brain.close();
403
+ return;
404
+ }
405
+ for (const r of results) {
406
+ const score = Math.round(r.score * 100);
407
+ const ctx = r.context ? ` \u2014 ${c.dim(r.context)}` : "";
408
+ console.log(`${c.magenta(`[DOC ${score}%]`)} ${c.bold(r.filePath)} [${r.metadata.collection}]${ctx}`);
409
+ const preview = r.content.split("\n").slice(0, 4).join("\n");
410
+ console.log(c.dim(preview));
411
+ console.log("");
412
+ }
413
+ brain.close();
414
+ }
415
+ __name(cmdDocSearch, "cmdDocSearch");
416
+ async function cmdContext() {
417
+ const sub = args[1];
418
+ if (sub === "add") {
419
+ const collection = args[2];
420
+ const path2 = args[3];
421
+ const desc = args.slice(4).join(" ");
422
+ if (!collection || !path2 || !desc) {
423
+ console.log(c.red("Usage: brainbank context add <collection> <path> <description>"));
424
+ process.exit(1);
425
+ }
426
+ const brain2 = await createBrain();
427
+ await brain2.initialize();
428
+ brain2.addContext(collection, path2, desc);
429
+ console.log(c.green(`\u2713 Context added: ${collection}:${path2} \u2192 "${desc}"`));
430
+ brain2.close();
431
+ return;
432
+ }
433
+ if (sub === "list") {
434
+ const brain2 = await createBrain();
435
+ await brain2.initialize();
436
+ const contexts = brain2.listContexts();
437
+ if (contexts.length === 0) {
438
+ console.log(c.yellow(" No contexts configured."));
439
+ } else {
440
+ console.log(c.bold("\n\u2501\u2501\u2501 Contexts \u2501\u2501\u2501\n"));
441
+ for (const ctx of contexts) {
442
+ console.log(` ${c.cyan(ctx.collection)}:${ctx.path} \u2192 ${c.dim(ctx.context)}`);
443
+ }
444
+ }
445
+ brain2.close();
446
+ return;
447
+ }
448
+ const task = args.slice(1).join(" ");
449
+ if (!task) {
450
+ console.log(c.red("Usage: brainbank context <task description>"));
451
+ console.log(c.dim(" brainbank context add <collection> <path> <description>"));
452
+ console.log(c.dim(" brainbank context list"));
453
+ process.exit(1);
454
+ }
455
+ const brain = await createBrain();
456
+ const context = await brain.getContext(task);
457
+ console.log(context);
458
+ brain.close();
459
+ }
460
+ __name(cmdContext, "cmdContext");
461
+ async function cmdSearch() {
462
+ const query = args.slice(1).join(" ");
463
+ if (!query) {
464
+ console.log(c.red("Usage: brainbank search <query>"));
465
+ process.exit(1);
466
+ }
467
+ const brain = await createBrain();
468
+ console.log(c.bold(`
469
+ \u2501\u2501\u2501 BrainBank Search: "${query}" \u2501\u2501\u2501
470
+ `));
471
+ const results = await brain.search(query);
472
+ printResults(results);
473
+ brain.close();
474
+ }
475
+ __name(cmdSearch, "cmdSearch");
476
+ async function cmdHybridSearch() {
477
+ const query = args.slice(1).filter((a) => !a.startsWith("--")).join(" ");
478
+ if (!query) {
479
+ console.log(c.red("Usage: brainbank hsearch <query>"));
480
+ process.exit(1);
481
+ }
482
+ const brain = await createBrain();
483
+ console.log(c.bold(`
484
+ \u2501\u2501\u2501 BrainBank Hybrid Search: "${query}" \u2501\u2501\u2501`));
485
+ console.log(c.dim(` Mode: vector + BM25 \u2192 Reciprocal Rank Fusion
486
+ `));
487
+ const results = await brain.hybridSearch(query);
488
+ printResults(results);
489
+ brain.close();
490
+ }
491
+ __name(cmdHybridSearch, "cmdHybridSearch");
492
+ async function cmdKeywordSearch() {
493
+ const query = args.slice(1).filter((a) => !a.startsWith("--")).join(" ");
494
+ if (!query) {
495
+ console.log(c.red("Usage: brainbank ksearch <query>"));
496
+ process.exit(1);
497
+ }
498
+ const brain = await createBrain();
499
+ await brain.initialize();
500
+ console.log(c.bold(`
501
+ \u2501\u2501\u2501 BrainBank Keyword Search: "${query}" \u2501\u2501\u2501`));
502
+ console.log(c.dim(` Mode: BM25 full-text (instant)
503
+ `));
504
+ const results = brain.searchBM25(query);
505
+ printResults(results);
506
+ brain.close();
507
+ }
508
+ __name(cmdKeywordSearch, "cmdKeywordSearch");
509
+ function printResults(results) {
510
+ if (results.length === 0) {
511
+ console.log(c.yellow(" No results found."));
512
+ return;
513
+ }
514
+ for (const r of results) {
515
+ const score = Math.round(r.score * 100);
516
+ if (r.type === "code") {
517
+ const m = r.metadata;
518
+ console.log(`${c.green(`[CODE ${score}%]`)} ${c.bold(r.filePath)} \u2014 ${m.name || m.chunkType} ${c.dim(`L${m.startLine}-${m.endLine}`)}`);
519
+ const preview = r.content.split("\n").slice(0, 5).join("\n");
520
+ console.log(c.dim(preview));
521
+ console.log("");
522
+ } else if (r.type === "commit") {
523
+ const m = r.metadata;
524
+ console.log(`${c.cyan(`[COMMIT ${score}%]`)} ${c.bold(m.shortHash)} ${r.content} ${c.dim(`(${m.author})`)}`);
525
+ if (m.files?.length) console.log(c.dim(` Files: ${m.files.slice(0, 4).join(", ")}`));
526
+ console.log("");
527
+ } else if (r.type === "document") {
528
+ const ctx = r.context ? ` \u2014 ${c.dim(r.context)}` : "";
529
+ console.log(`${c.magenta(`[DOC ${score}%]`)} ${c.bold(r.filePath)} [${r.metadata.collection}]${ctx}`);
530
+ const preview = r.content.split("\n").slice(0, 4).join("\n");
531
+ console.log(c.dim(preview));
532
+ console.log("");
533
+ }
534
+ }
535
+ }
536
+ __name(printResults, "printResults");
537
+ async function cmdStats() {
538
+ const brain = await createBrain();
539
+ await brain.initialize();
540
+ const s = brain.stats();
541
+ console.log(c.bold("\n\u2501\u2501\u2501 BrainBank Stats \u2501\u2501\u2501\n"));
542
+ console.log(` ${c.cyan("Indexers")}: ${brain.indexers.join(", ")}
543
+ `);
544
+ if (s.code) {
545
+ console.log(` ${c.cyan("Code")}`);
546
+ console.log(` Files indexed: ${s.code.files}`);
547
+ console.log(` Code chunks: ${s.code.chunks}`);
548
+ console.log(` HNSW vectors: ${s.code.hnswSize}`);
549
+ console.log("");
550
+ }
551
+ if (s.git) {
552
+ console.log(` ${c.cyan("Git History")}`);
553
+ console.log(` Commits: ${s.git.commits}`);
554
+ console.log(` Files tracked: ${s.git.filesTracked}`);
555
+ console.log(` Co-edit pairs: ${s.git.coEdits}`);
556
+ console.log(` HNSW vectors: ${s.git.hnswSize}`);
557
+ console.log("");
558
+ }
559
+ if (s.documents) {
560
+ console.log(` ${c.cyan("Documents")}`);
561
+ console.log(` Collections: ${s.documents.collections}`);
562
+ console.log(` Documents: ${s.documents.documents}`);
563
+ console.log(` Chunks: ${s.documents.chunks}`);
564
+ console.log(` HNSW vectors: ${s.documents.hnswSize}`);
565
+ console.log("");
566
+ }
567
+ const kvNames = brain.listCollectionNames();
568
+ if (kvNames.length > 0) {
569
+ console.log(` ${c.cyan("KV Collections")}`);
570
+ for (const name of kvNames) {
571
+ const coll = brain.collection(name);
572
+ console.log(` ${name}: ${coll.count()} items`);
573
+ }
574
+ console.log("");
575
+ }
576
+ brain.close();
577
+ }
578
+ __name(cmdStats, "cmdStats");
579
+ async function cmdReembed() {
580
+ const brain = await createBrain();
581
+ await brain.initialize();
582
+ console.log(c.bold("\n\u2501\u2501\u2501 BrainBank Re-embed \u2501\u2501\u2501\n"));
583
+ console.log(c.dim(" Regenerating vectors with current embedding provider..."));
584
+ console.log(c.dim(" Text, FTS, and metadata remain unchanged.\n"));
585
+ const result = await brain.reembed({
586
+ onProgress: /* @__PURE__ */ __name((table, current, total) => {
587
+ process.stdout.write(`\r ${c.cyan(table.padEnd(8))} ${current}/${total}`);
588
+ }, "onProgress")
589
+ });
590
+ console.log("\n");
591
+ if (result.code > 0) console.log(` ${c.green("\u2713")} Code: ${result.code} vectors`);
592
+ if (result.git > 0) console.log(` ${c.green("\u2713")} Git: ${result.git} vectors`);
593
+ if (result.docs > 0) console.log(` ${c.green("\u2713")} Docs: ${result.docs} vectors`);
594
+ if (result.kv > 0) console.log(` ${c.green("\u2713")} KV: ${result.kv} vectors`);
595
+ if (result.notes > 0) console.log(` ${c.green("\u2713")} Notes: ${result.notes} vectors`);
596
+ if (result.memory > 0) console.log(` ${c.green("\u2713")} Memory: ${result.memory} vectors`);
597
+ console.log(`
598
+ ${c.bold("Total")}: ${result.total} vectors regenerated
599
+ `);
600
+ brain.close();
601
+ }
602
+ __name(cmdReembed, "cmdReembed");
603
+ async function cmdWatch() {
604
+ const brain = await createBrain();
605
+ await brain.initialize();
606
+ console.log(c.bold("\n\u2501\u2501\u2501 BrainBank Watch \u2501\u2501\u2501\n"));
607
+ console.log(c.dim(` Watching ${brain.config.repoPath} for changes...`));
608
+ console.log(c.dim(" Press Ctrl+C to stop.\n"));
609
+ const watcher = brain.watch({
610
+ debounceMs: 2e3,
611
+ onIndex: /* @__PURE__ */ __name((file, indexer) => {
612
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
613
+ console.log(` ${c.dim(ts)} ${c.green("\u2713")} ${c.cyan(indexer)}: ${file}`);
614
+ }, "onIndex"),
615
+ onError: /* @__PURE__ */ __name((err) => {
616
+ console.error(` ${c.red("\u2717")} ${err.message}`);
617
+ }, "onError")
618
+ });
619
+ process.on("SIGINT", () => {
620
+ console.log(c.dim("\n Stopping watcher..."));
621
+ watcher.close();
622
+ brain.close();
623
+ process.exit(0);
624
+ });
625
+ await new Promise(() => {
626
+ });
627
+ }
628
+ __name(cmdWatch, "cmdWatch");
629
+ async function cmdServe() {
630
+ await import("@brainbank/mcp");
631
+ }
632
+ __name(cmdServe, "cmdServe");
633
+ function showHelp() {
634
+ console.log(c.bold("\n\u2501\u2501\u2501 BrainBank \u2014 Semantic Knowledge Bank \u2501\u2501\u2501\n"));
635
+ console.log(c.bold("Indexing:"));
636
+ console.log(` ${c.cyan("index")} [path] Index code + git history`);
637
+ console.log(` ${c.cyan("collection add")} <path> --name Add a document collection`);
638
+ console.log(` ${c.cyan("collection list")} List collections`);
639
+ console.log(` ${c.cyan("collection remove")} <name> Remove a collection`);
640
+ console.log(` ${c.cyan("docs")} [--collection <name>] Index document collections`);
641
+ console.log("");
642
+ console.log(c.bold("Search:"));
643
+ console.log(` ${c.cyan("search")} <query> Semantic search (vector)`);
644
+ console.log(` ${c.cyan("hsearch")} <query> Hybrid search (${c.bold("best quality")})`);
645
+ console.log(` ${c.cyan("ksearch")} <query> Keyword search (BM25, instant)`);
646
+ console.log(` ${c.cyan("dsearch")} <query> Document search`);
647
+ console.log("");
648
+ console.log(c.bold("Context:"));
649
+ console.log(` ${c.cyan("context")} <task> Get formatted context for a task`);
650
+ console.log(` ${c.cyan("context add")} <col> <path> <desc> Add context metadata`);
651
+ console.log(` ${c.cyan("context list")} List all context metadata`);
652
+ console.log("");
653
+ console.log(c.bold("KV Store:"));
654
+ console.log(` ${c.cyan("kv add")} <coll> <content> Add item to a collection`);
655
+ console.log(` ${c.cyan("kv search")} <coll> <query> Search a collection`);
656
+ console.log(` ${c.cyan("kv list")} [coll] List collections or items`);
657
+ console.log(` ${c.cyan("kv trim")} <coll> --keep <n> Keep only N most recent`);
658
+ console.log(` ${c.cyan("kv clear")} <coll> Clear all items`);
659
+ console.log("");
660
+ console.log(c.bold("Utility:"));
661
+ console.log(` ${c.cyan("stats")} Show index statistics`);
662
+ console.log(` ${c.cyan("reembed")} Re-embed all vectors`);
663
+ console.log(` ${c.cyan("watch")} Watch files, auto-re-index`);
664
+ console.log(` ${c.cyan("serve")} Start MCP server (stdio)`);
665
+ console.log("");
666
+ console.log(c.bold("Options:"));
667
+ console.log(` ${c.dim("--repo <path>")} Repository path (default: .)`);
668
+ console.log(` ${c.dim("--force")} Force re-index all files`);
669
+ console.log(` ${c.dim("--depth <n>")} Git history depth (default: 500)`);
670
+ console.log(` ${c.dim("--collection <name>")} Filter by collection`);
671
+ console.log(` ${c.dim("--pattern <glob>")} Collection glob (default: **/*.md)`);
672
+ console.log(` ${c.dim("--context <desc>")} Context description`);
673
+ console.log(` ${c.dim("--reranker <name>")} Reranker to use (qwen3)`);
674
+ console.log("");
675
+ console.log(c.bold("Examples:"));
676
+ console.log(c.dim(" brainbank index ."));
677
+ console.log(c.dim(' brainbank kv add errors "Fixed null pointer in api.ts"'));
678
+ console.log(c.dim(' brainbank kv search errors "null pointer"'));
679
+ console.log(c.dim(" brainbank kv list"));
680
+ console.log(c.dim(' brainbank hsearch "authentication middleware"'));
681
+ console.log(c.dim(' brainbank context "add rate limiting to the API"'));
682
+ console.log(c.dim(" brainbank serve"));
683
+ }
684
+ __name(showHelp, "showHelp");
685
+ async function main() {
686
+ switch (command) {
687
+ case "index":
688
+ return cmdIndex();
689
+ case "collection":
690
+ return cmdCollection();
691
+ case "kv":
692
+ return cmdKv();
693
+ case "docs":
694
+ return cmdDocs();
695
+ case "dsearch":
696
+ return cmdDocSearch();
697
+ case "search":
698
+ return cmdSearch();
699
+ case "hsearch":
700
+ return cmdHybridSearch();
701
+ case "ksearch":
702
+ return cmdKeywordSearch();
703
+ case "context":
704
+ return cmdContext();
705
+ case "stats":
706
+ return cmdStats();
707
+ case "reembed":
708
+ return cmdReembed();
709
+ case "watch":
710
+ return cmdWatch();
711
+ case "serve":
712
+ return cmdServe();
713
+ case "help":
714
+ case "--help":
715
+ case "-h":
716
+ showHelp();
717
+ break;
718
+ default:
719
+ if (command) console.log(c.red(`Unknown command: ${command}
720
+ `));
721
+ showHelp();
722
+ process.exit(command ? 1 : 0);
723
+ }
724
+ }
725
+ __name(main, "main");
726
+ main().catch((err) => {
727
+ console.error(c.red(`Error: ${err.message}`));
728
+ if (process.env.BRAINBANK_DEBUG) console.error(err.stack);
729
+ process.exit(1);
730
+ });
731
+ //# sourceMappingURL=cli.js.map