claude-brain 0.4.0 → 0.5.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.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -8,6 +8,14 @@ function getDefaultEnv(vaultPath: string): string {
8
8
  VAULT_PATH=${vaultPath}
9
9
  LOG_LEVEL=info
10
10
  NODE_ENV=production
11
+
12
+ # ChromaDB Configuration
13
+ # Start ChromaDB server: claude-brain chroma start
14
+ # Install ChromaDB: claude-brain chroma install
15
+ CHROMA_MODE=client-server
16
+ CHROMA_HOST=localhost
17
+ CHROMA_PORT=8000
18
+ CHROMA_EMBEDDING_PROVIDER=transformers
11
19
  `
12
20
  }
13
21
 
package/src/cli/bin.ts CHANGED
@@ -26,11 +26,13 @@ function printHelp() {
26
26
  console.log()
27
27
 
28
28
  const commands = [
29
+ ['start', 'Start ChromaDB + MCP server'],
29
30
  ['serve', 'Start the MCP server (default)'],
30
31
  ['setup', 'Run interactive setup wizard'],
31
32
  ['install', 'Register as MCP server in Claude Code'],
32
33
  ['uninstall', 'Remove MCP server from Claude Code'],
33
34
  ['update', 'Update package and refresh CLAUDE.md'],
35
+ ['chroma', 'Manage ChromaDB server (start/stop/status)'],
34
36
  ['health', 'Run health checks'],
35
37
  ['diagnose', 'Run diagnostics'],
36
38
  ['version', 'Show version'],
@@ -54,11 +56,11 @@ function printHelp() {
54
56
  ` ${theme.primary('-h'.padEnd(12))} ${dimText('Show help')}`,
55
57
  '',
56
58
  theme.bold('Examples:'),
57
- ` ${dimText('claude-brain')} ${dimText('Start MCP server')}`,
58
- ` ${dimText('claude-brain setup')} ${dimText('Configure Claude Brain')}`,
59
- ` ${dimText('claude-brain install')} ${dimText('Register with Claude Code')}`,
60
- ` ${dimText('claude-brain update')} ${dimText('Update to latest version')}`,
61
- ` ${dimText('claude-brain health')} ${dimText('Check system health')}`,
59
+ ` ${dimText('claude-brain start')} ${dimText('Start ChromaDB + MCP server')}`,
60
+ ` ${dimText('claude-brain start --chroma-only')} ${dimText('Start only ChromaDB')}`,
61
+ ` ${dimText('claude-brain setup')} ${dimText('Configure Claude Brain')}`,
62
+ ` ${dimText('claude-brain install')} ${dimText('Register with Claude Code')}`,
63
+ ` ${dimText('claude-brain health')} ${dimText('Check system health')}`,
62
64
  '',
63
65
  theme.bold('Environment:'),
64
66
  ` ${theme.primary('CLAUDE_BRAIN_HOME')} ${dimText('Override home directory (default: ~/.claude-brain/')}`,
@@ -72,6 +74,12 @@ async function main() {
72
74
  const command = process.argv[2] || 'serve'
73
75
 
74
76
  switch (command) {
77
+ case 'start': {
78
+ const { runStart } = await import('./commands/start')
79
+ await runStart()
80
+ break
81
+ }
82
+
75
83
  case 'serve': {
76
84
  const { runServe } = await import('./commands/serve')
77
85
  await runServe()
@@ -102,6 +110,12 @@ async function main() {
102
110
  break
103
111
  }
104
112
 
113
+ case 'chroma': {
114
+ const { runChroma } = await import('./commands/chroma')
115
+ await runChroma()
116
+ break
117
+ }
118
+
105
119
  case 'health': {
106
120
  const { runHealthCheck } = await import('@/health')
107
121
  await runHealthCheck()
@@ -0,0 +1,537 @@
1
+ /**
2
+ * ChromaDB Management Command
3
+ * Start, stop, and manage the ChromaDB server for Claude Brain
4
+ */
5
+
6
+ import { spawn, execSync } from 'node:child_process'
7
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'
8
+ import { join } from 'node:path'
9
+ import { getHomePaths } from '@/config/home'
10
+ import {
11
+ theme, heading, successText, errorText, warningText, dimText,
12
+ infoPanel,
13
+ } from '@/cli/ui/index.js'
14
+
15
+ const PID_FILENAME = 'chroma.pid'
16
+ const DEFAULT_PORT = '8000'
17
+
18
+ function getPidFilePath(): string {
19
+ const paths = getHomePaths()
20
+ return join(paths.data, PID_FILENAME)
21
+ }
22
+
23
+ function getChromaDataPath(): string {
24
+ const paths = getHomePaths()
25
+ return paths.chroma
26
+ }
27
+
28
+ /**
29
+ * Find the chroma binary - checks PATH first, then common pip install locations
30
+ */
31
+ function findChromaBinary(): string | null {
32
+ // Try bare 'chroma' first (on PATH)
33
+ try {
34
+ execSync('chroma --version', { stdio: 'pipe', timeout: 5000 })
35
+ return 'chroma'
36
+ } catch {}
37
+
38
+ // Search common pip install locations
39
+ const { homedir } = require('os')
40
+ const home = homedir()
41
+ const candidates = [
42
+ join(home, 'Library', 'Python', '3.9', 'bin', 'chroma'),
43
+ join(home, 'Library', 'Python', '3.10', 'bin', 'chroma'),
44
+ join(home, 'Library', 'Python', '3.11', 'bin', 'chroma'),
45
+ join(home, 'Library', 'Python', '3.12', 'bin', 'chroma'),
46
+ join(home, 'Library', 'Python', '3.13', 'bin', 'chroma'),
47
+ join(home, '.local', 'bin', 'chroma'),
48
+ '/usr/local/bin/chroma',
49
+ '/opt/homebrew/bin/chroma',
50
+ ]
51
+
52
+ for (const candidate of candidates) {
53
+ try {
54
+ if (existsSync(candidate)) {
55
+ execSync(`"${candidate}" --version`, { stdio: 'pipe', timeout: 5000 })
56
+ return candidate
57
+ }
58
+ } catch {}
59
+ }
60
+
61
+ // Try finding via python -m site
62
+ try {
63
+ const sitePackages = execSync('python3 -c "import site; print(site.getusersitepackages())"', {
64
+ encoding: 'utf-8', stdio: 'pipe', timeout: 5000
65
+ }).trim()
66
+ // User site-packages is like /Users/x/Library/Python/3.9/lib/python/site-packages
67
+ // The bin is at the same level as lib
68
+ const binDir = sitePackages.replace(/\/lib\/.*/, '/bin')
69
+ const chromaPath = join(binDir, 'chroma')
70
+ if (existsSync(chromaPath)) {
71
+ execSync(`"${chromaPath}" --version`, { stdio: 'pipe', timeout: 5000 })
72
+ return chromaPath
73
+ }
74
+ } catch {}
75
+
76
+ return null
77
+ }
78
+
79
+ let _cachedChromaBinary: string | null | undefined = undefined
80
+
81
+ function getChromaBinary(): string | null {
82
+ if (_cachedChromaBinary === undefined) {
83
+ _cachedChromaBinary = findChromaBinary()
84
+ }
85
+ return _cachedChromaBinary
86
+ }
87
+
88
+ function isChromaCliInstalled(): boolean {
89
+ return getChromaBinary() !== null
90
+ }
91
+
92
+ function getChromaVersion(): string {
93
+ const binary = getChromaBinary()
94
+ if (!binary) return 'unknown'
95
+ try {
96
+ return execSync(`"${binary}" --version`, { encoding: 'utf-8', stdio: 'pipe', timeout: 5000 }).trim()
97
+ } catch {
98
+ return 'unknown'
99
+ }
100
+ }
101
+
102
+ function isPythonInstalled(): { installed: boolean; cmd: string } {
103
+ for (const cmd of ['python3', 'python']) {
104
+ try {
105
+ execSync(`${cmd} --version`, { stdio: 'pipe', timeout: 5000 })
106
+ return { installed: true, cmd }
107
+ } catch {
108
+ // try next
109
+ }
110
+ }
111
+ return { installed: false, cmd: 'python3' }
112
+ }
113
+
114
+ function getRunningPid(): number | null {
115
+ const pidPath = getPidFilePath()
116
+ if (!existsSync(pidPath)) return null
117
+
118
+ try {
119
+ const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10)
120
+ if (isNaN(pid)) {
121
+ unlinkSync(pidPath)
122
+ return null
123
+ }
124
+ // Signal 0 tests if process exists without killing it
125
+ process.kill(pid, 0)
126
+ return pid
127
+ } catch {
128
+ // Process not running, clean up stale PID file
129
+ try { unlinkSync(pidPath) } catch {}
130
+ return null
131
+ }
132
+ }
133
+
134
+ function isChromaReachable(): boolean {
135
+ // Try v2 API first (ChromaDB 1.x server), then v1 (older servers)
136
+ for (const apiVersion of ['v2', 'v1']) {
137
+ try {
138
+ execSync(`curl -sf http://localhost:${DEFAULT_PORT}/api/${apiVersion}/heartbeat`, {
139
+ stdio: 'pipe',
140
+ timeout: 3000
141
+ })
142
+ return true
143
+ } catch {}
144
+ }
145
+ return false
146
+ }
147
+
148
+ async function sleep(ms: number): Promise<void> {
149
+ return new Promise(resolve => setTimeout(resolve, ms))
150
+ }
151
+
152
+ // ── Subcommands ───────────────────────────────────────────
153
+
154
+ async function chromaStart(): Promise<void> {
155
+ console.log()
156
+ console.log(heading('Starting ChromaDB Server'))
157
+ console.log()
158
+
159
+ // Check if already running
160
+ const existingPid = getRunningPid()
161
+ if (existingPid) {
162
+ console.log(warningText(`ChromaDB is already running (PID: ${existingPid})`))
163
+ console.log()
164
+ return
165
+ }
166
+
167
+ if (isChromaReachable()) {
168
+ console.log(warningText('A ChromaDB server is already running on port ' + DEFAULT_PORT))
169
+ console.log()
170
+ return
171
+ }
172
+
173
+ // Check if chroma CLI is installed
174
+ const chromaBinary = getChromaBinary()
175
+ if (!chromaBinary) {
176
+ console.log(errorText('ChromaDB CLI is not installed or not found on PATH.'))
177
+ console.log()
178
+ console.log(dimText('Install it with:'))
179
+ console.log(` ${theme.primary('pip install chromadb')}`)
180
+ console.log()
181
+ console.log(dimText('Or run:'))
182
+ console.log(` ${theme.primary('claude-brain chroma install')}`)
183
+ console.log()
184
+ process.exit(1)
185
+ }
186
+
187
+ const dataPath = getChromaDataPath()
188
+
189
+ console.log(dimText(`Binary: ${chromaBinary}`))
190
+ console.log(dimText(`Data path: ${dataPath}`))
191
+ console.log(dimText(`Port: ${DEFAULT_PORT}`))
192
+ console.log()
193
+
194
+ // Start ChromaDB server in background
195
+ const child = spawn(chromaBinary, ['run', '--path', dataPath, '--port', DEFAULT_PORT], {
196
+ detached: true,
197
+ stdio: ['ignore', 'pipe', 'pipe'],
198
+ env: { ...process.env }
199
+ })
200
+
201
+ const pid = child.pid
202
+ if (!pid) {
203
+ console.log(errorText('Failed to start ChromaDB server — no PID returned.'))
204
+ console.log()
205
+ process.exit(1)
206
+ }
207
+
208
+ // Detach the child process so it runs independently
209
+ child.unref()
210
+
211
+ // Save PID file
212
+ writeFileSync(getPidFilePath(), String(pid), 'utf-8')
213
+
214
+ // Capture early output for error detection
215
+ let startupOutput = ''
216
+ child.stderr?.on('data', (chunk: Buffer) => {
217
+ startupOutput += chunk.toString()
218
+ })
219
+ child.stdout?.on('data', (chunk: Buffer) => {
220
+ startupOutput += chunk.toString()
221
+ })
222
+
223
+ // Wait up to 15 seconds for server to become reachable
224
+ let ready = false
225
+ for (let i = 0; i < 30; i++) {
226
+ await sleep(500)
227
+ if (isChromaReachable()) {
228
+ ready = true
229
+ break
230
+ }
231
+ // Check if process exited early
232
+ try {
233
+ process.kill(pid, 0)
234
+ } catch {
235
+ break
236
+ }
237
+ }
238
+
239
+ if (ready) {
240
+ console.log(successText(`ChromaDB server started (PID: ${pid})`))
241
+ console.log()
242
+ console.log(dimText('The server is running in the background.'))
243
+ console.log(dimText('Stop it with: ') + theme.primary('claude-brain chroma stop'))
244
+ } else {
245
+ // Check if process is still alive
246
+ let alive = false
247
+ try {
248
+ process.kill(pid, 0)
249
+ alive = true
250
+ } catch {}
251
+
252
+ if (alive) {
253
+ console.log(warningText('ChromaDB server started but not yet responding.'))
254
+ console.log(dimText(`PID: ${pid} — it may still be initializing.`))
255
+ console.log(dimText('Check with: ') + theme.primary('claude-brain chroma status'))
256
+ } else {
257
+ console.log(errorText('ChromaDB server failed to start.'))
258
+ if (startupOutput) {
259
+ console.log(dimText('Output:'))
260
+ console.log(dimText(startupOutput.slice(0, 500)))
261
+ }
262
+ try { unlinkSync(getPidFilePath()) } catch {}
263
+ process.exit(1)
264
+ }
265
+ }
266
+ console.log()
267
+ }
268
+
269
+ async function chromaStop(): Promise<void> {
270
+ console.log()
271
+ console.log(heading('Stopping ChromaDB Server'))
272
+ console.log()
273
+
274
+ const pid = getRunningPid()
275
+ if (!pid) {
276
+ if (isChromaReachable()) {
277
+ console.log(warningText('A ChromaDB server is running on port ' + DEFAULT_PORT + ' but was not started by claude-brain.'))
278
+ console.log(dimText('Kill it manually if needed.'))
279
+ } else {
280
+ console.log(dimText('No ChromaDB server is currently running.'))
281
+ }
282
+ console.log()
283
+ return
284
+ }
285
+
286
+ try {
287
+ process.kill(pid, 'SIGTERM')
288
+
289
+ // Wait up to 5 seconds for graceful shutdown
290
+ for (let i = 0; i < 10; i++) {
291
+ await sleep(500)
292
+ try {
293
+ process.kill(pid, 0)
294
+ } catch {
295
+ // Process exited
296
+ break
297
+ }
298
+ }
299
+
300
+ // Force kill if still running
301
+ try {
302
+ process.kill(pid, 0)
303
+ // Still alive, force kill
304
+ process.kill(pid, 'SIGKILL')
305
+ } catch {
306
+ // Already dead, good
307
+ }
308
+
309
+ try { unlinkSync(getPidFilePath()) } catch {}
310
+
311
+ console.log(successText(`ChromaDB server stopped (PID: ${pid})`))
312
+ } catch (error) {
313
+ console.log(errorText(`Failed to stop ChromaDB: ${error instanceof Error ? error.message : String(error)}`))
314
+ try { unlinkSync(getPidFilePath()) } catch {}
315
+ }
316
+ console.log()
317
+ }
318
+
319
+ async function chromaStatus(): Promise<void> {
320
+ console.log()
321
+
322
+ const pid = getRunningPid()
323
+ const reachable = isChromaReachable()
324
+ const installed = isChromaCliInstalled()
325
+ const version = installed ? getChromaVersion() : 'N/A'
326
+ const dataPath = getChromaDataPath()
327
+ const dataExists = existsSync(dataPath)
328
+
329
+ const items: Record<string, string> = {
330
+ 'Installed': installed ? `Yes (${version})` : 'No',
331
+ 'Server': reachable ? `Running (port ${DEFAULT_PORT})` : 'Not running',
332
+ 'Managed PID': pid ? String(pid) : 'None',
333
+ 'Data Path': dataPath,
334
+ 'Data Exists': dataExists ? 'Yes' : 'No',
335
+ }
336
+
337
+ console.log(infoPanel('ChromaDB Status', items))
338
+
339
+ if (!installed) {
340
+ console.log()
341
+ console.log(warningText('ChromaDB is not installed.'))
342
+ console.log(dimText('Install with: ') + theme.primary('claude-brain chroma install'))
343
+ } else if (!reachable) {
344
+ console.log()
345
+ console.log(dimText('Start the server with: ') + theme.primary('claude-brain chroma start'))
346
+ }
347
+ console.log()
348
+ }
349
+
350
+ async function chromaInstall(): Promise<void> {
351
+ console.log()
352
+ console.log(heading('Installing ChromaDB'))
353
+ console.log()
354
+
355
+ if (isChromaCliInstalled()) {
356
+ const version = getChromaVersion()
357
+ console.log(successText(`ChromaDB is already installed (${version}).`))
358
+ console.log()
359
+ return
360
+ }
361
+
362
+ const python = isPythonInstalled()
363
+ if (!python.installed) {
364
+ console.log(errorText('Python 3 is required to install ChromaDB.'))
365
+ console.log()
366
+ console.log(dimText('Install Python 3 first:'))
367
+ console.log(` ${theme.primary('macOS:')} brew install python3`)
368
+ console.log(` ${theme.primary('Ubuntu:')} sudo apt install python3 python3-pip`)
369
+ console.log(` ${theme.primary('Windows:')} https://python.org/downloads`)
370
+ console.log()
371
+ process.exit(1)
372
+ }
373
+
374
+ console.log(dimText(`Found ${python.cmd}. Installing chromadb via pip...`))
375
+ console.log()
376
+
377
+ const pipCommands = python.cmd === 'python3'
378
+ ? ['pip3 install chromadb', 'python3 -m pip install chromadb']
379
+ : ['pip install chromadb', 'python -m pip install chromadb']
380
+
381
+ let installed = false
382
+ for (const cmd of pipCommands) {
383
+ try {
384
+ execSync(cmd, { stdio: 'inherit', timeout: 300_000 })
385
+ installed = true
386
+ break
387
+ } catch {
388
+ // Try next command
389
+ }
390
+ }
391
+
392
+ console.log()
393
+ if (installed) {
394
+ console.log(successText('ChromaDB installed successfully!'))
395
+ console.log()
396
+ console.log(dimText('Start the server with: ') + theme.primary('claude-brain chroma start'))
397
+ } else {
398
+ console.log(errorText('Failed to install ChromaDB.'))
399
+ console.log(dimText('Try installing manually:'))
400
+ console.log(` ${theme.primary('pip install chromadb')}`)
401
+ process.exit(1)
402
+ }
403
+ console.log()
404
+ }
405
+
406
+ // ── Help ──────────────────────────────────────────────────
407
+
408
+ function printChromaHelp(): void {
409
+ console.log()
410
+ console.log(heading('ChromaDB Management'))
411
+ console.log()
412
+ console.log(dimText('ChromaDB provides vector storage for semantic search,'))
413
+ console.log(dimText('knowledge graph, and advanced intelligence features.'))
414
+ console.log()
415
+
416
+ const commands = [
417
+ ['start', 'Start ChromaDB server in background'],
418
+ ['stop', 'Stop the running ChromaDB server'],
419
+ ['status', 'Show ChromaDB server status'],
420
+ ['install', 'Install ChromaDB (requires Python 3)'],
421
+ ]
422
+
423
+ const cmdLines = commands
424
+ .map(([cmd, desc]) => ` ${theme.primary(cmd!.padEnd(12))} ${dimText(desc!)}`)
425
+ .join('\n')
426
+
427
+ console.log(theme.bold('Commands:'))
428
+ console.log(cmdLines)
429
+ console.log()
430
+
431
+ console.log(theme.bold('Usage:'))
432
+ console.log(` ${dimText('claude-brain chroma install')} ${dimText('Install ChromaDB')}`)
433
+ console.log(` ${dimText('claude-brain chroma start')} ${dimText('Start the server')}`)
434
+ console.log(` ${dimText('claude-brain chroma status')} ${dimText('Check if running')}`)
435
+ console.log(` ${dimText('claude-brain chroma stop')} ${dimText('Stop the server')}`)
436
+ console.log()
437
+ }
438
+
439
+ // ── Auto-start for serve command ──────────────────────────
440
+
441
+ /**
442
+ * Ensures ChromaDB is running before the MCP server starts.
443
+ * Returns true if ChromaDB is reachable after this call, false otherwise.
444
+ * Designed to be called from serve.ts — does not call process.exit().
445
+ */
446
+ export async function ensureChromaRunning(options?: { silent?: boolean }): Promise<boolean> {
447
+ const log = options?.silent ? () => {} : console.error.bind(console)
448
+
449
+ // Already running? Great.
450
+ if (isChromaReachable()) {
451
+ return true
452
+ }
453
+
454
+ // Already managed by us but not responding? Clean up stale PID.
455
+ const stalePid = getRunningPid()
456
+ if (stalePid) {
457
+ try {
458
+ process.kill(stalePid, 'SIGTERM')
459
+ await sleep(1000)
460
+ } catch {}
461
+ try { unlinkSync(getPidFilePath()) } catch {}
462
+ }
463
+
464
+ const chromaBinary = getChromaBinary()
465
+ if (!chromaBinary) {
466
+ log('[ChromaDB] Not installed — running with SQLite fallback.')
467
+ log('[ChromaDB] Install with: claude-brain chroma install')
468
+ return false
469
+ }
470
+
471
+ const dataPath = getChromaDataPath()
472
+ log(`[ChromaDB] Starting server (port ${DEFAULT_PORT})...`)
473
+
474
+ const child = spawn(chromaBinary, ['run', '--path', dataPath, '--port', DEFAULT_PORT], {
475
+ detached: true,
476
+ stdio: ['ignore', 'pipe', 'pipe'],
477
+ env: { ...process.env }
478
+ })
479
+
480
+ const pid = child.pid
481
+ if (!pid) {
482
+ log('[ChromaDB] Failed to spawn server process.')
483
+ return false
484
+ }
485
+
486
+ child.unref()
487
+ writeFileSync(getPidFilePath(), String(pid), 'utf-8')
488
+
489
+ // Wait for server to become reachable (up to 15 seconds)
490
+ for (let i = 0; i < 30; i++) {
491
+ await sleep(500)
492
+ if (isChromaReachable()) {
493
+ log(`[ChromaDB] Server started (PID: ${pid})`)
494
+ return true
495
+ }
496
+ // Check if process died
497
+ try {
498
+ process.kill(pid, 0)
499
+ } catch {
500
+ break
501
+ }
502
+ }
503
+
504
+ log('[ChromaDB] Server started but not yet responding — will retry connection during initialization.')
505
+ return isChromaReachable()
506
+ }
507
+
508
+ // ── Entry Point ───────────────────────────────────────────
509
+
510
+ export async function runChroma(): Promise<void> {
511
+ const subcommand = process.argv[3] || 'help'
512
+
513
+ switch (subcommand) {
514
+ case 'start':
515
+ await chromaStart()
516
+ break
517
+ case 'stop':
518
+ await chromaStop()
519
+ break
520
+ case 'status':
521
+ await chromaStatus()
522
+ break
523
+ case 'install':
524
+ await chromaInstall()
525
+ break
526
+ case 'help':
527
+ case '--help':
528
+ case '-h':
529
+ printChromaHelp()
530
+ break
531
+ default:
532
+ console.log()
533
+ console.log(errorText(`Unknown subcommand: ${subcommand}`))
534
+ printChromaHelp()
535
+ process.exit(1)
536
+ }
537
+ }
@@ -5,6 +5,7 @@ import { ClaudeBrainMCPServer } from '@/server'
5
5
  import { initializeServices, shutdownServices, getVaultService, getMemoryService } from '@/server/services'
