brainbank 0.7.0 → 0.8.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 (156) hide show
  1. package/README.md +76 -1398
  2. package/bin/brainbank +5 -1
  3. package/dist/{chunk-N2OJRXSB.js → chunk-3HVCONGF.js} +1 -1
  4. package/dist/{chunk-N2OJRXSB.js.map → chunk-3HVCONGF.js.map} +1 -1
  5. package/dist/{chunk-CCXVL56V.js → chunk-3JZIM5AU.js} +6 -3
  6. package/dist/chunk-3JZIM5AU.js.map +1 -0
  7. package/dist/{chunk-6XOXM7MI.js → chunk-5KU2PP34.js} +2 -2
  8. package/dist/{chunk-6XOXM7MI.js.map → chunk-5KU2PP34.js.map} +1 -1
  9. package/dist/chunk-7JDCHUJV.js +89 -0
  10. package/dist/chunk-7JDCHUJV.js.map +1 -0
  11. package/dist/{chunk-B77KABWH.js → chunk-7T2ZCZQA.js} +17 -15
  12. package/dist/chunk-7T2ZCZQA.js.map +1 -0
  13. package/dist/chunk-E3J37GDA.js +74 -0
  14. package/dist/chunk-E3J37GDA.js.map +1 -0
  15. package/dist/chunk-JEFWMS5Z.js +217 -0
  16. package/dist/chunk-JEFWMS5Z.js.map +1 -0
  17. package/dist/chunk-JRVYSTMP.js +3256 -0
  18. package/dist/chunk-JRVYSTMP.js.map +1 -0
  19. package/dist/chunk-OLDHLOMT.js +69 -0
  20. package/dist/chunk-OLDHLOMT.js.map +1 -0
  21. package/dist/{chunk-424UFCY7.js → chunk-QTZNB6AK.js} +6 -2
  22. package/dist/chunk-QTZNB6AK.js.map +1 -0
  23. package/dist/chunk-RFF7HMP6.js +109 -0
  24. package/dist/chunk-RFF7HMP6.js.map +1 -0
  25. package/dist/{chunk-ZNLN2VWV.js → chunk-TD3TEFI3.js} +1 -1
  26. package/dist/chunk-TD3TEFI3.js.map +1 -0
  27. package/dist/cli.js +1036 -341
  28. package/dist/cli.js.map +1 -1
  29. package/dist/haiku-expander-WOVJIVXD.js +8 -0
  30. package/dist/haiku-pruner-DB77ZQLJ.js +8 -0
  31. package/dist/http-server-GIRELCCL.js +9 -0
  32. package/dist/index.d.ts +1774 -611
  33. package/dist/index.js +282 -70
  34. package/dist/index.js.map +1 -1
  35. package/dist/{local-embedding-ZIMTK6PU.js → local-embedding-2RNCC5EU.js} +2 -2
  36. package/dist/{openai-embedding-VQZCZQYT.js → openai-embedding-Z5I4K4CN.js} +2 -2
  37. package/dist/perplexity-context-embedding-V5YUMXDR.js +9 -0
  38. package/dist/{perplexity-embedding-227WQY4R.js → perplexity-embedding-X2S72OAC.js} +2 -2
  39. package/dist/plugin-FF4Q34TI.js +32 -0
  40. package/dist/{qwen3-reranker-3MHEENT5.js → qwen3-reranker-HVIQOLKS.js} +2 -2
  41. package/dist/{resolve-CUJWY6HP.js → resolve-Q5D6HECY.js} +2 -2
  42. package/package.json +25 -52
  43. package/src/brainbank.ts +620 -0
  44. package/src/cli/commands/collection.ts +77 -0
  45. package/src/cli/commands/context.ts +171 -0
  46. package/src/cli/commands/daemon.ts +100 -0
  47. package/src/cli/commands/docs.ts +71 -0
  48. package/src/cli/commands/files.ts +69 -0
  49. package/src/cli/commands/help.ts +72 -0
  50. package/src/cli/commands/index.ts +282 -0
  51. package/src/cli/commands/kv.ts +140 -0
  52. package/src/cli/commands/mcp.ts +13 -0
  53. package/src/cli/commands/reembed.ts +30 -0
  54. package/src/cli/commands/scan.ts +365 -0
  55. package/src/cli/commands/search.ts +130 -0
  56. package/src/cli/commands/stats.ts +44 -0
  57. package/src/cli/commands/status.ts +47 -0
  58. package/src/cli/commands/watch.ts +43 -0
  59. package/src/cli/factory/brain-context.ts +43 -0
  60. package/src/cli/factory/builtin-registration.ts +123 -0
  61. package/src/cli/factory/config-loader.ts +72 -0
  62. package/src/cli/factory/index.ts +65 -0
  63. package/src/cli/factory/plugin-loader.ts +146 -0
  64. package/src/cli/index.ts +63 -0
  65. package/src/cli/server-client.ts +135 -0
  66. package/src/cli/utils.ts +121 -0
  67. package/src/config.ts +50 -0
  68. package/src/constants.ts +13 -0
  69. package/src/db/adapter.ts +112 -0
  70. package/src/db/metadata.ts +130 -0
  71. package/src/db/migrations.ts +66 -0
  72. package/src/db/sqlite-adapter.ts +208 -0
  73. package/src/db/tracker.ts +91 -0
  74. package/src/engine/index-api.ts +85 -0
  75. package/src/engine/reembed.ts +206 -0
  76. package/src/engine/search-api.ts +222 -0
  77. package/src/index.ts +159 -0
  78. package/src/lib/fts.ts +57 -0
  79. package/src/lib/languages.ts +180 -0
  80. package/src/lib/logger.ts +125 -0
  81. package/src/lib/math.ts +87 -0
  82. package/src/lib/provider-key.ts +20 -0
  83. package/src/lib/prune.ts +71 -0
  84. package/src/lib/rerank.ts +33 -0
  85. package/src/lib/rrf.ts +133 -0
  86. package/src/lib/write-lock.ts +108 -0
  87. package/src/plugin.ts +323 -0
  88. package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
  89. package/src/providers/embeddings/embedding-worker.ts +141 -0
  90. package/src/providers/embeddings/local-embedding.ts +115 -0
  91. package/src/providers/embeddings/openai-embedding.ts +167 -0
  92. package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
  93. package/src/providers/embeddings/perplexity-embedding.ts +165 -0
  94. package/src/providers/embeddings/resolve.ts +34 -0
  95. package/src/providers/pruners/haiku-expander.ts +152 -0
  96. package/src/providers/pruners/haiku-pruner.ts +112 -0
  97. package/src/providers/rerankers/qwen3-reranker.ts +180 -0
  98. package/src/providers/vector/hnsw-index.ts +174 -0
  99. package/src/providers/vector/hnsw-loader.ts +129 -0
  100. package/src/search/bm25-boost.ts +61 -0
  101. package/src/search/context-builder.ts +298 -0
  102. package/src/search/keyword/composite-bm25-search.ts +62 -0
  103. package/src/search/types.ts +35 -0
  104. package/src/search/vector/composite-vector-search.ts +76 -0
  105. package/src/search/vector/mmr.ts +64 -0
  106. package/src/services/collection.ts +405 -0
  107. package/src/services/daemon.ts +87 -0
  108. package/src/services/http-server.ts +288 -0
  109. package/src/services/kv-service.ts +65 -0
  110. package/src/services/plugin-registry.ts +109 -0
  111. package/src/services/watch.ts +348 -0
  112. package/src/services/webhook-server.ts +100 -0
  113. package/src/types.ts +504 -0
  114. package/dist/base-3SNc_CeY.d.ts +0 -593
  115. package/dist/chunk-424UFCY7.js.map +0 -1
  116. package/dist/chunk-7EZR47JV.js +0 -232
  117. package/dist/chunk-7EZR47JV.js.map +0 -1
  118. package/dist/chunk-B77KABWH.js.map +0 -1
  119. package/dist/chunk-CCXVL56V.js.map +0 -1
  120. package/dist/chunk-DI3H6JVZ.js +0 -2432
  121. package/dist/chunk-DI3H6JVZ.js.map +0 -1
  122. package/dist/chunk-FGL32LUJ.js +0 -754
  123. package/dist/chunk-FGL32LUJ.js.map +0 -1
  124. package/dist/chunk-JRSKWF6K.js +0 -313
  125. package/dist/chunk-JRSKWF6K.js.map +0 -1
  126. package/dist/chunk-U2Q2XGPZ.js +0 -42
  127. package/dist/chunk-U2Q2XGPZ.js.map +0 -1
  128. package/dist/chunk-VQ27YUHH.js +0 -629
  129. package/dist/chunk-VQ27YUHH.js.map +0 -1
  130. package/dist/chunk-VVXYZIIB.js +0 -304
  131. package/dist/chunk-VVXYZIIB.js.map +0 -1
  132. package/dist/chunk-YOLKSYWK.js +0 -79
  133. package/dist/chunk-YOLKSYWK.js.map +0 -1
  134. package/dist/chunk-ZNLN2VWV.js.map +0 -1
  135. package/dist/code.d.ts +0 -33
  136. package/dist/code.js +0 -9
  137. package/dist/docs.d.ts +0 -21
  138. package/dist/docs.js +0 -9
  139. package/dist/git.d.ts +0 -33
  140. package/dist/git.js +0 -9
  141. package/dist/memory.d.ts +0 -17
  142. package/dist/memory.js +0 -9
  143. package/dist/notes.d.ts +0 -17
  144. package/dist/notes.js +0 -10
  145. package/dist/perplexity-context-embedding-KSVSZXMD.js +0 -9
  146. package/dist/resolve-CUJWY6HP.js.map +0 -1
  147. /package/dist/{code.js.map → haiku-expander-WOVJIVXD.js.map} +0 -0
  148. /package/dist/{docs.js.map → haiku-pruner-DB77ZQLJ.js.map} +0 -0
  149. /package/dist/{git.js.map → http-server-GIRELCCL.js.map} +0 -0
  150. /package/dist/{local-embedding-ZIMTK6PU.js.map → local-embedding-2RNCC5EU.js.map} +0 -0
  151. /package/dist/{memory.js.map → openai-embedding-Z5I4K4CN.js.map} +0 -0
  152. /package/dist/{notes.js.map → perplexity-context-embedding-V5YUMXDR.js.map} +0 -0
  153. /package/dist/{openai-embedding-VQZCZQYT.js.map → perplexity-embedding-X2S72OAC.js.map} +0 -0
  154. /package/dist/{perplexity-context-embedding-KSVSZXMD.js.map → plugin-FF4Q34TI.js.map} +0 -0
  155. /package/dist/{perplexity-embedding-227WQY4R.js.map → qwen3-reranker-HVIQOLKS.js.map} +0 -0
  156. /package/dist/{qwen3-reranker-3MHEENT5.js.map → resolve-Q5D6HECY.js.map} +0 -0
