claude-brain 0.30.2 → 0.31.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/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 +59 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +195 -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
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,531 +1,531 @@
|
|
|
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
|
-
* Phase 30: Zero-config — does EVERYTHING automatically:
|
|
10
|
-
* 1. Create data directory: ~/.claude-brain/data/
|
|
11
|
-
* 2. Create default config if none exists: ~/.claude-brain/config.yml
|
|
12
|
-
* 3. Copy hook files to ~/.claude-brain/hooks/
|
|
13
|
-
* 4. Register hooks in ~/.claude/settings.json (if Claude Code installed)
|
|
14
|
-
* 5. Print success message with next steps
|
|
15
|
-
*
|
|
16
|
-
* No interactive prompts. No setup wizard. Sensible defaults.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { mkdirSync, writeFileSync, readFileSync, existsSync, renameSync } from 'node:fs'
|
|
20
|
-
import { join, dirname } from 'node:path'
|
|
21
|
-
import { homedir } from 'node:os'
|
|
22
|
-
import { execSync } from 'node:child_process'
|
|
23
|
-
import { fileURLToPath } from 'node:url'
|
|
24
|
-
|
|
25
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
26
|
-
const __dirname = dirname(__filename)
|
|
27
|
-
|
|
28
|
-
const PREFIX = '[claude-brain]'
|
|
29
|
-
const HOME = join(homedir(), '.claude-brain')
|
|
30
|
-
const CLAUDE_DIR = join(homedir(), '.claude')
|
|
31
|
-
const CLAUDE_SETTINGS = join(CLAUDE_DIR, 'settings.json')
|
|
32
|
-
const CLAUDE_MD_PATH = join(CLAUDE_DIR, 'CLAUDE.md')
|
|
33
|
-
const HOOK_MARKER = 'claude-brain-hook'
|
|
34
|
-
|
|
35
|
-
function log(msg) {
|
|
36
|
-
console.error(`${PREFIX} ${msg}`)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ── Step 0: Skip if not a global install ─────────────────
|
|
40
|
-
|
|
41
|
-
function shouldSkip() {
|
|
42
|
-
// CI environments
|
|
43
|
-
if (process.env.CI === 'true' || process.env.CI === '1') {
|
|
44
|
-
return 'CI environment detected'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// If we're inside a node_modules directory, it's a local install
|
|
48
|
-
const scriptDir = dirname(__filename)
|
|
49
|
-
if (scriptDir.includes('node_modules') && !scriptDir.includes('global')) {
|
|
50
|
-
// Check if this is actually a global install by looking deeper
|
|
51
|
-
// bun global installs go to ~/.bun/install/global/node_modules
|
|
52
|
-
// npm global installs go to /usr/local/lib/node_modules or ~/.npm-global
|
|
53
|
-
const isGlobalPath =
|
|
54
|
-
scriptDir.includes('.bun/install/global') ||
|
|
55
|
-
scriptDir.includes('/usr/local/lib/node_modules') ||
|
|
56
|
-
scriptDir.includes('/usr/lib/node_modules') ||
|
|
57
|
-
scriptDir.includes('npm-global') ||
|
|
58
|
-
scriptDir.includes('AppData/Roaming/npm')
|
|
59
|
-
|
|
60
|
-
if (!isGlobalPath) {
|
|
61
|
-
return 'local install (not global)'
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return null
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ── Step 1: Create ~/.claude-brain/ data directory ───────
|
|
69
|
-
|
|
70
|
-
function setupHomeDirectory() {
|
|
71
|
-
const isAlreadySetup = existsSync(join(HOME, 'data'))
|
|
72
|
-
|
|
73
|
-
const dirs = [
|
|
74
|
-
join(HOME, 'data'),
|
|
75
|
-
join(HOME, 'logs'),
|
|
76
|
-
join(HOME, 'vault'),
|
|
77
|
-
join(HOME, 'vault', 'Projects'),
|
|
78
|
-
join(HOME, 'vault', 'Global'),
|
|
79
|
-
join(HOME, 'hooks'),
|
|
80
|
-
join(HOME, 'models'),
|
|
81
|
-
]
|
|
82
|
-
|
|
83
|
-
for (const dir of dirs) {
|
|
84
|
-
mkdirSync(dir, { recursive: true })
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Write default .env if not present (for backward compat)
|
|
88
|
-
const envPath = join(HOME, '.env')
|
|
89
|
-
if (!existsSync(envPath)) {
|
|
90
|
-
writeFileSync(envPath, `# Claude Brain Configuration
|
|
91
|
-
# Generated by postinstall
|
|
92
|
-
VAULT_PATH=${join(HOME, 'vault')}
|
|
93
|
-
LOG_LEVEL=warn
|
|
94
|
-
NODE_ENV=production
|
|
95
|
-
`, 'utf-8')
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Write default global standards
|
|
99
|
-
const standardsPath = join(HOME, 'vault', 'Global', 'standards.md')
|
|
100
|
-
if (!existsSync(standardsPath)) {
|
|
101
|
-
writeFileSync(standardsPath, `---
|
|
102
|
-
type: global-standards
|
|
103
|
-
last_updated: ${new Date().toISOString().split('T')[0]}
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
# Global Coding Standards
|
|
107
|
-
|
|
108
|
-
## General
|
|
109
|
-
- Write clear, readable code
|
|
110
|
-
- Prefer explicit over implicit
|
|
111
|
-
- Keep functions focused and small
|
|
112
|
-
|
|
113
|
-
## TypeScript
|
|
114
|
-
- Use strict mode
|
|
115
|
-
- Prefer const over let
|
|
116
|
-
- Add JSDoc comments for public APIs
|
|
117
|
-
`, 'utf-8')
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (isAlreadySetup) {
|
|
121
|
-
log('Data directory already exists')
|
|
122
|
-
} else {
|
|
123
|
-
log('Data directory created')
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return true
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// ── Step 2: Create default config.yml ────────────────────
|
|
130
|
-
|
|
131
|
-
function createDefaultConfig() {
|
|
132
|
-
const configPath = join(HOME, 'config.yml')
|
|
133
|
-
if (existsSync(configPath)) {
|
|
134
|
-
log('Config already exists')
|
|
135
|
-
return true
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const config = `# Claude Brain Configuration
|
|
139
|
-
# Auto-generated on install — edit as needed
|
|
140
|
-
# Docs: https://github.com/your-org/claude-brain
|
|
141
|
-
|
|
142
|
-
storage:
|
|
143
|
-
dataDir: ~/.claude-brain/data
|
|
144
|
-
|
|
145
|
-
# ChromaDB is optional. SQLite FTS5 is the default search backend.
|
|
146
|
-
# Enable for semantic/vector search (requires: pip install chromadb)
|
|
147
|
-
chromadb:
|
|
148
|
-
enabled: false
|
|
149
|
-
|
|
150
|
-
# Code intelligence indexes your project files for smarter context
|
|
151
|
-
codeIntelligence:
|
|
152
|
-
enabled: true
|
|
153
|
-
autoIndexOnSessionStart: true
|
|
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
|
-
|
|
169
|
-
logLevel: warn
|
|
170
|
-
`
|
|
171
|
-
|
|
172
|
-
writeFileSync(configPath, config, 'utf-8')
|
|
173
|
-
log('Created default config.yml')
|
|
174
|
-
return true
|
|
175
|
-
}
|
|
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
|
-
|
|
219
|
-
// ── Step 3: Copy hook files ──────────────────────────────
|
|
220
|
-
|
|
221
|
-
/** Files to copy from package src/hooks/ to ~/.claude-brain/hooks/ */
|
|
222
|
-
const HOOK_FILES = [
|
|
223
|
-
'brain-hook.ts',
|
|
224
|
-
'context-hook.ts',
|
|
225
|
-
'interceptor-hook.ts',
|
|
226
|
-
'capture.ts',
|
|
227
|
-
'queue.ts',
|
|
228
|
-
'types.ts',
|
|
229
|
-
'passive-classifier.ts',
|
|
230
|
-
'claude-code-mastery.md',
|
|
231
|
-
]
|
|
232
|
-
|
|
233
|
-
function copyHookFiles() {
|
|
234
|
-
const destDir = join(HOME, 'hooks')
|
|
235
|
-
mkdirSync(destDir, { recursive: true })
|
|
236
|
-
|
|
237
|
-
// Source: package root / src/hooks/
|
|
238
|
-
const srcDir = join(__dirname, '..', 'src', 'hooks')
|
|
239
|
-
|
|
240
|
-
let copied = 0
|
|
241
|
-
for (const file of HOOK_FILES) {
|
|
242
|
-
const src = join(srcDir, file)
|
|
243
|
-
if (existsSync(src)) {
|
|
244
|
-
writeFileSync(join(destDir, file), readFileSync(src, 'utf-8'), 'utf-8')
|
|
245
|
-
copied++
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
log(`Copied ${copied}/${HOOK_FILES.length} hook files`)
|
|
249
|
-
return copied > 0
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// ── Step 4: Register hooks in ~/.claude/settings.json ────
|
|
253
|
-
|
|
254
|
-
function installHooks() {
|
|
255
|
-
// Read existing settings
|
|
256
|
-
let settings = {}
|
|
257
|
-
if (existsSync(CLAUDE_SETTINGS)) {
|
|
258
|
-
try {
|
|
259
|
-
settings = JSON.parse(readFileSync(CLAUDE_SETTINGS, 'utf-8'))
|
|
260
|
-
} catch {}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Check if already installed
|
|
264
|
-
function hasOurHooks(entries) {
|
|
265
|
-
if (!Array.isArray(entries)) return false
|
|
266
|
-
return entries.some(entry =>
|
|
267
|
-
entry && Array.isArray(entry.hooks) &&
|
|
268
|
-
entry.hooks.some(h => typeof h.command === 'string' && h.command.includes(HOOK_MARKER))
|
|
269
|
-
)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (settings.hooks &&
|
|
273
|
-
hasOurHooks(settings.hooks.PostToolUse) &&
|
|
274
|
-
hasOurHooks(settings.hooks.Stop) &&
|
|
275
|
-
hasOurHooks(settings.hooks.UserPromptSubmit) &&
|
|
276
|
-
hasOurHooks(settings.hooks.SessionStart) &&
|
|
277
|
-
hasOurHooks(settings.hooks.PreToolUse)) {
|
|
278
|
-
log('Hooks already installed')
|
|
279
|
-
return true
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Build hook command
|
|
283
|
-
const brainScriptPath = join(HOME, 'hooks', 'brain-hook.ts')
|
|
284
|
-
const contextScriptPath = join(HOME, 'hooks', 'context-hook.ts')
|
|
285
|
-
const interceptorScriptPath = join(HOME, 'hooks', 'interceptor-hook.ts')
|
|
286
|
-
const port = process.env.CLAUDE_BRAIN_PORT || process.env.PORT || '3000'
|
|
287
|
-
function buildCmd(event, scriptPath) {
|
|
288
|
-
return `bun "${scriptPath}" --event ${event} --port ${port} # ${HOOK_MARKER}`
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (!settings.hooks) settings.hooks = {}
|
|
292
|
-
|
|
293
|
-
// PostToolUse — captures tool events
|
|
294
|
-
if (!hasOurHooks(settings.hooks.PostToolUse)) {
|
|
295
|
-
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = []
|
|
296
|
-
settings.hooks.PostToolUse.push({
|
|
297
|
-
matcher: '',
|
|
298
|
-
hooks: [{ type: 'command', command: buildCmd('PostToolUse', brainScriptPath) }],
|
|
299
|
-
})
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Stop — triggers session-end summary
|
|
303
|
-
if (!hasOurHooks(settings.hooks.Stop)) {
|
|
304
|
-
if (!settings.hooks.Stop) settings.hooks.Stop = []
|
|
305
|
-
settings.hooks.Stop.push({
|
|
306
|
-
matcher: '',
|
|
307
|
-
hooks: [{ type: 'command', command: buildCmd('Stop', brainScriptPath) }],
|
|
308
|
-
})
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// UserPromptSubmit — injects relevant memories into every prompt
|
|
312
|
-
if (!hasOurHooks(settings.hooks.UserPromptSubmit)) {
|
|
313
|
-
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = []
|
|
314
|
-
settings.hooks.UserPromptSubmit.push({
|
|
315
|
-
matcher: '',
|
|
316
|
-
hooks: [{ type: 'command', command: buildCmd('UserPromptSubmit', contextScriptPath) }],
|
|
317
|
-
})
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// SessionStart — injects project context on session start/resume
|
|
321
|
-
if (!hasOurHooks(settings.hooks.SessionStart)) {
|
|
322
|
-
if (!settings.hooks.SessionStart) settings.hooks.SessionStart = []
|
|
323
|
-
settings.hooks.SessionStart.push({
|
|
324
|
-
matcher: 'startup,resume,compact',
|
|
325
|
-
hooks: [{ type: 'command', command: buildCmd('SessionStart', contextScriptPath) }],
|
|
326
|
-
})
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// PreToolUse — code intelligence interceptor for Glob and Grep
|
|
330
|
-
if (!hasOurHooks(settings.hooks.PreToolUse)) {
|
|
331
|
-
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = []
|
|
332
|
-
settings.hooks.PreToolUse.push({
|
|
333
|
-
matcher: 'Glob',
|
|
334
|
-
hooks: [{ type: 'command', command: buildCmd('PreToolUse', interceptorScriptPath) }],
|
|
335
|
-
})
|
|
336
|
-
settings.hooks.PreToolUse.push({
|
|
337
|
-
matcher: 'Grep',
|
|
338
|
-
hooks: [{ type: 'command', command: buildCmd('PreToolUse', interceptorScriptPath) }],
|
|
339
|
-
})
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Write atomically
|
|
343
|
-
if (!existsSync(CLAUDE_DIR)) {
|
|
344
|
-
mkdirSync(CLAUDE_DIR, { recursive: true })
|
|
345
|
-
}
|
|
346
|
-
const tmpPath = CLAUDE_SETTINGS + '.tmp'
|
|
347
|
-
writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8')
|
|
348
|
-
renameSync(tmpPath, CLAUDE_SETTINGS)
|
|
349
|
-
|
|
350
|
-
log('Hooks registered in Claude Code')
|
|
351
|
-
return true
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// ── Step 5: Install CLAUDE.md ────────────────────────────
|
|
355
|
-
|
|
356
|
-
function installClaudeMd() {
|
|
357
|
-
// Find the assets/CLAUDE.md relative to this script
|
|
358
|
-
// scripts/postinstall.mjs → assets/CLAUDE.md
|
|
359
|
-
const assetsPath = join(__dirname, '..', 'assets', 'CLAUDE.md')
|
|
360
|
-
|
|
361
|
-
if (!existsSync(assetsPath)) {
|
|
362
|
-
log('CLAUDE.md asset not found — skipping')
|
|
363
|
-
return false
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Only install if no CLAUDE.md exists yet (don't overwrite user customizations)
|
|
367
|
-
if (existsSync(CLAUDE_MD_PATH)) {
|
|
368
|
-
// Check if it already mentions claude-brain
|
|
369
|
-
const existing = readFileSync(CLAUDE_MD_PATH, 'utf-8')
|
|
370
|
-
if (existing.includes('brain') || existing.includes('Brain')) {
|
|
371
|
-
log('CLAUDE.md already configured')
|
|
372
|
-
return true
|
|
373
|
-
}
|
|
374
|
-
// Append our section
|
|
375
|
-
const addition = readFileSync(assetsPath, 'utf-8')
|
|
376
|
-
writeFileSync(CLAUDE_MD_PATH, existing.trimEnd() + '\n\n' + addition, 'utf-8')
|
|
377
|
-
log('Appended brain instructions to existing CLAUDE.md')
|
|
378
|
-
return true
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Create new
|
|
382
|
-
if (!existsSync(CLAUDE_DIR)) {
|
|
383
|
-
mkdirSync(CLAUDE_DIR, { recursive: true })
|
|
384
|
-
}
|
|
385
|
-
const content = readFileSync(assetsPath, 'utf-8')
|
|
386
|
-
writeFileSync(CLAUDE_MD_PATH, content, 'utf-8')
|
|
387
|
-
log('Installed CLAUDE.md')
|
|
388
|
-
return true
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// ── Step 6: Install auto-start in shell profile ──────────
|
|
392
|
-
|
|
393
|
-
const AUTO_START_MARKER = '# >>> claude-brain auto-start >>>'
|
|
394
|
-
const AUTO_END_MARKER = '# <<< claude-brain auto-start <<<'
|
|
395
|
-
|
|
396
|
-
function getShellProfile() {
|
|
397
|
-
const home = homedir()
|
|
398
|
-
const os = process.platform
|
|
399
|
-
|
|
400
|
-
if (os === 'win32') {
|
|
401
|
-
const userProfile = process.env.USERPROFILE
|
|
402
|
-
if (userProfile) {
|
|
403
|
-
return join(userProfile, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1')
|
|
404
|
-
}
|
|
405
|
-
return null
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// macOS defaults to zsh, Linux to bash
|
|
409
|
-
return os === 'darwin' ? join(home, '.zshrc') : join(home, '.bashrc')
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
function buildAutoStartSnippet(profilePath) {
|
|
413
|
-
if (profilePath && profilePath.endsWith('.ps1')) {
|
|
414
|
-
return [
|
|
415
|
-
AUTO_START_MARKER,
|
|
416
|
-
'if (Get-Command claude-brain -ErrorAction SilentlyContinue) {',
|
|
417
|
-
' $listening = Get-NetTCPConnection -LocalPort 3000 -ErrorAction SilentlyContinue',
|
|
418
|
-
' if (-not $listening) {',
|
|
419
|
-
' Start-Process -NoNewWindow -FilePath "claude-brain" -ArgumentList "serve","--http-only" -WindowStyle Hidden',
|
|
420
|
-
' }',
|
|
421
|
-
'}',
|
|
422
|
-
AUTO_END_MARKER,
|
|
423
|
-
].join('\n')
|
|
424
|
-
}
|
|
425
|
-
return [
|
|
426
|
-
AUTO_START_MARKER,
|
|
427
|
-
'# Auto-start claude-brain HTTP server if not already running',
|
|
428
|
-
'(command -v claude-brain >/dev/null 2>&1 && ! lsof -ti :3000 >/dev/null 2>&1) && {',
|
|
429
|
-
' nohup claude-brain serve --http-only >/dev/null 2>&1 &',
|
|
430
|
-
' disown 2>/dev/null',
|
|
431
|
-
'}',
|
|
432
|
-
AUTO_END_MARKER,
|
|
433
|
-
].join('\n')
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function installShellAutoStart() {
|
|
437
|
-
const profilePath = getShellProfile()
|
|
438
|
-
if (!profilePath) {
|
|
439
|
-
log('No supported shell profile found, skipping auto-start')
|
|
440
|
-
return false
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Check if already installed
|
|
444
|
-
if (existsSync(profilePath)) {
|
|
445
|
-
const content = readFileSync(profilePath, 'utf-8')
|
|
446
|
-
if (content.includes(AUTO_START_MARKER)) {
|
|
447
|
-
log('Auto-start already installed')
|
|
448
|
-
return true
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Ensure parent directory exists
|
|
453
|
-
const dir = join(profilePath, '..')
|
|
454
|
-
mkdirSync(dir, { recursive: true })
|
|
455
|
-
|
|
456
|
-
const existing = existsSync(profilePath) ? readFileSync(profilePath, 'utf-8') : ''
|
|
457
|
-
const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n\n' : '\n'
|
|
458
|
-
const snippet = buildAutoStartSnippet(profilePath)
|
|
459
|
-
writeFileSync(profilePath, existing + separator + snippet + '\n', 'utf-8')
|
|
460
|
-
|
|
461
|
-
log(`Auto-start installed in ${profilePath}`)
|
|
462
|
-
return true
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// ── Main ─────────────────────────────────────────────────
|
|
466
|
-
|
|
467
|
-
async function main() {
|
|
468
|
-
const skipReason = shouldSkip()
|
|
469
|
-
if (skipReason) {
|
|
470
|
-
log(`Skipping postinstall (${skipReason})`)
|
|
471
|
-
return
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
log('Running zero-config setup...')
|
|
475
|
-
console.error('')
|
|
476
|
-
|
|
477
|
-
const results = {
|
|
478
|
-
home: false,
|
|
479
|
-
config: false,
|
|
480
|
-
hooks: false,
|
|
481
|
-
hookFiles: false,
|
|
482
|
-
claudemd: false,
|
|
483
|
-
autostart: false,
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Step 1: Create data directory
|
|
487
|
-
try { results.home = setupHomeDirectory() } catch (e) { log(`Home setup failed: ${e.message}`) }
|
|
488
|
-
|
|
489
|
-
// Step 2: Create default config.yml
|
|
490
|
-
try { results.config = createDefaultConfig() } catch (e) { log(`Config creation failed: ${e.message}`) }
|
|
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
|
-
|
|
495
|
-
// Step 3: Copy hook files
|
|
496
|
-
try { results.hookFiles = copyHookFiles() } catch (e) { log(`Hook file copy failed: ${e.message}`) }
|
|
497
|
-
|
|
498
|
-
// Step 4: Register hooks in Claude Code settings
|
|
499
|
-
try { results.hooks = installHooks() } catch (e) { log(`Hook registration failed: ${e.message}`) }
|
|
500
|
-
|
|
501
|
-
// Step 5: Install CLAUDE.md
|
|
502
|
-
try { results.claudemd = installClaudeMd() } catch (e) { log(`CLAUDE.md install failed: ${e.message}`) }
|
|
503
|
-
|
|
504
|
-
// Step 6: Install auto-start in shell profile
|
|
505
|
-
try { results.autostart = installShellAutoStart() } catch (e) { log(`Auto-start install failed: ${e.message}`) }
|
|
506
|
-
|
|
507
|
-
// Print success summary
|
|
508
|
-
console.error('')
|
|
509
|
-
console.error(`${PREFIX} ────────────────────────────────────────`)
|
|
510
|
-
console.error(`${PREFIX}`)
|
|
511
|
-
console.error(`${PREFIX} Claude Brain installed successfully!`)
|
|
512
|
-
console.error(`${PREFIX}`)
|
|
513
|
-
console.error(`${PREFIX} Server will auto-start on next terminal session.`)
|
|
514
|
-
console.error(`${PREFIX} Auto-updates check every 24h in the background.`)
|
|
515
|
-
console.error(`${PREFIX}`)
|
|
516
|
-
console.error(`${PREFIX} Data: ${HOME}/data/`)
|
|
517
|
-
console.error(`${PREFIX} Config: ${HOME}/config.yml`)
|
|
518
|
-
console.error(`${PREFIX} Hooks: ${HOME}/hooks/`)
|
|
519
|
-
console.error(`${PREFIX}`)
|
|
520
|
-
console.error(`${PREFIX} No setup wizard needed — everything works out of the box.`)
|
|
521
|
-
console.error(`${PREFIX} ChromaDB is optional (disabled by default, SQLite FTS5 is used).`)
|
|
522
|
-
console.error(`${PREFIX} To disable auto-start: claude-brain autostart uninstall`)
|
|
523
|
-
console.error(`${PREFIX} ────────────────────────────────────────`)
|
|
524
|
-
console.error('')
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
main().catch(err => {
|
|
528
|
-
log(`Postinstall error: ${err.message}`)
|
|
529
|
-
}).finally(() => {
|
|
530
|
-
process.exit(0)
|
|
531
|
-
})
|
|
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
|
+
* Phase 30: Zero-config — does EVERYTHING automatically:
|
|
10
|
+
* 1. Create data directory: ~/.claude-brain/data/
|
|
11
|
+
* 2. Create default config if none exists: ~/.claude-brain/config.yml
|
|
12
|
+
* 3. Copy hook files to ~/.claude-brain/hooks/
|
|
13
|
+
* 4. Register hooks in ~/.claude/settings.json (if Claude Code installed)
|
|
14
|
+
* 5. Print success message with next steps
|
|
15
|
+
*
|
|
16
|
+
* No interactive prompts. No setup wizard. Sensible defaults.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, renameSync } from 'node:fs'
|
|
20
|
+
import { join, dirname } from 'node:path'
|
|
21
|
+
import { homedir } from 'node:os'
|
|
22
|
+
import { execSync } from 'node:child_process'
|
|
23
|
+
import { fileURLToPath } from 'node:url'
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
26
|
+
const __dirname = dirname(__filename)
|
|
27
|
+
|
|
28
|
+
const PREFIX = '[claude-brain]'
|
|
29
|
+
const HOME = join(homedir(), '.claude-brain')
|
|
30
|
+
const CLAUDE_DIR = join(homedir(), '.claude')
|
|
31
|
+
const CLAUDE_SETTINGS = join(CLAUDE_DIR, 'settings.json')
|
|
32
|
+
const CLAUDE_MD_PATH = join(CLAUDE_DIR, 'CLAUDE.md')
|
|
33
|
+
const HOOK_MARKER = 'claude-brain-hook'
|
|
34
|
+
|
|
35
|
+
function log(msg) {
|
|
36
|
+
console.error(`${PREFIX} ${msg}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Step 0: Skip if not a global install ─────────────────
|
|
40
|
+
|
|
41
|
+
function shouldSkip() {
|
|
42
|
+
// CI environments
|
|
43
|
+
if (process.env.CI === 'true' || process.env.CI === '1') {
|
|
44
|
+
return 'CI environment detected'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If we're inside a node_modules directory, it's a local install
|
|
48
|
+
const scriptDir = dirname(__filename)
|
|
49
|
+
if (scriptDir.includes('node_modules') && !scriptDir.includes('global')) {
|
|
50
|
+
// Check if this is actually a global install by looking deeper
|
|
51
|
+
// bun global installs go to ~/.bun/install/global/node_modules
|
|
52
|
+
// npm global installs go to /usr/local/lib/node_modules or ~/.npm-global
|
|
53
|
+
const isGlobalPath =
|
|
54
|
+
scriptDir.includes('.bun/install/global') ||
|
|
55
|
+
scriptDir.includes('/usr/local/lib/node_modules') ||
|
|
56
|
+
scriptDir.includes('/usr/lib/node_modules') ||
|
|
57
|
+
scriptDir.includes('npm-global') ||
|
|
58
|
+
scriptDir.includes('AppData/Roaming/npm')
|
|
59
|
+
|
|
60
|
+
if (!isGlobalPath) {
|
|
61
|
+
return 'local install (not global)'
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Step 1: Create ~/.claude-brain/ data directory ───────
|
|
69
|
+
|
|
70
|
+
function setupHomeDirectory() {
|
|
71
|
+
const isAlreadySetup = existsSync(join(HOME, 'data'))
|
|
72
|
+
|
|
73
|
+
const dirs = [
|
|
74
|
+
join(HOME, 'data'),
|
|
75
|
+
join(HOME, 'logs'),
|
|
76
|
+
join(HOME, 'vault'),
|
|
77
|
+
join(HOME, 'vault', 'Projects'),
|
|
78
|
+
join(HOME, 'vault', 'Global'),
|
|
79
|
+
join(HOME, 'hooks'),
|
|
80
|
+
join(HOME, 'models'),
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
for (const dir of dirs) {
|
|
84
|
+
mkdirSync(dir, { recursive: true })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Write default .env if not present (for backward compat)
|
|
88
|
+
const envPath = join(HOME, '.env')
|
|
89
|
+
if (!existsSync(envPath)) {
|
|
90
|
+
writeFileSync(envPath, `# Claude Brain Configuration
|
|
91
|
+
# Generated by postinstall
|
|
92
|
+
VAULT_PATH=${join(HOME, 'vault')}
|
|
93
|
+
LOG_LEVEL=warn
|
|
94
|
+
NODE_ENV=production
|
|
95
|
+
`, 'utf-8')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Write default global standards
|
|
99
|
+
const standardsPath = join(HOME, 'vault', 'Global', 'standards.md')
|
|
100
|
+
if (!existsSync(standardsPath)) {
|
|
101
|
+
writeFileSync(standardsPath, `---
|
|
102
|
+
type: global-standards
|
|
103
|
+
last_updated: ${new Date().toISOString().split('T')[0]}
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
# Global Coding Standards
|
|
107
|
+
|
|
108
|
+
## General
|
|
109
|
+
- Write clear, readable code
|
|
110
|
+
- Prefer explicit over implicit
|
|
111
|
+
- Keep functions focused and small
|
|
112
|
+
|
|
113
|
+
## TypeScript
|
|
114
|
+
- Use strict mode
|
|
115
|
+
- Prefer const over let
|
|
116
|
+
- Add JSDoc comments for public APIs
|
|
117
|
+
`, 'utf-8')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (isAlreadySetup) {
|
|
121
|
+
log('Data directory already exists')
|
|
122
|
+
} else {
|
|
123
|
+
log('Data directory created')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Step 2: Create default config.yml ────────────────────
|
|
130
|
+
|
|
131
|
+
function createDefaultConfig() {
|
|
132
|
+
const configPath = join(HOME, 'config.yml')
|
|
133
|
+
if (existsSync(configPath)) {
|
|
134
|
+
log('Config already exists')
|
|
135
|
+
return true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const config = `# Claude Brain Configuration
|
|
139
|
+
# Auto-generated on install — edit as needed
|
|
140
|
+
# Docs: https://github.com/your-org/claude-brain
|
|
141
|
+
|
|
142
|
+
storage:
|
|
143
|
+
dataDir: ~/.claude-brain/data
|
|
144
|
+
|
|
145
|
+
# ChromaDB is optional. SQLite FTS5 is the default search backend.
|
|
146
|
+
# Enable for semantic/vector search (requires: pip install chromadb)
|
|
147
|
+
chromadb:
|
|
148
|
+
enabled: false
|
|
149
|
+
|
|
150
|
+
# Code intelligence indexes your project files for smarter context
|
|
151
|
+
codeIntelligence:
|
|
152
|
+
enabled: true
|
|
153
|
+
autoIndexOnSessionStart: true
|
|
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
|
+
|
|
169
|
+
logLevel: warn
|
|
170
|
+
`
|
|
171
|
+
|
|
172
|
+
writeFileSync(configPath, config, 'utf-8')
|
|
173
|
+
log('Created default config.yml')
|
|
174
|
+
return true
|
|
175
|
+
}
|
|
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
|
+
|
|
219
|
+
// ── Step 3: Copy hook files ──────────────────────────────
|
|
220
|
+
|
|
221
|
+
/** Files to copy from package src/hooks/ to ~/.claude-brain/hooks/ */
|
|
222
|
+
const HOOK_FILES = [
|
|
223
|
+
'brain-hook.ts',
|
|
224
|
+
'context-hook.ts',
|
|
225
|
+
'interceptor-hook.ts',
|
|
226
|
+
'capture.ts',
|
|
227
|
+
'queue.ts',
|
|
228
|
+
'types.ts',
|
|
229
|
+
'passive-classifier.ts',
|
|
230
|
+
'claude-code-mastery.md',
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
function copyHookFiles() {
|
|
234
|
+
const destDir = join(HOME, 'hooks')
|
|
235
|
+
mkdirSync(destDir, { recursive: true })
|
|
236
|
+
|
|
237
|
+
// Source: package root / src/hooks/
|
|
238
|
+
const srcDir = join(__dirname, '..', 'src', 'hooks')
|
|
239
|
+
|
|
240
|
+
let copied = 0
|
|
241
|
+
for (const file of HOOK_FILES) {
|
|
242
|
+
const src = join(srcDir, file)
|
|
243
|
+
if (existsSync(src)) {
|
|
244
|
+
writeFileSync(join(destDir, file), readFileSync(src, 'utf-8'), 'utf-8')
|
|
245
|
+
copied++
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
log(`Copied ${copied}/${HOOK_FILES.length} hook files`)
|
|
249
|
+
return copied > 0
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Step 4: Register hooks in ~/.claude/settings.json ────
|
|
253
|
+
|
|
254
|
+
function installHooks() {
|
|
255
|
+
// Read existing settings
|
|
256
|
+
let settings = {}
|
|
257
|
+
if (existsSync(CLAUDE_SETTINGS)) {
|
|
258
|
+
try {
|
|
259
|
+
settings = JSON.parse(readFileSync(CLAUDE_SETTINGS, 'utf-8'))
|
|
260
|
+
} catch {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Check if already installed
|
|
264
|
+
function hasOurHooks(entries) {
|
|
265
|
+
if (!Array.isArray(entries)) return false
|
|
266
|
+
return entries.some(entry =>
|
|
267
|
+
entry && Array.isArray(entry.hooks) &&
|
|
268
|
+
entry.hooks.some(h => typeof h.command === 'string' && h.command.includes(HOOK_MARKER))
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (settings.hooks &&
|
|
273
|
+
hasOurHooks(settings.hooks.PostToolUse) &&
|
|
274
|
+
hasOurHooks(settings.hooks.Stop) &&
|
|
275
|
+
hasOurHooks(settings.hooks.UserPromptSubmit) &&
|
|
276
|
+
hasOurHooks(settings.hooks.SessionStart) &&
|
|
277
|
+
hasOurHooks(settings.hooks.PreToolUse)) {
|
|
278
|
+
log('Hooks already installed')
|
|
279
|
+
return true
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Build hook command
|
|
283
|
+
const brainScriptPath = join(HOME, 'hooks', 'brain-hook.ts')
|
|
284
|
+
const contextScriptPath = join(HOME, 'hooks', 'context-hook.ts')
|
|
285
|
+
const interceptorScriptPath = join(HOME, 'hooks', 'interceptor-hook.ts')
|
|
286
|
+
const port = process.env.CLAUDE_BRAIN_PORT || process.env.PORT || '3000'
|
|
287
|
+
function buildCmd(event, scriptPath) {
|
|
288
|
+
return `bun "${scriptPath}" --event ${event} --port ${port} # ${HOOK_MARKER}`
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!settings.hooks) settings.hooks = {}
|
|
292
|
+
|
|
293
|
+
// PostToolUse — captures tool events
|
|
294
|
+
if (!hasOurHooks(settings.hooks.PostToolUse)) {
|
|
295
|
+
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = []
|
|
296
|
+
settings.hooks.PostToolUse.push({
|
|
297
|
+
matcher: '',
|
|
298
|
+
hooks: [{ type: 'command', command: buildCmd('PostToolUse', brainScriptPath) }],
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Stop — triggers session-end summary
|
|
303
|
+
if (!hasOurHooks(settings.hooks.Stop)) {
|
|
304
|
+
if (!settings.hooks.Stop) settings.hooks.Stop = []
|
|
305
|
+
settings.hooks.Stop.push({
|
|
306
|
+
matcher: '',
|
|
307
|
+
hooks: [{ type: 'command', command: buildCmd('Stop', brainScriptPath) }],
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// UserPromptSubmit — injects relevant memories into every prompt
|
|
312
|
+
if (!hasOurHooks(settings.hooks.UserPromptSubmit)) {
|
|
313
|
+
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = []
|
|
314
|
+
settings.hooks.UserPromptSubmit.push({
|
|
315
|
+
matcher: '',
|
|
316
|
+
hooks: [{ type: 'command', command: buildCmd('UserPromptSubmit', contextScriptPath) }],
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// SessionStart — injects project context on session start/resume
|
|
321
|
+
if (!hasOurHooks(settings.hooks.SessionStart)) {
|
|
322
|
+
if (!settings.hooks.SessionStart) settings.hooks.SessionStart = []
|
|
323
|
+
settings.hooks.SessionStart.push({
|
|
324
|
+
matcher: 'startup,resume,compact',
|
|
325
|
+
hooks: [{ type: 'command', command: buildCmd('SessionStart', contextScriptPath) }],
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// PreToolUse — code intelligence interceptor for Glob and Grep
|
|
330
|
+
if (!hasOurHooks(settings.hooks.PreToolUse)) {
|
|
331
|
+
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = []
|
|
332
|
+
settings.hooks.PreToolUse.push({
|
|
333
|
+
matcher: 'Glob',
|
|
334
|
+
hooks: [{ type: 'command', command: buildCmd('PreToolUse', interceptorScriptPath) }],
|
|
335
|
+
})
|
|
336
|
+
settings.hooks.PreToolUse.push({
|
|
337
|
+
matcher: 'Grep',
|
|
338
|
+
hooks: [{ type: 'command', command: buildCmd('PreToolUse', interceptorScriptPath) }],
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Write atomically
|
|
343
|
+
if (!existsSync(CLAUDE_DIR)) {
|
|
344
|
+
mkdirSync(CLAUDE_DIR, { recursive: true })
|
|
345
|
+
}
|
|
346
|
+
const tmpPath = CLAUDE_SETTINGS + '.tmp'
|
|
347
|
+
writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8')
|
|
348
|
+
renameSync(tmpPath, CLAUDE_SETTINGS)
|
|
349
|
+
|
|
350
|
+
log('Hooks registered in Claude Code')
|
|
351
|
+
return true
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ── Step 5: Install CLAUDE.md ────────────────────────────
|
|
355
|
+
|
|
356
|
+
function installClaudeMd() {
|
|
357
|
+
// Find the assets/CLAUDE.md relative to this script
|
|
358
|
+
// scripts/postinstall.mjs → assets/CLAUDE.md
|
|
359
|
+
const assetsPath = join(__dirname, '..', 'assets', 'CLAUDE.md')
|
|
360
|
+
|
|
361
|
+
if (!existsSync(assetsPath)) {
|
|
362
|
+
log('CLAUDE.md asset not found — skipping')
|
|
363
|
+
return false
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Only install if no CLAUDE.md exists yet (don't overwrite user customizations)
|
|
367
|
+
if (existsSync(CLAUDE_MD_PATH)) {
|
|
368
|
+
// Check if it already mentions claude-brain
|
|
369
|
+
const existing = readFileSync(CLAUDE_MD_PATH, 'utf-8')
|
|
370
|
+
if (existing.includes('brain') || existing.includes('Brain')) {
|
|
371
|
+
log('CLAUDE.md already configured')
|
|
372
|
+
return true
|
|
373
|
+
}
|
|
374
|
+
// Append our section
|
|
375
|
+
const addition = readFileSync(assetsPath, 'utf-8')
|
|
376
|
+
writeFileSync(CLAUDE_MD_PATH, existing.trimEnd() + '\n\n' + addition, 'utf-8')
|
|
377
|
+
log('Appended brain instructions to existing CLAUDE.md')
|
|
378
|
+
return true
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Create new
|
|
382
|
+
if (!existsSync(CLAUDE_DIR)) {
|
|
383
|
+
mkdirSync(CLAUDE_DIR, { recursive: true })
|
|
384
|
+
}
|
|
385
|
+
const content = readFileSync(assetsPath, 'utf-8')
|
|
386
|
+
writeFileSync(CLAUDE_MD_PATH, content, 'utf-8')
|
|
387
|
+
log('Installed CLAUDE.md')
|
|
388
|
+
return true
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ── Step 6: Install auto-start in shell profile ──────────
|
|
392
|
+
|
|
393
|
+
const AUTO_START_MARKER = '# >>> claude-brain auto-start >>>'
|
|
394
|
+
const AUTO_END_MARKER = '# <<< claude-brain auto-start <<<'
|
|
395
|
+
|
|
396
|
+
function getShellProfile() {
|
|
397
|
+
const home = homedir()
|
|
398
|
+
const os = process.platform
|
|
399
|
+
|
|
400
|
+
if (os === 'win32') {
|
|
401
|
+
const userProfile = process.env.USERPROFILE
|
|
402
|
+
if (userProfile) {
|
|
403
|
+
return join(userProfile, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1')
|
|
404
|
+
}
|
|
405
|
+
return null
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// macOS defaults to zsh, Linux to bash
|
|
409
|
+
return os === 'darwin' ? join(home, '.zshrc') : join(home, '.bashrc')
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function buildAutoStartSnippet(profilePath) {
|
|
413
|
+
if (profilePath && profilePath.endsWith('.ps1')) {
|
|
414
|
+
return [
|
|
415
|
+
AUTO_START_MARKER,
|
|
416
|
+
'if (Get-Command claude-brain -ErrorAction SilentlyContinue) {',
|
|
417
|
+
' $listening = Get-NetTCPConnection -LocalPort 3000 -ErrorAction SilentlyContinue',
|
|
418
|
+
' if (-not $listening) {',
|
|
419
|
+
' Start-Process -NoNewWindow -FilePath "claude-brain" -ArgumentList "serve","--http-only" -WindowStyle Hidden',
|
|
420
|
+
' }',
|
|
421
|
+
'}',
|
|
422
|
+
AUTO_END_MARKER,
|
|
423
|
+
].join('\n')
|
|
424
|
+
}
|
|
425
|
+
return [
|
|
426
|
+
AUTO_START_MARKER,
|
|
427
|
+
'# Auto-start claude-brain HTTP server if not already running',
|
|
428
|
+
'(command -v claude-brain >/dev/null 2>&1 && ! lsof -ti :3000 >/dev/null 2>&1) && {',
|
|
429
|
+
' nohup claude-brain serve --http-only >/dev/null 2>&1 &',
|
|
430
|
+
' disown 2>/dev/null',
|
|
431
|
+
'}',
|
|
432
|
+
AUTO_END_MARKER,
|
|
433
|
+
].join('\n')
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function installShellAutoStart() {
|
|
437
|
+
const profilePath = getShellProfile()
|
|
438
|
+
if (!profilePath) {
|
|
439
|
+
log('No supported shell profile found, skipping auto-start')
|
|
440
|
+
return false
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Check if already installed
|
|
444
|
+
if (existsSync(profilePath)) {
|
|
445
|
+
const content = readFileSync(profilePath, 'utf-8')
|
|
446
|
+
if (content.includes(AUTO_START_MARKER)) {
|
|
447
|
+
log('Auto-start already installed')
|
|
448
|
+
return true
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Ensure parent directory exists
|
|
453
|
+
const dir = join(profilePath, '..')
|
|
454
|
+
mkdirSync(dir, { recursive: true })
|
|
455
|
+
|
|
456
|
+
const existing = existsSync(profilePath) ? readFileSync(profilePath, 'utf-8') : ''
|
|
457
|
+
const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n\n' : '\n'
|
|
458
|
+
const snippet = buildAutoStartSnippet(profilePath)
|
|
459
|
+
writeFileSync(profilePath, existing + separator + snippet + '\n', 'utf-8')
|
|
460
|
+
|
|
461
|
+
log(`Auto-start installed in ${profilePath}`)
|
|
462
|
+
return true
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ── Main ─────────────────────────────────────────────────
|
|
466
|
+
|
|
467
|
+
async function main() {
|
|
468
|
+
const skipReason = shouldSkip()
|
|
469
|
+
if (skipReason) {
|
|
470
|
+
log(`Skipping postinstall (${skipReason})`)
|
|
471
|
+
return
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
log('Running zero-config setup...')
|
|
475
|
+
console.error('')
|
|
476
|
+
|
|
477
|
+
const results = {
|
|
478
|
+
home: false,
|
|
479
|
+
config: false,
|
|
480
|
+
hooks: false,
|
|
481
|
+
hookFiles: false,
|
|
482
|
+
claudemd: false,
|
|
483
|
+
autostart: false,
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Step 1: Create data directory
|
|
487
|
+
try { results.home = setupHomeDirectory() } catch (e) { log(`Home setup failed: ${e.message}`) }
|
|
488
|
+
|
|
489
|
+
// Step 2: Create default config.yml
|
|
490
|
+
try { results.config = createDefaultConfig() } catch (e) { log(`Config creation failed: ${e.message}`) }
|
|
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
|
+
|
|
495
|
+
// Step 3: Copy hook files
|
|
496
|
+
try { results.hookFiles = copyHookFiles() } catch (e) { log(`Hook file copy failed: ${e.message}`) }
|
|
497
|
+
|
|
498
|
+
// Step 4: Register hooks in Claude Code settings
|
|
499
|
+
try { results.hooks = installHooks() } catch (e) { log(`Hook registration failed: ${e.message}`) }
|
|
500
|
+
|
|
501
|
+
// Step 5: Install CLAUDE.md
|
|
502
|
+
try { results.claudemd = installClaudeMd() } catch (e) { log(`CLAUDE.md install failed: ${e.message}`) }
|
|
503
|
+
|
|
504
|
+
// Step 6: Install auto-start in shell profile
|
|
505
|
+
try { results.autostart = installShellAutoStart() } catch (e) { log(`Auto-start install failed: ${e.message}`) }
|
|
506
|
+
|
|
507
|
+
// Print success summary
|
|
508
|
+
console.error('')
|
|
509
|
+
console.error(`${PREFIX} ────────────────────────────────────────`)
|
|
510
|
+
console.error(`${PREFIX}`)
|
|
511
|
+
console.error(`${PREFIX} Claude Brain installed successfully!`)
|
|
512
|
+
console.error(`${PREFIX}`)
|
|
513
|
+
console.error(`${PREFIX} Server will auto-start on next terminal session.`)
|
|
514
|
+
console.error(`${PREFIX} Auto-updates check every 24h in the background.`)
|
|
515
|
+
console.error(`${PREFIX}`)
|
|
516
|
+
console.error(`${PREFIX} Data: ${HOME}/data/`)
|
|
517
|
+
console.error(`${PREFIX} Config: ${HOME}/config.yml`)
|
|
518
|
+
console.error(`${PREFIX} Hooks: ${HOME}/hooks/`)
|
|
519
|
+
console.error(`${PREFIX}`)
|
|
520
|
+
console.error(`${PREFIX} No setup wizard needed — everything works out of the box.`)
|
|
521
|
+
console.error(`${PREFIX} ChromaDB is optional (disabled by default, SQLite FTS5 is used).`)
|
|
522
|
+
console.error(`${PREFIX} To disable auto-start: claude-brain autostart uninstall`)
|
|
523
|
+
console.error(`${PREFIX} ────────────────────────────────────────`)
|
|
524
|
+
console.error('')
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
main().catch(err => {
|
|
528
|
+
log(`Postinstall error: ${err.message}`)
|
|
529
|
+
}).finally(() => {
|
|
530
|
+
process.exit(0)
|
|
531
|
+
})
|