claude-brain 0.4.0 → 0.4.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/VERSION +1 -1
- package/package.json +1 -1
- package/src/cli/auto-setup.ts +8 -0
- package/src/cli/bin.ts +8 -0
- package/src/cli/commands/chroma.ts +407 -0
- package/src/config/defaults.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/memory/chroma/client.ts +3 -3
- package/src/memory/chroma/config.ts +4 -0
- package/src/server/handlers/tools/analyze-decision-evolution.ts +11 -4
- package/src/server/handlers/tools/detect-trends.ts +11 -4
- package/src/server/handlers/tools/find-cross-project-patterns.ts +10 -0
- package/src/server/handlers/tools/get-decision-timeline.ts +11 -4
- package/src/server/handlers/tools/get-recommendations.ts +11 -4
- package/src/server/handlers/tools/search-knowledge-graph.ts +14 -1
- package/src/server/handlers/tools/what-if-analysis.ts +12 -4
- package/src/server/services.ts +12 -8
- package/src/setup/wizard.ts +81 -11
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.4.
|
|
1
|
+
0.4.1
|
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
|
@@ -31,6 +31,7 @@ function printHelp() {
|
|
|
31
31
|
['install', 'Register as MCP server in Claude Code'],
|
|
32
32
|
['uninstall', 'Remove MCP server from Claude Code'],
|
|
33
33
|
['update', 'Update package and refresh CLAUDE.md'],
|
|
34
|
+
['chroma', 'Manage ChromaDB server (start/stop/status)'],
|
|
34
35
|
['health', 'Run health checks'],
|
|
35
36
|
['diagnose', 'Run diagnostics'],
|
|
36
37
|
['version', 'Show version'],
|
|
@@ -58,6 +59,7 @@ function printHelp() {
|
|
|
58
59
|
` ${dimText('claude-brain setup')} ${dimText('Configure Claude Brain')}`,
|
|
59
60
|
` ${dimText('claude-brain install')} ${dimText('Register with Claude Code')}`,
|
|
60
61
|
` ${dimText('claude-brain update')} ${dimText('Update to latest version')}`,
|
|
62
|
+
` ${dimText('claude-brain chroma start')} ${dimText('Start ChromaDB server')}`,
|
|
61
63
|
` ${dimText('claude-brain health')} ${dimText('Check system health')}`,
|
|
62
64
|
'',
|
|
63
65
|
theme.bold('Environment:'),
|
|
@@ -102,6 +104,12 @@ async function main() {
|
|
|
102
104
|
break
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
case 'chroma': {
|
|
108
|
+
const { runChroma } = await import('./commands/chroma')
|
|
109
|
+
await runChroma()
|
|
110
|
+
break
|
|
111
|
+
}
|
|
112
|
+
|
|
105
113
|
case 'health': {
|
|
106
114
|
const { runHealthCheck } = await import('@/health')
|
|
107
115
|
await runHealthCheck()
|
|
@@ -0,0 +1,407 @@
|
|
|
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
|
+
function isChromaCliInstalled(): boolean {
|
|
29
|
+
try {
|
|
30
|
+
execSync('chroma --version', { stdio: 'pipe', timeout: 5000 })
|
|
31
|
+
return true
|
|
32
|
+
} catch {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getChromaVersion(): string {
|
|
38
|
+
try {
|
|
39
|
+
return execSync('chroma --version', { encoding: 'utf-8', stdio: 'pipe', timeout: 5000 }).trim()
|
|
40
|
+
} catch {
|
|
41
|
+
return 'unknown'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isPythonInstalled(): { installed: boolean; cmd: string } {
|
|
46
|
+
for (const cmd of ['python3', 'python']) {
|
|
47
|
+
try {
|
|
48
|
+
execSync(`${cmd} --version`, { stdio: 'pipe', timeout: 5000 })
|
|
49
|
+
return { installed: true, cmd }
|
|
50
|
+
} catch {
|
|
51
|
+
// try next
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { installed: false, cmd: 'python3' }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getRunningPid(): number | null {
|
|
58
|
+
const pidPath = getPidFilePath()
|
|
59
|
+
if (!existsSync(pidPath)) return null
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10)
|
|
63
|
+
if (isNaN(pid)) {
|
|
64
|
+
unlinkSync(pidPath)
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
// Signal 0 tests if process exists without killing it
|
|
68
|
+
process.kill(pid, 0)
|
|
69
|
+
return pid
|
|
70
|
+
} catch {
|
|
71
|
+
// Process not running, clean up stale PID file
|
|
72
|
+
try { unlinkSync(pidPath) } catch {}
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isChromaReachable(): boolean {
|
|
78
|
+
try {
|
|
79
|
+
execSync(`curl -sf http://localhost:${DEFAULT_PORT}/api/v1/heartbeat`, {
|
|
80
|
+
stdio: 'pipe',
|
|
81
|
+
timeout: 3000
|
|
82
|
+
})
|
|
83
|
+
return true
|
|
84
|
+
} catch {
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function sleep(ms: number): Promise<void> {
|
|
90
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Subcommands ───────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
async function chromaStart(): Promise<void> {
|
|
96
|
+
console.log()
|
|
97
|
+
console.log(heading('Starting ChromaDB Server'))
|
|
98
|
+
console.log()
|
|
99
|
+
|
|
100
|
+
// Check if already running
|
|
101
|
+
const existingPid = getRunningPid()
|
|
102
|
+
if (existingPid) {
|
|
103
|
+
console.log(warningText(`ChromaDB is already running (PID: ${existingPid})`))
|
|
104
|
+
console.log()
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isChromaReachable()) {
|
|
109
|
+
console.log(warningText('A ChromaDB server is already running on port ' + DEFAULT_PORT))
|
|
110
|
+
console.log()
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check if chroma CLI is installed
|
|
115
|
+
if (!isChromaCliInstalled()) {
|
|
116
|
+
console.log(errorText('ChromaDB CLI is not installed.'))
|
|
117
|
+
console.log()
|
|
118
|
+
console.log(dimText('Install it with:'))
|
|
119
|
+
console.log(` ${theme.primary('pip install chromadb')}`)
|
|
120
|
+
console.log()
|
|
121
|
+
console.log(dimText('Or run:'))
|
|
122
|
+
console.log(` ${theme.primary('claude-brain chroma install')}`)
|
|
123
|
+
console.log()
|
|
124
|
+
process.exit(1)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const dataPath = getChromaDataPath()
|
|
128
|
+
|
|
129
|
+
console.log(dimText(`Data path: ${dataPath}`))
|
|
130
|
+
console.log(dimText(`Port: ${DEFAULT_PORT}`))
|
|
131
|
+
console.log()
|
|
132
|
+
|
|
133
|
+
// Start ChromaDB server in background
|
|
134
|
+
const child = spawn('chroma', ['run', '--path', dataPath, '--port', DEFAULT_PORT], {
|
|
135
|
+
detached: true,
|
|
136
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
137
|
+
env: { ...process.env }
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const pid = child.pid
|
|
141
|
+
if (!pid) {
|
|
142
|
+
console.log(errorText('Failed to start ChromaDB server — no PID returned.'))
|
|
143
|
+
console.log()
|
|
144
|
+
process.exit(1)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Detach the child process so it runs independently
|
|
148
|
+
child.unref()
|
|
149
|
+
|
|
150
|
+
// Save PID file
|
|
151
|
+
writeFileSync(getPidFilePath(), String(pid), 'utf-8')
|
|
152
|
+
|
|
153
|
+
// Capture early output for error detection
|
|
154
|
+
let startupOutput = ''
|
|
155
|
+
child.stderr?.on('data', (chunk: Buffer) => {
|
|
156
|
+
startupOutput += chunk.toString()
|
|
157
|
+
})
|
|
158
|
+
child.stdout?.on('data', (chunk: Buffer) => {
|
|
159
|
+
startupOutput += chunk.toString()
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// Wait up to 15 seconds for server to become reachable
|
|
163
|
+
let ready = false
|
|
164
|
+
for (let i = 0; i < 30; i++) {
|
|
165
|
+
await sleep(500)
|
|
166
|
+
if (isChromaReachable()) {
|
|
167
|
+
ready = true
|
|
168
|
+
break
|
|
169
|
+
}
|
|
170
|
+
// Check if process exited early
|
|
171
|
+
try {
|
|
172
|
+
process.kill(pid, 0)
|
|
173
|
+
} catch {
|
|
174
|
+
break
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (ready) {
|
|
179
|
+
console.log(successText(`ChromaDB server started (PID: ${pid})`))
|
|
180
|
+
console.log()
|
|
181
|
+
console.log(dimText('The server is running in the background.'))
|
|
182
|
+
console.log(dimText('Stop it with: ') + theme.primary('claude-brain chroma stop'))
|
|
183
|
+
} else {
|
|
184
|
+
// Check if process is still alive
|
|
185
|
+
let alive = false
|
|
186
|
+
try {
|
|
187
|
+
process.kill(pid, 0)
|
|
188
|
+
alive = true
|
|
189
|
+
} catch {}
|
|
190
|
+
|
|
191
|
+
if (alive) {
|
|
192
|
+
console.log(warningText('ChromaDB server started but not yet responding.'))
|
|
193
|
+
console.log(dimText(`PID: ${pid} — it may still be initializing.`))
|
|
194
|
+
console.log(dimText('Check with: ') + theme.primary('claude-brain chroma status'))
|
|
195
|
+
} else {
|
|
196
|
+
console.log(errorText('ChromaDB server failed to start.'))
|
|
197
|
+
if (startupOutput) {
|
|
198
|
+
console.log(dimText('Output:'))
|
|
199
|
+
console.log(dimText(startupOutput.slice(0, 500)))
|
|
200
|
+
}
|
|
201
|
+
try { unlinkSync(getPidFilePath()) } catch {}
|
|
202
|
+
process.exit(1)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
console.log()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function chromaStop(): Promise<void> {
|
|
209
|
+
console.log()
|
|
210
|
+
console.log(heading('Stopping ChromaDB Server'))
|
|
211
|
+
console.log()
|
|
212
|
+
|
|
213
|
+
const pid = getRunningPid()
|
|
214
|
+
if (!pid) {
|
|
215
|
+
if (isChromaReachable()) {
|
|
216
|
+
console.log(warningText('A ChromaDB server is running on port ' + DEFAULT_PORT + ' but was not started by claude-brain.'))
|
|
217
|
+
console.log(dimText('Kill it manually if needed.'))
|
|
218
|
+
} else {
|
|
219
|
+
console.log(dimText('No ChromaDB server is currently running.'))
|
|
220
|
+
}
|
|
221
|
+
console.log()
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
process.kill(pid, 'SIGTERM')
|
|
227
|
+
|
|
228
|
+
// Wait up to 5 seconds for graceful shutdown
|
|
229
|
+
for (let i = 0; i < 10; i++) {
|
|
230
|
+
await sleep(500)
|
|
231
|
+
try {
|
|
232
|
+
process.kill(pid, 0)
|
|
233
|
+
} catch {
|
|
234
|
+
// Process exited
|
|
235
|
+
break
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Force kill if still running
|
|
240
|
+
try {
|
|
241
|
+
process.kill(pid, 0)
|
|
242
|
+
// Still alive, force kill
|
|
243
|
+
process.kill(pid, 'SIGKILL')
|
|
244
|
+
} catch {
|
|
245
|
+
// Already dead, good
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try { unlinkSync(getPidFilePath()) } catch {}
|
|
249
|
+
|
|
250
|
+
console.log(successText(`ChromaDB server stopped (PID: ${pid})`))
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.log(errorText(`Failed to stop ChromaDB: ${error instanceof Error ? error.message : String(error)}`))
|
|
253
|
+
try { unlinkSync(getPidFilePath()) } catch {}
|
|
254
|
+
}
|
|
255
|
+
console.log()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function chromaStatus(): Promise<void> {
|
|
259
|
+
console.log()
|
|
260
|
+
|
|
261
|
+
const pid = getRunningPid()
|
|
262
|
+
const reachable = isChromaReachable()
|
|
263
|
+
const installed = isChromaCliInstalled()
|
|
264
|
+
const version = installed ? getChromaVersion() : 'N/A'
|
|
265
|
+
const dataPath = getChromaDataPath()
|
|
266
|
+
const dataExists = existsSync(dataPath)
|
|
267
|
+
|
|
268
|
+
const items: Record<string, string> = {
|
|
269
|
+
'Installed': installed ? `Yes (${version})` : 'No',
|
|
270
|
+
'Server': reachable ? `Running (port ${DEFAULT_PORT})` : 'Not running',
|
|
271
|
+
'Managed PID': pid ? String(pid) : 'None',
|
|
272
|
+
'Data Path': dataPath,
|
|
273
|
+
'Data Exists': dataExists ? 'Yes' : 'No',
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
console.log(infoPanel('ChromaDB Status', items))
|
|
277
|
+
|
|
278
|
+
if (!installed) {
|
|
279
|
+
console.log()
|
|
280
|
+
console.log(warningText('ChromaDB is not installed.'))
|
|
281
|
+
console.log(dimText('Install with: ') + theme.primary('claude-brain chroma install'))
|
|
282
|
+
} else if (!reachable) {
|
|
283
|
+
console.log()
|
|
284
|
+
console.log(dimText('Start the server with: ') + theme.primary('claude-brain chroma start'))
|
|
285
|
+
}
|
|
286
|
+
console.log()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function chromaInstall(): Promise<void> {
|
|
290
|
+
console.log()
|
|
291
|
+
console.log(heading('Installing ChromaDB'))
|
|
292
|
+
console.log()
|
|
293
|
+
|
|
294
|
+
if (isChromaCliInstalled()) {
|
|
295
|
+
const version = getChromaVersion()
|
|
296
|
+
console.log(successText(`ChromaDB is already installed (${version}).`))
|
|
297
|
+
console.log()
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const python = isPythonInstalled()
|
|
302
|
+
if (!python.installed) {
|
|
303
|
+
console.log(errorText('Python 3 is required to install ChromaDB.'))
|
|
304
|
+
console.log()
|
|
305
|
+
console.log(dimText('Install Python 3 first:'))
|
|
306
|
+
console.log(` ${theme.primary('macOS:')} brew install python3`)
|
|
307
|
+
console.log(` ${theme.primary('Ubuntu:')} sudo apt install python3 python3-pip`)
|
|
308
|
+
console.log(` ${theme.primary('Windows:')} https://python.org/downloads`)
|
|
309
|
+
console.log()
|
|
310
|
+
process.exit(1)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
console.log(dimText(`Found ${python.cmd}. Installing chromadb via pip...`))
|
|
314
|
+
console.log()
|
|
315
|
+
|
|
316
|
+
const pipCommands = python.cmd === 'python3'
|
|
317
|
+
? ['pip3 install chromadb', 'python3 -m pip install chromadb']
|
|
318
|
+
: ['pip install chromadb', 'python -m pip install chromadb']
|
|
319
|
+
|
|
320
|
+
let installed = false
|
|
321
|
+
for (const cmd of pipCommands) {
|
|
322
|
+
try {
|
|
323
|
+
execSync(cmd, { stdio: 'inherit', timeout: 300_000 })
|
|
324
|
+
installed = true
|
|
325
|
+
break
|
|
326
|
+
} catch {
|
|
327
|
+
// Try next command
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log()
|
|
332
|
+
if (installed) {
|
|
333
|
+
console.log(successText('ChromaDB installed successfully!'))
|
|
334
|
+
console.log()
|
|
335
|
+
console.log(dimText('Start the server with: ') + theme.primary('claude-brain chroma start'))
|
|
336
|
+
} else {
|
|
337
|
+
console.log(errorText('Failed to install ChromaDB.'))
|
|
338
|
+
console.log(dimText('Try installing manually:'))
|
|
339
|
+
console.log(` ${theme.primary('pip install chromadb')}`)
|
|
340
|
+
process.exit(1)
|
|
341
|
+
}
|
|
342
|
+
console.log()
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ── Help ──────────────────────────────────────────────────
|
|
346
|
+
|
|
347
|
+
function printChromaHelp(): void {
|
|
348
|
+
console.log()
|
|
349
|
+
console.log(heading('ChromaDB Management'))
|
|
350
|
+
console.log()
|
|
351
|
+
console.log(dimText('ChromaDB provides vector storage for semantic search,'))
|
|
352
|
+
console.log(dimText('knowledge graph, and advanced intelligence features.'))
|
|
353
|
+
console.log()
|
|
354
|
+
|
|
355
|
+
const commands = [
|
|
356
|
+
['start', 'Start ChromaDB server in background'],
|
|
357
|
+
['stop', 'Stop the running ChromaDB server'],
|
|
358
|
+
['status', 'Show ChromaDB server status'],
|
|
359
|
+
['install', 'Install ChromaDB (requires Python 3)'],
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
const cmdLines = commands
|
|
363
|
+
.map(([cmd, desc]) => ` ${theme.primary(cmd!.padEnd(12))} ${dimText(desc!)}`)
|
|
364
|
+
.join('\n')
|
|
365
|
+
|
|
366
|
+
console.log(theme.bold('Commands:'))
|
|
367
|
+
console.log(cmdLines)
|
|
368
|
+
console.log()
|
|
369
|
+
|
|
370
|
+
console.log(theme.bold('Usage:'))
|
|
371
|
+
console.log(` ${dimText('claude-brain chroma install')} ${dimText('Install ChromaDB')}`)
|
|
372
|
+
console.log(` ${dimText('claude-brain chroma start')} ${dimText('Start the server')}`)
|
|
373
|
+
console.log(` ${dimText('claude-brain chroma status')} ${dimText('Check if running')}`)
|
|
374
|
+
console.log(` ${dimText('claude-brain chroma stop')} ${dimText('Stop the server')}`)
|
|
375
|
+
console.log()
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ── Entry Point ───────────────────────────────────────────
|
|
379
|
+
|
|
380
|
+
export async function runChroma(): Promise<void> {
|
|
381
|
+
const subcommand = process.argv[3] || 'help'
|
|
382
|
+
|
|
383
|
+
switch (subcommand) {
|
|
384
|
+
case 'start':
|
|
385
|
+
await chromaStart()
|
|
386
|
+
break
|
|
387
|
+
case 'stop':
|
|
388
|
+
await chromaStop()
|
|
389
|
+
break
|
|
390
|
+
case 'status':
|
|
391
|
+
await chromaStatus()
|
|
392
|
+
break
|
|
393
|
+
case 'install':
|
|
394
|
+
await chromaInstall()
|
|
395
|
+
break
|
|
396
|
+
case 'help':
|
|
397
|
+
case '--help':
|
|
398
|
+
case '-h':
|
|
399
|
+
printChromaHelp()
|
|
400
|
+
break
|
|
401
|
+
default:
|
|
402
|
+
console.log()
|
|
403
|
+
console.log(errorText(`Unknown subcommand: ${subcommand}`))
|
|
404
|
+
printChromaHelp()
|
|
405
|
+
process.exit(1)
|
|
406
|
+
}
|
|
407
|
+
}
|
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.4.
|
|
6
|
+
serverVersion: '0.4.1',
|
|
7
7
|
logLevel: 'info',
|
|
8
8
|
logFilePath: './logs/claude-brain.log',
|
|
9
9
|
dbPath: './data/memory.db',
|
package/src/config/schema.ts
CHANGED
|
@@ -196,7 +196,7 @@ export const ConfigSchema = z.object({
|
|
|
196
196
|
serverName: z.string().default('claude-brain'),
|
|
197
197
|
|
|
198
198
|
/** Server version in semver format */
|
|
199
|
-
serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.4.
|
|
199
|
+
serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.4.1'),
|
|
200
200
|
|
|
201
201
|
/** Logging level */
|
|
202
202
|
logLevel: LogLevelSchema.default('info'),
|
|
@@ -36,12 +36,12 @@ export class ChromaClientManager {
|
|
|
36
36
|
break
|
|
37
37
|
|
|
38
38
|
case 'client-server':
|
|
39
|
-
if (!this.config.host
|
|
40
|
-
throw new Error('Host
|
|
39
|
+
if (!this.config.host) {
|
|
40
|
+
throw new Error('Host required for client-server mode')
|
|
41
41
|
}
|
|
42
42
|
this.client = new ChromaClient({
|
|
43
43
|
host: this.config.host,
|
|
44
|
-
port: this.config.port,
|
|
44
|
+
port: this.config.port || 8000,
|
|
45
45
|
...authConfig
|
|
46
46
|
})
|
|
47
47
|
break
|
|
@@ -45,6 +45,10 @@ export function getChromaConfigFromEnv(): Partial<ChromaConfig> {
|
|
|
45
45
|
config.mode = process.env.CHROMA_MODE as any
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
if (process.env.CHROMA_PORT) {
|
|
49
|
+
config.port = parseInt(process.env.CHROMA_PORT, 10)
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
if (process.env.CHROMA_EMBEDDING_PROVIDER) {
|
|
49
53
|
config.embeddingProvider = process.env.CHROMA_EMBEDDING_PROVIDER as any
|
|
50
54
|
}
|
|
@@ -23,6 +23,16 @@ export async function handleAnalyzeDecisionEvolution(
|
|
|
23
23
|
const { topic, project_name, limit } = input
|
|
24
24
|
|
|
25
25
|
const memory = getMemoryService()
|
|
26
|
+
|
|
27
|
+
if (!memory.isChromaDBEnabled()) {
|
|
28
|
+
return ResponseFormatter.text(
|
|
29
|
+
`No decisions found for topic: "${topic}"\n\n` +
|
|
30
|
+
'Note: ChromaDB is not connected. Decision evolution analysis requires ChromaDB for semantic search. ' +
|
|
31
|
+
'Decisions stored via SQLite fallback are available through recall_similar. ' +
|
|
32
|
+
'To enable evolution tracking, start a ChromaDB server or configure persistent mode.'
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
const tracker = new DecisionEvolutionTracker(
|
|
27
37
|
logger,
|
|
28
38
|
memory.chroma.collections,
|
|
@@ -35,10 +45,7 @@ export async function handleAnalyzeDecisionEvolution(
|
|
|
35
45
|
})
|
|
36
46
|
|
|
37
47
|
if (analysis.totalDecisions === 0) {
|
|
38
|
-
|
|
39
|
-
? '\n\nNote: ChromaDB is not connected. Decision evolution analysis requires ChromaDB for semantic search. Start a ChromaDB server or switch to persistent mode.'
|
|
40
|
-
: ''
|
|
41
|
-
return ResponseFormatter.text(`No decisions found for topic: "${topic}"` + chromaNote)
|
|
48
|
+
return ResponseFormatter.text(`No decisions found for topic: "${topic}"`)
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
const parts: string[] = []
|
|
@@ -23,6 +23,16 @@ export async function handleDetectTrends(
|
|
|
23
23
|
const { project_name, period_days, min_occurrences, limit } = input
|
|
24
24
|
|
|
25
25
|
const memory = getMemoryService()
|
|
26
|
+
|
|
27
|
+
if (!memory.isChromaDBEnabled()) {
|
|
28
|
+
return ResponseFormatter.text(
|
|
29
|
+
'No decisions found for trend analysis.\n\n' +
|
|
30
|
+
'Note: ChromaDB is not connected. Trend detection requires ChromaDB for semantic search across decisions. ' +
|
|
31
|
+
'Decisions stored via SQLite fallback are available through recall_similar and get_patterns. ' +
|
|
32
|
+
'To enable trend detection, start a ChromaDB server or configure persistent mode.'
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
const detector = new TrendDetector(logger, memory.chroma.collections)
|
|
27
37
|
|
|
28
38
|
const analysis = await detector.detectTrends({
|
|
@@ -33,10 +43,7 @@ export async function handleDetectTrends(
|
|
|
33
43
|
})
|
|
34
44
|
|
|
35
45
|
if (analysis.totalDecisionsAnalyzed === 0) {
|
|
36
|
-
|
|
37
|
-
? '\n\nNote: ChromaDB is not connected. Trend detection requires ChromaDB for semantic search across decisions. Start a ChromaDB server or switch to persistent mode.'
|
|
38
|
-
: ''
|
|
39
|
-
return ResponseFormatter.text('No decisions found for trend analysis.' + chromaNote)
|
|
46
|
+
return ResponseFormatter.text('No decisions found for trend analysis.')
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
const parts: string[] = []
|
|
@@ -23,6 +23,16 @@ export async function handleFindCrossProjectPatterns(
|
|
|
23
23
|
const { min_projects, limit, query } = input
|
|
24
24
|
|
|
25
25
|
const memory = getMemoryService()
|
|
26
|
+
|
|
27
|
+
if (!memory.isChromaDBEnabled()) {
|
|
28
|
+
return ResponseFormatter.text(
|
|
29
|
+
'No cross-project patterns found.\n\n' +
|
|
30
|
+
'Note: ChromaDB is not connected. Cross-project pattern analysis requires ChromaDB for semantic search across decisions. ' +
|
|
31
|
+
'Decisions stored via SQLite fallback are available through recall_similar and get_patterns. ' +
|
|
32
|
+
'To enable cross-project analysis, start a ChromaDB server or configure persistent mode.'
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
const generalizer = new PatternGeneralizer(
|
|
27
37
|
logger,
|
|
28
38
|
memory.chroma.collections,
|
|
@@ -24,6 +24,16 @@ export async function handleGetDecisionTimeline(
|
|
|
24
24
|
const { project_name, topic, time_range, limit } = input
|
|
25
25
|
|
|
26
26
|
const memory = getMemoryService()
|
|
27
|
+
|
|
28
|
+
if (!memory.isChromaDBEnabled()) {
|
|
29
|
+
return ResponseFormatter.text(
|
|
30
|
+
'No decisions found for the specified criteria.\n\n' +
|
|
31
|
+
'Note: ChromaDB is not connected. Decision timeline requires ChromaDB for semantic search. ' +
|
|
32
|
+
'Decisions stored via SQLite fallback are available through recall_similar. ' +
|
|
33
|
+
'To enable timeline view, start a ChromaDB server or configure persistent mode.'
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
const timelineBuilder = new TimelineBuilder(
|
|
28
38
|
logger,
|
|
29
39
|
memory.chroma.collections,
|
|
@@ -50,10 +60,7 @@ export async function handleGetDecisionTimeline(
|
|
|
50
60
|
})
|
|
51
61
|
|
|
52
62
|
if (timeline.entries.length === 0) {
|
|
53
|
-
|
|
54
|
-
? '\n\nNote: ChromaDB is not connected. Decision timeline requires ChromaDB for semantic search. Start a ChromaDB server or switch to persistent mode.'
|
|
55
|
-
: ''
|
|
56
|
-
return ResponseFormatter.text('No decisions found for the specified criteria.' + chromaNote)
|
|
63
|
+
return ResponseFormatter.text('No decisions found for the specified criteria.')
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
// Format timeline
|
|
@@ -23,6 +23,16 @@ export async function handleGetRecommendations(
|
|
|
23
23
|
const { query, project_name, limit } = input
|
|
24
24
|
|
|
25
25
|
const memory = getMemoryService()
|
|
26
|
+
|
|
27
|
+
if (!memory.isChromaDBEnabled()) {
|
|
28
|
+
return ResponseFormatter.text(
|
|
29
|
+
`No recommendations found for: "${query}"\n\n` +
|
|
30
|
+
'Note: ChromaDB is not connected. Recommendations require ChromaDB for semantic search across patterns, corrections, and decisions. ' +
|
|
31
|
+
'Decisions stored via SQLite fallback are available through recall_similar, get_patterns, and get_corrections. ' +
|
|
32
|
+
'To enable recommendations, start a ChromaDB server or configure persistent mode.'
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
const recommender = new Recommender(
|
|
27
37
|
logger,
|
|
28
38
|
memory.chroma.collections,
|
|
@@ -35,10 +45,7 @@ export async function handleGetRecommendations(
|
|
|
35
45
|
})
|
|
36
46
|
|
|
37
47
|
if (result.recommendations.length === 0) {
|
|
38
|
-
|
|
39
|
-
? '\n\nNote: ChromaDB is not connected. Recommendations require ChromaDB for semantic search across patterns, corrections, and decisions. Start a ChromaDB server or switch to persistent mode.'
|
|
40
|
-
: ''
|
|
41
|
-
return ResponseFormatter.text(`No recommendations found for: "${query}"` + chromaNote)
|
|
48
|
+
return ResponseFormatter.text(`No recommendations found for: "${query}"`)
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
const parts: string[] = []
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Logger } from 'pino'
|
|
7
7
|
import type { ToolResponse } from '@/tools/types'
|
|
8
|
-
import { getKnowledgeGraphService, isServicesInitialized } from '@/server/services'
|
|
8
|
+
import { getKnowledgeGraphService, getMemoryService, isServicesInitialized } from '@/server/services'
|
|
9
9
|
import { ToolValidator } from '@/server/utils/validators'
|
|
10
10
|
import { ResponseFormatter } from '@/server/utils/response-formatter'
|
|
11
11
|
import { ErrorHandler } from '@/server/utils/error-handler'
|
|
@@ -72,6 +72,19 @@ export async function handleSearchKnowledgeGraph(
|
|
|
72
72
|
edgeCount: result.edges.length
|
|
73
73
|
}, 'Knowledge graph search complete')
|
|
74
74
|
|
|
75
|
+
// If graph is empty and ChromaDB is not connected, add guidance
|
|
76
|
+
if (result.nodes.length === 0 && result.edges.length === 0) {
|
|
77
|
+
const memory = getMemoryService()
|
|
78
|
+
if (!memory.isChromaDBEnabled()) {
|
|
79
|
+
return ResponseFormatter.text(
|
|
80
|
+
'Knowledge graph is empty (0 nodes, 0 edges).\n\n' +
|
|
81
|
+
'Note: ChromaDB is not connected. The knowledge graph is populated from stored decisions. ' +
|
|
82
|
+
'New decisions stored via SQLite will populate the graph going forward. ' +
|
|
83
|
+
'To migrate existing decisions into the graph, start a ChromaDB server or configure persistent mode.'
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
75
88
|
return ResponseFormatter.json(response, 'Knowledge Graph Search Results')
|
|
76
89
|
|
|
77
90
|
} catch (error) {
|
|
@@ -23,6 +23,17 @@ export async function handleWhatIfAnalysis(
|
|
|
23
23
|
const { change, project_name, max_results } = input
|
|
24
24
|
|
|
25
25
|
const memory = getMemoryService()
|
|
26
|
+
|
|
27
|
+
if (!memory.isChromaDBEnabled()) {
|
|
28
|
+
return ResponseFormatter.text(
|
|
29
|
+
`## What-If: "${change}"\n\n` +
|
|
30
|
+
'Unable to perform what-if analysis.\n\n' +
|
|
31
|
+
'Note: ChromaDB is not connected. What-if analysis requires ChromaDB for semantic search across decisions. ' +
|
|
32
|
+
'Decisions stored via SQLite fallback are available through recall_similar. ' +
|
|
33
|
+
'To enable what-if analysis, start a ChromaDB server or configure persistent mode.'
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
const kgService = getKnowledgeGraphService()
|
|
27
38
|
const graph = kgService?.graph || null
|
|
28
39
|
|
|
@@ -63,10 +74,7 @@ export async function handleWhatIfAnalysis(
|
|
|
63
74
|
return ResponseFormatter.text(withMemoryIndicator(content, totalAffected))
|
|
64
75
|
}
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
? '\n\nNote: ChromaDB is not connected. What-if analysis requires ChromaDB for semantic search across decisions. Start a ChromaDB server or switch to persistent mode.'
|
|
68
|
-
: ''
|
|
69
|
-
return ResponseFormatter.text(content + chromaNote)
|
|
77
|
+
return ResponseFormatter.text(content)
|
|
70
78
|
|
|
71
79
|
} catch (error) {
|
|
72
80
|
ErrorHandler.logError(logger, error, { tool: 'what_if_analysis' })
|
package/src/server/services.ts
CHANGED
|
@@ -112,9 +112,9 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
112
112
|
await phase12.initialize()
|
|
113
113
|
serviceLogger.info('Phase 12 service initialized')
|
|
114
114
|
|
|
115
|
-
// Initialize Retrieval Service (Phase 13)
|
|
115
|
+
// Initialize Retrieval Service (Phase 13) — requires ChromaDB
|
|
116
116
|
let retrieval: RetrievalService | null = null
|
|
117
|
-
if (config.retrieval?.feedback?.enabled || config.retrieval?.enabled) {
|
|
117
|
+
if ((config.retrieval?.feedback?.enabled || config.retrieval?.enabled) && memory.isChromaDBEnabled()) {
|
|
118
118
|
retrieval = new RetrievalService(
|
|
119
119
|
logger,
|
|
120
120
|
memory.chroma.collections,
|
|
@@ -123,6 +123,8 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
123
123
|
)
|
|
124
124
|
await retrieval.initialize()
|
|
125
125
|
serviceLogger.info('Retrieval service initialized')
|
|
126
|
+
} else if (config.retrieval?.enabled && !memory.isChromaDBEnabled()) {
|
|
127
|
+
serviceLogger.warn('Retrieval service requires ChromaDB, skipping initialization')
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
// Initialize Knowledge Graph Service (Phase 14)
|
|
@@ -146,14 +148,16 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
146
148
|
|
|
147
149
|
knowledgeGraph = { graph, search, builder, linker }
|
|
148
150
|
|
|
149
|
-
// Migrate existing decisions if graph is empty
|
|
150
|
-
if (graph.getNodeCount() === 0) {
|
|
151
|
+
// Migrate existing decisions if graph is empty (requires ChromaDB)
|
|
152
|
+
if (graph.getNodeCount() === 0 && memory.isChromaDBEnabled()) {
|
|
151
153
|
serviceLogger.info('Empty graph detected, migrating existing decisions...')
|
|
152
154
|
const migrationResult = await builder.migrateExistingDecisions(memory.chroma.collections)
|
|
153
155
|
serviceLogger.info(
|
|
154
156
|
{ processed: migrationResult.processed, errors: migrationResult.errors },
|
|
155
157
|
'Initial graph migration complete'
|
|
156
158
|
)
|
|
159
|
+
} else if (graph.getNodeCount() === 0) {
|
|
160
|
+
serviceLogger.info('Empty graph detected, but ChromaDB unavailable — graph will populate as new decisions are stored')
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
// Hook builder into decision storage for real-time graph population
|
|
@@ -171,9 +175,9 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
171
175
|
}
|
|
172
176
|
}
|
|
173
177
|
|
|
174
|
-
// Initialize Episode Manager (Phase 14)
|
|
178
|
+
// Initialize Episode Manager (Phase 14) — requires ChromaDB
|
|
175
179
|
let episodeManager: EpisodeManager | null = null
|
|
176
|
-
if (config.knowledge?.episodic?.enabled !== false) {
|
|
180
|
+
if (config.knowledge?.episodic?.enabled !== false && memory.isChromaDBEnabled()) {
|
|
177
181
|
try {
|
|
178
182
|
episodeManager = new EpisodeManager(
|
|
179
183
|
logger,
|
|
@@ -187,10 +191,10 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
193
|
|
|
190
|
-
// Initialize Semantic Cache & Precompute (Phase 15)
|
|
194
|
+
// Initialize Semantic Cache & Precompute (Phase 15) — requires ChromaDB
|
|
191
195
|
let semanticCache: SemanticCache | null = null
|
|
192
196
|
let precompute: PrecomputeEngine | null = null
|
|
193
|
-
if (config.advancedIntelligence?.enabled !== false && config.advancedIntelligence?.cache?.enabled !== false) {
|
|
197
|
+
if (config.advancedIntelligence?.enabled !== false && config.advancedIntelligence?.cache?.enabled !== false && memory.isChromaDBEnabled()) {
|
|
194
198
|
try {
|
|
195
199
|
const cacheConfig = config.advancedIntelligence?.cache || {}
|
|
196
200
|
semanticCache = new SemanticCache(logger, {
|
package/src/setup/wizard.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import prompts from 'prompts'
|
|
2
2
|
import fs from 'fs/promises'
|
|
3
3
|
import { existsSync } from 'fs'
|
|
4
|
+
import { execSync } from 'child_process'
|
|
4
5
|
import path from 'path'
|
|
5
6
|
import os from 'os'
|
|
6
7
|
import { fileURLToPath } from 'url'
|
|
@@ -21,6 +22,7 @@ export interface SetupAnswers {
|
|
|
21
22
|
vaultPath: string
|
|
22
23
|
logLevel: string
|
|
23
24
|
enableFileWatch: boolean
|
|
25
|
+
enableChromaDB: boolean
|
|
24
26
|
createSampleProject: boolean
|
|
25
27
|
installClaudeMd: boolean
|
|
26
28
|
}
|
|
@@ -34,7 +36,7 @@ export class SetupWizard {
|
|
|
34
36
|
|
|
35
37
|
async run(): Promise<SetupAnswers> {
|
|
36
38
|
// Step 1: Detect Vaults
|
|
37
|
-
console.log(stepIndicator(1,
|
|
39
|
+
console.log(stepIndicator(1, 6, 'Detecting Obsidian Vaults'))
|
|
38
40
|
await transition()
|
|
39
41
|
|
|
40
42
|
const suggestedPaths = await withSpinner('Scanning for Obsidian vaults', () =>
|
|
@@ -49,7 +51,7 @@ export class SetupWizard {
|
|
|
49
51
|
console.log()
|
|
50
52
|
|
|
51
53
|
// Step 2: Vault Configuration
|
|
52
|
-
console.log(stepIndicator(2,
|
|
54
|
+
console.log(stepIndicator(2, 6, 'Vault Configuration'))
|
|
53
55
|
await transition()
|
|
54
56
|
|
|
55
57
|
const vaultAnswers = await prompts([
|
|
@@ -86,7 +88,7 @@ export class SetupWizard {
|
|
|
86
88
|
const finalVaultPath = vaultAnswers.vaultPath || vaultAnswers.vaultPathChoice
|
|
87
89
|
|
|
88
90
|
// Step 3: Logging
|
|
89
|
-
console.log(stepIndicator(3,
|
|
91
|
+
console.log(stepIndicator(3, 6, 'Logging Configuration'))
|
|
90
92
|
await transition()
|
|
91
93
|
|
|
92
94
|
const loggingAnswers = await prompts({
|
|
@@ -103,7 +105,7 @@ export class SetupWizard {
|
|
|
103
105
|
})
|
|
104
106
|
|
|
105
107
|
// Step 4: Features
|
|
106
|
-
console.log(stepIndicator(4,
|
|
108
|
+
console.log(stepIndicator(4, 6, 'Feature Selection'))
|
|
107
109
|
await transition()
|
|
108
110
|
|
|
109
111
|
const featureAnswers = await prompts([
|
|
@@ -127,14 +129,40 @@ export class SetupWizard {
|
|
|
127
129
|
}
|
|
128
130
|
])
|
|
129
131
|
|
|
130
|
-
// Step 5:
|
|
131
|
-
console.log(stepIndicator(5,
|
|
132
|
+
// Step 5: ChromaDB
|
|
133
|
+
console.log(stepIndicator(5, 6, 'ChromaDB (Vector Database)'))
|
|
134
|
+
await transition()
|
|
135
|
+
|
|
136
|
+
console.log(dimText('ChromaDB enables semantic search, knowledge graph,'))
|
|
137
|
+
console.log(dimText('and advanced intelligence features. Requires Python 3.'))
|
|
138
|
+
console.log()
|
|
139
|
+
|
|
140
|
+
const chromaInstalled = this.isChromaInstalled()
|
|
141
|
+
if (chromaInstalled) {
|
|
142
|
+
console.log(successText('ChromaDB CLI detected'))
|
|
143
|
+
} else {
|
|
144
|
+
console.log(warningText('ChromaDB CLI not found — you can install it later'))
|
|
145
|
+
}
|
|
146
|
+
console.log()
|
|
147
|
+
|
|
148
|
+
const chromaAnswers = await prompts({
|
|
149
|
+
type: 'confirm',
|
|
150
|
+
name: 'enableChromaDB',
|
|
151
|
+
message: chromaInstalled
|
|
152
|
+
? 'Enable ChromaDB for advanced features?'
|
|
153
|
+
: 'Enable ChromaDB? (install later: pip install chromadb)',
|
|
154
|
+
initial: chromaInstalled
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// Step 6: Review
|
|
158
|
+
console.log(stepIndicator(6, 6, 'Review Configuration'))
|
|
132
159
|
await transition()
|
|
133
160
|
|
|
134
161
|
const answers: SetupAnswers = {
|
|
135
162
|
vaultPath: finalVaultPath,
|
|
136
163
|
logLevel: loggingAnswers.logLevel ?? 'warn',
|
|
137
164
|
enableFileWatch: featureAnswers.enableFileWatch ?? true,
|
|
165
|
+
enableChromaDB: chromaAnswers.enableChromaDB ?? false,
|
|
138
166
|
createSampleProject: featureAnswers.createSampleProject ?? true,
|
|
139
167
|
installClaudeMd: featureAnswers.installClaudeMd ?? true,
|
|
140
168
|
}
|
|
@@ -143,6 +171,7 @@ export class SetupWizard {
|
|
|
143
171
|
{ label: 'Vault Path', value: answers.vaultPath, status: 'success' },
|
|
144
172
|
{ label: 'Log Level', value: answers.logLevel, status: 'info' },
|
|
145
173
|
{ label: 'File Watching', value: answers.enableFileWatch ? 'Enabled' : 'Disabled', status: answers.enableFileWatch ? 'success' : 'warning' },
|
|
174
|
+
{ label: 'ChromaDB', value: answers.enableChromaDB ? 'Enabled' : 'Disabled', status: answers.enableChromaDB ? 'success' : 'warning' },
|
|
146
175
|
{ label: 'Sample Project', value: answers.createSampleProject ? 'Yes' : 'No', status: 'info' },
|
|
147
176
|
{ label: 'Install CLAUDE.md', value: answers.installClaudeMd ? 'Yes' : 'No', status: 'info' },
|
|
148
177
|
]))
|
|
@@ -191,11 +220,31 @@ export class SetupWizard {
|
|
|
191
220
|
return locations
|
|
192
221
|
}
|
|
193
222
|
|
|
223
|
+
private isChromaInstalled(): boolean {
|
|
224
|
+
try {
|
|
225
|
+
execSync('chroma --version', { stdio: 'pipe', timeout: 5000 })
|
|
226
|
+
return true
|
|
227
|
+
} catch {
|
|
228
|
+
return false
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
194
232
|
async applyConfiguration(answers: SetupAnswers): Promise<void> {
|
|
195
233
|
console.log('\n' + heading('Applying configuration...') + '\n')
|
|
196
234
|
|
|
197
235
|
const homePaths = getHomePaths()
|
|
198
236
|
|
|
237
|
+
const chromaEnvLines = answers.enableChromaDB
|
|
238
|
+
? [
|
|
239
|
+
'',
|
|
240
|
+
'# ChromaDB Configuration',
|
|
241
|
+
'CHROMA_MODE=client-server',
|
|
242
|
+
'CHROMA_HOST=localhost',
|
|
243
|
+
'CHROMA_PORT=8000',
|
|
244
|
+
'CHROMA_EMBEDDING_PROVIDER=transformers',
|
|
245
|
+
].join('\n')
|
|
246
|
+
: ''
|
|
247
|
+
|
|
199
248
|
const envContent = `# Claude Brain Configuration
|
|
200
249
|
VAULT_PATH=${answers.vaultPath}
|
|
201
250
|
LOG_LEVEL=${answers.logLevel}
|
|
@@ -203,6 +252,7 @@ ENABLE_FILE_WATCH=${answers.enableFileWatch}
|
|
|
203
252
|
DB_PATH=./data/memory.db
|
|
204
253
|
LOG_FILE_PATH=./logs/claude-brain.log
|
|
205
254
|
SERVER_NAME=claude-brain
|
|
255
|
+
${chromaEnvLines}
|
|
206
256
|
`
|
|
207
257
|
|
|
208
258
|
await withSpinner('Writing .env configuration', async () => {
|
|
@@ -211,6 +261,7 @@ SERVER_NAME=claude-brain
|
|
|
211
261
|
|
|
212
262
|
await withSpinner('Creating data and log directories', async () => {
|
|
213
263
|
await fs.mkdir(homePaths.data, { recursive: true })
|
|
264
|
+
await fs.mkdir(homePaths.chroma, { recursive: true })
|
|
214
265
|
await fs.mkdir(homePaths.logs, { recursive: true })
|
|
215
266
|
})
|
|
216
267
|
|
|
@@ -229,16 +280,35 @@ SERVER_NAME=claude-brain
|
|
|
229
280
|
}
|
|
230
281
|
}
|
|
231
282
|
|
|
232
|
-
|
|
233
|
-
console.log(box([
|
|
283
|
+
const nextSteps: string[] = [
|
|
234
284
|
heading('Setup complete!'),
|
|
235
285
|
'',
|
|
236
286
|
dimText('Next steps:'),
|
|
237
287
|
` ${theme.primary('1.')} ${theme.bold('claude-brain install')}`,
|
|
238
288
|
` ${dimText('Register as MCP server')}`,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
289
|
+
]
|
|
290
|
+
|
|
291
|
+
if (answers.enableChromaDB) {
|
|
292
|
+
nextSteps.push(
|
|
293
|
+
` ${theme.primary('2.')} ${theme.bold('claude-brain chroma install')}`,
|
|
294
|
+
` ${dimText('Install ChromaDB (if not already installed)')}`,
|
|
295
|
+
` ${theme.primary('3.')} ${theme.bold('claude-brain chroma start')}`,
|
|
296
|
+
` ${dimText('Start the ChromaDB server')}`,
|
|
297
|
+
` ${theme.primary('4.')} ${theme.bold('claude-brain health')}`,
|
|
298
|
+
` ${dimText('Verify everything works')}`,
|
|
299
|
+
)
|
|
300
|
+
} else {
|
|
301
|
+
nextSteps.push(
|
|
302
|
+
` ${theme.primary('2.')} ${theme.bold('claude-brain health')}`,
|
|
303
|
+
` ${dimText('Verify everything works')}`,
|
|
304
|
+
'',
|
|
305
|
+
dimText('Optional: Enable ChromaDB later for advanced features:'),
|
|
306
|
+
` ${dimText('claude-brain chroma install && claude-brain chroma start')}`,
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log()
|
|
311
|
+
console.log(box(nextSteps.join('\n'), 'Done'))
|
|
242
312
|
console.log()
|
|
243
313
|
}
|
|
244
314
|
|