brainbank 0.9.0 → 0.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainbank",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Pluggable semantic memory for AI agents — hybrid search (vector + BM25) in a single SQLite file. Built-in code, git, and docs indexers. Bring your own.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,11 +36,13 @@ export function showHelp(): void {
36
36
  console.log(` ${c.cyan('reembed')} Re-embed all vectors`);
37
37
  console.log(` ${c.cyan('watch')} Watch files, auto-re-index`);
38
38
  console.log(` ${c.cyan('mcp')} Start MCP server (stdio)`);
39
+ console.log(` ${c.cyan('mcp:export')} [target] Export MCP config (antigravity)`);
39
40
  console.log(` ${c.cyan('daemon')} Start HTTP daemon (foreground)`);
40
41
  console.log(` ${c.cyan('daemon start')} Start HTTP daemon (background)`);
41
42
  console.log(` ${c.cyan('daemon stop')} Stop background daemon`);
42
43
  console.log(` ${c.cyan('daemon restart')} Restart background daemon`);
43
44
  console.log(` ${c.cyan('status')} Show daemon status`);
45
+ console.log(` ${c.cyan('--version')} ${c.dim('(-v)')} Show version`);
44
46
  console.log('');
45
47
  console.log(c.bold('Options:'));
46
48
  console.log(` ${c.dim('--repo <path>')} Repository path (default: .)`);
@@ -69,4 +71,5 @@ export function showHelp(): void {
69
71
  console.log(c.dim(' brainbank context "auth flow" | pbcopy # → clipboard'));
70
72
  console.log(c.dim(' brainbank daemon start # background HTTP'));
71
73
  console.log(c.dim(' brainbank mcp # MCP stdio'));
74
+ console.log(c.dim(' brainbank mcp:export antigravity # export MCP config'));
72
75
  }
@@ -12,6 +12,7 @@ import * as path from 'node:path';
12
12
  import { c, args, getFlag, hasFlag, stripFlags } from '@/cli/utils.ts';
13
13
  import { createBrain, getConfig, registerConfigCollections } from '@/cli/factory/index.ts';
14
14
  import { findDocsPlugin } from '@/cli/utils.ts';
15
+ import { autoExportMcp } from './mcp-export.ts';
15
16
  import { scanRepo } from './scan.ts';
16
17
 
17
18
  export async function cmdIndex(): Promise<void> {
@@ -126,6 +127,9 @@ export async function cmdIndex(): Promise<void> {
126
127
  }
127
128
 
128
129
  brain.close();
130
+
131
+ // Auto-export MCP config to Antigravity if detected and not already configured
132
+ await autoExportMcp(repoPath);
129
133
  }
130
134
 
131
135
 
