claude-brain 0.28.1 → 0.28.2
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/scripts/postinstall.mjs +60 -0
- package/src/cli/auto-setup.ts +1 -0
- package/src/cli/commands/models.ts +248 -51
- package/src/intelligence/model-manager.ts +14 -0
- package/src/server/services.ts +35 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.28.
|
|
1
|
+
0.28.2
|
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -77,6 +77,7 @@ function setupHomeDirectory() {
|
|
|
77
77
|
join(HOME, 'vault', 'Projects'),
|
|
78
78
|
join(HOME, 'vault', 'Global'),
|
|
79
79
|
join(HOME, 'hooks'),
|
|
80
|
+
join(HOME, 'models'),
|
|
80
81
|
]
|
|
81
82
|
|
|
82
83
|
for (const dir of dirs) {
|
|
@@ -151,6 +152,20 @@ codeIntelligence:
|
|
|
151
152
|
enabled: true
|
|
152
153
|
autoIndexOnSessionStart: true
|
|
153
154
|
|
|
155
|
+
# SLM: Local model inference (replaces regex classifiers)
|
|
156
|
+
# Models are optional — install with: claude-brain models download
|
|
157
|
+
slm:
|
|
158
|
+
enabled: false
|
|
159
|
+
modelsDir: ~/.claude-brain/models
|
|
160
|
+
confidenceThreshold: 0.7
|
|
161
|
+
tasks:
|
|
162
|
+
intent: regex
|
|
163
|
+
entity: regex
|
|
164
|
+
query: regex
|
|
165
|
+
knowledge: regex
|
|
166
|
+
compress: api
|
|
167
|
+
pattern: regex
|
|
168
|
+
|
|
154
169
|
logLevel: warn
|
|
155
170
|
`
|
|
156
171
|
|
|
@@ -159,6 +174,48 @@ logLevel: warn
|
|
|
159
174
|
return true
|
|
160
175
|
}
|
|
161
176
|
|
|
177
|
+
// ── Step 2b: Upgrade existing config.yml (add missing sections) ──
|
|
178
|
+
|
|
179
|
+
function upgradeExistingConfig() {
|
|
180
|
+
const configPath = join(HOME, 'config.yml')
|
|
181
|
+
if (!existsSync(configPath)) {
|
|
182
|
+
return false
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let content
|
|
186
|
+
try {
|
|
187
|
+
content = readFileSync(configPath, 'utf-8')
|
|
188
|
+
} catch {
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Don't touch if user already has an slm: section
|
|
193
|
+
if (/^slm:/m.test(content)) {
|
|
194
|
+
log('Config already has SLM section')
|
|
195
|
+
return true
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const slmSection = `
|
|
199
|
+
# SLM: Local model inference (replaces regex classifiers)
|
|
200
|
+
# Models are optional — install with: claude-brain models download
|
|
201
|
+
slm:
|
|
202
|
+
enabled: false
|
|
203
|
+
modelsDir: ~/.claude-brain/models
|
|
204
|
+
confidenceThreshold: 0.7
|
|
205
|
+
tasks:
|
|
206
|
+
intent: regex
|
|
207
|
+
entity: regex
|
|
208
|
+
query: regex
|
|
209
|
+
knowledge: regex
|
|
210
|
+
compress: api
|
|
211
|
+
pattern: regex
|
|
212
|
+
`
|
|
213
|
+
|
|
214
|
+
writeFileSync(configPath, content.trimEnd() + '\n' + slmSection, 'utf-8')
|
|
215
|
+
log('Upgraded config.yml with SLM section')
|
|
216
|
+
return true
|
|
217
|
+
}
|
|
218
|
+
|
|
162
219
|
// ── Step 3: Copy hook files ──────────────────────────────
|
|
163
220
|
|
|
164
221
|
/** Files to copy from package src/hooks/ to ~/.claude-brain/hooks/ */
|
|
@@ -432,6 +489,9 @@ async function main() {
|
|
|
432
489
|
// Step 2: Create default config.yml
|
|
433
490
|
try { results.config = createDefaultConfig() } catch (e) { log(`Config creation failed: ${e.message}`) }
|
|
434
491
|
|
|
492
|
+
// Step 2b: Upgrade existing config.yml with new sections (e.g. SLM)
|
|
493
|
+
try { upgradeExistingConfig() } catch (e) { log(`Config upgrade failed: ${e.message}`) }
|
|
494
|
+
|
|
435
495
|
// Step 3: Copy hook files
|
|
436
496
|
try { results.hookFiles = copyHookFiles() } catch (e) { log(`Hook file copy failed: ${e.message}`) }
|
|
437
497
|
|
package/src/cli/auto-setup.ts
CHANGED
|
@@ -23,10 +23,20 @@ import { shouldRetrain, retrainTask, retrainAll, type RetrainConfig } from '@/tr
|
|
|
23
23
|
|
|
24
24
|
const ALL_TASKS: ModelTask[] = ['intent', 'entity', 'query', 'knowledge', 'compress', 'pattern']
|
|
25
25
|
|
|
26
|
+
/** Default mode when disabling a task */
|
|
27
|
+
const DISABLE_MODE: Record<ModelTask, string> = {
|
|
28
|
+
intent: 'regex',
|
|
29
|
+
entity: 'regex',
|
|
30
|
+
query: 'regex',
|
|
31
|
+
knowledge: 'regex',
|
|
32
|
+
compress: 'api',
|
|
33
|
+
pattern: 'regex',
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
export async function runModels() {
|
|
27
37
|
const args = parseArgs(process.argv.slice(3), {
|
|
28
|
-
subcommand: { type: 'positional', required: false, description: 'Subcommand: list, download, enable, disable, benchmark, stats, retrain' },
|
|
29
|
-
taskArg: { type: 'positional', required: false, description: 'Task name (for enable/disable/benchmark/retrain)' },
|
|
38
|
+
subcommand: { type: 'positional', required: false, description: 'Subcommand: list, status, download, enable, disable, benchmark, stats, retrain' },
|
|
39
|
+
taskArg: { type: 'positional', required: false, description: 'Task name or "all" (for enable/disable/benchmark/retrain)' },
|
|
30
40
|
task: { type: 'string', description: 'Target task (for download --task)' },
|
|
31
41
|
source: { type: 'string', description: 'Source: local (default) or release' },
|
|
32
42
|
force: { type: 'boolean', description: 'Force retrain even if checks say not needed' },
|
|
@@ -38,6 +48,8 @@ export async function runModels() {
|
|
|
38
48
|
switch (subcommand) {
|
|
39
49
|
case 'list':
|
|
40
50
|
return listModels()
|
|
51
|
+
case 'status':
|
|
52
|
+
return showStatus()
|
|
41
53
|
case 'download':
|
|
42
54
|
return downloadModels(taskArg || 'all', (args.source as string) || 'local')
|
|
43
55
|
case 'enable':
|
|
@@ -135,6 +147,113 @@ function listModels() {
|
|
|
135
147
|
console.log()
|
|
136
148
|
}
|
|
137
149
|
|
|
150
|
+
// ─── status ──────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
async function showStatus() {
|
|
153
|
+
console.log()
|
|
154
|
+
console.log(renderLogo())
|
|
155
|
+
console.log()
|
|
156
|
+
console.log(heading('SLM Inference Status'))
|
|
157
|
+
console.log()
|
|
158
|
+
|
|
159
|
+
// Check ONNX runtime availability
|
|
160
|
+
let onnxAvailable = false
|
|
161
|
+
let onnxBackend = 'not installed'
|
|
162
|
+
try {
|
|
163
|
+
await import('onnxruntime-node')
|
|
164
|
+
onnxAvailable = true
|
|
165
|
+
onnxBackend = 'onnxruntime-node (native)'
|
|
166
|
+
} catch {
|
|
167
|
+
try {
|
|
168
|
+
await import('onnxruntime-web')
|
|
169
|
+
onnxAvailable = true
|
|
170
|
+
onnxBackend = 'onnxruntime-web (WASM)'
|
|
171
|
+
} catch {
|
|
172
|
+
// Neither available
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const config = loadConfigFile()
|
|
177
|
+
const slmEnabled = config.slm?.enabled ?? false
|
|
178
|
+
const taskConfig = config.slm?.tasks ?? {}
|
|
179
|
+
|
|
180
|
+
const paths = getHomePaths()
|
|
181
|
+
const manifest = loadManifest()
|
|
182
|
+
|
|
183
|
+
const headerItems: Array<{ label: string; value: string; status: 'success' | 'warning' | 'error' | 'info' }> = [
|
|
184
|
+
{
|
|
185
|
+
label: 'ONNX Runtime',
|
|
186
|
+
value: onnxBackend,
|
|
187
|
+
status: onnxAvailable ? 'success' : 'warning',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
label: 'SLM Enabled',
|
|
191
|
+
value: slmEnabled ? 'yes' : 'no',
|
|
192
|
+
status: slmEnabled ? 'success' : 'info',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
label: 'Confidence Threshold',
|
|
196
|
+
value: `${config.slm?.confidenceThreshold ?? 0.7}`,
|
|
197
|
+
status: 'info',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
label: 'Models Dir',
|
|
201
|
+
value: paths.models,
|
|
202
|
+
status: existsSync(paths.models) ? 'success' : 'warning',
|
|
203
|
+
},
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
console.log(summaryPanel('Configuration', headerItems))
|
|
207
|
+
console.log()
|
|
208
|
+
|
|
209
|
+
// Per-task status
|
|
210
|
+
const taskItems: Array<{ label: string; value: string; status: 'success' | 'warning' | 'error' | 'info' }> = []
|
|
211
|
+
|
|
212
|
+
for (const task of ALL_TASKS) {
|
|
213
|
+
const mode = taskConfig[task] || DISABLE_MODE[task]
|
|
214
|
+
const entry = manifest?.models?.[task]
|
|
215
|
+
const fileExists = entry ? existsSync(join(paths.models, entry.file)) : false
|
|
216
|
+
|
|
217
|
+
let statusStr = `mode: ${mode}`
|
|
218
|
+
if (entry) {
|
|
219
|
+
statusStr += fileExists ? ', model: available' : ', model: MISSING'
|
|
220
|
+
} else {
|
|
221
|
+
statusStr += ', no manifest entry'
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const isActive = (mode === 'model' || mode === 'both') && fileExists && onnxAvailable
|
|
225
|
+
taskItems.push({
|
|
226
|
+
label: task,
|
|
227
|
+
value: statusStr,
|
|
228
|
+
status: isActive ? 'success' : mode === 'model' || mode === 'both' ? 'error' : 'info',
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log(summaryPanel('Task Routing', taskItems))
|
|
233
|
+
console.log()
|
|
234
|
+
|
|
235
|
+
if (!onnxAvailable) {
|
|
236
|
+
console.log(warningText(' ONNX Runtime is not installed. Models cannot be loaded.'))
|
|
237
|
+
console.log(dimText(' Install with: npm install onnxruntime-node'))
|
|
238
|
+
console.log()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!slmEnabled && manifest) {
|
|
242
|
+
console.log(dimText(' SLM is disabled. Enable with: claude-brain models enable all'))
|
|
243
|
+
console.log()
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function loadManifest(): ModelManifest | null {
|
|
248
|
+
const manifestPath = join(getHomePaths().models, 'manifest.json')
|
|
249
|
+
if (!existsSync(manifestPath)) return null
|
|
250
|
+
try {
|
|
251
|
+
return JSON.parse(readFileSync(manifestPath, 'utf-8')) as ModelManifest
|
|
252
|
+
} catch {
|
|
253
|
+
return null
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
138
257
|
// ─── download ─────────────────────────────────────────────────────
|
|
139
258
|
|
|
140
259
|
function downloadModels(taskFilter: string, source: string) {
|
|
@@ -274,15 +393,15 @@ function downloadModels(taskFilter: string, source: string) {
|
|
|
274
393
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
|
275
394
|
|
|
276
395
|
// Auto-enable successfully installed models in config
|
|
277
|
-
const
|
|
278
|
-
const config = loadConfigFile(configPath)
|
|
396
|
+
const config = loadConfigFile()
|
|
279
397
|
if (!config.slm) config.slm = {}
|
|
280
398
|
config.slm.enabled = true
|
|
281
399
|
if (!config.slm.tasks) config.slm.tasks = {}
|
|
282
400
|
for (const task of installedTasks) {
|
|
283
401
|
config.slm.tasks[task] = 'model'
|
|
284
402
|
}
|
|
285
|
-
|
|
403
|
+
saveConfigFile(config)
|
|
404
|
+
updateConfigYml(installedTasks, 'model')
|
|
286
405
|
|
|
287
406
|
console.log(successText(`Installed ${installed} model${installed !== 1 ? 's' : ''} (total: ${formatBytes(totalBytes)})`))
|
|
288
407
|
console.log(successText(`Auto-enabled ${installedTasks.join(', ')} in config`))
|
|
@@ -292,64 +411,96 @@ function downloadModels(taskFilter: string, source: string) {
|
|
|
292
411
|
|
|
293
412
|
// ─── enable ───────────────────────────────────────────────────────
|
|
294
413
|
|
|
295
|
-
function enableTask(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
console.log(dimText(`Usage: claude-brain models enable <task>`))
|
|
299
|
-
console.log(dimText(`Tasks: ${ALL_TASKS.join(', ')}`))
|
|
300
|
-
return
|
|
301
|
-
}
|
|
414
|
+
function enableTask(taskArg: string) {
|
|
415
|
+
const tasks = resolveTaskArg(taskArg, 'enable')
|
|
416
|
+
if (!tasks) return
|
|
302
417
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return
|
|
307
|
-
}
|
|
418
|
+
console.log()
|
|
419
|
+
console.log(heading('Enable SLM Models'))
|
|
420
|
+
console.log()
|
|
308
421
|
|
|
309
|
-
const
|
|
310
|
-
const config = loadConfigFile(configPath)
|
|
422
|
+
const config = loadConfigFile()
|
|
311
423
|
|
|
312
424
|
// Ensure slm section exists
|
|
313
425
|
if (!config.slm) config.slm = {}
|
|
314
426
|
config.slm.enabled = true
|
|
315
427
|
if (!config.slm.tasks) config.slm.tasks = {}
|
|
316
428
|
|
|
317
|
-
|
|
318
|
-
|
|
429
|
+
for (const task of tasks) {
|
|
430
|
+
config.slm.tasks[task] = 'model'
|
|
431
|
+
}
|
|
319
432
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
433
|
+
saveConfigFile(config)
|
|
434
|
+
updateConfigYml(tasks, 'model')
|
|
435
|
+
|
|
436
|
+
for (const task of tasks) {
|
|
437
|
+
console.log(successText(`${task} -> model`))
|
|
438
|
+
}
|
|
439
|
+
console.log()
|
|
440
|
+
console.log(successText('SLM enabled'))
|
|
441
|
+
console.log(dimText(' Changes take effect on next server restart'))
|
|
442
|
+
console.log()
|
|
323
443
|
}
|
|
324
444
|
|
|
325
445
|
// ─── disable ──────────────────────────────────────────────────────
|
|
326
446
|
|
|
327
|
-
function disableTask(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
447
|
+
function disableTask(taskArg: string) {
|
|
448
|
+
const tasks = resolveTaskArg(taskArg, 'disable')
|
|
449
|
+
if (!tasks) return
|
|
450
|
+
|
|
451
|
+
console.log()
|
|
452
|
+
console.log(heading('Disable SLM Models'))
|
|
453
|
+
console.log()
|
|
454
|
+
|
|
455
|
+
const config = loadConfigFile()
|
|
456
|
+
if (!config.slm) config.slm = {}
|
|
457
|
+
if (!config.slm.tasks) config.slm.tasks = {}
|
|
458
|
+
|
|
459
|
+
for (const task of tasks) {
|
|
460
|
+
config.slm.tasks[task] = DISABLE_MODE[task]
|
|
333
461
|
}
|
|
334
462
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return
|
|
463
|
+
// If all tasks are now regex/api, disable SLM globally
|
|
464
|
+
const allDisabled = ALL_TASKS.every(t => {
|
|
465
|
+
const mode = config.slm.tasks[t] || DISABLE_MODE[t]
|
|
466
|
+
return mode === 'regex' || mode === 'api'
|
|
467
|
+
})
|
|
468
|
+
if (allDisabled) {
|
|
469
|
+
config.slm.enabled = false
|
|
339
470
|
}
|
|
340
471
|
|
|
341
|
-
|
|
342
|
-
|
|
472
|
+
saveConfigFile(config)
|
|
473
|
+
updateConfigYml(tasks, 'disable')
|
|
343
474
|
|
|
344
|
-
|
|
345
|
-
|
|
475
|
+
for (const task of tasks) {
|
|
476
|
+
console.log(warningText(`${task} -> ${DISABLE_MODE[task]}`))
|
|
477
|
+
}
|
|
478
|
+
console.log()
|
|
479
|
+
if (allDisabled) {
|
|
480
|
+
console.log(warningText('All tasks disabled — SLM globally disabled'))
|
|
481
|
+
}
|
|
482
|
+
console.log(dimText(' Changes take effect on next server restart'))
|
|
483
|
+
console.log()
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/** Resolve a task argument to a list of tasks, or null if invalid */
|
|
487
|
+
function resolveTaskArg(taskArg: string, verb: string): ModelTask[] | null {
|
|
488
|
+
if (!taskArg) {
|
|
489
|
+
console.log(errorText('Missing task argument'))
|
|
490
|
+
console.log(dimText(`Usage: claude-brain models ${verb} <task|all>`))
|
|
491
|
+
console.log(dimText(`Tasks: ${ALL_TASKS.join(', ')}, all`))
|
|
492
|
+
return null
|
|
493
|
+
}
|
|
346
494
|
|
|
347
|
-
|
|
348
|
-
config.slm.tasks[task] = task === 'compress' ? 'api' : 'regex'
|
|
495
|
+
if (taskArg === 'all') return [...ALL_TASKS]
|
|
349
496
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
497
|
+
if (!ALL_TASKS.includes(taskArg as ModelTask)) {
|
|
498
|
+
console.log(errorText(`Invalid task: ${taskArg}`))
|
|
499
|
+
console.log(dimText(`Valid tasks: ${ALL_TASKS.join(', ')}, all`))
|
|
500
|
+
return null
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return [taskArg as ModelTask]
|
|
353
504
|
}
|
|
354
505
|
|
|
355
506
|
// ─── benchmark ────────────────────────────────────────────────────
|
|
@@ -535,9 +686,8 @@ async function retrainModels(taskFilter: string, force: boolean) {
|
|
|
535
686
|
return
|
|
536
687
|
}
|
|
537
688
|
|
|
538
|
-
// Build config from schema defaults +
|
|
539
|
-
const
|
|
540
|
-
const userConfig = loadConfigFile(configPath)
|
|
689
|
+
// Build config from schema defaults + .claudebrainrc.json overrides
|
|
690
|
+
const userConfig = loadConfigFile()
|
|
541
691
|
const retrainCfg = userConfig?.slm?.retrain ?? {}
|
|
542
692
|
|
|
543
693
|
const config: RetrainConfig = {
|
|
@@ -632,9 +782,10 @@ function printModelsHelp() {
|
|
|
632
782
|
|
|
633
783
|
const subcommands = [
|
|
634
784
|
['list', 'Show installed models and their status'],
|
|
785
|
+
['status', 'Show inference routing and ONNX runtime status'],
|
|
635
786
|
['download', 'Download pre-trained models (--task <task>|all)'],
|
|
636
|
-
['enable <task>', 'Enable model inference for
|
|
637
|
-
['disable <task>', 'Disable model inference for
|
|
787
|
+
['enable <task|all>', 'Enable model inference for task(s)'],
|
|
788
|
+
['disable <task|all>', 'Disable model inference for task(s)'],
|
|
638
789
|
['benchmark <task>', 'Run accuracy benchmark on test data'],
|
|
639
790
|
['stats', 'Show training data statistics'],
|
|
640
791
|
['retrain [<task>|all]', 'Retrain models from feedback (--force)'],
|
|
@@ -653,7 +804,10 @@ function printModelsHelp() {
|
|
|
653
804
|
console.log()
|
|
654
805
|
console.log(theme.bold('Examples:'))
|
|
655
806
|
console.log(` ${dimText('claude-brain models list')}`)
|
|
807
|
+
console.log(` ${dimText('claude-brain models status')}`)
|
|
808
|
+
console.log(` ${dimText('claude-brain models enable all')}`)
|
|
656
809
|
console.log(` ${dimText('claude-brain models enable intent')}`)
|
|
810
|
+
console.log(` ${dimText('claude-brain models disable pattern')}`)
|
|
657
811
|
console.log(` ${dimText('claude-brain models benchmark intent')}`)
|
|
658
812
|
console.log(` ${dimText('claude-brain models stats')}`)
|
|
659
813
|
console.log(` ${dimText('claude-brain models retrain intent')}`)
|
|
@@ -663,15 +817,58 @@ function printModelsHelp() {
|
|
|
663
817
|
|
|
664
818
|
// ─── helpers ──────────────────────────────────────────────────────
|
|
665
819
|
|
|
666
|
-
|
|
667
|
-
|
|
820
|
+
const RC_FILE = '.claudebrainrc.json'
|
|
821
|
+
|
|
822
|
+
function loadConfigFile(): Record<string, any> {
|
|
823
|
+
const rcPath = join(getClaudeBrainHome(), RC_FILE)
|
|
824
|
+
if (!existsSync(rcPath)) return {}
|
|
668
825
|
try {
|
|
669
|
-
return JSON.parse(readFileSync(
|
|
826
|
+
return JSON.parse(readFileSync(rcPath, 'utf-8'))
|
|
670
827
|
} catch {
|
|
671
828
|
return {}
|
|
672
829
|
}
|
|
673
830
|
}
|
|
674
831
|
|
|
832
|
+
function saveConfigFile(config: Record<string, any>): void {
|
|
833
|
+
const rcPath = join(getClaudeBrainHome(), RC_FILE)
|
|
834
|
+
writeFileSync(rcPath, JSON.stringify(config, null, 2) + '\n', 'utf-8')
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/** Best-effort update of config.yml task lines for user visibility */
|
|
838
|
+
function updateConfigYml(tasks: ModelTask[], mode: string): void {
|
|
839
|
+
const ymlPath = join(getClaudeBrainHome(), 'config.yml')
|
|
840
|
+
if (!existsSync(ymlPath)) return
|
|
841
|
+
|
|
842
|
+
try {
|
|
843
|
+
let content = readFileSync(ymlPath, 'utf-8')
|
|
844
|
+
|
|
845
|
+
// Update each task line (e.g. " intent: regex" -> " intent: model")
|
|
846
|
+
for (const task of tasks) {
|
|
847
|
+
const newMode = mode === 'disable' ? DISABLE_MODE[task] : mode
|
|
848
|
+
const regex = new RegExp(`^(\\s*${task}:\\s*)\\S+`, 'm')
|
|
849
|
+
content = content.replace(regex, `$1${newMode}`)
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Update slm.enabled line if present
|
|
853
|
+
// Use a heuristic: find "enabled:" that appears after "slm:"
|
|
854
|
+
const slmIdx = content.indexOf('slm:')
|
|
855
|
+
if (slmIdx !== -1) {
|
|
856
|
+
const afterSlm = content.slice(slmIdx)
|
|
857
|
+
const enabledMatch = afterSlm.match(/^(\s+enabled:\s*)\S+/m)
|
|
858
|
+
if (enabledMatch) {
|
|
859
|
+
const config = loadConfigFile()
|
|
860
|
+
const slmEnabled = config.slm?.enabled ?? false
|
|
861
|
+
const newLine = `${enabledMatch[1]}${slmEnabled}`
|
|
862
|
+
content = content.slice(0, slmIdx) + afterSlm.replace(enabledMatch[0], newLine)
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
writeFileSync(ymlPath, content, 'utf-8')
|
|
867
|
+
} catch {
|
|
868
|
+
// Non-critical — .claudebrainrc.json is the authoritative config
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
675
872
|
function formatBytes(bytes: number): string {
|
|
676
873
|
if (bytes === 0) return '0 B'
|
|
677
874
|
const units = ['B', 'KB', 'MB', 'GB']
|
|
@@ -179,6 +179,20 @@ export class ModelManager {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Public accessor for the models directory path
|
|
184
|
+
*/
|
|
185
|
+
getModelsDir(): string {
|
|
186
|
+
return this.modelsDir
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Public check for ONNX Runtime availability
|
|
191
|
+
*/
|
|
192
|
+
async isOnnxAvailable(): Promise<boolean> {
|
|
193
|
+
return this.checkOnnxRuntime()
|
|
194
|
+
}
|
|
195
|
+
|
|
182
196
|
/**
|
|
183
197
|
* Get status of all models (for CLI and health checks)
|
|
184
198
|
*/
|
package/src/server/services.ts
CHANGED
|
@@ -394,6 +394,41 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
394
394
|
serviceLogger.info('SLM inference wired into PatternRecognizer')
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
+
// SLM startup detection: log model availability and configuration state
|
|
398
|
+
if (modelManager) {
|
|
399
|
+
const modelStatus = modelManager.getStatus()
|
|
400
|
+
const modelsDir = modelManager.getModelsDir()
|
|
401
|
+
const availableCount = Object.values(modelStatus).filter(s => s.available).length
|
|
402
|
+
const slmEnabled = config.slm?.enabled ?? false
|
|
403
|
+
|
|
404
|
+
if (availableCount > 0 && !slmEnabled) {
|
|
405
|
+
serviceLogger.info(
|
|
406
|
+
{ count: availableCount, modelsDir },
|
|
407
|
+
`Found ${availableCount} ONNX models in ${modelsDir}. SLM is disabled. Run "claude-brain models enable all" to activate local inference.`
|
|
408
|
+
)
|
|
409
|
+
} else if (availableCount > 0 && slmEnabled) {
|
|
410
|
+
const tasks = config.slm?.tasks ?? {}
|
|
411
|
+
const enabledTasks = Object.entries(tasks)
|
|
412
|
+
.filter(([_, mode]) => mode === 'model' || mode === 'both')
|
|
413
|
+
.map(([task]) => task)
|
|
414
|
+
serviceLogger.info(
|
|
415
|
+
{ count: availableCount, enabledTasks },
|
|
416
|
+
`SLM active: ${enabledTasks.length} tasks using local model inference`
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
// Check onnxruntime availability when SLM is enabled
|
|
420
|
+
const onnxAvailable = await modelManager.isOnnxAvailable()
|
|
421
|
+
if (!onnxAvailable) {
|
|
422
|
+
serviceLogger.warn('SLM enabled but onnxruntime-node not installed. Run: bun add onnxruntime-node')
|
|
423
|
+
}
|
|
424
|
+
} else if (availableCount === 0 && slmEnabled) {
|
|
425
|
+
serviceLogger.warn(
|
|
426
|
+
{ modelsDir },
|
|
427
|
+
`SLM enabled but no models found in ${modelsDir}. Run "claude-brain models download" to install models.`
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
397
432
|
// Store services
|
|
398
433
|
services = {
|
|
399
434
|
memory,
|