claude-brain 0.30.2 → 0.30.3
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/README.md +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- package/skills/persistent-memory/references/tool-reference.md +0 -90
|
@@ -1,577 +1,578 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ChromaDB Management Command
|
|
3
|
-
* Start, stop, and manage the ChromaDB server for Claude Brain
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { spawn,
|
|
7
|
-
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'
|
|
8
|
-
import { join } from 'node:path'
|
|
9
|
-
import { parseArgs } from 'citty'
|
|
10
|
-
import { getHomePaths } from '@/config/home'
|
|
11
|
-
import {
|
|
12
|
-
theme, heading, successText, errorText, warningText, dimText,
|
|
13
|
-
infoPanel,
|
|
14
|
-
} from '@/cli/ui/index.js'
|
|
15
|
-
|
|
16
|
-
const PID_FILENAME = 'chroma.pid'
|
|
17
|
-
const DEFAULT_PORT = '8000'
|
|
18
|
-
|
|
19
|
-
function getPidFilePath(): string {
|
|
20
|
-
const paths = getHomePaths()
|
|
21
|
-
return join(paths.data, PID_FILENAME)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function getChromaDataPath(): string {
|
|
25
|
-
const paths = getHomePaths()
|
|
26
|
-
return paths.chroma
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Find the chroma binary - checks PATH first, then common pip install locations
|
|
31
|
-
*/
|
|
32
|
-
function findChromaBinary(): string | null {
|
|
33
|
-
const isWindows = process.platform === 'win32'
|
|
34
|
-
const chromaName = isWindows ? 'chroma.exe' : 'chroma'
|
|
35
|
-
|
|
36
|
-
// Try bare 'chroma' first (on PATH)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return 'chroma'
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const { homedir } = require('os')
|
|
43
|
-
const home = homedir()
|
|
44
|
-
|
|
45
|
-
// Platform-specific search paths
|
|
46
|
-
const candidates: string[] = isWindows
|
|
47
|
-
? [
|
|
48
|
-
// Windows pip install locations
|
|
49
|
-
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python39', 'Scripts', chromaName),
|
|
50
|
-
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python310', 'Scripts', chromaName),
|
|
51
|
-
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python311', 'Scripts', chromaName),
|
|
52
|
-
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python312', 'Scripts', chromaName),
|
|
53
|
-
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python313', 'Scripts', chromaName),
|
|
54
|
-
// Windows user pip install (pip install --user)
|
|
55
|
-
join(home, 'AppData', 'Roaming', 'Python', 'Python39', 'Scripts', chromaName),
|
|
56
|
-
join(home, 'AppData', 'Roaming', 'Python', 'Python310', 'Scripts', chromaName),
|
|
57
|
-
join(home, 'AppData', 'Roaming', 'Python', 'Python311', 'Scripts', chromaName),
|
|
58
|
-
join(home, 'AppData', 'Roaming', 'Python', 'Python312', 'Scripts', chromaName),
|
|
59
|
-
join(home, 'AppData', 'Roaming', 'Python', 'Python313', 'Scripts', chromaName),
|
|
60
|
-
// Scoop / Chocolatey / winget
|
|
61
|
-
join(home, 'scoop', 'shims', chromaName),
|
|
62
|
-
'C:\\Python39\\Scripts\\' + chromaName,
|
|
63
|
-
'C:\\Python310\\Scripts\\' + chromaName,
|
|
64
|
-
'C:\\Python311\\Scripts\\' + chromaName,
|
|
65
|
-
'C:\\Python312\\Scripts\\' + chromaName,
|
|
66
|
-
'C:\\Python313\\Scripts\\' + chromaName,
|
|
67
|
-
]
|
|
68
|
-
: [
|
|
69
|
-
// macOS pip install locations
|
|
70
|
-
join(home, 'Library', 'Python', '3.9', 'bin', chromaName),
|
|
71
|
-
join(home, 'Library', 'Python', '3.10', 'bin', chromaName),
|
|
72
|
-
join(home, 'Library', 'Python', '3.11', 'bin', chromaName),
|
|
73
|
-
join(home, 'Library', 'Python', '3.12', 'bin', chromaName),
|
|
74
|
-
join(home, 'Library', 'Python', '3.13', 'bin', chromaName),
|
|
75
|
-
// Linux pip install locations
|
|
76
|
-
join(home, '.local', 'bin', chromaName),
|
|
77
|
-
'/usr/local/bin/' + chromaName,
|
|
78
|
-
'/opt/homebrew/bin/' + chromaName,
|
|
79
|
-
]
|
|
80
|
-
|
|
81
|
-
for (const candidate of candidates) {
|
|
82
|
-
try {
|
|
83
|
-
if (existsSync(candidate)) {
|
|
84
|
-
|
|
85
|
-
return candidate
|
|
86
|
-
}
|
|
87
|
-
} catch {}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Try finding via python -m site (works on all platforms)
|
|
91
|
-
const pythonCmd = isWindows ? 'python' : 'python3'
|
|
92
|
-
try {
|
|
93
|
-
const
|
|
94
|
-
encoding: 'utf-8', stdio: 'pipe', timeout: 5000
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.log(
|
|
194
|
-
console.log()
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
console.log()
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
console.log()
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
console.log()
|
|
215
|
-
console.log(
|
|
216
|
-
console.log(
|
|
217
|
-
console.log()
|
|
218
|
-
console.log(
|
|
219
|
-
console.log(
|
|
220
|
-
console.log()
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
console.log(dimText(`
|
|
228
|
-
console.log(dimText(`
|
|
229
|
-
console.log()
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
console.log()
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
child
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
console.log()
|
|
279
|
-
console.log(
|
|
280
|
-
console.log(dimText('
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
console.log(
|
|
292
|
-
console.log(dimText(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
console.log(dimText(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
console.log(
|
|
309
|
-
console.log()
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
console.log(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
'
|
|
369
|
-
'
|
|
370
|
-
'
|
|
371
|
-
'Data
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
console.log(
|
|
379
|
-
console.log(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
console.log(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
console.log(
|
|
390
|
-
console.log()
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
console.log()
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
console.log()
|
|
403
|
-
console.log(
|
|
404
|
-
console.log(
|
|
405
|
-
console.log(` ${theme.primary('
|
|
406
|
-
console.log(` ${theme.primary('
|
|
407
|
-
console.log()
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
console.log()
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
console.log()
|
|
433
|
-
console.log(
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
console.log(
|
|
437
|
-
console.log(
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
console.log(
|
|
448
|
-
console.log()
|
|
449
|
-
console.log(
|
|
450
|
-
console.log(dimText('
|
|
451
|
-
console.log()
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
['
|
|
456
|
-
['
|
|
457
|
-
['
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
console.log(
|
|
466
|
-
console.log()
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
console.log(
|
|
470
|
-
console.log(` ${dimText('claude-brain chroma
|
|
471
|
-
console.log(` ${dimText('claude-brain chroma
|
|
472
|
-
console.log(` ${dimText('claude-brain chroma
|
|
473
|
-
console.log()
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
log('[ChromaDB]
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
for
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
case '
|
|
568
|
-
case '
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
console.log(
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* ChromaDB Management Command
|
|
3
|
+
* Start, stop, and manage the ChromaDB server for Claude Brain
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'
|
|
8
|
+
import { join } from 'node:path'
|
|
9
|
+
import { parseArgs } from 'citty'
|
|
10
|
+
import { getHomePaths } from '@/config/home'
|
|
11
|
+
import {
|
|
12
|
+
theme, heading, successText, errorText, warningText, dimText,
|
|
13
|
+
infoPanel,
|
|
14
|
+
} from '@/cli/ui/index.js'
|
|
15
|
+
|
|
16
|
+
const PID_FILENAME = 'chroma.pid'
|
|
17
|
+
const DEFAULT_PORT = '8000'
|
|
18
|
+
|
|
19
|
+
function getPidFilePath(): string {
|
|
20
|
+
const paths = getHomePaths()
|
|
21
|
+
return join(paths.data, PID_FILENAME)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getChromaDataPath(): string {
|
|
25
|
+
const paths = getHomePaths()
|
|
26
|
+
return paths.chroma
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Find the chroma binary - checks PATH first, then common pip install locations
|
|
31
|
+
*/
|
|
32
|
+
function findChromaBinary(): string | null {
|
|
33
|
+
const isWindows = process.platform === 'win32'
|
|
34
|
+
const chromaName = isWindows ? 'chroma.exe' : 'chroma'
|
|
35
|
+
|
|
36
|
+
// Try bare 'chroma' first (on PATH)
|
|
37
|
+
{
|
|
38
|
+
const result = spawnSync('chroma', ['--version'], { stdio: 'pipe', timeout: 5000 })
|
|
39
|
+
if (!result.error && result.status === 0) return 'chroma'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { homedir } = require('os')
|
|
43
|
+
const home = homedir()
|
|
44
|
+
|
|
45
|
+
// Platform-specific search paths
|
|
46
|
+
const candidates: string[] = isWindows
|
|
47
|
+
? [
|
|
48
|
+
// Windows pip install locations
|
|
49
|
+
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python39', 'Scripts', chromaName),
|
|
50
|
+
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python310', 'Scripts', chromaName),
|
|
51
|
+
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python311', 'Scripts', chromaName),
|
|
52
|
+
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python312', 'Scripts', chromaName),
|
|
53
|
+
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python313', 'Scripts', chromaName),
|
|
54
|
+
// Windows user pip install (pip install --user)
|
|
55
|
+
join(home, 'AppData', 'Roaming', 'Python', 'Python39', 'Scripts', chromaName),
|
|
56
|
+
join(home, 'AppData', 'Roaming', 'Python', 'Python310', 'Scripts', chromaName),
|
|
57
|
+
join(home, 'AppData', 'Roaming', 'Python', 'Python311', 'Scripts', chromaName),
|
|
58
|
+
join(home, 'AppData', 'Roaming', 'Python', 'Python312', 'Scripts', chromaName),
|
|
59
|
+
join(home, 'AppData', 'Roaming', 'Python', 'Python313', 'Scripts', chromaName),
|
|
60
|
+
// Scoop / Chocolatey / winget
|
|
61
|
+
join(home, 'scoop', 'shims', chromaName),
|
|
62
|
+
'C:\\Python39\\Scripts\\' + chromaName,
|
|
63
|
+
'C:\\Python310\\Scripts\\' + chromaName,
|
|
64
|
+
'C:\\Python311\\Scripts\\' + chromaName,
|
|
65
|
+
'C:\\Python312\\Scripts\\' + chromaName,
|
|
66
|
+
'C:\\Python313\\Scripts\\' + chromaName,
|
|
67
|
+
]
|
|
68
|
+
: [
|
|
69
|
+
// macOS pip install locations
|
|
70
|
+
join(home, 'Library', 'Python', '3.9', 'bin', chromaName),
|
|
71
|
+
join(home, 'Library', 'Python', '3.10', 'bin', chromaName),
|
|
72
|
+
join(home, 'Library', 'Python', '3.11', 'bin', chromaName),
|
|
73
|
+
join(home, 'Library', 'Python', '3.12', 'bin', chromaName),
|
|
74
|
+
join(home, 'Library', 'Python', '3.13', 'bin', chromaName),
|
|
75
|
+
// Linux pip install locations
|
|
76
|
+
join(home, '.local', 'bin', chromaName),
|
|
77
|
+
'/usr/local/bin/' + chromaName,
|
|
78
|
+
'/opt/homebrew/bin/' + chromaName,
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
for (const candidate of candidates) {
|
|
82
|
+
try {
|
|
83
|
+
if (existsSync(candidate)) {
|
|
84
|
+
const result = spawnSync(candidate, ['--version'], { stdio: 'pipe', timeout: 5000 })
|
|
85
|
+
if (!result.error && result.status === 0) return candidate
|
|
86
|
+
}
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Try finding via python -m site (works on all platforms)
|
|
91
|
+
const pythonCmd = isWindows ? 'python' : 'python3'
|
|
92
|
+
try {
|
|
93
|
+
const siteResult = spawnSync(pythonCmd, ['-c', 'import site; print(site.getusersitepackages())'], {
|
|
94
|
+
encoding: 'utf-8', stdio: 'pipe', timeout: 5000
|
|
95
|
+
})
|
|
96
|
+
const sitePackages = siteResult.stdout?.trim() || ''
|
|
97
|
+
|
|
98
|
+
if (sitePackages && !siteResult.error && siteResult.status === 0) {
|
|
99
|
+
let binDir: string
|
|
100
|
+
if (isWindows) {
|
|
101
|
+
// Windows: C:\Users\x\AppData\Roaming\Python\Python311\site-packages → Scripts
|
|
102
|
+
binDir = sitePackages.replace(/[\\\/]site-packages$/, '\\Scripts')
|
|
103
|
+
} else {
|
|
104
|
+
// Unix: /Users/x/Library/Python/3.9/lib/python/site-packages → bin
|
|
105
|
+
binDir = sitePackages.replace(/\/lib\/.*/, '/bin')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const chromaPath = join(binDir, chromaName)
|
|
109
|
+
if (existsSync(chromaPath)) {
|
|
110
|
+
const verResult = spawnSync(chromaPath, ['--version'], { stdio: 'pipe', timeout: 5000 })
|
|
111
|
+
if (!verResult.error && verResult.status === 0) return chromaPath
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch {}
|
|
115
|
+
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let _cachedChromaBinary: string | null | undefined = undefined
|
|
120
|
+
|
|
121
|
+
function getChromaBinary(): string | null {
|
|
122
|
+
if (_cachedChromaBinary === undefined) {
|
|
123
|
+
_cachedChromaBinary = findChromaBinary()
|
|
124
|
+
}
|
|
125
|
+
return _cachedChromaBinary
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isChromaCliInstalled(): boolean {
|
|
129
|
+
return getChromaBinary() !== null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getChromaVersion(): string {
|
|
133
|
+
const binary = getChromaBinary()
|
|
134
|
+
if (!binary) return 'unknown'
|
|
135
|
+
try {
|
|
136
|
+
const result = spawnSync(binary, ['--version'], { encoding: 'utf-8', stdio: 'pipe', timeout: 5000 })
|
|
137
|
+
if (result.error || result.status !== 0) return 'unknown'
|
|
138
|
+
return result.stdout?.trim() || 'unknown'
|
|
139
|
+
} catch {
|
|
140
|
+
return 'unknown'
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isPythonInstalled(): { installed: boolean; cmd: string } {
|
|
145
|
+
for (const cmd of ['python3', 'python']) {
|
|
146
|
+
const result = spawnSync(cmd, ['--version'], { stdio: 'pipe', timeout: 5000 })
|
|
147
|
+
if (!result.error && result.status === 0) {
|
|
148
|
+
return { installed: true, cmd }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { installed: false, cmd: 'python3' }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getRunningPid(): number | null {
|
|
155
|
+
const pidPath = getPidFilePath()
|
|
156
|
+
if (!existsSync(pidPath)) return null
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10)
|
|
160
|
+
if (isNaN(pid)) {
|
|
161
|
+
unlinkSync(pidPath)
|
|
162
|
+
return null
|
|
163
|
+
}
|
|
164
|
+
// Signal 0 tests if process exists without killing it
|
|
165
|
+
process.kill(pid, 0)
|
|
166
|
+
return pid
|
|
167
|
+
} catch {
|
|
168
|
+
// Process not running, clean up stale PID file
|
|
169
|
+
try { unlinkSync(pidPath) } catch {}
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isChromaReachable(): boolean {
|
|
175
|
+
// Try v2 API first (ChromaDB 1.x server), then v1 (older servers)
|
|
176
|
+
for (const apiVersion of ['v2', 'v1']) {
|
|
177
|
+
const result = spawnSync('curl', ['-sf', `http://localhost:${DEFAULT_PORT}/api/${apiVersion}/heartbeat`], {
|
|
178
|
+
stdio: 'pipe',
|
|
179
|
+
timeout: 3000
|
|
180
|
+
})
|
|
181
|
+
if (!result.error && result.status === 0) return true
|
|
182
|
+
}
|
|
183
|
+
return false
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function sleep(ms: number): Promise<void> {
|
|
187
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Subcommands ───────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
async function chromaStart(): Promise<void> {
|
|
193
|
+
console.log()
|
|
194
|
+
console.log(heading('Starting ChromaDB Server'))
|
|
195
|
+
console.log()
|
|
196
|
+
|
|
197
|
+
// Check if already running
|
|
198
|
+
const existingPid = getRunningPid()
|
|
199
|
+
if (existingPid) {
|
|
200
|
+
console.log(warningText(`ChromaDB is already running (PID: ${existingPid})`))
|
|
201
|
+
console.log()
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (isChromaReachable()) {
|
|
206
|
+
console.log(warningText('A ChromaDB server is already running on port ' + DEFAULT_PORT))
|
|
207
|
+
console.log()
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check if chroma CLI is installed
|
|
212
|
+
const chromaBinary = getChromaBinary()
|
|
213
|
+
if (!chromaBinary) {
|
|
214
|
+
console.log(errorText('ChromaDB CLI is not installed or not found on PATH.'))
|
|
215
|
+
console.log()
|
|
216
|
+
console.log(dimText('Install it with:'))
|
|
217
|
+
console.log(` ${theme.primary('pip install chromadb')}`)
|
|
218
|
+
console.log()
|
|
219
|
+
console.log(dimText('Or run:'))
|
|
220
|
+
console.log(` ${theme.primary('claude-brain chroma install')}`)
|
|
221
|
+
console.log()
|
|
222
|
+
process.exit(1)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const dataPath = getChromaDataPath()
|
|
226
|
+
|
|
227
|
+
console.log(dimText(`Binary: ${chromaBinary}`))
|
|
228
|
+
console.log(dimText(`Data path: ${dataPath}`))
|
|
229
|
+
console.log(dimText(`Port: ${DEFAULT_PORT}`))
|
|
230
|
+
console.log()
|
|
231
|
+
|
|
232
|
+
// Start ChromaDB server in background
|
|
233
|
+
const child = spawn(chromaBinary, ['run', '--path', dataPath, '--port', DEFAULT_PORT], {
|
|
234
|
+
detached: true,
|
|
235
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
236
|
+
env: { ...process.env }
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const pid = child.pid
|
|
240
|
+
if (!pid) {
|
|
241
|
+
console.log(errorText('Failed to start ChromaDB server — no PID returned.'))
|
|
242
|
+
console.log()
|
|
243
|
+
process.exit(1)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Detach the child process so it runs independently
|
|
247
|
+
child.unref()
|
|
248
|
+
|
|
249
|
+
// Save PID file
|
|
250
|
+
writeFileSync(getPidFilePath(), String(pid), 'utf-8')
|
|
251
|
+
|
|
252
|
+
// Capture early output for error detection
|
|
253
|
+
let startupOutput = ''
|
|
254
|
+
child.stderr?.on('data', (chunk: Buffer) => {
|
|
255
|
+
startupOutput += chunk.toString()
|
|
256
|
+
})
|
|
257
|
+
child.stdout?.on('data', (chunk: Buffer) => {
|
|
258
|
+
startupOutput += chunk.toString()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// Wait up to 15 seconds for server to become reachable
|
|
262
|
+
let ready = false
|
|
263
|
+
for (let i = 0; i < 30; i++) {
|
|
264
|
+
await sleep(500)
|
|
265
|
+
if (isChromaReachable()) {
|
|
266
|
+
ready = true
|
|
267
|
+
break
|
|
268
|
+
}
|
|
269
|
+
// Check if process exited early
|
|
270
|
+
try {
|
|
271
|
+
process.kill(pid, 0)
|
|
272
|
+
} catch {
|
|
273
|
+
break
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (ready) {
|
|
278
|
+
console.log(successText(`ChromaDB server started (PID: ${pid})`))
|
|
279
|
+
console.log()
|
|
280
|
+
console.log(dimText('The server is running in the background.'))
|
|
281
|
+
console.log(dimText('Stop it with: ') + theme.primary('claude-brain chroma stop'))
|
|
282
|
+
} else {
|
|
283
|
+
// Check if process is still alive
|
|
284
|
+
let alive = false
|
|
285
|
+
try {
|
|
286
|
+
process.kill(pid, 0)
|
|
287
|
+
alive = true
|
|
288
|
+
} catch {}
|
|
289
|
+
|
|
290
|
+
if (alive) {
|
|
291
|
+
console.log(warningText('ChromaDB server started but not yet responding.'))
|
|
292
|
+
console.log(dimText(`PID: ${pid} — it may still be initializing.`))
|
|
293
|
+
console.log(dimText('Check with: ') + theme.primary('claude-brain chroma status'))
|
|
294
|
+
} else {
|
|
295
|
+
console.log(errorText('ChromaDB server failed to start.'))
|
|
296
|
+
if (startupOutput) {
|
|
297
|
+
console.log(dimText('Output:'))
|
|
298
|
+
console.log(dimText(startupOutput.slice(0, 500)))
|
|
299
|
+
}
|
|
300
|
+
try { unlinkSync(getPidFilePath()) } catch {}
|
|
301
|
+
process.exit(1)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
console.log()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function chromaStop(): Promise<void> {
|
|
308
|
+
console.log()
|
|
309
|
+
console.log(heading('Stopping ChromaDB Server'))
|
|
310
|
+
console.log()
|
|
311
|
+
|
|
312
|
+
const pid = getRunningPid()
|
|
313
|
+
if (!pid) {
|
|
314
|
+
if (isChromaReachable()) {
|
|
315
|
+
console.log(warningText('A ChromaDB server is running on port ' + DEFAULT_PORT + ' but was not started by claude-brain.'))
|
|
316
|
+
console.log(dimText('Kill it manually if needed.'))
|
|
317
|
+
} else {
|
|
318
|
+
console.log(dimText('No ChromaDB server is currently running.'))
|
|
319
|
+
}
|
|
320
|
+
console.log()
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
process.kill(pid, 'SIGTERM')
|
|
326
|
+
|
|
327
|
+
// Wait up to 5 seconds for graceful shutdown
|
|
328
|
+
for (let i = 0; i < 10; i++) {
|
|
329
|
+
await sleep(500)
|
|
330
|
+
try {
|
|
331
|
+
process.kill(pid, 0)
|
|
332
|
+
} catch {
|
|
333
|
+
// Process exited
|
|
334
|
+
break
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Force kill if still running
|
|
339
|
+
try {
|
|
340
|
+
process.kill(pid, 0)
|
|
341
|
+
// Still alive, force kill
|
|
342
|
+
process.kill(pid, 'SIGKILL')
|
|
343
|
+
} catch {
|
|
344
|
+
// Already dead, good
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
try { unlinkSync(getPidFilePath()) } catch {}
|
|
348
|
+
|
|
349
|
+
console.log(successText(`ChromaDB server stopped (PID: ${pid})`))
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.log(errorText(`Failed to stop ChromaDB: ${error instanceof Error ? error.message : String(error)}`))
|
|
352
|
+
try { unlinkSync(getPidFilePath()) } catch {}
|
|
353
|
+
}
|
|
354
|
+
console.log()
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function chromaStatus(): Promise<void> {
|
|
358
|
+
console.log()
|
|
359
|
+
|
|
360
|
+
const pid = getRunningPid()
|
|
361
|
+
const reachable = isChromaReachable()
|
|
362
|
+
const installed = isChromaCliInstalled()
|
|
363
|
+
const version = installed ? getChromaVersion() : 'N/A'
|
|
364
|
+
const dataPath = getChromaDataPath()
|
|
365
|
+
const dataExists = existsSync(dataPath)
|
|
366
|
+
|
|
367
|
+
const items: Record<string, string> = {
|
|
368
|
+
'Installed': installed ? `Yes (${version})` : 'No',
|
|
369
|
+
'Server': reachable ? `Running (port ${DEFAULT_PORT})` : 'Not running',
|
|
370
|
+
'Managed PID': pid ? String(pid) : 'None',
|
|
371
|
+
'Data Path': dataPath,
|
|
372
|
+
'Data Exists': dataExists ? 'Yes' : 'No',
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
console.log(infoPanel('ChromaDB Status', items))
|
|
376
|
+
|
|
377
|
+
if (!installed) {
|
|
378
|
+
console.log()
|
|
379
|
+
console.log(warningText('ChromaDB is not installed.'))
|
|
380
|
+
console.log(dimText('Install with: ') + theme.primary('claude-brain chroma install'))
|
|
381
|
+
} else if (!reachable) {
|
|
382
|
+
console.log()
|
|
383
|
+
console.log(dimText('Start the server with: ') + theme.primary('claude-brain chroma start'))
|
|
384
|
+
}
|
|
385
|
+
console.log()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function chromaInstall(): Promise<void> {
|
|
389
|
+
console.log()
|
|
390
|
+
console.log(heading('Installing ChromaDB'))
|
|
391
|
+
console.log()
|
|
392
|
+
|
|
393
|
+
if (isChromaCliInstalled()) {
|
|
394
|
+
const version = getChromaVersion()
|
|
395
|
+
console.log(successText(`ChromaDB is already installed (${version}).`))
|
|
396
|
+
console.log()
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const python = isPythonInstalled()
|
|
401
|
+
if (!python.installed) {
|
|
402
|
+
console.log(errorText('Python 3 is required to install ChromaDB.'))
|
|
403
|
+
console.log()
|
|
404
|
+
console.log(dimText('Install Python 3 first:'))
|
|
405
|
+
console.log(` ${theme.primary('macOS:')} brew install python3`)
|
|
406
|
+
console.log(` ${theme.primary('Ubuntu:')} sudo apt install python3 python3-pip`)
|
|
407
|
+
console.log(` ${theme.primary('Windows:')} https://python.org/downloads`)
|
|
408
|
+
console.log()
|
|
409
|
+
process.exit(1)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
console.log(dimText(`Found ${python.cmd}. Installing chromadb via pip...`))
|
|
413
|
+
console.log()
|
|
414
|
+
|
|
415
|
+
const pipCommands = python.cmd === 'python3'
|
|
416
|
+
? ['pip3 install chromadb', 'python3 -m pip install chromadb']
|
|
417
|
+
: ['pip install chromadb', 'python -m pip install chromadb']
|
|
418
|
+
|
|
419
|
+
let installed = false
|
|
420
|
+
for (const cmd of pipCommands) {
|
|
421
|
+
const parts = cmd.split(' ')
|
|
422
|
+
const result = spawnSync(parts[0]!, parts.slice(1), { stdio: 'inherit', timeout: 300_000 })
|
|
423
|
+
if (!result.error && result.status === 0) {
|
|
424
|
+
installed = true
|
|
425
|
+
break
|
|
426
|
+
}
|
|
427
|
+
// Try next command
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
console.log()
|
|
431
|
+
if (installed) {
|
|
432
|
+
console.log(successText('ChromaDB installed successfully!'))
|
|
433
|
+
console.log()
|
|
434
|
+
console.log(dimText('Start the server with: ') + theme.primary('claude-brain chroma start'))
|
|
435
|
+
} else {
|
|
436
|
+
console.log(errorText('Failed to install ChromaDB.'))
|
|
437
|
+
console.log(dimText('Try installing manually:'))
|
|
438
|
+
console.log(` ${theme.primary('pip install chromadb')}`)
|
|
439
|
+
process.exit(1)
|
|
440
|
+
}
|
|
441
|
+
console.log()
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// ── Help ──────────────────────────────────────────────────
|
|
445
|
+
|
|
446
|
+
function printChromaHelp(): void {
|
|
447
|
+
console.log()
|
|
448
|
+
console.log(heading('ChromaDB Management'))
|
|
449
|
+
console.log()
|
|
450
|
+
console.log(dimText('ChromaDB provides vector storage for semantic search,'))
|
|
451
|
+
console.log(dimText('knowledge graph, and advanced intelligence features.'))
|
|
452
|
+
console.log()
|
|
453
|
+
|
|
454
|
+
const commands = [
|
|
455
|
+
['start', 'Start ChromaDB server in background'],
|
|
456
|
+
['stop', 'Stop the running ChromaDB server'],
|
|
457
|
+
['status', 'Show ChromaDB server status'],
|
|
458
|
+
['install', 'Install ChromaDB (requires Python 3)'],
|
|
459
|
+
]
|
|
460
|
+
|
|
461
|
+
const cmdLines = commands
|
|
462
|
+
.map(([cmd, desc]) => ` ${theme.primary(cmd!.padEnd(12))} ${dimText(desc!)}`)
|
|
463
|
+
.join('\n')
|
|
464
|
+
|
|
465
|
+
console.log(theme.bold('Commands:'))
|
|
466
|
+
console.log(cmdLines)
|
|
467
|
+
console.log()
|
|
468
|
+
|
|
469
|
+
console.log(theme.bold('Usage:'))
|
|
470
|
+
console.log(` ${dimText('claude-brain chroma install')} ${dimText('Install ChromaDB')}`)
|
|
471
|
+
console.log(` ${dimText('claude-brain chroma start')} ${dimText('Start the server')}`)
|
|
472
|
+
console.log(` ${dimText('claude-brain chroma status')} ${dimText('Check if running')}`)
|
|
473
|
+
console.log(` ${dimText('claude-brain chroma stop')} ${dimText('Stop the server')}`)
|
|
474
|
+
console.log()
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ── Auto-start for serve command ──────────────────────────
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Ensures ChromaDB is running before the MCP server starts.
|
|
481
|
+
* Returns true if ChromaDB is reachable after this call, false otherwise.
|
|
482
|
+
* Designed to be called from serve.ts — does not call process.exit().
|
|
483
|
+
*/
|
|
484
|
+
export async function ensureChromaRunning(options?: { silent?: boolean }): Promise<boolean> {
|
|
485
|
+
const log = options?.silent ? () => {} : console.error.bind(console)
|
|
486
|
+
|
|
487
|
+
// Already running? Great.
|
|
488
|
+
if (isChromaReachable()) {
|
|
489
|
+
return true
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Already managed by us but not responding? Clean up stale PID.
|
|
493
|
+
const stalePid = getRunningPid()
|
|
494
|
+
if (stalePid) {
|
|
495
|
+
try {
|
|
496
|
+
process.kill(stalePid, 'SIGTERM')
|
|
497
|
+
await sleep(1000)
|
|
498
|
+
} catch {}
|
|
499
|
+
try { unlinkSync(getPidFilePath()) } catch {}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const chromaBinary = getChromaBinary()
|
|
503
|
+
if (!chromaBinary) {
|
|
504
|
+
log('[ChromaDB] Not installed — running with SQLite fallback.')
|
|
505
|
+
log('[ChromaDB] Install with: claude-brain chroma install')
|
|
506
|
+
return false
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const dataPath = getChromaDataPath()
|
|
510
|
+
log(`[ChromaDB] Starting server (port ${DEFAULT_PORT})...`)
|
|
511
|
+
|
|
512
|
+
const child = spawn(chromaBinary, ['run', '--path', dataPath, '--port', DEFAULT_PORT], {
|
|
513
|
+
detached: true,
|
|
514
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
515
|
+
env: { ...process.env }
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
const pid = child.pid
|
|
519
|
+
if (!pid) {
|
|
520
|
+
log('[ChromaDB] Failed to spawn server process.')
|
|
521
|
+
return false
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
child.unref()
|
|
525
|
+
writeFileSync(getPidFilePath(), String(pid), 'utf-8')
|
|
526
|
+
|
|
527
|
+
// Wait for server to become reachable (up to 15 seconds)
|
|
528
|
+
for (let i = 0; i < 30; i++) {
|
|
529
|
+
await sleep(500)
|
|
530
|
+
if (isChromaReachable()) {
|
|
531
|
+
log(`[ChromaDB] Server started (PID: ${pid})`)
|
|
532
|
+
return true
|
|
533
|
+
}
|
|
534
|
+
// Check if process died
|
|
535
|
+
try {
|
|
536
|
+
process.kill(pid, 0)
|
|
537
|
+
} catch {
|
|
538
|
+
break
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
log('[ChromaDB] Server started but not yet responding — will retry connection during initialization.')
|
|
543
|
+
return isChromaReachable()
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ── Entry Point ───────────────────────────────────────────
|
|
547
|
+
|
|
548
|
+
export async function runChroma(): Promise<void> {
|
|
549
|
+
const args = parseArgs(process.argv.slice(3), {
|
|
550
|
+
subcommand: { type: 'positional', required: false, description: 'start|stop|status|install|help' },
|
|
551
|
+
})
|
|
552
|
+
const subcommand: string = args.subcommand ?? 'help'
|
|
553
|
+
|
|
554
|
+
switch (subcommand) {
|
|
555
|
+
case 'start':
|
|
556
|
+
await chromaStart()
|
|
557
|
+
break
|
|
558
|
+
case 'stop':
|
|
559
|
+
await chromaStop()
|
|
560
|
+
break
|
|
561
|
+
case 'status':
|
|
562
|
+
await chromaStatus()
|
|
563
|
+
break
|
|
564
|
+
case 'install':
|
|
565
|
+
await chromaInstall()
|
|
566
|
+
break
|
|
567
|
+
case 'help':
|
|
568
|
+
case '--help':
|
|
569
|
+
case '-h':
|
|
570
|
+
printChromaHelp()
|
|
571
|
+
break
|
|
572
|
+
default:
|
|
573
|
+
console.log()
|
|
574
|
+
console.log(errorText(`Unknown subcommand: ${subcommand}`))
|
|
575
|
+
printChromaHelp()
|
|
576
|
+
process.exit(1)
|
|
577
|
+
}
|
|
578
|
+
}
|