@@ -220,7 +224,7 @@ function capitalizeFirst(s: string): string {
220
224
 
221
225
  /** Generate .brainbank/config.json from the selected modules. */
222
226
  async function saveConfig(repoPath: string, modules: string[]): Promise<void> {
223
- const { select } = await import('@inquirer/prompts');
227
+ const { select, confirm } = await import('@inquirer/prompts');
224
228
 
225
229
  // Embedding provider selection
226
230
  const envEmbedding = process.env.BRAINBANK_EMBEDDING;
@@ -252,15 +256,15 @@ async function saveConfig(repoPath: string, modules: string[]): Promise<void> {
252
256
  message: 'Noise pruner:',
253
257
  choices: [
254
258
  {
255
- name: 'none no pruning (default)',
256
- value: 'none',
259
+ name: 'haiku AI-powered noise filter (recommended)',
260
+ value: 'haiku',
257
261
  },
258
262
  {
259
- name: 'haiku AI-powered noise filter (Claude Haiku)',
260
- value: 'haiku',
263
+ name: 'none no pruning',
264
+ value: 'none',
261
265
  },
262
266
  ],
263
- default: 'none',
267
+ default: 'haiku',
264
268
  });
265
269
 
266
270
  const configDir = path.join(repoPath, '.brainbank');
@@ -275,6 +279,33 @@ async function saveConfig(repoPath: string, modules: string[]): Promise<void> {
275
279
  config.pruner = pruner;
276
280
  }
277
281
 
282
+ // Key management: detect available keys and offer to save
283
+ const detectedKeys: Record<string, string> = {};
284
+ const needsPerplexity = embedding.startsWith('perplexity');
285
+ const needsAnthropic = pruner === 'haiku';
286
+ const needsOpenai = embedding === 'openai';
287
+
288
+ if (needsPerplexity && process.env.PERPLEXITY_API_KEY) {
289
+ detectedKeys.perplexity = process.env.PERPLEXITY_API_KEY;
290
+ }
291
+ if (needsAnthropic && process.env.ANTHROPIC_API_KEY) {
292
+ detectedKeys.anthropic = process.env.ANTHROPIC_API_KEY;
293
+ }
294
+ if (needsOpenai && process.env.OPENAI_API_KEY) {
295
+ detectedKeys.openai = process.env.OPENAI_API_KEY;
296
+ }
297
+
298
+ if (Object.keys(detectedKeys).length > 0) {
299
+ const keyNames = Object.keys(detectedKeys).join(', ');
300
+ const saveKeys = await confirm({
301
+ message: `Save API keys (${keyNames}) to config.json? (portable, no env vars needed)`,
302
+ default: true,
303
+ });
304
+ if (saveKeys) {
305
+ config.keys = detectedKeys;
306
+ }
307
+ }
308
+
278
309
  fs.mkdirSync(configDir, { recursive: true });
279
310
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
280
311
  console.log(c.green(` ✓ Saved ${path.relative(process.cwd(), configPath)}`));
@@ -0,0 +1,163 @@
1
+ /**
2
+ * brainbank mcp:export [target] — Export MCP server config for AI IDEs.
3
+ *
4
+ * Generates the MCP server config block for brainbank and merges it into
5
+ * the target IDE's config file. Currently supports: antigravity.
6
+ *
7
+ * Detects: node path, cli.js path, API keys from config or env vars.
8
+ */
9
+
10
+ import type { ProjectConfig } from '@/cli/factory/config-loader.ts';
11
+
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { c, args, getFlag } from '@/cli/utils.ts';
16
+ import { getConfig } from '@/cli/factory/index.ts';
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = path.dirname(__filename);
20
+
21
+ /** Supported export targets and their config file paths. */
22
+ const TARGETS: Record<string, { configPath: string; label: string }> = {
23
+ antigravity: {
24
+ configPath: path.join(process.env.HOME ?? '~', '.gemini', 'antigravity', 'mcp_config.json'),
25
+ label: 'Gemini Antigravity',
26
+ },
27
+ };
28
+
29
+ interface McpServerConfig {
30
+ command: string;
31
+ args: string[];
32
+ env?: Record<string, string>;
33
+ cwd?: string;
34
+ }
35
+
36
+ interface McpConfig {
37
+ mcpServers: Record<string, McpServerConfig>;
38
+ }
39
+
40
+ /**
41
+ * Build the brainbank MCP server config block.
42
+ * Resolves node binary, dist/cli.js path, and API keys.
43
+ */
44
+ function buildBrainbankMcpBlock(config: ProjectConfig | null): McpServerConfig {
45
+ const nodeBin = process.execPath;
46
+
47
+ // Resolve dist/cli.js from the global install location (node_prefix/lib/node_modules/brainbank/dist/cli.js)
48
+ const globalCliJs = path.join(path.dirname(nodeBin), '..', 'lib', 'node_modules', 'brainbank', 'dist', 'cli.js');
49
+ // Fallback: relative to this file (dev / npm link)
50
+ const localCliJs = path.resolve(__dirname, '..', '..', 'dist', 'cli.js');
51
+ const resolvedCliJs = fs.existsSync(globalCliJs) ? globalCliJs : localCliJs;
52
+
53
+ const env: Record<string, string> = {};
54
+
55
+ // Resolve API keys: config.keys > env vars
56
+ const keys = config?.keys;
57
+ const perplexityKey = keys?.perplexity ?? process.env.PERPLEXITY_API_KEY;
58
+ const anthropicKey = keys?.anthropic ?? process.env.ANTHROPIC_API_KEY;
59
+ const openaiKey = keys?.openai ?? process.env.OPENAI_API_KEY;
60
+
61
+ if (perplexityKey) env.PERPLEXITY_API_KEY = perplexityKey;
62
+ if (anthropicKey) env.ANTHROPIC_API_KEY = anthropicKey;
63
+ if (openaiKey) env.OPENAI_API_KEY = openaiKey;
64
+
65
+ const block: McpServerConfig = {
66
+ command: nodeBin,
67
+ args: [resolvedCliJs, 'mcp'],
68
+ };
69
+
70
+ if (Object.keys(env).length > 0) {
71
+ block.env = env;
72
+ }
73
+
74
+ return block;
75
+ }
76
+
77
+ /**
78
+ * Load existing MCP config, merge brainbank entry, and write back.
79
+ * Preserves all other server entries.
80
+ */
81
+ function mergeAndWrite(targetPath: string, block: McpServerConfig): { created: boolean } {
82
+ let existing: McpConfig = { mcpServers: {} };
83
+ const created = !fs.existsSync(targetPath);
84
+
85
+ if (!created) {
86
+ try {
87
+ const raw = fs.readFileSync(targetPath, 'utf-8');
88
+ existing = JSON.parse(raw) as McpConfig;
89
+ if (!existing.mcpServers) existing.mcpServers = {};
90
+ } catch {
91
+ // Corrupt or empty file — start fresh
92
+ existing = { mcpServers: {} };
93
+ }
94
+ }
95
+
96
+ existing.mcpServers.brainbank = block;
97
+
98
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
99
+ fs.writeFileSync(targetPath, JSON.stringify(existing, null, 2) + '\n');
100
+
101
+ return { created };
102
+ }
103
+
104
+ /** Check if an MCP config already has a brainbank entry. */
105
+ export function hasBrainbankMcpEntry(targetPath: string): boolean {
106
+ if (!fs.existsSync(targetPath)) return false;
107
+ try {
108
+ const raw = fs.readFileSync(targetPath, 'utf-8');
109
+ const config = JSON.parse(raw) as McpConfig;
110
+ return !!config.mcpServers?.brainbank;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ /** Auto-export: called after index when Antigravity is detected. */
117
+ export async function autoExportMcp(repoPath: string): Promise<void> {
118
+ const target = TARGETS.antigravity;
119
+ if (!target) return;
120
+
121
+ // Only auto-export if Antigravity dir exists
122
+ const antigravityDir = path.dirname(target.configPath);
123
+ if (!fs.existsSync(antigravityDir)) return;
124
+
125
+ // Only auto-export if brainbank isn't already configured
126
+ if (hasBrainbankMcpEntry(target.configPath)) return;
127
+
128
+ const config = await getConfig(repoPath);
129
+ const block = buildBrainbankMcpBlock(config);
130
+ mergeAndWrite(target.configPath, block);
131
+ console.log(` ${c.green('✓')} Exported MCP config to ${c.dim(path.relative(process.env.HOME ?? '', target.configPath))}`);
132
+ }
133
+
134
+ /** CLI command: brainbank mcp:export [target] */
135
+ export async function cmdMcpExport(): Promise<void> {
136
+ const targetName = args[1] || getFlag('target') || 'antigravity';
137
+ const repoPath = getFlag('repo') || '.';
138
+
139
+ const target = TARGETS[targetName];
140
+ if (!target) {
141
+ console.error(c.red(`Unknown export target: ${targetName}`));
142
+ console.error(c.dim(` Available: ${Object.keys(TARGETS).join(', ')}`));
143
+ process.exit(1);
144
+ }
145
+
146
+ const config = await getConfig(repoPath);
147
+ const block = buildBrainbankMcpBlock(config);
148
+ const { created } = mergeAndWrite(target.configPath, block);
149
+
150
+ console.log(c.bold(`\n━━━ MCP Export: ${target.label} ━━━\n`));
151
+ console.log(` ${c.green('✓')} ${created ? 'Created' : 'Updated'} ${c.dim(target.configPath)}`);
152
+ console.log(` ${c.dim('Node:')} ${block.command}`);
153
+ console.log(` ${c.dim('CLI:')} ${block.args[0]}`);
154
+
155
+ const envKeys = block.env ? Object.keys(block.env) : [];
156
+ if (envKeys.length > 0) {
157
+ console.log(` ${c.dim('Keys:')} ${envKeys.join(', ')}`);
158
+ } else {
159
+ console.log(` ${c.yellow('⚠')} No API keys found. Set env vars or add keys to .brainbank/config.json`);
160
+ }
161
+
162
+ console.log(`\n ${c.dim('Restart your IDE to apply changes.')}\n`);
163
+ }
package/src/cli/index.ts CHANGED
@@ -18,14 +18,20 @@ import { cmdStats } from './commands/stats.ts';
18
18
  import { cmdReembed } from './commands/reembed.ts';
19
19
  import { cmdWatch } from './commands/watch.ts';
20
20
  import { cmdMcp } from './commands/mcp.ts';
21
+ import { cmdMcpExport } from './commands/mcp-export.ts';
21
22
  import { cmdDaemon } from './commands/daemon.ts';
22
23
  import { cmdStatus } from './commands/status.ts';
23
24
  import { showHelp } from './commands/help.ts';
25
+ import { VERSION } from '@/constants.ts';
24
26
 
25
27
  const command = args[0];
26
28
 
27
29
  async function main(): Promise<void> {
28
30
  switch (command) {
31
+ case '--version':
32
+ case '-v':
33
+ console.log(`brainbank v${VERSION}`);
34
+ break;
29
35
  case 'i':
30
36
  case 'index': return cmdIndex();
31
37
  case 'collection': return cmdCollection();
@@ -41,6 +47,7 @@ async function main(): Promise<void> {
41
47
  case 'reembed': return cmdReembed();
42
48
  case 'watch': return cmdWatch();
43
49
  case 'mcp': return cmdMcp();
50
+ case 'mcp:export': return cmdMcpExport();
44
51
  case 'serve': return cmdMcp(); // backward compat
45
52
  case 'daemon': return cmdDaemon();
46
53
  case 'status': return cmdStatus();
package/src/constants.ts CHANGED
@@ -5,6 +5,14 @@
5
5
  * to their respective packages. Only keys owned by the core live here.
6
6
  */
7
7
 
8
+ import { createRequire } from 'node:module';
9
+
10
+ const require = createRequire(import.meta.url);
11
+ const pkg = require('../package.json') as { version: string };
12
+
13
+ /** Package version from package.json. */
14
+ export const VERSION: string = pkg.version;
15
+
8
16
  /** HNSW index key for KV collections (core-owned). */
9
17
  export const HNSW = {
10
18
  KV: 'kv',