claude-brain 0.3.1 → 0.3.3
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 +69 -37
- package/VERSION +1 -1
- package/package.json +3 -1
- package/src/cli/bin.ts +55 -29
- package/src/cli/commands/install-mcp.ts +51 -29
- package/src/cli/commands/uninstall-mcp.ts +41 -0
- package/src/cli/ui/animations.ts +80 -0
- package/src/cli/ui/components.ts +82 -0
- package/src/cli/ui/index.ts +4 -0
- package/src/cli/ui/logo.ts +36 -0
- package/src/cli/ui/theme.ts +55 -0
- package/src/setup/index.ts +27 -1
- package/src/setup/wizard.ts +156 -90
package/README.md
CHANGED
|
@@ -22,55 +22,82 @@ A locally-running development assistant that bridges Obsidian knowledge vaults w
|
|
|
22
22
|
- Runs completely locally with zero cloud dependencies
|
|
23
23
|
- Compiles to single portable executable
|
|
24
24
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
- [Bun](https://bun.sh) >= 1.0.0
|
|
28
|
-
- An Obsidian vault (or any markdown folder)
|
|
29
|
-
|
|
30
|
-
## Installation
|
|
25
|
+
## Quick Start
|
|
31
26
|
|
|
32
27
|
```bash
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
cd claude-brain
|
|
28
|
+
# Install globally (requires Bun)
|
|
29
|
+
bun install -g claude-brain
|
|
36
30
|
|
|
37
|
-
#
|
|
38
|
-
|
|
31
|
+
# Interactive setup (vault path, log level, installs ~/CLAUDE.md)
|
|
32
|
+
claude-brain setup
|
|
39
33
|
|
|
40
|
-
#
|
|
41
|
-
|
|
34
|
+
# Register with Claude Code
|
|
35
|
+
claude mcp add claude-brain -- bunx claude-brain@latest
|
|
42
36
|
```
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
That's it. Every Claude Code session now has 25 brain tools available.
|
|
45
39
|
|
|
46
|
-
|
|
40
|
+
## Zero-Install Alternative
|
|
41
|
+
|
|
42
|
+
Skip the global install — just register with Claude Code directly:
|
|
47
43
|
|
|
48
44
|
```bash
|
|
49
|
-
|
|
45
|
+
claude mcp add claude-brain -- bunx claude-brain@latest
|
|
50
46
|
```
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
On first run, `~/.claude-brain/` is auto-created with default config.
|
|
49
|
+
|
|
50
|
+
## Prerequisites
|
|
51
|
+
|
|
52
|
+
- [Bun](https://bun.sh) >= 1.0.0
|
|
53
|
+
- An Obsidian vault (or any markdown folder)
|
|
54
|
+
|
|
55
|
+
## CLI Commands
|
|
56
|
+
|
|
57
|
+
| Command | Description |
|
|
58
|
+
|---------|-------------|
|
|
59
|
+
| `claude-brain` | Start MCP server (default) |
|
|
60
|
+
| `claude-brain setup` | Interactive setup wizard (run once per machine) |
|
|
61
|
+
| `claude-brain install` | Register as MCP server in Claude Code |
|
|
62
|
+
| `claude-brain health` | Run health checks |
|
|
63
|
+
| `claude-brain diagnose` | Run diagnostics |
|
|
64
|
+
| `claude-brain version` | Show version |
|
|
65
|
+
| `claude-brain help` | Show help |
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
Configuration lives in `~/.claude-brain/.env`. Created automatically by `claude-brain setup`, or on first run with defaults.
|
|
70
|
+
|
|
71
|
+
| Variable | Description | Default |
|
|
72
|
+
|----------|-------------|---------|
|
|
73
|
+
| `VAULT_PATH` | Path to your Obsidian vault | `~/.claude-brain/vault` |
|
|
74
|
+
| `LOG_LEVEL` | Log level (debug/info/warn/error) | `info` |
|
|
75
|
+
| `NODE_ENV` | Environment | `production` |
|
|
76
|
+
| `CLAUDE_BRAIN_HOME` | Override home directory | `~/.claude-brain/` |
|
|
54
77
|
|
|
55
78
|
## Development
|
|
56
79
|
|
|
57
80
|
```bash
|
|
58
|
-
#
|
|
81
|
+
# Clone and install dependencies
|
|
82
|
+
git clone <repo-url>
|
|
83
|
+
cd claude-brain
|
|
84
|
+
bun install
|
|
85
|
+
|
|
86
|
+
# Start dev server (uses local ./data, ./logs)
|
|
59
87
|
bun run dev
|
|
60
88
|
|
|
61
89
|
# Run tests
|
|
62
|
-
bun
|
|
90
|
+
bun test
|
|
63
91
|
|
|
64
92
|
# Run tests in watch mode
|
|
65
|
-
bun
|
|
93
|
+
bun test --watch
|
|
66
94
|
```
|
|
67
95
|
|
|
96
|
+
The `dev` script sets `CLAUDE_BRAIN_HOME=.` so all data stays in the project directory.
|
|
97
|
+
|
|
68
98
|
## Building
|
|
69
99
|
|
|
70
100
|
```bash
|
|
71
|
-
# Build for production (bundled JS)
|
|
72
|
-
bun run build
|
|
73
|
-
|
|
74
101
|
# Build standalone executable for current platform
|
|
75
102
|
bun run build:binary
|
|
76
103
|
|
|
@@ -83,7 +110,15 @@ bun run build:all
|
|
|
83
110
|
```
|
|
84
111
|
claude-brain/
|
|
85
112
|
├── src/
|
|
86
|
-
│ ├── index.ts #
|
|
113
|
+
│ ├── index.ts # Entry point (thin wrapper)
|
|
114
|
+
│ ├── cli/
|
|
115
|
+
│ │ ├── bin.ts # CLI entry point (claude-brain command)
|
|
116
|
+
│ │ ├── auto-setup.ts # First-run home directory initialization
|
|
117
|
+
│ │ └── commands/ # serve, install-mcp
|
|
118
|
+
│ ├── config/
|
|
119
|
+
│ │ ├── home.ts # ~/.claude-brain/ path resolution
|
|
120
|
+
│ │ ├── loader.ts # Config loading (defaults → file → env)
|
|
121
|
+
│ │ └── schema.ts # Zod config schemas
|
|
87
122
|
│ ├── server/ # MCP server code
|
|
88
123
|
│ │ └── handlers/tools/ # 25 tool handlers with Zod validation
|
|
89
124
|
│ ├── vault/ # Obsidian integration
|
|
@@ -94,27 +129,24 @@ claude-brain/
|
|
|
94
129
|
│ ├── knowledge/ # Knowledge graph & entity extraction
|
|
95
130
|
│ │ └── graph/ # In-memory graph with search & auto-population
|
|
96
131
|
│ ├── retrieval/ # Hybrid search (BM25 + semantic + reranking)
|
|
97
|
-
│ │ ├── bm25/ # BM25 keyword search engine
|
|
98
|
-
│ │ ├── fusion/ # RRF/linear/max fusion strategies
|
|
99
|
-
│ │ ├── reranker/ # Cross-encoder neural reranking
|
|
100
|
-
│ │ ├── query/ # Intent classification & query expansion
|
|
101
|
-
│ │ └── feedback/ # Feedback collection & adaptive learning
|
|
102
132
|
│ ├── temporal/ # Timeline construction & trend detection
|
|
103
133
|
│ ├── reasoning/ # Multi-hop chain retrieval & what-if analysis
|
|
104
134
|
│ ├── prediction/ # Decision prediction & recommendations
|
|
105
135
|
│ ├── cross-project/ # Cross-project pattern discovery & transfer
|
|
106
136
|
│ ├── optimization/ # Semantic caching & precomputation
|
|
107
137
|
│ ├── orchestrator/ # Event-driven coordination system
|
|
138
|
+
│ ├── setup/ # Interactive setup wizard
|
|
108
139
|
│ ├── context/ # Context assembly
|
|
109
140
|
│ ├── tools/ # MCP tool definitions & registry
|
|
110
|
-
│
|
|
111
|
-
|
|
112
|
-
│
|
|
113
|
-
│ └── scripts/ # CLI scripts
|
|
114
|
-
├── data/ # Local data storage
|
|
115
|
-
├── logs/ # Log files
|
|
141
|
+
│ └── utils/ # Shared utilities
|
|
142
|
+
├── assets/
|
|
143
|
+
│ └── CLAUDE.md # Protocol file (shipped with npm package)
|
|
116
144
|
├── tests/ # 750+ tests
|
|
117
|
-
└──
|
|
145
|
+
└── ~/.claude-brain/ # User data (created at runtime)
|
|
146
|
+
├── .env # Configuration
|
|
147
|
+
├── data/ # SQLite + ChromaDB
|
|
148
|
+
├── logs/ # Log files
|
|
149
|
+
└── vault/ # Default vault location
|
|
118
150
|
```
|
|
119
151
|
|
|
120
152
|
## Architecture Highlights
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-brain",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@chroma-core/default-embed": "0.1.9",
|
|
58
58
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
59
59
|
"@xenova/transformers": "2.17.2",
|
|
60
|
+
"chalk": "5.6.2",
|
|
60
61
|
"chromadb": "3.2.2",
|
|
61
62
|
"chromadb-default-embed": "2.14.0",
|
|
62
63
|
"chrono-node": "2.9.0",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"hono": "4.11.5",
|
|
67
68
|
"lru-cache": "11.2.5",
|
|
68
69
|
"minisearch": "^6.3.0",
|
|
70
|
+
"ora": "9.2.0",
|
|
69
71
|
"pino": "^10.1.1",
|
|
70
72
|
"pino-pretty": "^13.1.3",
|
|
71
73
|
"prompts": "2.4.2",
|
package/src/cli/bin.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { readFileSync } from 'node:fs'
|
|
4
4
|
import { resolve, dirname } from 'node:path'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { renderLogo, theme, dimText, box, errorText } from '@/cli/ui/index.js'
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url)
|
|
8
9
|
const __dirname = dirname(__filename)
|
|
@@ -19,33 +20,50 @@ function getVersion(): string {
|
|
|
19
20
|
|
|
20
21
|
function printHelp() {
|
|
21
22
|
const version = getVersion()
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
23
|
+
|
|
24
|
+
console.log()
|
|
25
|
+
console.log(renderLogo())
|
|
26
|
+
console.log()
|
|
27
|
+
|
|
28
|
+
const commands = [
|
|
29
|
+
['serve', 'Start the MCP server (default)'],
|
|
30
|
+
['setup', 'Run interactive setup wizard'],
|
|
31
|
+
['install', 'Register as MCP server in Claude Code'],
|
|
32
|
+
['uninstall', 'Remove MCP server from Claude Code'],
|
|
33
|
+
['health', 'Run health checks'],
|
|
34
|
+
['diagnose', 'Run diagnostics'],
|
|
35
|
+
['version', 'Show version'],
|
|
36
|
+
['help', 'Show this help message'],
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const cmdLines = commands
|
|
40
|
+
.map(([cmd, desc]) => ` ${theme.primary(cmd!.padEnd(12))} ${dimText(desc!)}`)
|
|
41
|
+
.join('\n')
|
|
42
|
+
|
|
43
|
+
const content = [
|
|
44
|
+
`${dimText('v' + version)} ${dimText('Local Development Assistant with Memory')}`,
|
|
45
|
+
'',
|
|
46
|
+
`${theme.bold('Usage:')} ${dimText('claude-brain [command]')}`,
|
|
47
|
+
'',
|
|
48
|
+
theme.bold('Commands:'),
|
|
49
|
+
cmdLines,
|
|
50
|
+
'',
|
|
51
|
+
theme.bold('Options:'),
|
|
52
|
+
` ${theme.primary('-v'.padEnd(12))} ${dimText('Show version')}`,
|
|
53
|
+
` ${theme.primary('-h'.padEnd(12))} ${dimText('Show help')}`,
|
|
54
|
+
'',
|
|
55
|
+
theme.bold('Examples:'),
|
|
56
|
+
` ${dimText('claude-brain')} ${dimText('Start MCP server')}`,
|
|
57
|
+
` ${dimText('claude-brain setup')} ${dimText('Configure Claude Brain')}`,
|
|
58
|
+
` ${dimText('claude-brain install')} ${dimText('Register with Claude Code')}`,
|
|
59
|
+
` ${dimText('claude-brain health')} ${dimText('Check system health')}`,
|
|
60
|
+
'',
|
|
61
|
+
theme.bold('Environment:'),
|
|
62
|
+
` ${theme.primary('CLAUDE_BRAIN_HOME')} ${dimText('Override home directory (default: ~/.claude-brain/')}`,
|
|
63
|
+
].join('\n')
|
|
64
|
+
|
|
65
|
+
console.log(content)
|
|
66
|
+
console.log()
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
async function main() {
|
|
@@ -70,6 +88,12 @@ async function main() {
|
|
|
70
88
|
break
|
|
71
89
|
}
|
|
72
90
|
|
|
91
|
+
case 'uninstall': {
|
|
92
|
+
const { runUninstall } = await import('./commands/uninstall-mcp')
|
|
93
|
+
await runUninstall()
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
|
|
73
97
|
case 'health': {
|
|
74
98
|
const { runHealthCheck } = await import('@/health')
|
|
75
99
|
await runHealthCheck()
|
|
@@ -97,7 +121,8 @@ async function main() {
|
|
|
97
121
|
}
|
|
98
122
|
|
|
99
123
|
default: {
|
|
100
|
-
console.
|
|
124
|
+
console.log()
|
|
125
|
+
console.log(errorText(`Unknown command: ${command}`))
|
|
101
126
|
printHelp()
|
|
102
127
|
process.exit(1)
|
|
103
128
|
}
|
|
@@ -105,6 +130,7 @@ async function main() {
|
|
|
105
130
|
}
|
|
106
131
|
|
|
107
132
|
main().catch((error) => {
|
|
108
|
-
console.
|
|
133
|
+
console.log()
|
|
134
|
+
console.log(box(errorText(`Fatal error: ${error instanceof Error ? error.message : String(error)}`), 'Error'))
|
|
109
135
|
process.exit(1)
|
|
110
136
|
})
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process'
|
|
2
|
+
import {
|
|
3
|
+
renderLogo, theme, heading, successText, warningText, errorText, dimText,
|
|
4
|
+
box, withSpinner,
|
|
5
|
+
} from '@/cli/ui/index.js'
|
|
2
6
|
|
|
3
7
|
function isGloballyInstalled(): boolean {
|
|
4
8
|
const cmd = process.platform === 'win32' ? 'where claude-brain' : 'which claude-brain'
|
|
@@ -11,41 +15,59 @@ function isGloballyInstalled(): boolean {
|
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export async function runInstall() {
|
|
14
|
-
console.log(
|
|
18
|
+
console.log()
|
|
19
|
+
console.log(renderLogo())
|
|
20
|
+
console.log()
|
|
21
|
+
console.log(heading('MCP Installation'))
|
|
22
|
+
console.log()
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
const installed = isGloballyInstalled()
|
|
25
|
+
|
|
26
|
+
if (installed) {
|
|
27
|
+
console.log(successText('claude-brain is globally installed'))
|
|
28
|
+
console.log()
|
|
18
29
|
|
|
19
30
|
try {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
await withSpinner('Registering with Claude Code', async () => {
|
|
32
|
+
execSync('claude mcp add claude-brain -- claude-brain serve', {
|
|
33
|
+
encoding: 'utf-8',
|
|
34
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
35
|
+
})
|
|
23
36
|
})
|
|
24
|
-
console.log(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
console.log(
|
|
37
|
+
console.log()
|
|
38
|
+
console.log(box(successText('Claude Brain registered as MCP server in Claude Code.'), 'Success'))
|
|
39
|
+
} catch {
|
|
40
|
+
console.log()
|
|
41
|
+
console.log(box([
|
|
42
|
+
errorText('Failed to register automatically.'),
|
|
43
|
+
'',
|
|
44
|
+
dimText('Run manually:'),
|
|
45
|
+
` ${theme.bold('claude mcp add claude-brain -- claude-brain serve')}`,
|
|
46
|
+
].join('\n'), 'Error'))
|
|
28
47
|
}
|
|
29
48
|
} else {
|
|
30
|
-
console.log('claude-brain is not globally installed
|
|
31
|
-
console.log(
|
|
32
|
-
|
|
33
|
-
console.log('Option 1: Install globally, then register:')
|
|
34
|
-
console.log(' bun install -g claude-brain')
|
|
35
|
-
console.log(' claude mcp add claude-brain -- claude-brain serve\n')
|
|
36
|
-
|
|
37
|
-
console.log('Option 2: Use bunx (zero-install):')
|
|
38
|
-
console.log(' claude mcp add claude-brain -- bunx claude-brain@latest\n')
|
|
49
|
+
console.log(warningText('claude-brain is not globally installed'))
|
|
50
|
+
console.log()
|
|
39
51
|
|
|
40
|
-
console.log(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
console.log(box([
|
|
53
|
+
`${theme.primary('Option 1:')} Install globally, then register`,
|
|
54
|
+
` ${theme.bold('bun install -g claude-brain')}`,
|
|
55
|
+
` ${theme.bold('claude mcp add claude-brain -- claude-brain serve')}`,
|
|
56
|
+
'',
|
|
57
|
+
`${theme.primary('Option 2:')} Use bunx ${dimText('(zero-install)')}`,
|
|
58
|
+
` ${theme.bold('claude mcp add claude-brain -- bunx claude-brain@latest')}`,
|
|
59
|
+
'',
|
|
60
|
+
`${theme.primary('Option 3:')} Add to Claude Code config manually`,
|
|
61
|
+
dimText(' Add to your Claude Code MCP settings:'),
|
|
62
|
+
` ${theme.dim('{')}`,
|
|
63
|
+
` ${theme.dim('"mcpServers": {')}`,
|
|
64
|
+
` ${theme.dim('"claude-brain": {')}`,
|
|
65
|
+
` ${theme.dim('"command": "bunx",')}`,
|
|
66
|
+
` ${theme.dim('"args": ["claude-brain@latest"]')}`,
|
|
67
|
+
` ${theme.dim('}')}`,
|
|
68
|
+
` ${theme.dim('}')}`,
|
|
69
|
+
` ${theme.dim('}')}`,
|
|
70
|
+
].join('\n'), 'Install Options'))
|
|
50
71
|
}
|
|
72
|
+
console.log()
|
|
51
73
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import {
|
|
3
|
+
renderLogo, theme, heading, successText, errorText, dimText,
|
|
4
|
+
box, withSpinner,
|
|
5
|
+
} from '@/cli/ui/index.js'
|
|
6
|
+
|
|
7
|
+
export async function runUninstall() {
|
|
8
|
+
console.log()
|
|
9
|
+
console.log(renderLogo())
|
|
10
|
+
console.log()
|
|
11
|
+
console.log(heading('MCP Uninstall'))
|
|
12
|
+
console.log()
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await withSpinner('Removing Claude Brain from Claude Code', async () => {
|
|
16
|
+
try {
|
|
17
|
+
execSync('claude mcp remove claude-brain', {
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
20
|
+
})
|
|
21
|
+
} catch {
|
|
22
|
+
// Falls back to scoped removal when registered in multiple scopes
|
|
23
|
+
execSync('claude mcp remove claude-brain -s local', {
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
console.log()
|
|
30
|
+
console.log(box(successText('Claude Brain MCP server removed from Claude Code.'), 'Success'))
|
|
31
|
+
} catch {
|
|
32
|
+
console.log()
|
|
33
|
+
console.log(box([
|
|
34
|
+
errorText('Failed to remove automatically.'),
|
|
35
|
+
'',
|
|
36
|
+
dimText('Run manually:'),
|
|
37
|
+
` ${theme.bold('claude mcp remove claude-brain')}`,
|
|
38
|
+
].join('\n'), 'Error'))
|
|
39
|
+
}
|
|
40
|
+
console.log()
|
|
41
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import ora from 'ora'
|
|
2
|
+
import { theme } from './theme.js'
|
|
3
|
+
|
|
4
|
+
const isTTY = process.stdout.isTTY === true
|
|
5
|
+
|
|
6
|
+
// Async delay utility
|
|
7
|
+
export function delay(ms: number): Promise<void> {
|
|
8
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Character-by-character text output with configurable speed
|
|
12
|
+
export async function typewrite(text: string, speed = 30): Promise<void> {
|
|
13
|
+
if (!isTTY) {
|
|
14
|
+
process.stdout.write(text + '\n')
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
for (const char of text) {
|
|
18
|
+
process.stdout.write(char)
|
|
19
|
+
await delay(speed)
|
|
20
|
+
}
|
|
21
|
+
process.stdout.write('\n')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Wraps async operations with ora spinner, auto succeed/fail
|
|
25
|
+
export async function withSpinner<T>(label: string, fn: () => Promise<T>): Promise<T> {
|
|
26
|
+
if (!isTTY) {
|
|
27
|
+
process.stdout.write(`${label}...`)
|
|
28
|
+
try {
|
|
29
|
+
const result = await fn()
|
|
30
|
+
process.stdout.write(' done\n')
|
|
31
|
+
return result
|
|
32
|
+
} catch (error) {
|
|
33
|
+
process.stdout.write(' failed\n')
|
|
34
|
+
throw error
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const spinner = ora({
|
|
39
|
+
text: label,
|
|
40
|
+
color: 'magenta',
|
|
41
|
+
}).start()
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await fn()
|
|
45
|
+
spinner.succeed(theme.success(label))
|
|
46
|
+
return result
|
|
47
|
+
} catch (error) {
|
|
48
|
+
spinner.fail(theme.error(label))
|
|
49
|
+
throw error
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Pause between sections for visual breathing room
|
|
54
|
+
export async function transition(ms = 300): Promise<void> {
|
|
55
|
+
if (!isTTY) return
|
|
56
|
+
await delay(ms)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Progress bar that fills over time
|
|
60
|
+
export async function animateProgress(label: string, durationMs = 1000, width = 30): Promise<void> {
|
|
61
|
+
if (!isTTY) {
|
|
62
|
+
console.log(`${label}... done`)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const steps = 20
|
|
67
|
+
const stepDelay = durationMs / steps
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i <= steps; i++) {
|
|
70
|
+
const percent = Math.round((i / steps) * 100)
|
|
71
|
+
const filled = Math.round((percent / 100) * width)
|
|
72
|
+
const empty = width - filled
|
|
73
|
+
const bar = theme.primary('='.repeat(Math.max(0, filled - 1)) + (filled > 0 ? '>' : '')) +
|
|
74
|
+
' '.repeat(empty)
|
|
75
|
+
const pct = theme.dim(`${percent}%`)
|
|
76
|
+
process.stdout.write(`\r ${label} [${bar}] ${pct}`)
|
|
77
|
+
await delay(stepDelay)
|
|
78
|
+
}
|
|
79
|
+
process.stdout.write('\n')
|
|
80
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { theme, dimText, heading } from './theme.js'
|
|
2
|
+
|
|
3
|
+
const BOX_WIDTH = 60
|
|
4
|
+
|
|
5
|
+
// Unicode box-drawing panel with optional title
|
|
6
|
+
export function box(content: string, title?: string): string {
|
|
7
|
+
const innerWidth = BOX_WIDTH - 2
|
|
8
|
+
const lines = content.split('\n')
|
|
9
|
+
|
|
10
|
+
let top: string
|
|
11
|
+
if (title) {
|
|
12
|
+
const titleText = ` ${title} `
|
|
13
|
+
const remaining = innerWidth - titleText.length - 1
|
|
14
|
+
top = theme.secondary('╭─') + theme.primary.bold(titleText) + theme.secondary('─'.repeat(Math.max(0, remaining)) + '╮')
|
|
15
|
+
} else {
|
|
16
|
+
top = theme.secondary('╭' + '─'.repeat(innerWidth) + '╮')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const bottom = theme.secondary('╰' + '─'.repeat(innerWidth) + '╯')
|
|
20
|
+
|
|
21
|
+
const body = lines.map(line => {
|
|
22
|
+
// Strip ANSI codes to calculate visible length
|
|
23
|
+
const visible = stripAnsi(line)
|
|
24
|
+
const padding = Math.max(0, innerWidth - visible.length)
|
|
25
|
+
return theme.secondary('│') + ' ' + line + ' '.repeat(padding - 1) + theme.secondary('│')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return [top, ...body, bottom].join('\n')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Step indicator: [Step 2/5] Configuring Vault
|
|
32
|
+
export function stepIndicator(current: number, total: number, label: string): string {
|
|
33
|
+
const badge = theme.secondary(`[Step ${current}/${total}]`)
|
|
34
|
+
const text = heading(label)
|
|
35
|
+
return `\n${badge} ${text}\n`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Progress bar: [========> ] 40%
|
|
39
|
+
export function progressBar(percent: number, width = 30): string {
|
|
40
|
+
const filled = Math.round((percent / 100) * width)
|
|
41
|
+
const empty = width - filled
|
|
42
|
+
const bar = theme.primary('='.repeat(Math.max(0, filled - 1)) + (filled > 0 ? '>' : '')) +
|
|
43
|
+
dimText(' '.repeat(empty))
|
|
44
|
+
const pct = theme.dim(`${Math.round(percent)}%`)
|
|
45
|
+
return `[${bar}] ${pct}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Info panel: key-value list in a box
|
|
49
|
+
export function infoPanel(title: string, items: Record<string, string>): string {
|
|
50
|
+
const lines = Object.entries(items).map(([key, value]) => {
|
|
51
|
+
return `${theme.dim(key + ':')} ${theme.white(value)}`
|
|
52
|
+
})
|
|
53
|
+
return box(lines.join('\n'), title)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Styled horizontal divider
|
|
57
|
+
export function divider(): string {
|
|
58
|
+
return theme.secondary('─'.repeat(BOX_WIDTH))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Summary panel with status icons
|
|
62
|
+
export function summaryPanel(title: string, items: Array<{ label: string; value: string; status?: 'success' | 'warning' | 'error' | 'info' }>): string {
|
|
63
|
+
const statusIcons: Record<string, string> = {
|
|
64
|
+
success: theme.success('+'),
|
|
65
|
+
warning: theme.warning('!'),
|
|
66
|
+
error: theme.error('x'),
|
|
67
|
+
info: theme.primary('*'),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const lines = items.map(item => {
|
|
71
|
+
const icon = item.status ? statusIcons[item.status] ?? ' ' : ' '
|
|
72
|
+
return ` ${icon} ${theme.dim(item.label + ':')} ${theme.white(item.value)}`
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return box(lines.join('\n'), title)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Strip ANSI escape codes for length calculation
|
|
79
|
+
function stripAnsi(str: string): string {
|
|
80
|
+
// eslint-disable-next-line no-control-regex
|
|
81
|
+
return str.replace(/\u001B\[[0-9;]*m/g, '')
|
|
82
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { box } from './components.js'
|
|
3
|
+
import { dimText } from './theme.js'
|
|
4
|
+
|
|
5
|
+
// 5-shade gradient from light purple to dark violet
|
|
6
|
+
const gradientColors = [
|
|
7
|
+
'#A78BFA', // light purple
|
|
8
|
+
'#8B5CF6', // medium purple
|
|
9
|
+
'#7C3AED', // deep purple (primary)
|
|
10
|
+
'#6D28D9', // dark purple
|
|
11
|
+
'#5B21B6', // dark violet (secondary)
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const logoLines = [
|
|
15
|
+
' ██████╗ ██████╗ █████╗ ██╗███╗ ██╗ ',
|
|
16
|
+
' ██╔══██╗██╔══██╗██╔══██╗██║████╗ ██║ ',
|
|
17
|
+
' ██████╔╝██████╔╝███████║██║██╔██╗ ██║ ',
|
|
18
|
+
' ██╔══██╗██╔══██╗██╔══██║██║██║╚██╗██║ ',
|
|
19
|
+
' ██████╔╝██║ ██║██║ ██║██║██║ ╚████║ ',
|
|
20
|
+
' ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
export function renderLogo(): string {
|
|
24
|
+
return logoLines
|
|
25
|
+
.map((line, i) => chalk.hex(gradientColors[i] ?? gradientColors[gradientColors.length - 1]!)(line))
|
|
26
|
+
.join('\n')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function renderBanner(version: string): string {
|
|
30
|
+
const logo = renderLogo()
|
|
31
|
+
const tagline = dimText(' Local Development Assistant with Memory')
|
|
32
|
+
const ver = dimText(` v${version}`)
|
|
33
|
+
|
|
34
|
+
const content = [logo, '', tagline, ver].join('\n')
|
|
35
|
+
return box(content, 'Claude Brain')
|
|
36
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
// Color palette
|
|
4
|
+
export const colors = {
|
|
5
|
+
deepPurple: '#7C3AED',
|
|
6
|
+
darkViolet: '#5B21B6',
|
|
7
|
+
charcoal: '#1A1A1A',
|
|
8
|
+
nearBlack: '#0A0A0A',
|
|
9
|
+
darkRed: '#B91C1C',
|
|
10
|
+
green: '#22C55E',
|
|
11
|
+
amber: '#F59E0B',
|
|
12
|
+
white: '#FFFFFF',
|
|
13
|
+
gray: '#6B7280',
|
|
14
|
+
} as const
|
|
15
|
+
|
|
16
|
+
// Pre-built chalk instances
|
|
17
|
+
export const theme = {
|
|
18
|
+
primary: chalk.hex(colors.deepPurple),
|
|
19
|
+
secondary: chalk.hex(colors.darkViolet),
|
|
20
|
+
error: chalk.hex(colors.darkRed),
|
|
21
|
+
success: chalk.hex(colors.green),
|
|
22
|
+
warning: chalk.hex(colors.amber),
|
|
23
|
+
dim: chalk.hex(colors.gray),
|
|
24
|
+
bold: chalk.bold,
|
|
25
|
+
white: chalk.white,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Styled text helpers
|
|
29
|
+
export function heading(text: string): string {
|
|
30
|
+
return theme.primary.bold(text)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function successText(text: string): string {
|
|
34
|
+
return theme.success(` ${text}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function errorText(text: string): string {
|
|
38
|
+
return theme.error(` ${text}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function warningText(text: string): string {
|
|
42
|
+
return theme.warning(` ${text}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function dimText(text: string): string {
|
|
46
|
+
return theme.dim(text)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function highlight(text: string): string {
|
|
50
|
+
return theme.primary(text)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function keyValue(key: string, value: string): string {
|
|
54
|
+
return `${theme.dim(key + ':')} ${theme.white(value)}`
|
|
55
|
+
}
|
package/src/setup/index.ts
CHANGED
|
@@ -2,12 +2,37 @@ import { createLogger } from '@/utils/logger'
|
|
|
2
2
|
import { resolveHomePath } from '@/config/home'
|
|
3
3
|
import { ensureHomeDirectory } from '@/cli/auto-setup'
|
|
4
4
|
import { SetupWizard } from './wizard'
|
|
5
|
+
import { renderBanner, typewrite, transition, box, errorText, theme } from '@/cli/ui/index.js'
|
|
6
|
+
import { readFileSync } from 'node:fs'
|
|
7
|
+
import { resolve, dirname } from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
5
9
|
|
|
6
10
|
export * from './wizard'
|
|
7
11
|
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
13
|
+
const __dirname = dirname(__filename)
|
|
14
|
+
const PACKAGE_ROOT = resolve(__dirname, '..', '..')
|
|
15
|
+
|
|
16
|
+
function getVersion(): string {
|
|
17
|
+
try {
|
|
18
|
+
const pkg = JSON.parse(readFileSync(resolve(PACKAGE_ROOT, 'package.json'), 'utf-8'))
|
|
19
|
+
return pkg.version || 'unknown'
|
|
20
|
+
} catch {
|
|
21
|
+
return 'unknown'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
8
25
|
export async function runSetup() {
|
|
9
26
|
ensureHomeDirectory()
|
|
10
27
|
|
|
28
|
+
const version = getVersion()
|
|
29
|
+
console.log()
|
|
30
|
+
console.log(renderBanner(version))
|
|
31
|
+
console.log()
|
|
32
|
+
|
|
33
|
+
await typewrite(theme.dim('Welcome! Let\'s configure Claude Brain for your system.'))
|
|
34
|
+
await transition(400)
|
|
35
|
+
|
|
11
36
|
const logger = createLogger('info', resolveHomePath('./logs/setup.log'))
|
|
12
37
|
|
|
13
38
|
try {
|
|
@@ -16,7 +41,8 @@ export async function runSetup() {
|
|
|
16
41
|
await wizard.applyConfiguration(answers)
|
|
17
42
|
|
|
18
43
|
} catch (error) {
|
|
19
|
-
console.
|
|
44
|
+
console.log()
|
|
45
|
+
console.log(box(errorText(`Setup failed: ${error instanceof Error ? error.message : String(error)}`), 'Error'))
|
|
20
46
|
process.exit(1)
|
|
21
47
|
}
|
|
22
48
|
}
|
package/src/setup/wizard.ts
CHANGED
|
@@ -6,6 +6,11 @@ import os from 'os'
|
|
|
6
6
|
import { fileURLToPath } from 'url'
|
|
7
7
|
import type { Logger } from 'pino'
|
|
8
8
|
import { getHomePaths } from '@/config/home'
|
|
9
|
+
import {
|
|
10
|
+
theme, heading, successText, errorText, warningText, dimText,
|
|
11
|
+
box, stepIndicator, summaryPanel,
|
|
12
|
+
withSpinner, transition,
|
|
13
|
+
} from '@/cli/ui/index.js'
|
|
9
14
|
|
|
10
15
|
const __filename = fileURLToPath(import.meta.url)
|
|
11
16
|
const __dirname = path.dirname(__filename)
|
|
@@ -28,12 +33,26 @@ export class SetupWizard {
|
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
async run(): Promise<SetupAnswers> {
|
|
31
|
-
|
|
32
|
-
console.log(
|
|
36
|
+
// Step 1: Detect Vaults
|
|
37
|
+
console.log(stepIndicator(1, 5, 'Detecting Obsidian Vaults'))
|
|
38
|
+
await transition()
|
|
39
|
+
|
|
40
|
+
const suggestedPaths = await withSpinner('Scanning for Obsidian vaults', () =>
|
|
41
|
+
this.detectVaultLocations()
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if (suggestedPaths.length > 0) {
|
|
45
|
+
console.log(successText(`Found ${suggestedPaths.length} vault${suggestedPaths.length > 1 ? 's' : ''}`))
|
|
46
|
+
} else {
|
|
47
|
+
console.log(warningText('No vaults auto-detected — enter path manually'))
|
|
48
|
+
}
|
|
49
|
+
console.log()
|
|
33
50
|
|
|
34
|
-
|
|
51
|
+
// Step 2: Vault Configuration
|
|
52
|
+
console.log(stepIndicator(2, 5, 'Vault Configuration'))
|
|
53
|
+
await transition()
|
|
35
54
|
|
|
36
|
-
const
|
|
55
|
+
const vaultAnswers = await prompts([
|
|
37
56
|
{
|
|
38
57
|
type: 'select',
|
|
39
58
|
name: 'vaultPathChoice',
|
|
@@ -57,18 +76,37 @@ export class SetupWizard {
|
|
|
57
76
|
}
|
|
58
77
|
}
|
|
59
78
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
])
|
|
80
|
+
|
|
81
|
+
if (!vaultAnswers.vaultPathChoice) {
|
|
82
|
+
console.log('\n' + errorText('Setup cancelled.'))
|
|
83
|
+
process.exit(0)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const finalVaultPath = vaultAnswers.vaultPath || vaultAnswers.vaultPathChoice
|
|
87
|
+
|
|
88
|
+
// Step 3: Logging
|
|
89
|
+
console.log(stepIndicator(3, 5, 'Logging Configuration'))
|
|
90
|
+
await transition()
|
|
91
|
+
|
|
92
|
+
const loggingAnswers = await prompts({
|
|
93
|
+
type: 'select',
|
|
94
|
+
name: 'logLevel',
|
|
95
|
+
message: 'Select log level:',
|
|
96
|
+
choices: [
|
|
97
|
+
{ title: 'Error (production)', value: 'error' },
|
|
98
|
+
{ title: 'Warn (recommended)', value: 'warn' },
|
|
99
|
+
{ title: 'Info', value: 'info' },
|
|
100
|
+
{ title: 'Debug (verbose)', value: 'debug' }
|
|
101
|
+
],
|
|
102
|
+
initial: 1
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Step 4: Features
|
|
106
|
+
console.log(stepIndicator(4, 5, 'Feature Selection'))
|
|
107
|
+
await transition()
|
|
108
|
+
|
|
109
|
+
const featureAnswers = await prompts([
|
|
72
110
|
{
|
|
73
111
|
type: 'confirm',
|
|
74
112
|
name: 'enableFileWatch',
|
|
@@ -89,20 +127,40 @@ export class SetupWizard {
|
|
|
89
127
|
}
|
|
90
128
|
])
|
|
91
129
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const finalVaultPath = answers.vaultPath || answers.vaultPathChoice
|
|
130
|
+
// Step 5: Review
|
|
131
|
+
console.log(stepIndicator(5, 5, 'Review Configuration'))
|
|
132
|
+
await transition()
|
|
98
133
|
|
|
99
|
-
|
|
134
|
+
const answers: SetupAnswers = {
|
|
100
135
|
vaultPath: finalVaultPath,
|
|
101
|
-
logLevel:
|
|
102
|
-
enableFileWatch:
|
|
103
|
-
createSampleProject:
|
|
104
|
-
installClaudeMd:
|
|
136
|
+
logLevel: loggingAnswers.logLevel ?? 'warn',
|
|
137
|
+
enableFileWatch: featureAnswers.enableFileWatch ?? true,
|
|
138
|
+
createSampleProject: featureAnswers.createSampleProject ?? true,
|
|
139
|
+
installClaudeMd: featureAnswers.installClaudeMd ?? true,
|
|
105
140
|
}
|
|
141
|
+
|
|
142
|
+
console.log(summaryPanel('Configuration Summary', [
|
|
143
|
+
{ label: 'Vault Path', value: answers.vaultPath, status: 'success' },
|
|
144
|
+
{ label: 'Log Level', value: answers.logLevel, status: 'info' },
|
|
145
|
+
{ label: 'File Watching', value: answers.enableFileWatch ? 'Enabled' : 'Disabled', status: answers.enableFileWatch ? 'success' : 'warning' },
|
|
146
|
+
{ label: 'Sample Project', value: answers.createSampleProject ? 'Yes' : 'No', status: 'info' },
|
|
147
|
+
{ label: 'Install CLAUDE.md', value: answers.installClaudeMd ? 'Yes' : 'No', status: 'info' },
|
|
148
|
+
]))
|
|
149
|
+
console.log()
|
|
150
|
+
|
|
151
|
+
const { confirm } = await prompts({
|
|
152
|
+
type: 'confirm',
|
|
153
|
+
name: 'confirm',
|
|
154
|
+
message: 'Apply this configuration?',
|
|
155
|
+
initial: true
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if (!confirm) {
|
|
159
|
+
console.log('\n' + errorText('Setup cancelled.'))
|
|
160
|
+
process.exit(0)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return answers
|
|
106
164
|
}
|
|
107
165
|
|
|
108
166
|
private async detectVaultLocations(): Promise<string[]> {
|
|
@@ -134,7 +192,7 @@ export class SetupWizard {
|
|
|
134
192
|
}
|
|
135
193
|
|
|
136
194
|
async applyConfiguration(answers: SetupAnswers): Promise<void> {
|
|
137
|
-
console.log('\
|
|
195
|
+
console.log('\n' + heading('Applying configuration...') + '\n')
|
|
138
196
|
|
|
139
197
|
const homePaths = getHomePaths()
|
|
140
198
|
|
|
@@ -147,34 +205,48 @@ LOG_FILE_PATH=./logs/claude-brain.log
|
|
|
147
205
|
SERVER_NAME=claude-brain
|
|
148
206
|
`
|
|
149
207
|
|
|
150
|
-
await
|
|
151
|
-
|
|
208
|
+
await withSpinner('Writing .env configuration', async () => {
|
|
209
|
+
await fs.writeFile(homePaths.env, envContent, 'utf-8')
|
|
210
|
+
})
|
|
152
211
|
|
|
153
|
-
await
|
|
154
|
-
|
|
155
|
-
|
|
212
|
+
await withSpinner('Creating data and log directories', async () => {
|
|
213
|
+
await fs.mkdir(homePaths.data, { recursive: true })
|
|
214
|
+
await fs.mkdir(homePaths.logs, { recursive: true })
|
|
215
|
+
})
|
|
156
216
|
|
|
157
217
|
if (answers.createSampleProject) {
|
|
158
|
-
await
|
|
218
|
+
await withSpinner('Creating sample project in vault', async () => {
|
|
219
|
+
await this.createSampleProject(answers.vaultPath)
|
|
220
|
+
})
|
|
159
221
|
}
|
|
160
222
|
|
|
161
223
|
if (answers.installClaudeMd) {
|
|
162
|
-
await this.
|
|
224
|
+
const shouldInstall = await this.confirmClaudeMdInstall()
|
|
225
|
+
if (shouldInstall) {
|
|
226
|
+
await withSpinner('Installing CLAUDE.md', async () => {
|
|
227
|
+
await this.copyClaudeMd()
|
|
228
|
+
})
|
|
229
|
+
}
|
|
163
230
|
}
|
|
164
231
|
|
|
165
|
-
console.log(
|
|
166
|
-
console.log(
|
|
167
|
-
|
|
168
|
-
|
|
232
|
+
console.log()
|
|
233
|
+
console.log(box([
|
|
234
|
+
heading('Setup complete!'),
|
|
235
|
+
'',
|
|
236
|
+
dimText('Next steps:'),
|
|
237
|
+
` ${theme.primary('1.')} Run: ${theme.bold('claude-brain install')} ${dimText('Register MCP with Claude Code')}`,
|
|
238
|
+
` ${theme.primary('2.')} Run: ${theme.bold('claude-brain health')} ${dimText('Verify everything works')}`,
|
|
239
|
+
].join('\n'), 'Done'))
|
|
240
|
+
console.log()
|
|
169
241
|
}
|
|
170
242
|
|
|
171
|
-
private async
|
|
243
|
+
private async confirmClaudeMdInstall(): Promise<boolean> {
|
|
172
244
|
const sourcePath = path.join(PACKAGE_ROOT, 'assets', 'CLAUDE.md')
|
|
173
245
|
const destPath = path.join(os.homedir(), 'CLAUDE.md')
|
|
174
246
|
|
|
175
247
|
if (!existsSync(sourcePath)) {
|
|
176
|
-
console.log('CLAUDE.md asset not found, skipping')
|
|
177
|
-
return
|
|
248
|
+
console.log(warningText('CLAUDE.md asset not found, skipping'))
|
|
249
|
+
return false
|
|
178
250
|
}
|
|
179
251
|
|
|
180
252
|
if (existsSync(destPath)) {
|
|
@@ -182,31 +254,31 @@ SERVER_NAME=claude-brain
|
|
|
182
254
|
type: 'confirm',
|
|
183
255
|
name: 'overwrite',
|
|
184
256
|
message: '~/CLAUDE.md already exists. Overwrite?',
|
|
185
|
-
initial: false
|
|
257
|
+
initial: false,
|
|
186
258
|
})
|
|
187
259
|
if (!overwrite) {
|
|
188
|
-
console.log('Skipped CLAUDE.md installation')
|
|
189
|
-
return
|
|
260
|
+
console.log(dimText(' Skipped CLAUDE.md installation'))
|
|
261
|
+
return false
|
|
190
262
|
}
|
|
191
263
|
}
|
|
192
264
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
265
|
+
return true
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private async copyClaudeMd(): Promise<void> {
|
|
269
|
+
const sourcePath = path.join(PACKAGE_ROOT, 'assets', 'CLAUDE.md')
|
|
270
|
+
const destPath = path.join(os.homedir(), 'CLAUDE.md')
|
|
271
|
+
await fs.copyFile(sourcePath, destPath)
|
|
199
272
|
}
|
|
200
273
|
|
|
201
274
|
private async createSampleProject(vaultPath: string): Promise<void> {
|
|
202
275
|
const projectPath = path.join(vaultPath, 'Projects', 'sample-project')
|
|
203
276
|
|
|
204
|
-
|
|
205
|
-
await fs.mkdir(projectPath, { recursive: true })
|
|
277
|
+
await fs.mkdir(projectPath, { recursive: true })
|
|
206
278
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
279
|
+
await fs.writeFile(
|
|
280
|
+
path.join(projectPath, 'context.md'),
|
|
281
|
+
`---
|
|
210
282
|
type: project-context
|
|
211
283
|
project: sample-project
|
|
212
284
|
status: active
|
|
@@ -233,12 +305,12 @@ This is a sample project created by Claude Brain setup wizard.
|
|
|
233
305
|
- Test Claude Brain integration
|
|
234
306
|
- Learn the workflow
|
|
235
307
|
`,
|
|
236
|
-
|
|
237
|
-
|
|
308
|
+
'utf-8'
|
|
309
|
+
)
|
|
238
310
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
311
|
+
await fs.writeFile(
|
|
312
|
+
path.join(projectPath, 'progress.md'),
|
|
313
|
+
`---
|
|
242
314
|
type: progress-tracker
|
|
243
315
|
project: sample-project
|
|
244
316
|
status: in-progress
|
|
@@ -253,12 +325,12 @@ completion_percentage: 0
|
|
|
253
325
|
- [ ] Test MCP integration
|
|
254
326
|
- [ ] Try all tools
|
|
255
327
|
`,
|
|
256
|
-
|
|
257
|
-
|
|
328
|
+
'utf-8'
|
|
329
|
+
)
|
|
258
330
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
331
|
+
await fs.writeFile(
|
|
332
|
+
path.join(projectPath, 'decisions.md'),
|
|
333
|
+
`---
|
|
262
334
|
type: decision-log
|
|
263
335
|
project: sample-project
|
|
264
336
|
decision_count: 0
|
|
@@ -268,12 +340,12 @@ decision_count: 0
|
|
|
268
340
|
|
|
269
341
|
This file will track important decisions for the project.
|
|
270
342
|
`,
|
|
271
|
-
|
|
272
|
-
|
|
343
|
+
'utf-8'
|
|
344
|
+
)
|
|
273
345
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
346
|
+
await fs.writeFile(
|
|
347
|
+
path.join(projectPath, 'standards.md'),
|
|
348
|
+
`---
|
|
277
349
|
type: coding-standards
|
|
278
350
|
project: sample-project
|
|
279
351
|
---
|
|
@@ -284,19 +356,19 @@ project: sample-project
|
|
|
284
356
|
- Use strict mode
|
|
285
357
|
- Prefer const over let
|
|
286
358
|
`,
|
|
287
|
-
|
|
288
|
-
|
|
359
|
+
'utf-8'
|
|
360
|
+
)
|
|
289
361
|
|
|
290
|
-
|
|
291
|
-
|
|
362
|
+
const globalPath = path.join(vaultPath, 'Global')
|
|
363
|
+
await fs.mkdir(globalPath, { recursive: true })
|
|
292
364
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
365
|
+
const globalStandardsPath = path.join(globalPath, 'standards.md')
|
|
366
|
+
try {
|
|
367
|
+
await fs.access(globalStandardsPath)
|
|
368
|
+
} catch {
|
|
369
|
+
await fs.writeFile(
|
|
370
|
+
globalStandardsPath,
|
|
371
|
+
`---
|
|
300
372
|
type: global-standards
|
|
301
373
|
last_updated: ${new Date().toISOString().split('T')[0]}
|
|
302
374
|
---
|
|
@@ -308,14 +380,8 @@ last_updated: ${new Date().toISOString().split('T')[0]}
|
|
|
308
380
|
- Prefer const over let
|
|
309
381
|
- Add JSDoc comments for public APIs
|
|
310
382
|
`,
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
console.log('Created sample project in vault')
|
|
316
|
-
|
|
317
|
-
} catch (error) {
|
|
318
|
-
console.error('Failed to create sample project:', error)
|
|
383
|
+
'utf-8'
|
|
384
|
+
)
|
|
319
385
|
}
|
|
320
386
|
}
|
|
321
387
|
}
|