6
6
  import { createOrchestrator, type Orchestrator } from '@/orchestrator'
7
7
  import { ensureHomeDirectory } from '@/cli/auto-setup'
8
+ import { ensureChromaRunning } from '@/cli/commands/chroma'
8
9
 
9
10
  const BANNER = `
10
11
  ╔═══════════════════════════════════════════════════════╗
@@ -42,6 +43,11 @@ export async function runServe() {
42
43
  cacheSize: config.cacheSize
43
44
  }, 'Configuration loaded')
44
45
 
46
+ // Auto-start ChromaDB if not already running
47
+ mainLogger.info('Ensuring ChromaDB is available...')
48
+ const chromaReady = await ensureChromaRunning({ silent: process.env.NODE_ENV === 'production' })
49
+ mainLogger.info({ chromaReady }, chromaReady ? 'ChromaDB is ready' : 'ChromaDB not available, using SQLite fallback')
50
+
45
51
  mainLogger.info('Initializing services...')
46
52
  await initializeServices(config, logger)
47
53
  mainLogger.info('Services initialized successfully')
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Start Command
3
+ * Starts ChromaDB + MCP server together, or just ChromaDB with --chroma-only
4
+ *
5
+ * Usage:
6
+ * claude-brain start Start ChromaDB + MCP server
7
+ * claude-brain start --chroma-only Start only ChromaDB server
8
+ */
9
+
10
+ import {
11
+ heading, successText, warningText, dimText,
12
+ } from '@/cli/ui/index.js'
13
+ import { ensureChromaRunning } from '@/cli/commands/chroma'
14
+
15
+ export async function runStart(): Promise<void> {
16
+ const chromaOnly = process.argv.includes('--chroma-only')
17
+
18
+ if (chromaOnly) {
19
+ console.log()
20
+ console.log(heading('Starting ChromaDB'))
21
+ console.log()
22
+
23
+ const chromaReady = await ensureChromaRunning()
24
+ console.log()
25
+
26
+ if (chromaReady) {
27
+ console.log(successText('ChromaDB is running and ready.'))
28
+ } else {
29
+ console.log(warningText('ChromaDB could not be started. Check installation with: claude-brain chroma status'))
30
+ }
31
+ console.log()
32
+ return
33
+ }
34
+
35
+ // Full start: ChromaDB + MCP server
36
+ // serve.ts already calls ensureChromaRunning(), so just delegate
37
+ console.error(dimText('Starting ChromaDB + MCP server...'))
38
+ console.error()
39
+
40
+ const { runServe } = await import('./serve')
41
+ await runServe()
42
+ }
@@ -3,7 +3,7 @@ import type { PartialConfig } from './schema'
3
3
  /** Default configuration values for Claude Brain */
4
4
  export const defaultConfig: PartialConfig = {
5
5
  serverName: 'claude-brain',
6
- serverVersion: '0.4.0',
6
+ serverVersion: '0.5.0',
7
7
  logLevel: 'info',
8
8
  logFilePath: './logs/claude-brain.log',
9
9
  dbPath: './data/memory.db',