claude-brain 0.5.0 → 0.5.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 CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.5.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -29,25 +29,53 @@ function getChromaDataPath(): string {
29
29
  * Find the chroma binary - checks PATH first, then common pip install locations
30
30
  */
31
31
  function findChromaBinary(): string | null {
32
+ const isWindows = process.platform === 'win32'
33
+ const chromaName = isWindows ? 'chroma.exe' : 'chroma'
34
+
32
35
  // Try bare 'chroma' first (on PATH)
33
36
  try {
34
37
  execSync('chroma --version', { stdio: 'pipe', timeout: 5000 })
35
38
  return 'chroma'
36
39
  } catch {}
37
40
 
38
- // Search common pip install locations
39
41
  const { homedir } = require('os')
40
42
  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
- ]
43
+
44
+ // Platform-specific search paths
45
+ const candidates: string[] = isWindows
46
+ ? [
47
+ // Windows pip install locations
48
+ join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python39', 'Scripts', chromaName),
49
+ join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python310', 'Scripts', chromaName),
50
+ join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python311', 'Scripts', chromaName),
51
+ join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python312', 'Scripts', chromaName),
52
+ join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python313', 'Scripts', chromaName),
53
+ // Windows user pip install (pip install --user)
54
+ join(home, 'AppData', 'Roaming', 'Python', 'Python39', 'Scripts', chromaName),
55
+ join(home, 'AppData', 'Roaming', 'Python', 'Python310', 'Scripts', chromaName),
56
+ join(home, 'AppData', 'Roaming', 'Python', 'Python311', 'Scripts', chromaName),
57
+ join(home, 'AppData', 'Roaming', 'Python', 'Python312', 'Scripts', chromaName),
58
+ join(home, 'AppData', 'Roaming', 'Python', 'Python313', 'Scripts', chromaName),
59
+ // Scoop / Chocolatey / winget
60
+ join(home, 'scoop', 'shims', chromaName),
61
+ 'C:\\Python39\\Scripts\\' + chromaName,
62
+ 'C:\\Python310\\Scripts\\' + chromaName,
63
+ 'C:\\Python311\\Scripts\\' + chromaName,
64
+ 'C:\\Python312\\Scripts\\' + chromaName,
65
+ 'C:\\Python313\\Scripts\\' + chromaName,
66
+ ]
67
+ : [
68
+ // macOS pip install locations
69
+ join(home, 'Library', 'Python', '3.9', 'bin', chromaName),
70
+ join(home, 'Library', 'Python', '3.10', 'bin', chromaName),
71
+ join(home, 'Library', 'Python', '3.11', 'bin', chromaName),
72
+ join(home, 'Library', 'Python', '3.12', 'bin', chromaName),
73
+ join(home, 'Library', 'Python', '3.13', 'bin', chromaName),
74
+ // Linux pip install locations
75
+ join(home, '.local', 'bin', chromaName),
76
+ '/usr/local/bin/' + chromaName,
77
+ '/opt/homebrew/bin/' + chromaName,
78
+ ]
51
79
 
52
80
  for (const candidate of candidates) {
53
81
  try {
@@ -58,15 +86,23 @@ function findChromaBinary(): string | null {
58
86
  } catch {}
59
87
  }
60
88
 
61
- // Try finding via python -m site
89
+ // Try finding via python -m site (works on all platforms)
90
+ const pythonCmd = isWindows ? 'python' : 'python3'
62
91
  try {
63
- const sitePackages = execSync('python3 -c "import site; print(site.getusersitepackages())"', {
92
+ const sitePackages = execSync(`${pythonCmd} -c "import site; print(site.getusersitepackages())"`, {
64
93
  encoding: 'utf-8', stdio: 'pipe', timeout: 5000
65
94
  }).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')
95
+
96
+ let binDir: string
97
+ if (isWindows) {
98
+ // Windows: C:\Users\x\AppData\Roaming\Python\Python311\site-packages Scripts
99
+ binDir = sitePackages.replace(/[\\\/]site-packages$/, '\\Scripts')
100
+ } else {
101
+ // Unix: /Users/x/Library/Python/3.9/lib/python/site-packages → bin
102
+ binDir = sitePackages.replace(/\/lib\/.*/, '/bin')
103
+ }
104
+
105
+ const chromaPath = join(binDir, chromaName)
70
106
  if (existsSync(chromaPath)) {
71
107
  execSync(`"${chromaPath}" --version`, { stdio: 'pipe', timeout: 5000 })
72
108
  return chromaPath
@@ -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.5.0',
6
+ serverVersion: '0.5.1',
7
7
  logLevel: 'info',
8
8
  logFilePath: './logs/claude-brain.log',
9
9
  dbPath: './data/memory.db',
@@ -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.5.0'),
199
+ serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.5.1'),
200
200
 
201
201
  /** Logging level */
202
202
  logLevel: LogLevelSchema.default('info'),
@@ -92,7 +92,7 @@ export class ChromaClientManager {
92
92
  }
93
93
 
94
94
  } catch (error) {
95
- this.logger.error({ error }, 'Failed to initialize ChromaDB client')
95
+ this.logger.warn({ error }, 'ChromaDB not available, will use SQLite fallback')
96
96
  throw error
97
97
  }
98
98
  }
@@ -54,7 +54,7 @@ export class ChromaManager {
54
54
  this.logger.info('ChromaDB initialized successfully')
55
55
 
56
56
  } catch (error) {
57
- this.logger.error({ error }, 'Failed to initialize ChromaDB')
57
+ this.logger.warn({ error }, 'ChromaDB unavailable')
58
58
  throw error
59
59
  }
60
60
  }
@@ -6,6 +6,25 @@ import type { DecisionMetadata, MemoryMetadata } from './schemas'
6
6
  import type { EmbeddingProvider } from './embeddings'
7
7
  import type { SearchResult } from './search'
8
8
 
9
+ /**
10
+ * Sanitize metadata for ChromaDB v3.x compatibility.
11
+ * Strips undefined/null values (ChromaDB only accepts string, number, boolean).
12
+ */
13
+ function sanitizeMetadata(metadata: Record<string, any>): Record<string, string | number | boolean> {
14
+ const clean: Record<string, string | number | boolean> = {}
15
+ for (const [key, value] of Object.entries(metadata)) {
16
+ if (value === undefined || value === null) continue
17
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
18
+ clean[key] = value
19
+ } else if (Array.isArray(value)) {
20
+ clean[key] = JSON.stringify(value)
21
+ } else {
22
+ clean[key] = String(value)
23
+ }
24
+ }
25
+ return clean
26
+ }
27
+
9
28
  export interface StoreDecisionInput {
10
29
  project: string
11
30
  context: string
@@ -145,7 +164,7 @@ export class ChromaMemoryStore {
145
164
  await collection.add({
146
165
  ids: [id],
147
166
  documents: [input.decision],
148
- metadatas: [metadata as Record<string, any>],
167
+ metadatas: [sanitizeMetadata(metadata as Record<string, any>)],
149
168
  ...(embeddings ? { embeddings } : {})
150
169
  })
151
170
 
@@ -166,7 +185,7 @@ export class ChromaMemoryStore {
166
185
  await memoriesCollection.add({
167
186
  ids: [id], // Use same ID for cross-reference
168
187
  documents: [memoryContent],
169
- metadatas: [memoryMetadata],
188
+ metadatas: [sanitizeMetadata(memoryMetadata)],
170
189
  ...(embeddings ? { embeddings } : {})
171
190
  })
172
191
 
@@ -230,7 +249,7 @@ export class ChromaMemoryStore {
230
249
  await collection.add({
231
250
  ids: [id],
232
251
  documents: [input.description],
233
- metadatas: [metadata],
252
+ metadatas: [sanitizeMetadata(metadata)],
234
253
  ...(embeddings ? { embeddings } : {})
235
254
  })
236
255
 
@@ -252,7 +271,7 @@ export class ChromaMemoryStore {
252
271
  await memoriesCollection.add({
253
272
  ids: [id],
254
273
  documents: [memoryContent],
255
- metadatas: [memoryMetadata],
274
+ metadatas: [sanitizeMetadata(memoryMetadata)],
256
275
  ...(embeddings ? { embeddings } : {})
257
276
  })
258
277
 
@@ -304,7 +323,7 @@ export class ChromaMemoryStore {
304
323
  await collection.add({
305
324
  ids: [id],
306
325
  documents: [input.correction],
307
- metadatas: [metadata],
326
+ metadatas: [sanitizeMetadata(metadata)],
308
327
  ...(embeddings ? { embeddings } : {})
309
328
  })
310
329
 
@@ -325,7 +344,7 @@ export class ChromaMemoryStore {
325
344
  await memoriesCollection.add({
326
345
  ids: [id],
327
346
  documents: [memoryContent],
328
- metadatas: [memoryMetadata],
347
+ metadatas: [sanitizeMetadata(memoryMetadata)],
329
348
  ...(embeddings ? { embeddings } : {})
330
349
  })
331
350
 
@@ -367,7 +386,7 @@ export class ChromaMemoryStore {
367
386
  await collection.add({
368
387
  ids: [id],
369
388
  documents: [input.content],
370
- metadatas: [metadata as Record<string, any>],
389
+ metadatas: [sanitizeMetadata(metadata as Record<string, any>)],
371
390
  ...(embeddings ? { embeddings } : {})
372
391
  })
373
392
 
@@ -409,7 +428,7 @@ export class ChromaMemoryStore {
409
428
  await collection.upsert({
410
429
  ids: [id],
411
430
  documents: [input.decision],
412
- metadatas: [metadata as Record<string, any>],
431
+ metadatas: [sanitizeMetadata(metadata as Record<string, any>)],
413
432
  ...(embeddings ? { embeddings } : {})
414
433
  })
415
434