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.
Files changed (236) hide show
  1. package/README.md +241 -191
  2. package/VERSION +1 -1
  3. package/assets/CLAUDE-unified.md +11 -11
  4. package/assets/CLAUDE.md +29 -29
  5. package/package.json +7 -3
  6. package/packs/backend/node.json +173 -173
  7. package/packs/core/javascript.json +176 -176
  8. package/packs/core/typescript.json +222 -222
  9. package/packs/frontend/react.json +254 -254
  10. package/packs/meta/testing.json +172 -172
  11. package/scripts/postinstall.mjs +531 -531
  12. package/src/automation/decision-detector.ts +452 -452
  13. package/src/automation/phase12-manager.ts +456 -456
  14. package/src/automation/proactive-recall.ts +373 -373
  15. package/src/automation/project-detector.ts +310 -310
  16. package/src/automation/repo-scanner.ts +210 -205
  17. package/src/cli/auto-setup.ts +75 -75
  18. package/src/cli/auto-start.ts +266 -266
  19. package/src/cli/bin.ts +264 -264
  20. package/src/cli/commands/autostart.ts +90 -90
  21. package/src/cli/commands/chroma.ts +578 -577
  22. package/src/cli/commands/export-training.ts +70 -70
  23. package/src/cli/commands/export.ts +130 -130
  24. package/src/cli/commands/git-hook.ts +183 -183
  25. package/src/cli/commands/hooks.ts +217 -217
  26. package/src/cli/commands/init.ts +123 -123
  27. package/src/cli/commands/install-mcp.ts +122 -111
  28. package/src/cli/commands/models.ts +979 -979
  29. package/src/cli/commands/pack.ts +200 -200
  30. package/src/cli/commands/refresh.ts +344 -339
  31. package/src/cli/commands/reindex.ts +120 -120
  32. package/src/cli/commands/serve.ts +466 -463
  33. package/src/cli/commands/start.ts +44 -44
  34. package/src/cli/commands/status.ts +220 -203
  35. package/src/cli/commands/uninstall-mcp.ts +45 -41
  36. package/src/cli/commands/update.ts +130 -124
  37. package/src/cli/migrate-chroma.ts +106 -106
  38. package/src/cli/ui/animations.ts +80 -80
  39. package/src/cli/ui/components.ts +82 -82
  40. package/src/cli/ui/index.ts +4 -4
  41. package/src/cli/ui/logo.ts +36 -36
  42. package/src/cli/ui/theme.ts +55 -55
  43. package/src/code-intelligence/indexer.ts +352 -352
  44. package/src/code-intelligence/linker.ts +178 -178
  45. package/src/code-intelligence/parser.ts +484 -484
  46. package/src/code-intelligence/query.ts +291 -291
  47. package/src/code-intelligence/schema.ts +83 -83
  48. package/src/code-intelligence/types.ts +95 -95
  49. package/src/config/defaults.ts +52 -52
  50. package/src/config/home.ts +59 -56
  51. package/src/config/index.ts +5 -5
  52. package/src/config/loader.ts +195 -192
  53. package/src/config/schema.ts +446 -415
  54. package/src/config/validator.ts +182 -182
  55. package/src/context/assembler.ts +407 -400
  56. package/src/context/index.ts +79 -79
  57. package/src/context/progress-tracker.ts +174 -174
  58. package/src/context/standards-manager.ts +287 -287
  59. package/src/context/validator.ts +58 -58
  60. package/src/diagnostics/index.ts +122 -121
  61. package/src/health/index.ts +233 -232
  62. package/src/hooks/brain-hook.ts +134 -131
  63. package/src/hooks/capture.ts +168 -168
  64. package/src/hooks/claude-code-mastery.md +112 -112
  65. package/src/hooks/context-hook.ts +260 -245
  66. package/src/hooks/deduplicator.ts +72 -72
  67. package/src/hooks/git-capture.ts +109 -109
  68. package/src/hooks/git-hook-installer.ts +211 -207
  69. package/src/hooks/index.ts +20 -20
  70. package/src/hooks/installer.ts +306 -288
  71. package/src/hooks/interceptor-hook.ts +204 -201
  72. package/src/hooks/passive-classifier.ts +397 -397
  73. package/src/hooks/queue.ts +160 -129
  74. package/src/hooks/session-tracker.ts +312 -312
  75. package/src/hooks/types.ts +52 -52
  76. package/src/index.ts +7 -7
  77. package/src/intelligence/cross-project/generalizer.ts +283 -283
  78. package/src/intelligence/cross-project/index.ts +7 -7
  79. package/src/intelligence/hf-downloader.ts +222 -222
  80. package/src/intelligence/hf-manifest.json +78 -78
  81. package/src/intelligence/index.ts +24 -24
  82. package/src/intelligence/inference-router.ts +762 -762
  83. package/src/intelligence/model-manager.ts +263 -245
  84. package/src/intelligence/optimization/index.ts +10 -10
  85. package/src/intelligence/optimization/precompute.ts +202 -202
  86. package/src/intelligence/optimization/semantic-cache.ts +213 -207
  87. package/src/intelligence/prediction/index.ts +7 -7
  88. package/src/intelligence/prediction/recommender.ts +276 -268
  89. package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
  90. package/src/intelligence/reasoning/index.ts +7 -7
  91. package/src/intelligence/temporal/evolution.ts +193 -197
  92. package/src/intelligence/temporal/index.ts +16 -16
  93. package/src/intelligence/temporal/query-processor.ts +190 -190
  94. package/src/intelligence/temporal/timeline.ts +272 -259
  95. package/src/intelligence/temporal/trends.ts +263 -263
  96. package/src/intelligence/tokenizer.ts +118 -118
  97. package/src/knowledge/entity-extractor.ts +447 -443
  98. package/src/knowledge/graph/builder.ts +185 -185
  99. package/src/knowledge/graph/linker.ts +201 -201
  100. package/src/knowledge/graph/memory-graph.ts +359 -359
  101. package/src/knowledge/graph/schema.ts +99 -99
  102. package/src/knowledge/graph/search.ts +166 -166
  103. package/src/knowledge/relationship-extractor.ts +108 -108
  104. package/src/memory/chroma/client.ts +211 -192
  105. package/src/memory/chroma/collection-manager.ts +92 -92
  106. package/src/memory/chroma/config.ts +57 -57
  107. package/src/memory/chroma/embeddings.ts +177 -175
  108. package/src/memory/chroma/index.ts +82 -82
  109. package/src/memory/chroma/migration.ts +270 -270
  110. package/src/memory/chroma/schemas.ts +69 -69
  111. package/src/memory/chroma/search.ts +319 -315
  112. package/src/memory/chroma/store.ts +755 -747
  113. package/src/memory/compression.ts +121 -121
  114. package/src/memory/consolidation/archiver.ts +162 -165
  115. package/src/memory/consolidation/merger.ts +182 -186
  116. package/src/memory/consolidation/scorer.ts +136 -136
  117. package/src/memory/database.ts +9 -0
  118. package/src/memory/dual-write.ts +145 -0
  119. package/src/memory/embeddings.ts +226 -226
  120. package/src/memory/episodic/detector.ts +108 -108
  121. package/src/memory/episodic/manager.ts +347 -351
  122. package/src/memory/episodic/summarizer.ts +179 -179
  123. package/src/memory/episodic/types.ts +52 -52
  124. package/src/memory/fts5-search.ts +692 -633
  125. package/src/memory/index.ts +943 -1060
  126. package/src/memory/migrations/add-fts5.ts +118 -108
  127. package/src/memory/patterns.ts +438 -438
  128. package/src/memory/pruning.ts +60 -60
  129. package/src/memory/schema.ts +88 -88
  130. package/src/memory/store.ts +911 -787
  131. package/src/orchestrator/handlers/decision-handler.ts +204 -204
  132. package/src/packs/index.ts +9 -9
  133. package/src/packs/loader.ts +134 -134
  134. package/src/packs/manager.ts +204 -204
  135. package/src/packs/ranker.ts +78 -78
  136. package/src/packs/types.ts +81 -81
  137. package/src/phase12/index.ts +5 -5
  138. package/src/retrieval/bm25/index.ts +300 -297
  139. package/src/retrieval/bm25/tokenizer.ts +184 -184
  140. package/src/retrieval/feedback/adaptive.ts +221 -221
  141. package/src/retrieval/feedback/index.ts +16 -16
  142. package/src/retrieval/feedback/metrics.ts +221 -221
  143. package/src/retrieval/feedback/store.ts +283 -283
  144. package/src/retrieval/fusion/index.ts +194 -194
  145. package/src/retrieval/fusion/rrf.ts +165 -165
  146. package/src/retrieval/index.ts +12 -12
  147. package/src/retrieval/pipeline.ts +375 -375
  148. package/src/retrieval/query/expander.ts +203 -203
  149. package/src/retrieval/query/index.ts +27 -27
  150. package/src/retrieval/query/intent-classifier.ts +252 -252
  151. package/src/retrieval/query/temporal-parser.ts +295 -295
  152. package/src/retrieval/reranker/index.ts +189 -188
  153. package/src/retrieval/reranker/model.ts +99 -95
  154. package/src/retrieval/service.ts +125 -125
  155. package/src/retrieval/types.ts +162 -162
  156. package/src/routing/entity-extractor.ts +454 -454
  157. package/src/routing/handlers/exploration-handler.ts +369 -0
  158. package/src/routing/handlers/index.ts +19 -0
  159. package/src/routing/handlers/memory-handler.ts +273 -0
  160. package/src/routing/handlers/mutation-handler.ts +241 -0
  161. package/src/routing/handlers/recall-handler.ts +642 -0
  162. package/src/routing/handlers/shared.ts +515 -0
  163. package/src/routing/handlers/types.ts +48 -0
  164. package/src/routing/intent-classifier.ts +552 -552
  165. package/src/routing/response-filter.ts +399 -391
  166. package/src/routing/router.ts +245 -2193
  167. package/src/routing/search-engine.ts +521 -514
  168. package/src/routing/types.ts +104 -94
  169. package/src/scripts/health-check.ts +118 -118
  170. package/src/scripts/setup.ts +122 -122
  171. package/src/server/auto-updater.ts +283 -276
  172. package/src/server/handlers/call-tool.ts +159 -159
  173. package/src/server/handlers/list-tools.ts +35 -35
  174. package/src/server/handlers/tools/auto-remember.ts +165 -165
  175. package/src/server/handlers/tools/brain.ts +86 -86
  176. package/src/server/handlers/tools/create-project.ts +135 -135
  177. package/src/server/handlers/tools/get-code-standards.ts +123 -123
  178. package/src/server/handlers/tools/get-corrections.ts +152 -152
  179. package/src/server/handlers/tools/get-patterns.ts +156 -156
  180. package/src/server/handlers/tools/get-project-context.ts +75 -75
  181. package/src/server/handlers/tools/index.ts +30 -30
  182. package/src/server/handlers/tools/init-project.ts +756 -756
  183. package/src/server/handlers/tools/list-projects.ts +126 -126
  184. package/src/server/handlers/tools/recall-similar.ts +87 -87
  185. package/src/server/handlers/tools/recognize-pattern.ts +132 -132
  186. package/src/server/handlers/tools/record-correction.ts +131 -131
  187. package/src/server/handlers/tools/remember-decision.ts +168 -168
  188. package/src/server/handlers/tools/schemas.ts +179 -179
  189. package/src/server/handlers/tools/search-code.ts +122 -122
  190. package/src/server/handlers/tools/smart-context.ts +146 -146
  191. package/src/server/handlers/tools/update-progress.ts +131 -131
  192. package/src/server/http-api.ts +215 -1229
  193. package/src/server/mcp-proxy.ts +85 -84
  194. package/src/server/mcp-server.ts +285 -284
  195. package/src/server/middleware/auth.ts +39 -0
  196. package/src/server/middleware/error-handler.ts +37 -0
  197. package/src/server/middleware/rate-limit.ts +53 -0
  198. package/src/server/middleware/validate.ts +42 -0
  199. package/src/server/pid-manager.ts +137 -136
  200. package/src/server/providers/resources.ts +581 -581
  201. package/src/server/routes/code.ts +228 -0
  202. package/src/server/routes/context.ts +26 -0
  203. package/src/server/routes/health.ts +19 -0
  204. package/src/server/routes/helpers.ts +100 -0
  205. package/src/server/routes/hooks.ts +197 -0
  206. package/src/server/routes/mcp.ts +47 -0
  207. package/src/server/routes/memory.ts +397 -0
  208. package/src/server/routes/models.ts +96 -0
  209. package/src/server/routes/projects.ts +89 -0
  210. package/src/server/routes/types.ts +21 -0
  211. package/src/server/schemas/api-schemas.ts +202 -0
  212. package/src/server/services.ts +720 -720
  213. package/src/server/utils/memory-indicator.ts +84 -84
  214. package/src/server/utils/response-formatter.ts +129 -129
  215. package/src/server/web-viewer.ts +1145 -1115
  216. package/src/setup/index.ts +38 -38
  217. package/src/tools/registry.ts +115 -115
  218. package/src/tools/schemas.ts +666 -666
  219. package/src/tools/types.ts +412 -412
  220. package/src/training/data-store.ts +320 -298
  221. package/src/training/retrain-pipeline.ts +399 -394
  222. package/src/utils/error-handler.ts +136 -136
  223. package/src/utils/index.ts +58 -58
  224. package/src/utils/kill-port.ts +55 -53
  225. package/src/utils/phase12-helper.ts +56 -56
  226. package/src/utils/safe-path.ts +43 -0
  227. package/src/utils/timing.ts +47 -47
  228. package/src/utils/transaction.ts +63 -63
  229. package/src/vault/index.ts +4 -3
  230. package/src/vault/paths.ts +106 -106
  231. package/src/vault/query.ts +4 -1
  232. package/src/vault/reader.ts +44 -1
  233. package/src/vault/watcher.ts +24 -1
  234. package/src/vault/writer.ts +487 -413
  235. package/skills/persistent-memory/SKILL.md +0 -148
  236. package/skills/persistent-memory/references/tool-reference.md +0 -90
@@ -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
+ })