claude-brain 0.14.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/package.json +3 -1
- package/scripts/postinstall.mjs +341 -0
- package/src/cli/auto-update.ts +157 -0
- package/src/cli/commands/serve.ts +6 -0
- package/src/config/defaults.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/memory/chroma/embeddings.ts +4 -2
- package/src/memory/chroma/index.ts +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.15.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-brain",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"src/**/*.ts",
|
|
12
|
+
"scripts/postinstall.mjs",
|
|
12
13
|
"packs/",
|
|
13
14
|
"assets/",
|
|
14
15
|
"package.json",
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
"LICENSE"
|
|
20
21
|
],
|
|
21
22
|
"scripts": {
|
|
23
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
22
24
|
"dev": "CLAUDE_BRAIN_HOME=. bun --watch src/index.ts",
|
|
23
25
|
"build": "bun build src/index.ts --outdir dist --target bun",
|
|
24
26
|
"build:binary": "bun build src/cli/bin.ts --compile --outfile claude-brain",
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postinstall script for claude-brain.
|
|
5
|
+
* Runs automatically after `npm install -g claude-brain` or `bun install -g claude-brain`.
|
|
6
|
+
* Pure Node.js — no TypeScript, no path aliases, no Bun APIs.
|
|
7
|
+
* Always exits 0 so it never blocks the install.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, renameSync } from 'node:fs'
|
|
11
|
+
import { join, dirname } from 'node:path'
|
|
12
|
+
import { homedir } from 'node:os'
|
|
13
|
+
import { execSync } from 'node:child_process'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
17
|
+
const __dirname = dirname(__filename)
|
|
18
|
+
|
|
19
|
+
const PREFIX = '[claude-brain]'
|
|
20
|
+
const HOME = join(homedir(), '.claude-brain')
|
|
21
|
+
const CLAUDE_DIR = join(homedir(), '.claude')
|
|
22
|
+
const CLAUDE_SETTINGS = join(CLAUDE_DIR, 'settings.json')
|
|
23
|
+
const CLAUDE_MD_PATH = join(CLAUDE_DIR, 'CLAUDE.md')
|
|
24
|
+
const HOOK_MARKER = 'claude-brain-hook'
|
|
25
|
+
|
|
26
|
+
function log(msg) {
|
|
27
|
+
console.error(`${PREFIX} ${msg}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Step 0: Skip if not a global install ─────────────────
|
|
31
|
+
|
|
32
|
+
function shouldSkip() {
|
|
33
|
+
// CI environments
|
|
34
|
+
if (process.env.CI === 'true' || process.env.CI === '1') {
|
|
35
|
+
return 'CI environment detected'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// If we're inside a node_modules directory, it's a local install
|
|
39
|
+
const scriptDir = dirname(__filename)
|
|
40
|
+
if (scriptDir.includes('node_modules') && !scriptDir.includes('global')) {
|
|
41
|
+
// Check if this is actually a global install by looking deeper
|
|
42
|
+
// bun global installs go to ~/.bun/install/global/node_modules
|
|
43
|
+
// npm global installs go to /usr/local/lib/node_modules or ~/.npm-global
|
|
44
|
+
const isGlobalPath =
|
|
45
|
+
scriptDir.includes('.bun/install/global') ||
|
|
46
|
+
scriptDir.includes('/usr/local/lib/node_modules') ||
|
|
47
|
+
scriptDir.includes('/usr/lib/node_modules') ||
|
|
48
|
+
scriptDir.includes('npm-global') ||
|
|
49
|
+
scriptDir.includes('AppData/Roaming/npm')
|
|
50
|
+
|
|
51
|
+
if (!isGlobalPath) {
|
|
52
|
+
return 'local install (not global)'
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Step 1: Create ~/.claude-brain/ ──────────────────────
|
|
60
|
+
|
|
61
|
+
function setupHomeDirectory() {
|
|
62
|
+
if (existsSync(join(HOME, 'data'))) {
|
|
63
|
+
log('Home directory already initialized')
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log(`Setting up ${HOME}/...`)
|
|
68
|
+
|
|
69
|
+
const dirs = [
|
|
70
|
+
join(HOME, 'data'),
|
|
71
|
+
join(HOME, 'data', 'chroma'),
|
|
72
|
+
join(HOME, 'logs'),
|
|
73
|
+
join(HOME, 'vault'),
|
|
74
|
+
join(HOME, 'vault', 'Projects'),
|
|
75
|
+
join(HOME, 'vault', 'Global'),
|
|
76
|
+
join(HOME, 'hooks'),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
for (const dir of dirs) {
|
|
80
|
+
mkdirSync(dir, { recursive: true })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Write default .env if not present
|
|
84
|
+
const envPath = join(HOME, '.env')
|
|
85
|
+
if (!existsSync(envPath)) {
|
|
86
|
+
writeFileSync(envPath, `# Claude Brain Configuration
|
|
87
|
+
# Generated by postinstall
|
|
88
|
+
VAULT_PATH=${join(HOME, 'vault')}
|
|
89
|
+
LOG_LEVEL=info
|
|
90
|
+
NODE_ENV=production
|
|
91
|
+
|
|
92
|
+
# ChromaDB Configuration
|
|
93
|
+
CHROMA_MODE=client-server
|
|
94
|
+
CHROMA_HOST=localhost
|
|
95
|
+
CHROMA_PORT=8000
|
|
96
|
+
CHROMA_EMBEDDING_PROVIDER=transformers
|
|
97
|
+
`, 'utf-8')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Write default global standards
|
|
101
|
+
const standardsPath = join(HOME, 'vault', 'Global', 'standards.md')
|
|
102
|
+
if (!existsSync(standardsPath)) {
|
|
103
|
+
writeFileSync(standardsPath, `---
|
|
104
|
+
type: global-standards
|
|
105
|
+
last_updated: ${new Date().toISOString().split('T')[0]}
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
# Global Coding Standards
|
|
109
|
+
|
|
110
|
+
## General
|
|
111
|
+
- Write clear, readable code
|
|
112
|
+
- Prefer explicit over implicit
|
|
113
|
+
- Keep functions focused and small
|
|
114
|
+
|
|
115
|
+
## TypeScript
|
|
116
|
+
- Use strict mode
|
|
117
|
+
- Prefer const over let
|
|
118
|
+
- Add JSDoc comments for public APIs
|
|
119
|
+
`, 'utf-8')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
log('Home directory initialized')
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Step 2: Install ChromaDB via pip ─────────────────────
|
|
127
|
+
|
|
128
|
+
function installChromaDB() {
|
|
129
|
+
// Check if already installed
|
|
130
|
+
try {
|
|
131
|
+
execSync('chroma --version', { stdio: 'pipe', timeout: 5000 })
|
|
132
|
+
log('ChromaDB already installed')
|
|
133
|
+
return true
|
|
134
|
+
} catch {}
|
|
135
|
+
|
|
136
|
+
// Check for Python
|
|
137
|
+
let pythonCmd = null
|
|
138
|
+
for (const cmd of ['python3', 'python']) {
|
|
139
|
+
try {
|
|
140
|
+
const ver = execSync(`${cmd} --version`, { encoding: 'utf-8', stdio: 'pipe', timeout: 5000 })
|
|
141
|
+
if (ver.includes('3.')) {
|
|
142
|
+
pythonCmd = cmd
|
|
143
|
+
break
|
|
144
|
+
}
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!pythonCmd) {
|
|
149
|
+
log('Python 3 not found — skipping ChromaDB install (SQLite fallback will be used)')
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
log('Installing ChromaDB...')
|
|
154
|
+
|
|
155
|
+
const pipCommands = pythonCmd === 'python3'
|
|
156
|
+
? ['pip3 install chromadb', 'python3 -m pip install chromadb']
|
|
157
|
+
: ['pip install chromadb', 'python -m pip install chromadb']
|
|
158
|
+
|
|
159
|
+
for (const cmd of pipCommands) {
|
|
160
|
+
try {
|
|
161
|
+
execSync(cmd, { stdio: 'pipe', timeout: 300_000 })
|
|
162
|
+
log('ChromaDB installed')
|
|
163
|
+
return true
|
|
164
|
+
} catch {}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
log('ChromaDB install failed — SQLite fallback will be used')
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Step 3: Register MCP server ──────────────────────────
|
|
172
|
+
|
|
173
|
+
function registerMcpServer() {
|
|
174
|
+
// Check if claude CLI exists
|
|
175
|
+
try {
|
|
176
|
+
execSync('claude --version', { stdio: 'pipe', timeout: 5000 })
|
|
177
|
+
} catch {
|
|
178
|
+
log('Claude CLI not found — skipping MCP registration')
|
|
179
|
+
log(' Run manually: claude mcp add claude-brain -- claude-brain serve')
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check if already configured
|
|
184
|
+
try {
|
|
185
|
+
const result = execSync('claude mcp list', { encoding: 'utf-8', stdio: 'pipe', timeout: 10000 })
|
|
186
|
+
if (result.includes('claude-brain')) {
|
|
187
|
+
log('MCP server already registered')
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
} catch {}
|
|
191
|
+
|
|
192
|
+
// Register
|
|
193
|
+
try {
|
|
194
|
+
execSync('claude mcp add claude-brain -- claude-brain serve', {
|
|
195
|
+
stdio: 'pipe',
|
|
196
|
+
timeout: 15000,
|
|
197
|
+
})
|
|
198
|
+
log('Registered as MCP server in Claude Code')
|
|
199
|
+
return true
|
|
200
|
+
} catch (err) {
|
|
201
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
202
|
+
if (msg.includes('already') || msg.includes('exists')) {
|
|
203
|
+
log('MCP server already registered')
|
|
204
|
+
return true
|
|
205
|
+
}
|
|
206
|
+
log('MCP registration failed — run manually: claude mcp add claude-brain -- claude-brain serve')
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Step 4: Install hooks ────────────────────────────────
|
|
212
|
+
|
|
213
|
+
function installHooks() {
|
|
214
|
+
// Read existing settings
|
|
215
|
+
let settings = {}
|
|
216
|
+
if (existsSync(CLAUDE_SETTINGS)) {
|
|
217
|
+
try {
|
|
218
|
+
settings = JSON.parse(readFileSync(CLAUDE_SETTINGS, 'utf-8'))
|
|
219
|
+
} catch {}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check if already installed
|
|
223
|
+
function hasOurHooks(entries) {
|
|
224
|
+
if (!Array.isArray(entries)) return false
|
|
225
|
+
return entries.some(entry =>
|
|
226
|
+
entry && Array.isArray(entry.hooks) &&
|
|
227
|
+
entry.hooks.some(h => typeof h.command === 'string' && h.command.includes(HOOK_MARKER))
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (settings.hooks && (hasOurHooks(settings.hooks.PostToolUse) || hasOurHooks(settings.hooks.Stop))) {
|
|
232
|
+
log('Hooks already installed')
|
|
233
|
+
return true
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Build hook command
|
|
237
|
+
const scriptPath = join(HOME, 'hooks', 'brain-hook.ts')
|
|
238
|
+
function buildCmd(event) {
|
|
239
|
+
return `bun "${scriptPath}" --event ${event} # ${HOOK_MARKER}`
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!settings.hooks) settings.hooks = {}
|
|
243
|
+
|
|
244
|
+
// PostToolUse
|
|
245
|
+
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = []
|
|
246
|
+
settings.hooks.PostToolUse.push({
|
|
247
|
+
matcher: '',
|
|
248
|
+
hooks: [{ type: 'command', command: buildCmd('PostToolUse') }],
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
// Stop
|
|
252
|
+
if (!settings.hooks.Stop) settings.hooks.Stop = []
|
|
253
|
+
settings.hooks.Stop.push({
|
|
254
|
+
matcher: '',
|
|
255
|
+
hooks: [{ type: 'command', command: buildCmd('Stop') }],
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// Write atomically
|
|
259
|
+
if (!existsSync(CLAUDE_DIR)) {
|
|
260
|
+
mkdirSync(CLAUDE_DIR, { recursive: true })
|
|
261
|
+
}
|
|
262
|
+
const tmpPath = CLAUDE_SETTINGS + '.tmp'
|
|
263
|
+
writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8')
|
|
264
|
+
renameSync(tmpPath, CLAUDE_SETTINGS)
|
|
265
|
+
|
|
266
|
+
log('Hooks installed')
|
|
267
|
+
return true
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── Step 5: Install CLAUDE.md ────────────────────────────
|
|
271
|
+
|
|
272
|
+
function installClaudeMd() {
|
|
273
|
+
// Find the assets/CLAUDE.md relative to this script
|
|
274
|
+
// scripts/postinstall.mjs → assets/CLAUDE.md
|
|
275
|
+
const assetsPath = join(__dirname, '..', 'assets', 'CLAUDE.md')
|
|
276
|
+
|
|
277
|
+
if (!existsSync(assetsPath)) {
|
|
278
|
+
log('CLAUDE.md asset not found — skipping')
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Only install if no CLAUDE.md exists yet (don't overwrite user customizations)
|
|
283
|
+
if (existsSync(CLAUDE_MD_PATH)) {
|
|
284
|
+
// Check if it already mentions claude-brain
|
|
285
|
+
const existing = readFileSync(CLAUDE_MD_PATH, 'utf-8')
|
|
286
|
+
if (existing.includes('brain') || existing.includes('Brain')) {
|
|
287
|
+
log('CLAUDE.md already configured')
|
|
288
|
+
return true
|
|
289
|
+
}
|
|
290
|
+
// Append our section
|
|
291
|
+
const addition = readFileSync(assetsPath, 'utf-8')
|
|
292
|
+
writeFileSync(CLAUDE_MD_PATH, existing.trimEnd() + '\n\n' + addition, 'utf-8')
|
|
293
|
+
log('Appended brain instructions to existing CLAUDE.md')
|
|
294
|
+
return true
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Create new
|
|
298
|
+
if (!existsSync(CLAUDE_DIR)) {
|
|
299
|
+
mkdirSync(CLAUDE_DIR, { recursive: true })
|
|
300
|
+
}
|
|
301
|
+
const content = readFileSync(assetsPath, 'utf-8')
|
|
302
|
+
writeFileSync(CLAUDE_MD_PATH, content, 'utf-8')
|
|
303
|
+
log('Installed CLAUDE.md')
|
|
304
|
+
return true
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ── Main ─────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
async function main() {
|
|
310
|
+
const skipReason = shouldSkip()
|
|
311
|
+
if (skipReason) {
|
|
312
|
+
log(`Skipping postinstall (${skipReason})`)
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
log('Running postinstall setup...')
|
|
317
|
+
console.error('')
|
|
318
|
+
|
|
319
|
+
const results = {
|
|
320
|
+
home: false,
|
|
321
|
+
chromadb: false,
|
|
322
|
+
mcp: false,
|
|
323
|
+
hooks: false,
|
|
324
|
+
claudemd: false,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
try { results.home = setupHomeDirectory() } catch (e) { log(`Home setup failed: ${e.message}`) }
|
|
328
|
+
try { results.chromadb = installChromaDB() } catch (e) { log(`ChromaDB install failed: ${e.message}`) }
|
|
329
|
+
try { results.mcp = registerMcpServer() } catch (e) { log(`MCP registration failed: ${e.message}`) }
|
|
330
|
+
try { results.hooks = installHooks() } catch (e) { log(`Hook install failed: ${e.message}`) }
|
|
331
|
+
try { results.claudemd = installClaudeMd() } catch (e) { log(`CLAUDE.md install failed: ${e.message}`) }
|
|
332
|
+
|
|
333
|
+
console.error('')
|
|
334
|
+
log('Setup complete! Restart Claude Code to activate.')
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
main().catch(err => {
|
|
338
|
+
log(`Postinstall error: ${err.message}`)
|
|
339
|
+
}).finally(() => {
|
|
340
|
+
process.exit(0)
|
|
341
|
+
})
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background auto-update check for claude-brain.
|
|
3
|
+
* Runs on every `serve` start, rate-limited to 1 check per hour.
|
|
4
|
+
* All output to stderr (stdout reserved for MCP JSON-RPC).
|
|
5
|
+
* Never throws — all errors silently caught.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
9
|
+
import { join, dirname } from 'node:path'
|
|
10
|
+
import { execSync } from 'node:child_process'
|
|
11
|
+
import { getHomePaths } from '@/config/home'
|
|
12
|
+
|
|
13
|
+
const CHECK_INTERVAL_MS = 60 * 60 * 1000 // 1 hour
|
|
14
|
+
const TIMESTAMP_FILE = 'last-update-check'
|
|
15
|
+
|
|
16
|
+
function getTimestampPath(): string {
|
|
17
|
+
return join(getHomePaths().data, TIMESTAMP_FILE)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function shouldCheck(): boolean {
|
|
21
|
+
const tsPath = getTimestampPath()
|
|
22
|
+
if (!existsSync(tsPath)) return true
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const lastCheck = parseInt(readFileSync(tsPath, 'utf-8').trim(), 10)
|
|
26
|
+
return Date.now() - lastCheck > CHECK_INTERVAL_MS
|
|
27
|
+
} catch {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writeTimestamp(): void {
|
|
33
|
+
const tsPath = getTimestampPath()
|
|
34
|
+
const dir = dirname(tsPath)
|
|
35
|
+
if (!existsSync(dir)) {
|
|
36
|
+
mkdirSync(dir, { recursive: true })
|
|
37
|
+
}
|
|
38
|
+
writeFileSync(tsPath, String(Date.now()), 'utf-8')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getCurrentVersion(): string | null {
|
|
42
|
+
try {
|
|
43
|
+
// Read from our own package.json
|
|
44
|
+
const pkgPath = join(dirname(new URL(import.meta.url).pathname), '..', '..', 'package.json')
|
|
45
|
+
if (existsSync(pkgPath)) {
|
|
46
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
47
|
+
return pkg.version || null
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
|
|
51
|
+
// Fallback: read VERSION file
|
|
52
|
+
try {
|
|
53
|
+
const versionPath = join(dirname(new URL(import.meta.url).pathname), '..', '..', 'VERSION')
|
|
54
|
+
if (existsSync(versionPath)) {
|
|
55
|
+
return readFileSync(versionPath, 'utf-8').trim()
|
|
56
|
+
}
|
|
57
|
+
} catch {}
|
|
58
|
+
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getLatestVersion(): string | null {
|
|
63
|
+
try {
|
|
64
|
+
const result = execSync('npm view claude-brain version', {
|
|
65
|
+
encoding: 'utf-8',
|
|
66
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
67
|
+
timeout: 10_000,
|
|
68
|
+
})
|
|
69
|
+
return result.trim() || null
|
|
70
|
+
} catch {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isNewer(latest: string, current: string): boolean {
|
|
76
|
+
const parse = (v: string) => v.split('.').map(Number)
|
|
77
|
+
const [lMaj, lMin, lPat] = parse(latest)
|
|
78
|
+
const [cMaj, cMin, cPat] = parse(current)
|
|
79
|
+
|
|
80
|
+
if (lMaj !== cMaj) return lMaj > cMaj
|
|
81
|
+
if (lMin !== cMin) return lMin > cMin
|
|
82
|
+
return lPat > cPat
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function performUpdate(): boolean {
|
|
86
|
+
// Try bun first, fallback to npm
|
|
87
|
+
const commands = [
|
|
88
|
+
'bun update -g claude-brain',
|
|
89
|
+
'npm update -g claude-brain',
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
for (const cmd of commands) {
|
|
93
|
+
try {
|
|
94
|
+
execSync(cmd, {
|
|
95
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
96
|
+
timeout: 120_000,
|
|
97
|
+
})
|
|
98
|
+
return true
|
|
99
|
+
} catch {}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function postUpdateTasks(): void {
|
|
106
|
+
// Re-install hooks (idempotent)
|
|
107
|
+
try {
|
|
108
|
+
const { installHooks } = require('@/hooks/installer')
|
|
109
|
+
installHooks()
|
|
110
|
+
} catch {}
|
|
111
|
+
|
|
112
|
+
// Re-install CLAUDE.md if asset exists
|
|
113
|
+
try {
|
|
114
|
+
const { existsSync: exists, readFileSync: read, writeFileSync: write } = require('node:fs')
|
|
115
|
+
const { join: pjoin } = require('node:path')
|
|
116
|
+
const { homedir } = require('node:os')
|
|
117
|
+
|
|
118
|
+
const assetsPath = pjoin(dirname(new URL(import.meta.url).pathname), '..', '..', 'assets', 'CLAUDE.md')
|
|
119
|
+
const claudeMdPath = pjoin(homedir(), '.claude', 'CLAUDE.md')
|
|
120
|
+
|
|
121
|
+
if (exists(assetsPath)) {
|
|
122
|
+
if (!exists(claudeMdPath)) {
|
|
123
|
+
write(claudeMdPath, read(assetsPath, 'utf-8'), 'utf-8')
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function checkAndUpdate(): Promise<void> {
|
|
130
|
+
if (!shouldCheck()) return
|
|
131
|
+
|
|
132
|
+
writeTimestamp()
|
|
133
|
+
|
|
134
|
+
const current = getCurrentVersion()
|
|
135
|
+
if (!current) return
|
|
136
|
+
|
|
137
|
+
const latest = getLatestVersion()
|
|
138
|
+
if (!latest) return
|
|
139
|
+
|
|
140
|
+
if (!isNewer(latest, current)) return
|
|
141
|
+
|
|
142
|
+
console.error(`[claude-brain] Update available: ${current} → ${latest}`)
|
|
143
|
+
|
|
144
|
+
const updated = performUpdate()
|
|
145
|
+
if (updated) {
|
|
146
|
+
console.error(`[claude-brain] Updated to ${latest}`)
|
|
147
|
+
postUpdateTasks()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Fire-and-forget background update check.
|
|
153
|
+
* Call this from serve.ts — it never rejects.
|
|
154
|
+
*/
|
|
155
|
+
export function checkForUpdateInBackground(): Promise<void> {
|
|
156
|
+
return checkAndUpdate().catch(() => {})
|
|
157
|
+
}
|
|
@@ -18,6 +18,12 @@ export async function runServe() {
|
|
|
18
18
|
// Auto-initialize home directory on first run
|
|
19
19
|
ensureHomeDirectory()
|
|
20
20
|
|
|
21
|
+
// Background auto-update (non-blocking)
|
|
22
|
+
try {
|
|
23
|
+
const { checkForUpdateInBackground } = await import('@/cli/auto-update')
|
|
24
|
+
checkForUpdateInBackground().catch(() => {})
|
|
25
|
+
} catch {}
|
|
26
|
+
|
|
21
27
|
// Auto-install Claude Code hooks (idempotent, non-fatal)
|
|
22
28
|
try {
|
|
23
29
|
const { installHooks } = await import('@/hooks/installer')
|
package/src/config/defaults.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { PartialConfig } from './schema'
|
|
|
3
3
|
/** Default configuration values for Claude Brain */
|
|
4
4
|
export const defaultConfig: PartialConfig = {
|
|
5
5
|
serverName: 'claude-brain',
|
|
6
|
-
serverVersion: '0.
|
|
6
|
+
serverVersion: '0.15.0',
|
|
7
7
|
logLevel: 'info',
|
|
8
8
|
logFilePath: './logs/claude-brain.log',
|
|
9
9
|
dbPath: './data/memory.db',
|
package/src/config/schema.ts
CHANGED
|
@@ -284,7 +284,7 @@ export const ConfigSchema = z.object({
|
|
|
284
284
|
serverName: z.string().default('claude-brain'),
|
|
285
285
|
|
|
286
286
|
/** Server version in semver format */
|
|
287
|
-
serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.
|
|
287
|
+
serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.15.0'),
|
|
288
288
|
|
|
289
289
|
/** Logging level */
|
|
290
290
|
logLevel: LogLevelSchema.default('info'),
|
|
@@ -135,7 +135,7 @@ export class OpenAIEmbeddingProvider implements EmbeddingProvider {
|
|
|
135
135
|
export function createEmbeddingProvider(
|
|
136
136
|
logger: Logger,
|
|
137
137
|
config: ChromaConfig
|
|
138
|
-
): EmbeddingProvider {
|
|
138
|
+
): EmbeddingProvider | undefined {
|
|
139
139
|
switch (config.embeddingProvider) {
|
|
140
140
|
case 'openai':
|
|
141
141
|
if (!config.openaiApiKey) {
|
|
@@ -148,6 +148,8 @@ export function createEmbeddingProvider(
|
|
|
148
148
|
|
|
149
149
|
case 'default':
|
|
150
150
|
default:
|
|
151
|
-
|
|
151
|
+
// Return undefined so store/search code falls through to queryTexts path
|
|
152
|
+
// (ChromaDB's built-in auto-embedding)
|
|
153
|
+
return undefined
|
|
152
154
|
}
|
|
153
155
|
}
|
|
@@ -23,7 +23,7 @@ export class ChromaManager {
|
|
|
23
23
|
public collections: CollectionManager
|
|
24
24
|
public store: ChromaMemoryStore
|
|
25
25
|
public search: ChromaSearchEngine
|
|
26
|
-
public embeddings: EmbeddingProvider
|
|
26
|
+
public embeddings: EmbeddingProvider | undefined
|
|
27
27
|
|
|
28
28
|
private initialized: boolean = false
|
|
29
29
|
|