@@ -0,0 +1,282 @@
1
+ /**
2
+ * brainbank index [path] — Interactive scan → select → index
3
+ *
4
+ * Scans the repo first, shows a summary tree, and prompts the user
5
+ * with checkboxes to select which modules to index. Use --yes to skip.
6
+ */
7
+
8
+ import type { ScanResult, ScanModule } from './scan.ts';
9
+
10
+ import * as fs from 'node:fs';
11
+ import * as path from 'node:path';
12
+ import { c, args, getFlag, hasFlag, stripFlags } from '@/cli/utils.ts';
13
+ import { createBrain, getConfig, registerConfigCollections } from '@/cli/factory/index.ts';
14
+ import { findDocsPlugin } from '@/cli/utils.ts';
15
+ import { scanRepo } from './scan.ts';
16
+
17
+ export async function cmdIndex(): Promise<void> {
18
+ const positional = stripFlags(args);
19
+ const repoPath = positional[1] || '.';
20
+ const force = hasFlag('force');
21
+ const depth = parseInt(getFlag('depth') || '500', 10);
22
+ const onlyRaw = getFlag('only');
23
+ const docsPath = getFlag('docs');
24
+ const skipPrompt = hasFlag('yes') || hasFlag('y');
25
+
26
+
27
+ const scan = scanRepo(repoPath);
28
+ printScanTree(scan, depth);
29
+
30
+
31
+ let modules: string[];
32
+
33
+ if (onlyRaw) {
34
+ // --only flag: explicit module selection
35
+ modules = onlyRaw.split(',').map(s => s.trim());
36
+ } else if (scan.config.plugins && scan.config.plugins.length > 0) {
37
+ // Config exists with plugins field — use it as source of truth
38
+ modules = scan.config.plugins;
39
+ console.log(c.dim(`\n Using config: plugins = [${modules.join(', ')}]\n`));
40
+ } else if (skipPrompt) {
41
+ modules = buildDefaultModules(scan);
42
+ } else {
43
+ modules = await promptModules(scan);
44
+ if (modules.length === 0) {
45
+ console.log(c.dim('\n Nothing selected. Exiting.\n'));
46
+ return;
47
+ }
48
+
49
+ // Clean screen and show selection summary
50
+ console.clear();
51
+ console.log(c.bold('\n━━━ BrainBank ━━━\n'));
52
+ console.log(' Selected modules:');
53
+ for (const m of modules) {
54
+ console.log(` ${c.green('✓')} ${m}`);
55
+ }
56
+ console.log('');
57
+ }
58
+
59
+ // If --docs is passed, auto-include 'docs' in modules
60
+ if (docsPath && !modules.includes('docs')) {
61
+ modules.push('docs');
62
+ }
63
+
64
+ // Auto-generate config.json if it doesn't exist yet
65
+ if (!scan.config.exists && !skipPrompt) {
66
+ await saveConfig(scan.repoPath, modules);
67
+ }
68
+
69
+
70
+ console.log(c.bold(`\n━━━ Indexing: ${modules.join(', ')} ━━━`));
71
+
72
+ const brain = await createBrain(repoPath);
73
+ await brain.initialize();
74
+
75
+ const config = await getConfig(repoPath);
76
+ await registerConfigCollections(brain, repoPath, config);
77
+
78
+ if (docsPath) {
79
+ const absDocsPath = path.resolve(docsPath);
80
+ const collName = path.basename(absDocsPath);
81
+ try {
82
+ const docsPlugin = findDocsPlugin(brain);
83
+ await docsPlugin?.addCollection({
84
+ name: collName,
85
+ path: absDocsPath,
86
+ pattern: '**/*.md',
87
+ ignore: ['deprecated/**', 'node_modules/**'],
88
+ });
89
+ console.log(c.dim(` Registered docs collection: ${collName}`));
90
+ } catch {
91
+ console.log(c.yellow(` Warning: docs module not loaded, skipping --docs`));
92
+ }
93
+ }
94
+
95
+ const result = await brain.index({
96
+ modules,
97
+ forceReindex: force,
98
+ pluginOptions: { depth },
99
+ onProgress: (stage, msg) => {
100
+ process.stdout.write(`\r ${c.cyan(stage.toUpperCase())} ${msg} `);
101
+ },
102
+ });
103
+
104
+ console.log('\n');
105
+ for (const [name, value] of Object.entries(result)) {
106
+ if (!value) continue;
107
+ const v = value as Record<string, unknown>;
108
+ if (typeof v.indexed === 'number') {
109
+ const parts = [`${v.indexed} indexed`, `${v.skipped ?? 0} skipped`];
110
+ if (typeof v.chunks === 'number') parts.push(`${v.chunks} chunks`);
111
+ if (typeof v.removed === 'number' && v.removed > 0) parts.push(`${v.removed} removed`);
112
+ console.log(` ${c.green(name)}: ${parts.join(', ')}`);
113
+ } else {
114
+ console.log(` ${c.green(name)}: done`);
115
+ }
116
+ }
117
+
118
+ const stats = brain.stats();
119
+ console.log(`\n ${c.bold('Totals')}:`);
120
+ for (const [name, s] of Object.entries(stats)) {
121
+ if (!s || typeof s !== 'object') continue;
122
+ const entries = Object.entries(s as Record<string, unknown>)
123
+ .map(([k, v]) => `${k}: ${v}`)
124
+ .join(', ');
125
+ console.log(` ${name}: ${entries}`);
126
+ }
127
+
128
+ brain.close();
129
+ }
130
+
131
+
132
+ function printScanTree(scan: ScanResult, depth: number): void {
133
+ console.clear();
134
+ console.log(c.bold('\n━━━ BrainBank Scan ━━━'));
135
+ console.log(c.dim(` Repo: ${scan.repoPath}`));
136
+
137
+ // Multi-repo detection
138
+ if (scan.gitSubdirs.length > 0) {
139
+ console.log(c.cyan(`\n 🔀 Multi-repo — ${scan.gitSubdirs.length} git repos detected`));
140
+ for (const sub of scan.gitSubdirs) {
141
+ console.log(c.dim(` └── ${sub.name}`));
142
+ }
143
+ }
144
+
145
+ // Dynamic module display
146
+ for (const mod of scan.modules) {
147
+ console.log('');
148
+ if (mod.available) {
149
+ const extra = mod.name === 'git' ? ` (depth: ${depth})` : '';
150
+ console.log(` ${mod.icon} ${c.bold(capitalizeFirst(mod.name))} — ${mod.summary}${extra}`);
151
+ if (mod.details) {
152
+ for (const d of mod.details) {
153
+ console.log(c.dim(` ${d}`));
154
+ }
155
+ }
156
+ } else {
157
+ console.log(` ${mod.icon} ${c.dim(`${capitalizeFirst(mod.name)} — ${mod.summary}`)}`);
158
+ }
159
+ }
160
+
161
+ // Config ignore
162
+ if (scan.config.ignore?.length) {
163
+ console.log(c.dim(` Ignore: ${scan.config.ignore.join(', ')}`));
164
+ }
165
+
166
+ // Config & DB
167
+ console.log('');
168
+ if (scan.config.exists) {
169
+ console.log(` ⚙️ ${c.dim('Config:')} .brainbank/config.json ${c.green('✓')}`);
170
+ }
171
+ if (scan.db?.exists) {
172
+ const ago = scan.db.lastModified ? timeSince(scan.db.lastModified) : '';
173
+ console.log(` 💾 ${c.dim('DB:')} ${scan.db.sizeMB} MB${ago ? `, last indexed ${ago}` : ''}`);
174
+ } else {
175
+ console.log(` 💾 ${c.dim('DB: new (first index)')}`);
176
+ }
177
+ console.log('');
178
+ }
179
+
180
+
181
+ /** Build the default list of available modules based on scan. */
182
+ function buildDefaultModules(scan: ScanResult): string[] {
183
+ return scan.modules.filter(m => m.available && m.checked).map(m => m.name);
184
+ }
185
+
186
+ /** Interactive checkbox prompt via @inquirer/prompts. */
187
+ async function promptModules(scan: ScanResult): Promise<string[]> {
188
+ const { checkbox } = await import('@inquirer/prompts');
189
+ console.log(c.dim(' ─────────────────────────────────────────\n'));
190
+
191
+ const choices = scan.modules.map((m: ScanModule) => ({
192
+ name: `${capitalizeFirst(m.name).padEnd(6)} — ${m.summary}`,
193
+ value: m.name,
194
+ checked: m.checked && m.available,
195
+ disabled: m.available ? undefined : m.disabled,
196
+ }));
197
+
198
+ return checkbox<string>({
199
+ message: 'Select modules to index:\n',
200
+ choices,
201
+ });
202
+ }
203
+
204
+
205
+ function timeSince(date: Date): string {
206
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
207
+ if (seconds < 60) return 'just now';
208
+ const minutes = Math.floor(seconds / 60);
209
+ if (minutes < 60) return `${minutes}m ago`;
210
+ const hours = Math.floor(minutes / 60);
211
+ if (hours < 24) return `${hours}h ago`;
212
+ const days = Math.floor(hours / 24);
213
+ return `${days}d ago`;
214
+ }
215
+
216
+ /** Capitalize first letter. */
217
+ function capitalizeFirst(s: string): string {
218
+ return s.charAt(0).toUpperCase() + s.slice(1);
219
+ }
220
+
221
+ /** Generate .brainbank/config.json from the selected modules. */
222
+ async function saveConfig(repoPath: string, modules: string[]): Promise<void> {
223
+ const { select } = await import('@inquirer/prompts');
224
+
225
+ // Embedding provider selection
226
+ const envEmbedding = process.env.BRAINBANK_EMBEDDING;
227
+ const embedding = await select<string>({
228
+ message: 'Embedding provider:',
229
+ choices: [
230
+ {
231
+ name: 'perplexity-context — best accuracy (recommended)',
232
+ value: 'perplexity-context',
233
+ },
234
+ {
235
+ name: 'perplexity — fast, high quality',
236
+ value: 'perplexity',
237
+ },
238
+ {
239
+ name: 'openai — text-embedding-3-small',
240
+ value: 'openai',
241
+ },
242
+ {
243
+ name: 'local — offline, no API key needed',
244
+ value: 'local',
245
+ },
246
+ ],
247
+ default: envEmbedding ?? 'perplexity-context',
248
+ });
249
+
250
+ // Pruner selection
251
+ const pruner = await select<string>({
252
+ message: 'Noise pruner:',
253
+ choices: [
254
+ {
255
+ name: 'none — no pruning (default)',
256
+ value: 'none',
257
+ },
258
+ {
259
+ name: 'haiku — AI-powered noise filter (Claude Haiku)',
260
+ value: 'haiku',
261
+ },
262
+ ],
263
+ default: 'none',
264
+ });
265
+
266
+ const configDir = path.join(repoPath, '.brainbank');
267
+ const configPath = path.join(configDir, 'config.json');
268
+
269
+ const config: Record<string, unknown> = {
270
+ plugins: modules,
271
+ embedding,
272
+ };
273
+
274
+ if (pruner !== 'none') {
275
+ config.pruner = pruner;
276
+ }
277
+
278
+ fs.mkdirSync(configDir, { recursive: true });
279
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
280
+ console.log(c.green(` ✓ Saved ${path.relative(process.cwd(), configPath)}`));
281
+ }
282
+
@@ -0,0 +1,140 @@
1
+ /**
2
+ * brainbank kv add|search|list|trim|clear — Dynamic KV collection management
3
+ */
4
+
5
+ import { c, args, getFlag, stripFlags } from '@/cli/utils.ts';
6
+ import { createBrain } from '@/cli/factory/index.ts';
7
+
8
+ export async function cmdKv(): Promise<void> {
9
+ const pos = stripFlags(args);
10
+ const sub = pos[1];
11
+
12
+ if (sub === 'add') {
13
+ const collName = pos[2];
14
+ const content = pos.slice(3).join(' ');
15
+ const metaRaw = getFlag('meta');
16
+
17
+ if (!collName || !content) {
18
+ console.log(c.red("Usage: brainbank kv add <collection> <content> [--meta '{\"key\":\"val\"}']"));
19
+ process.exit(1);
20
+ }
21
+
22
+ const brain = await createBrain();
23
+ await brain.initialize();
24
+ const coll = brain.collection(collName);
25
+ const meta = metaRaw ? JSON.parse(metaRaw) : {};
26
+ const id = await coll.add(content, meta);
27
+ console.log(c.green(`✓ Added item #${id} to '${collName}'`));
28
+ brain.close();
29
+ return;
30
+ }
31
+
32
+ if (sub === 'search') {
33
+ const collName = pos[2];
34
+ const query = pos.slice(3).join(' ');
35
+ const k = parseInt(getFlag('k') || '5', 10);
36
+ const mode = (getFlag('mode') || 'hybrid') as 'hybrid' | 'vector' | 'keyword';
37
+
38
+ if (!collName || !query) {
39
+ console.log(c.red('Usage: brainbank kv search <collection> <query> [--k 5] [--mode hybrid|keyword|vector]'));
40
+ process.exit(1);
41
+ }
42
+
43
+ const brain = await createBrain();
44
+ await brain.initialize();
45
+ const coll = brain.collection(collName);
46
+ const results = await coll.search(query, { k, mode });
47
+
48
+ if (results.length === 0) {
49
+ console.log(c.yellow(' No results found.'));
50
+ } else {
51
+ console.log(c.bold(`\n━━━ ${collName}: "${query}" ━━━\n`));
52
+ for (const r of results) {
53
+ const score = Math.round((r.score ?? 0) * 100);
54
+ console.log(` ${c.cyan(`[${score}%]`)} ${r.content}`);
55
+ if (Object.keys(r.metadata).length > 0) {
56
+ console.log(` ${c.dim(JSON.stringify(r.metadata))}`);
57
+ }
58
+ }
59
+ }
60
+ brain.close();
61
+ return;
62
+ }
63
+
64
+ if (sub === 'list') {
65
+ const collName = pos[2];
66
+ const limit = parseInt(getFlag('limit') || '20', 10);
67
+
68
+ if (!collName) {
69
+ const brain = await createBrain();
70
+ await brain.initialize();
71
+ const names = brain.listCollectionNames();
72
+ if (names.length === 0) {
73
+ console.log(c.yellow(' No KV collections found.'));
74
+ } else {
75
+ console.log(c.bold('\n━━━ KV Collections ━━━\n'));
76
+ for (const n of names) {
77
+ const coll = brain.collection(n);
78
+ console.log(` ${c.cyan(n)} — ${coll.count()} items`);
79
+ }
80
+ }
81
+ brain.close();
82
+ return;
83
+ }
84
+
85
+ const brain = await createBrain();
86
+ await brain.initialize();
87
+ const coll = brain.collection(collName);
88
+ const items = coll.list({ limit });
89
+ if (items.length === 0) {
90
+ console.log(c.yellow(` Collection '${collName}' is empty.`));
91
+ } else {
92
+ console.log(c.bold(`\n━━━ ${collName} (${coll.count()} items) ━━━\n`));
93
+ for (const item of items) {
94
+ const age = Math.round((Date.now() / 1000 - item.createdAt) / 60);
95
+ console.log(` #${item.id} ${c.dim(`(${age}m ago)`)} ${item.content.slice(0, 80)}`);
96
+ }
97
+ }
98
+ brain.close();
99
+ return;
100
+ }
101
+
102
+ if (sub === 'trim') {
103
+ const collName = pos[2];
104
+ const keep = parseInt(getFlag('keep') || '0', 10);
105
+
106
+ if (!collName || keep <= 0) {
107
+ console.log(c.red('Usage: brainbank kv trim <collection> --keep <n>'));
108
+ process.exit(1);
109
+ }
110
+
111
+ const brain = await createBrain();
112
+ await brain.initialize();
113
+ const coll = brain.collection(collName);
114
+ const result = await coll.trim({ keep });
115
+ console.log(c.green(`✓ Trimmed ${result.removed} items from '${collName}' (kept ${keep})`));
116
+ brain.close();
117
+ return;
118
+ }
119
+
120
+ if (sub === 'clear') {
121
+ const collName = pos[2];
122
+ if (!collName) {
123
+ console.log(c.red('Usage: brainbank kv clear <collection>'));
124
+ process.exit(1);
125
+ }
126
+
127
+ const brain = await createBrain();
128
+ await brain.initialize();
129
+ const coll = brain.collection(collName);
130
+ const before = coll.count();
131
+ coll.clear();
132
+ console.log(c.green(`✓ Cleared ${before} items from '${collName}'`));
133
+ brain.close();
134
+ return;
135
+ }
136
+
137
+ console.log(c.red('Usage: brainbank kv <add|search|list|trim|clear>'));
138
+ process.exit(1);
139
+ }
140
+
@@ -0,0 +1,13 @@
1
+ /** brainbank mcp — Start MCP server (stdio). Requires @brainbank/mcp. */
2
+
3
+ import { c } from '@/cli/utils.ts';
4
+
5
+ export async function cmdMcp(): Promise<void> {
6
+ try {
7
+ await import('@brainbank/mcp');
8
+ } catch {
9
+ console.error(c.red('Error: @brainbank/mcp is not installed.'));
10
+ console.error(c.dim(' Install: npm i @brainbank/mcp'));
11
+ process.exit(1);
12
+ }
13
+ }
@@ -0,0 +1,30 @@
1
+ /** brainbank reembed — Re-embed all vectors. */
2
+
3
+ import { c } from '@/cli/utils.ts';
4
+ import { createBrain } from '@/cli/factory/index.ts';
5
+
6
+ export async function cmdReembed(): Promise<void> {
7
+ const brain = await createBrain();
8
+ await brain.initialize();
9
+
10
+ console.log(c.bold('\n━━━ BrainBank Re-embed ━━━\n'));
11
+ console.log(c.dim(' Regenerating vectors with current embedding provider...'));
12
+ console.log(c.dim(' Text, FTS, and metadata remain unchanged.\n'));
13
+
14
+ const result = await brain.reembed({
15
+ onProgress: (table: string, current: number, total: number) => {
16
+ process.stdout.write(`\r ${c.cyan(table.padEnd(8))} ${current}/${total}`);
17
+ },
18
+ });
19
+
20
+ console.log('\n');
21
+ for (const [name, count] of Object.entries(result.counts)) {
22
+ if (count > 0) {
23
+ const label = name.charAt(0).toUpperCase() + name.slice(1);
24
+ console.log(` ${c.green('✓')} ${label.padEnd(8)} ${count} vectors`);
25
+ }
26
+ }
27
+ console.log(`\n ${c.bold('Total')}: ${result.total} vectors regenerated\n`);
28
+
29
+ brain.close();
30
+ }