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 +1 -1
- package/package.json +1 -1
- package/src/cli/auto-setup.ts +8 -0
- package/src/cli/bin.ts +19 -5
- package/src/cli/commands/chroma.ts +537 -0
- package/src/cli/commands/serve.ts +6 -0
- package/src/cli/commands/start.ts +42 -0
- package/src/config/defaults.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/memory/chroma/client.ts +8 -4
- package/src/memory/chroma/config.ts +4 -0
- package/src/memory/index.ts +91 -0
- package/src/memory/store.ts +136 -4
- package/src/server/handlers/tools/analyze-decision-evolution.ts +81 -4
- package/src/server/handlers/tools/detect-trends.ts +65 -4
- package/src/server/handlers/tools/find-cross-project-patterns.ts +95 -0
- package/src/server/handlers/tools/get-decision-timeline.ts +92 -9
- package/src/server/handlers/tools/get-episode.ts +11 -1
- package/src/server/handlers/tools/get-recommendations.ts +81 -4
- package/src/server/handlers/tools/list-episodes.ts +11 -1
- package/src/server/handlers/tools/rate-memory.ts +8 -2
- package/src/server/handlers/tools/search-knowledge-graph.ts +14 -1
- package/src/server/handlers/tools/smart-context.ts +23 -1
- package/src/server/handlers/tools/update-progress.ts +18 -13
- package/src/server/handlers/tools/what-if-analysis.ts +63 -4
- package/src/server/services.ts +12 -8
- package/src/setup/wizard.ts +81 -11
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.5.0
|
package/package.json
CHANGED
package/src/cli/auto-setup.ts
CHANGED
|
@@ -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')}
|
|
58
|
-
` ${dimText('claude-brain
|
|
59
|
-
` ${dimText('claude-brain
|
|
60
|
-
` ${dimText('claude-brain
|
|
61
|
-
` ${dimText('claude-brain 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
|
+
}
|
package/src/config/defaults.ts
CHANGED
|
@@ -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.
|
|
6
|
+
serverVersion: '0.5.0',
|
|
7
7
|
logLevel: 'info',
|
|
8
8
|
logFilePath: './logs/claude-brain.log',
|
|
9
9
|
dbPath: './data/memory.db',
|