brainctl 0.1.5 → 0.1.7
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 +181 -131
- package/dist/executor/resolver.js +1 -38
- package/dist/mcp/server.js +183 -0
- package/dist/services/agent-config-service.d.ts +35 -0
- package/dist/services/agent-config-service.js +222 -0
- package/dist/services/mcp-preflight-service.d.ts +25 -0
- package/dist/services/mcp-preflight-service.js +84 -0
- package/dist/services/plugin-install-service.d.ts +92 -0
- package/dist/services/plugin-install-service.js +243 -0
- package/dist/services/profile-export-service.js +5 -5
- package/dist/services/profile-import-service.js +1 -1
- package/dist/services/profile-service.d.ts +10 -0
- package/dist/services/profile-service.js +140 -28
- package/dist/services/skill-paths.d.ts +2 -0
- package/dist/services/skill-paths.js +12 -0
- package/dist/services/skill-preflight-service.d.ts +23 -0
- package/dist/services/skill-preflight-service.js +40 -0
- package/dist/services/sync/agent-reader.d.ts +30 -0
- package/dist/services/sync/agent-reader.js +232 -0
- package/dist/services/sync/claude-writer.js +4 -1
- package/dist/services/sync/codex-writer.js +6 -2
- package/dist/services/sync/gemini-writer.js +4 -1
- package/dist/services/sync/managed-plugin-registry.d.ts +17 -0
- package/dist/services/sync/managed-plugin-registry.js +75 -0
- package/dist/services/sync/plugin-skill-reader.d.ts +2 -0
- package/dist/services/sync/plugin-skill-reader.js +33 -0
- package/dist/services/sync-service.js +5 -0
- package/dist/system/executables.d.ts +1 -0
- package/dist/system/executables.js +38 -0
- package/dist/types.d.ts +15 -5
- package/dist/ui/routes.js +423 -1
- package/dist/web/assets/index-BCkorugl.css +1 -0
- package/dist/web/assets/index-sGnTMhkX.js +16 -0
- package/dist/web/index.html +2 -2
- package/package.json +7 -1
- package/dist/web/assets/index-CRJ6cM0Q.css +0 -1
- package/dist/web/assets/index-Cr8gt3VF.js +0 -9
package/README.md
CHANGED
|
@@ -1,121 +1,146 @@
|
|
|
1
1
|
# 🧠 brainctl
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> One AI setup. Multiple agents. Zero reconfiguration.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**brainctl** is a cross-agent AI workflow manager that unifies your environment across Claude Code, Codex, and Gemini CLI — with a web dashboard, MCP server, and portable profiles.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/brainctl)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://github.com/Rorogogogo/brainctl/actions)
|
|
8
10
|
|
|
9
11
|
---
|
|
10
12
|
|
|
11
|
-
## ✨
|
|
13
|
+
## ✨ Features
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
- 🔀 **Multi-agent support** — Claude Code, Codex, and Gemini CLI from one config
|
|
16
|
+
- 🖥️ **Web dashboard** — Visual drag-and-drop MCP management across agents
|
|
17
|
+
- 🔌 **MCP server** — 20 tools exposable to any MCP-compatible agent
|
|
18
|
+
- 📦 **Portable profiles** — Export/import skill + MCP bundles as tarballs
|
|
19
|
+
- 🧠 **Shared memory** — Markdown-based context files shared across agents
|
|
20
|
+
- 🧩 **Reusable skills** — Prompt templates stored in `ai-stack.yaml`
|
|
21
|
+
- 🔄 **Profile sync** — Push configs to all agents in one command
|
|
22
|
+
- 🩺 **Health checks** — `status` and `doctor` commands for visibility
|
|
23
|
+
- 🔁 **Fallback agents** — Automatic failover if primary agent is unavailable
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
- Losing context between tools
|
|
17
|
-
- Rebuilding your environment every time you switch
|
|
18
|
-
|
|
19
|
-
`brainctl` solves that with one core idea:
|
|
25
|
+
---
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
## 📸 Demo
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
### Web Dashboard — Drag MCPs between agents
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
```
|
|
32
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
33
|
+
│ Claude │ │ Codex │ │ Gemini │
|
|
34
|
+
│ ┌──────────┐ │ │ ┌──────────┐ │ │ │
|
|
35
|
+
│ │ github │◄├──┤►│ github │ │ │ Drop here │
|
|
36
|
+
│ │ brainctl │ │ │ │ brainctl │ │ │ to copy │
|
|
37
|
+
│ └──────────┘ │ │ └──────────┘ │ │ │
|
|
38
|
+
│ Skills: 11 │ │ Skills: 3 │ │ Skills: 0 │
|
|
39
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
40
|
+
```
|
|
26
41
|
|
|
27
|
-
|
|
28
|
-
- 🧩 Reusable skills stored in `ai-stack.yaml`
|
|
29
|
-
- 🔌 Multi-agent execution with Claude and Codex
|
|
30
|
-
- ⚙️ Unified context builder
|
|
31
|
-
- 🛠 CLI-first workflow
|
|
32
|
-
- 🔍 `status` and `doctor` for visibility
|
|
33
|
-
- 🔁 Optional fallback agent support with `--fallback`
|
|
42
|
+
The dashboard reads **live config files** from each agent (`~/.claude.json`, `~/.codex/config.toml`, `~/.gemini/settings.json`) and lets you drag MCPs between them. Changes are staged and only applied on confirm.
|
|
34
43
|
|
|
35
44
|
---
|
|
36
45
|
|
|
37
46
|
## 📦 Installation
|
|
38
47
|
|
|
39
|
-
### Option 1: Install from npm
|
|
40
|
-
|
|
41
48
|
```bash
|
|
42
49
|
npm install -g brainctl
|
|
43
50
|
```
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
Or from source:
|
|
46
53
|
|
|
47
54
|
```bash
|
|
48
|
-
brainctl
|
|
55
|
+
git clone https://github.com/Rorogogogo/brainctl.git
|
|
56
|
+
cd brainctl
|
|
57
|
+
npm install && npm run build && npm link
|
|
49
58
|
```
|
|
50
59
|
|
|
51
|
-
|
|
60
|
+
> **Prerequisite:** At least one supported agent CLI must be installed and on your `PATH` (`claude`, `codex`, or `gemini`).
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
npm install
|
|
55
|
-
npm run build
|
|
56
|
-
npm link
|
|
57
|
-
```
|
|
62
|
+
---
|
|
58
63
|
|
|
59
|
-
|
|
64
|
+
## 🚀 Quick Start
|
|
60
65
|
|
|
61
66
|
```bash
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
# 1. Initialize a project
|
|
68
|
+
brainctl init
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
# 2. Check your setup
|
|
71
|
+
brainctl status
|
|
72
|
+
brainctl doctor
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
npm run build
|
|
70
|
-
node dist/cli.js --help
|
|
71
|
-
```
|
|
74
|
+
# 3. Run a skill
|
|
75
|
+
brainctl run summarize ./memory/notes.md --with claude
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
# 4. Launch the web dashboard
|
|
78
|
+
brainctl ui
|
|
79
|
+
# → http://127.0.0.1:3333
|
|
80
|
+
```
|
|
74
81
|
|
|
75
82
|
---
|
|
76
83
|
|
|
77
|
-
##
|
|
84
|
+
## 📖 Usage
|
|
78
85
|
|
|
79
|
-
###
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
brainctl init
|
|
83
|
-
```
|
|
86
|
+
### CLI Commands
|
|
84
87
|
|
|
85
|
-
|
|
88
|
+
| Command | Description |
|
|
89
|
+
|---------|-------------|
|
|
90
|
+
| `brainctl init` | Scaffold `ai-stack.yaml` and `memory/` directory |
|
|
91
|
+
| `brainctl status` | Show memory, skills, MCPs, and agent availability |
|
|
92
|
+
| `brainctl doctor` | Validate config, paths, and installed agents |
|
|
93
|
+
| `brainctl run <skill> <file> --with <agent>` | Execute a skill through an agent |
|
|
94
|
+
| `brainctl profile list` | List available profiles |
|
|
95
|
+
| `brainctl profile create <name>` | Create a new profile |
|
|
96
|
+
| `brainctl profile use <name>` | Switch active profile |
|
|
97
|
+
| `brainctl profile export <name>` | Export profile as portable tarball |
|
|
98
|
+
| `brainctl profile import <archive>` | Import profile from tarball |
|
|
99
|
+
| `brainctl sync` | Sync active profile to all agent configs |
|
|
100
|
+
| `brainctl ui` | Start the web dashboard |
|
|
86
101
|
|
|
87
|
-
|
|
88
|
-
- `memory/`
|
|
89
|
-
- `memory/notes.md`
|
|
90
|
-
|
|
91
|
-
### 2. Inspect the setup
|
|
102
|
+
### Run Examples
|
|
92
103
|
|
|
93
104
|
```bash
|
|
94
|
-
|
|
95
|
-
brainctl
|
|
96
|
-
```
|
|
105
|
+
# Basic execution
|
|
106
|
+
brainctl run summarize ./notes.md --with claude
|
|
97
107
|
|
|
98
|
-
|
|
108
|
+
# With fallback agent
|
|
109
|
+
brainctl run analyze ./report.md --with codex --fallback claude
|
|
99
110
|
|
|
100
|
-
|
|
101
|
-
brainctl run
|
|
111
|
+
# Using Gemini
|
|
112
|
+
brainctl run review ./code.md --with gemini
|
|
102
113
|
```
|
|
103
114
|
|
|
104
|
-
|
|
115
|
+
### Profile MCP Format
|
|
105
116
|
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
Packed/published profiles should classify every MCP as either `local` or `remote`.
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
mcps:
|
|
121
|
+
github:
|
|
122
|
+
kind: local
|
|
123
|
+
source: npm
|
|
124
|
+
package: "@modelcontextprotocol/server-github"
|
|
125
|
+
|
|
126
|
+
internal-docs:
|
|
127
|
+
kind: remote
|
|
128
|
+
transport: http
|
|
129
|
+
url: "https://mcp.example.com"
|
|
108
130
|
```
|
|
109
131
|
|
|
110
|
-
|
|
132
|
+
Rules:
|
|
111
133
|
|
|
112
|
-
|
|
113
|
-
brainctl
|
|
114
|
-
|
|
134
|
+
- local profile files may still use the older `type: npm` / `type: bundled` MCP shape
|
|
135
|
+
- `brainctl profile export` writes the packed profile using the explicit format below
|
|
136
|
+
- `local` MCPs must declare `source: npm` or `source: bundled`
|
|
137
|
+
- `remote` MCPs must declare `transport` and `url`
|
|
138
|
+
- bundled local MCPs must declare `path` and `command`
|
|
139
|
+
- `brainctl sync` currently supports local MCPs only; remote MCPs remain in the profile package but are not written into agent configs yet
|
|
115
140
|
|
|
116
141
|
---
|
|
117
142
|
|
|
118
|
-
## 🧠
|
|
143
|
+
## 🧠 Config: `ai-stack.yaml`
|
|
119
144
|
|
|
120
145
|
```yaml
|
|
121
146
|
memory:
|
|
@@ -124,119 +149,144 @@ memory:
|
|
|
124
149
|
|
|
125
150
|
skills:
|
|
126
151
|
summarize:
|
|
127
|
-
description: Summarize content
|
|
152
|
+
description: Summarize content into bullet points
|
|
128
153
|
prompt: |
|
|
129
154
|
Summarize the following content into concise bullet points.
|
|
130
155
|
|
|
131
|
-
|
|
132
|
-
description:
|
|
156
|
+
review:
|
|
157
|
+
description: Code review with actionable feedback
|
|
133
158
|
prompt: |
|
|
134
|
-
|
|
159
|
+
Review the following code and provide actionable feedback.
|
|
135
160
|
|
|
136
161
|
mcps: {}
|
|
137
162
|
```
|
|
138
163
|
|
|
139
|
-
|
|
164
|
+
### How Context Assembly Works
|
|
140
165
|
|
|
141
|
-
|
|
166
|
+
```
|
|
167
|
+
┌─────────────┐
|
|
168
|
+
│ MEMORY │ ← Markdown files from configured paths
|
|
169
|
+
├─────────────┤
|
|
170
|
+
│ SKILL │ ← Prompt template from ai-stack.yaml
|
|
171
|
+
├─────────────┤
|
|
172
|
+
│ INPUT │ ← Your file
|
|
173
|
+
└─────────────┘
|
|
174
|
+
↓
|
|
175
|
+
Agent CLI (claude / codex / gemini)
|
|
176
|
+
```
|
|
142
177
|
|
|
143
|
-
|
|
178
|
+
---
|
|
144
179
|
|
|
145
|
-
|
|
146
|
-
--- MEMORY ---
|
|
147
|
-
[your markdown files]
|
|
180
|
+
## 🔌 MCP Server
|
|
148
181
|
|
|
149
|
-
|
|
150
|
-
[prompt template]
|
|
182
|
+
brainctl exposes **20 MCP tools** that any compatible agent can call:
|
|
151
183
|
|
|
152
|
-
|
|
153
|
-
|
|
184
|
+
```bash
|
|
185
|
+
# Add brainctl as an MCP server in your agent config
|
|
186
|
+
# Claude (~/.claude.json):
|
|
187
|
+
{
|
|
188
|
+
"mcpServers": {
|
|
189
|
+
"brainctl": {
|
|
190
|
+
"type": "stdio",
|
|
191
|
+
"command": "npx",
|
|
192
|
+
"args": ["-y", "brainctl", "mcp"]
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
154
196
|
```
|
|
155
197
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## 🛠 Usage
|
|
198
|
+
**Available tools:**
|
|
161
199
|
|
|
162
|
-
|
|
200
|
+
| Category | Tools |
|
|
201
|
+
|----------|-------|
|
|
202
|
+
| **Skills** | `list_skills`, `get_skill`, `run` |
|
|
203
|
+
| **Memory** | `read_memory`, `write_memory` |
|
|
204
|
+
| **Profiles** | `list_profiles`, `get_profile`, `create_profile`, `update_profile`, `delete_profile`, `switch_profile`, `copy_profile_items`, `export_profile`, `import_profile` |
|
|
205
|
+
| **Agent Configs** | `read_agent_configs`, `add_agent_mcp`, `remove_agent_mcp` |
|
|
206
|
+
| **System** | `status`, `doctor`, `sync` |
|
|
163
207
|
|
|
164
|
-
|
|
165
|
-
| --- | --- |
|
|
166
|
-
| `brainctl init` | Initialize `ai-stack.yaml` and memory files |
|
|
167
|
-
| `brainctl status` | Show memory, skills, MCP count, and agent availability |
|
|
168
|
-
| `brainctl doctor` | Validate config, memory paths, skills, and installed agents |
|
|
169
|
-
| `brainctl run <skill> <file> --with <agent>` | Build context and execute with an agent |
|
|
208
|
+
---
|
|
170
209
|
|
|
171
|
-
|
|
210
|
+
## 🖥️ Web Dashboard
|
|
172
211
|
|
|
173
212
|
```bash
|
|
174
|
-
brainctl
|
|
175
|
-
brainctl run analyze ./memory/notes.md --with codex
|
|
176
|
-
brainctl run summarize ./memory/notes.md --with claude --fallback codex
|
|
213
|
+
brainctl ui
|
|
177
214
|
```
|
|
178
215
|
|
|
216
|
+
Opens a local dashboard at `http://127.0.0.1:3333` with:
|
|
217
|
+
|
|
218
|
+
- **Agent Profiles** — See live MCPs and skills for Claude, Codex, and Gemini side-by-side
|
|
219
|
+
- **Drag & Drop** — Copy MCPs between agents by dragging cards
|
|
220
|
+
- **Staged Changes** — Preview adds/removes before applying, with undo support
|
|
221
|
+
- **Skills Editor** — Edit skill prompts with live preview
|
|
222
|
+
- **MCP Manager** — View and edit MCP configurations
|
|
223
|
+
- **Memory Viewer** — Browse shared markdown memory files
|
|
224
|
+
- **Run Console** — Execute skills with real-time streaming output
|
|
225
|
+
|
|
179
226
|
---
|
|
180
227
|
|
|
181
|
-
##
|
|
228
|
+
## 🏗️ Architecture
|
|
182
229
|
|
|
183
|
-
```
|
|
230
|
+
```
|
|
184
231
|
brainctl/
|
|
185
232
|
├── src/
|
|
186
|
-
│ ├── cli.ts
|
|
187
|
-
│ ├──
|
|
188
|
-
│ ├──
|
|
189
|
-
│
|
|
190
|
-
│ ├──
|
|
191
|
-
│
|
|
192
|
-
├──
|
|
193
|
-
|
|
194
|
-
├──
|
|
195
|
-
├──
|
|
196
|
-
└──
|
|
233
|
+
│ ├── cli.ts # CLI entry point (Commander)
|
|
234
|
+
│ ├── commands/ # 8 command handlers
|
|
235
|
+
│ ├── services/ # 11 business logic services
|
|
236
|
+
│ │ └── sync/ # Agent config readers/writers
|
|
237
|
+
│ ├── context/ # Memory loader, skill resolver, context builder
|
|
238
|
+
│ ├── executor/ # Agent spawning (Claude, Codex, Gemini)
|
|
239
|
+
│ ├── mcp/ # FastMCP server (20 tools)
|
|
240
|
+
│ └── ui/ # HTTP server with SSE streaming
|
|
241
|
+
├── web/src/ # React dashboard (Vite + dnd-kit)
|
|
242
|
+
├── tests/ # Vitest test suite
|
|
243
|
+
└── ai-stack.yaml # Project config
|
|
197
244
|
```
|
|
198
245
|
|
|
246
|
+
### Agent Config Locations
|
|
247
|
+
|
|
248
|
+
| Agent | Config Path | MCP Location |
|
|
249
|
+
|-------|-------------|-------------|
|
|
250
|
+
| Claude | `~/.claude.json` | `projects[cwd].mcpServers` |
|
|
251
|
+
| Codex | `~/.codex/config.toml` | `[mcp_servers.*]` |
|
|
252
|
+
| Gemini | `~/.gemini/settings.json` | `mcpServers` |
|
|
253
|
+
|
|
199
254
|
---
|
|
200
255
|
|
|
201
256
|
## 🧪 Development
|
|
202
257
|
|
|
203
258
|
```bash
|
|
204
259
|
npm install
|
|
205
|
-
npm test
|
|
206
|
-
npm run build
|
|
260
|
+
npm test # Run all tests (Vitest)
|
|
261
|
+
npm run build # Build server (tsc) + web (Vite)
|
|
262
|
+
npm run dev -- <args> # Run CLI via tsx
|
|
207
263
|
```
|
|
208
264
|
|
|
209
265
|
---
|
|
210
266
|
|
|
211
|
-
##
|
|
212
|
-
|
|
213
|
-
`brainctl` does not replace your AI tools.
|
|
214
|
-
|
|
215
|
-
It sits between you and them as a thin orchestration layer:
|
|
267
|
+
## 🤝 Contributing
|
|
216
268
|
|
|
217
|
-
|
|
218
|
-
- `brainctl` keeps the environment consistent
|
|
269
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
219
270
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
- [ ] MCP runtime integration
|
|
227
|
-
- [ ] Better execution tracing and logs
|
|
228
|
-
- [ ] UI / dashboard
|
|
271
|
+
```bash
|
|
272
|
+
git clone https://github.com/Rorogogogo/brainctl.git
|
|
273
|
+
cd brainctl
|
|
274
|
+
npm install
|
|
275
|
+
npm test
|
|
276
|
+
```
|
|
229
277
|
|
|
230
278
|
---
|
|
231
279
|
|
|
232
|
-
## 💡
|
|
280
|
+
## 💡 Philosophy
|
|
233
281
|
|
|
234
|
-
AI tools
|
|
282
|
+
brainctl doesn't replace your AI tools. It sits between you and them as a thin orchestration layer:
|
|
235
283
|
|
|
236
|
-
|
|
284
|
+
- **You keep using** Claude Code, Codex, and Gemini CLI directly
|
|
285
|
+
- **brainctl keeps** the environment consistent across all of them
|
|
286
|
+
- **Profiles make it portable** — share your setup with your team
|
|
237
287
|
|
|
238
288
|
---
|
|
239
289
|
|
|
240
290
|
## 📄 License
|
|
241
291
|
|
|
242
|
-
MIT
|
|
292
|
+
[MIT](https://opensource.org/licenses/MIT) — use it however you want.
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { access } from 'node:fs/promises';
|
|
2
|
-
import { constants } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
1
|
import { AgentNotAvailableError } from '../errors.js';
|
|
5
2
|
import { ClaudeExecutor } from './claude.js';
|
|
6
3
|
import { CodexExecutor } from './codex.js';
|
|
4
|
+
import { findExecutable } from '../system/executables.js';
|
|
7
5
|
const SUPPORTED_AGENTS = ['claude', 'codex', 'gemini'];
|
|
8
6
|
const AGENT_COMMANDS = {
|
|
9
7
|
claude: 'claude',
|
|
@@ -60,38 +58,3 @@ async function checkAvailability(agentName) {
|
|
|
60
58
|
resolvedPath: resolvedPath ?? undefined
|
|
61
59
|
};
|
|
62
60
|
}
|
|
63
|
-
async function findExecutable(command) {
|
|
64
|
-
if (command.includes(path.sep)) {
|
|
65
|
-
return (await isExecutable(command)) ? command : null;
|
|
66
|
-
}
|
|
67
|
-
const pathEntries = (process.env.PATH ?? '')
|
|
68
|
-
.split(path.delimiter)
|
|
69
|
-
.filter((entry) => entry.length > 0);
|
|
70
|
-
const extensions = process.platform === 'win32'
|
|
71
|
-
? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM')
|
|
72
|
-
.split(';')
|
|
73
|
-
.filter((entry) => entry.length > 0)
|
|
74
|
-
: [''];
|
|
75
|
-
for (const pathEntry of pathEntries) {
|
|
76
|
-
for (const extension of extensions) {
|
|
77
|
-
const candidate = process.platform === 'win32' &&
|
|
78
|
-
extension.length > 0 &&
|
|
79
|
-
!command.toLowerCase().endsWith(extension.toLowerCase())
|
|
80
|
-
? path.join(pathEntry, `${command}${extension}`)
|
|
81
|
-
: path.join(pathEntry, command);
|
|
82
|
-
if (await isExecutable(candidate)) {
|
|
83
|
-
return candidate;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
async function isExecutable(filePath) {
|
|
90
|
-
try {
|
|
91
|
-
await access(filePath, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -4,7 +4,9 @@ import { FastMCP } from 'fastmcp';
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { loadConfig } from '../config.js';
|
|
6
6
|
import { loadMemory } from '../context/memory.js';
|
|
7
|
+
import { createAgentConfigService } from '../services/agent-config-service.js';
|
|
7
8
|
import { createDoctorService } from '../services/doctor-service.js';
|
|
9
|
+
import { startUiServer } from '../ui/server.js';
|
|
8
10
|
import { createMemoryWriteService } from '../services/memory-write-service.js';
|
|
9
11
|
import { createProfileExportService } from '../services/profile-export-service.js';
|
|
10
12
|
import { createProfileImportService } from '../services/profile-import-service.js';
|
|
@@ -177,6 +179,111 @@ export function createMcpServer(options = {}) {
|
|
|
177
179
|
return JSON.stringify(result, null, 2);
|
|
178
180
|
},
|
|
179
181
|
});
|
|
182
|
+
server.addTool({
|
|
183
|
+
name: 'brainctl_get_profile',
|
|
184
|
+
description: 'Get the full config of a profile including all skills, MCPs, and memory paths.',
|
|
185
|
+
parameters: z.object({
|
|
186
|
+
name: z.string().describe('Profile name'),
|
|
187
|
+
}),
|
|
188
|
+
execute: async (args) => {
|
|
189
|
+
const profileService = createProfileService();
|
|
190
|
+
const profile = await profileService.get({ cwd, name: args.name });
|
|
191
|
+
return JSON.stringify(profile, null, 2);
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
server.addTool({
|
|
195
|
+
name: 'brainctl_create_profile',
|
|
196
|
+
description: 'Create a new profile with a default example skill.',
|
|
197
|
+
parameters: z.object({
|
|
198
|
+
name: z.string().describe('Profile name to create'),
|
|
199
|
+
description: z.string().optional().describe('Profile description'),
|
|
200
|
+
}),
|
|
201
|
+
execute: async (args) => {
|
|
202
|
+
const profileService = createProfileService();
|
|
203
|
+
const result = await profileService.create({
|
|
204
|
+
cwd,
|
|
205
|
+
name: args.name,
|
|
206
|
+
description: args.description,
|
|
207
|
+
});
|
|
208
|
+
return JSON.stringify(result, null, 2);
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
server.addTool({
|
|
212
|
+
name: 'brainctl_update_profile',
|
|
213
|
+
description: 'Update a profile config. Pass the full profile object with skills, mcps, and memory fields. Use this to add, remove, or modify skills and MCPs within a profile.',
|
|
214
|
+
parameters: z.object({
|
|
215
|
+
name: z.string().describe('Profile name to update'),
|
|
216
|
+
config: z.object({
|
|
217
|
+
name: z.string(),
|
|
218
|
+
description: z.string().optional(),
|
|
219
|
+
skills: z.record(z.string(), z.object({
|
|
220
|
+
description: z.string().optional(),
|
|
221
|
+
prompt: z.string(),
|
|
222
|
+
})),
|
|
223
|
+
mcps: z.record(z.string(), z.unknown()),
|
|
224
|
+
memory: z.object({
|
|
225
|
+
paths: z.array(z.string()),
|
|
226
|
+
}),
|
|
227
|
+
}).describe('Full profile config object'),
|
|
228
|
+
}),
|
|
229
|
+
execute: async (args) => {
|
|
230
|
+
const profileService = createProfileService();
|
|
231
|
+
await profileService.update({
|
|
232
|
+
cwd,
|
|
233
|
+
name: args.name,
|
|
234
|
+
config: args.config,
|
|
235
|
+
});
|
|
236
|
+
return JSON.stringify({ ok: true, updated: args.name });
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
server.addTool({
|
|
240
|
+
name: 'brainctl_delete_profile',
|
|
241
|
+
description: 'Delete a profile. Cannot delete the currently active profile.',
|
|
242
|
+
parameters: z.object({
|
|
243
|
+
name: z.string().describe('Profile name to delete'),
|
|
244
|
+
}),
|
|
245
|
+
execute: async (args) => {
|
|
246
|
+
const profileService = createProfileService();
|
|
247
|
+
await profileService.delete({ cwd, name: args.name });
|
|
248
|
+
return JSON.stringify({ ok: true, deleted: args.name });
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
server.addTool({
|
|
252
|
+
name: 'brainctl_copy_profile_items',
|
|
253
|
+
description: 'Copy skills and/or MCPs from one profile to another. Specify which skill and MCP keys to copy. Existing items with the same key in the target are overwritten.',
|
|
254
|
+
parameters: z.object({
|
|
255
|
+
source: z.string().describe('Source profile name'),
|
|
256
|
+
target: z.string().describe('Target profile name'),
|
|
257
|
+
skills: z.array(z.string()).default([]).describe('Skill keys to copy'),
|
|
258
|
+
mcps: z.array(z.string()).default([]).describe('MCP keys to copy'),
|
|
259
|
+
}),
|
|
260
|
+
execute: async (args) => {
|
|
261
|
+
const profileService = createProfileService();
|
|
262
|
+
const sourceProfile = await profileService.get({ cwd, name: args.source });
|
|
263
|
+
const targetProfile = await profileService.get({ cwd, name: args.target });
|
|
264
|
+
const copiedSkills = [];
|
|
265
|
+
const copiedMcps = [];
|
|
266
|
+
for (const key of args.skills) {
|
|
267
|
+
if (sourceProfile.skills[key]) {
|
|
268
|
+
targetProfile.skills[key] = sourceProfile.skills[key];
|
|
269
|
+
copiedSkills.push(key);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
for (const key of args.mcps) {
|
|
273
|
+
if (sourceProfile.mcps[key]) {
|
|
274
|
+
targetProfile.mcps[key] = sourceProfile.mcps[key];
|
|
275
|
+
copiedMcps.push(key);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
await profileService.update({ cwd, name: args.target, config: targetProfile });
|
|
279
|
+
return JSON.stringify({
|
|
280
|
+
source: args.source,
|
|
281
|
+
target: args.target,
|
|
282
|
+
copiedSkills,
|
|
283
|
+
copiedMcps,
|
|
284
|
+
}, null, 2);
|
|
285
|
+
},
|
|
286
|
+
});
|
|
180
287
|
server.addTool({
|
|
181
288
|
name: 'brainctl_export_profile',
|
|
182
289
|
description: 'Export a profile as a portable tarball. Packages the profile config and bundled MCP source code for sharing.',
|
|
@@ -211,6 +318,82 @@ export function createMcpServer(options = {}) {
|
|
|
211
318
|
return JSON.stringify(result, null, 2);
|
|
212
319
|
},
|
|
213
320
|
});
|
|
321
|
+
let uiServerInstance = null;
|
|
322
|
+
server.addTool({
|
|
323
|
+
name: 'brainctl_open_ui',
|
|
324
|
+
description: 'Start the brainctl web dashboard. Returns the URL to open in a browser. If already running, returns the existing URL.',
|
|
325
|
+
parameters: z.object({
|
|
326
|
+
port: z.number().default(3333).describe('Port number for the UI server'),
|
|
327
|
+
}),
|
|
328
|
+
execute: async (args) => {
|
|
329
|
+
if (uiServerInstance) {
|
|
330
|
+
return JSON.stringify({ url: uiServerInstance.url, status: 'already_running' });
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
uiServerInstance = await startUiServer({ cwd, port: args.port });
|
|
334
|
+
return JSON.stringify({ url: uiServerInstance.url, status: 'started' });
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
return JSON.stringify({ error: err.message, status: 'failed' });
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
server.addTool({
|
|
342
|
+
name: 'brainctl_close_ui',
|
|
343
|
+
description: 'Stop the brainctl web dashboard if it is running.',
|
|
344
|
+
parameters: z.object({}),
|
|
345
|
+
execute: async () => {
|
|
346
|
+
if (!uiServerInstance) {
|
|
347
|
+
return JSON.stringify({ status: 'not_running' });
|
|
348
|
+
}
|
|
349
|
+
await uiServerInstance.close();
|
|
350
|
+
uiServerInstance = null;
|
|
351
|
+
return JSON.stringify({ status: 'stopped' });
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
server.addTool({
|
|
355
|
+
name: 'brainctl_read_agent_configs',
|
|
356
|
+
description: 'Read the live MCP configs from all agents (Claude, Codex, Gemini). Shows what is actually configured in each agent right now, by reading their real config files.',
|
|
357
|
+
parameters: z.object({}),
|
|
358
|
+
execute: async () => {
|
|
359
|
+
const agentConfigService = createAgentConfigService();
|
|
360
|
+
const configs = await agentConfigService.readAll({ cwd });
|
|
361
|
+
return JSON.stringify(configs, null, 2);
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
server.addTool({
|
|
365
|
+
name: 'brainctl_add_agent_mcp',
|
|
366
|
+
description: 'Add or overwrite an MCP server entry in a specific agent config. Writes directly to the agent config file (e.g., ~/.claude.json).',
|
|
367
|
+
parameters: z.object({
|
|
368
|
+
agent: z.enum(['claude', 'codex', 'gemini']).describe('Target agent'),
|
|
369
|
+
key: z.string().describe('MCP server name/key'),
|
|
370
|
+
command: z.string().describe('Command to run the MCP server'),
|
|
371
|
+
args: z.array(z.string()).default([]).describe('Arguments for the command'),
|
|
372
|
+
}),
|
|
373
|
+
execute: async (args) => {
|
|
374
|
+
const agentConfigService = createAgentConfigService();
|
|
375
|
+
await agentConfigService.addMcp({
|
|
376
|
+
cwd,
|
|
377
|
+
agent: args.agent,
|
|
378
|
+
key: args.key,
|
|
379
|
+
entry: { command: args.command, args: args.args },
|
|
380
|
+
});
|
|
381
|
+
return JSON.stringify({ ok: true, agent: args.agent, key: args.key });
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
server.addTool({
|
|
385
|
+
name: 'brainctl_remove_agent_mcp',
|
|
386
|
+
description: 'Remove an MCP server entry from a specific agent config.',
|
|
387
|
+
parameters: z.object({
|
|
388
|
+
agent: z.enum(['claude', 'codex', 'gemini']).describe('Target agent'),
|
|
389
|
+
key: z.string().describe('MCP server name/key to remove'),
|
|
390
|
+
}),
|
|
391
|
+
execute: async (args) => {
|
|
392
|
+
const agentConfigService = createAgentConfigService();
|
|
393
|
+
await agentConfigService.removeMcp({ cwd, agent: args.agent, key: args.key });
|
|
394
|
+
return JSON.stringify({ ok: true, agent: args.agent, removed: args.key });
|
|
395
|
+
},
|
|
396
|
+
});
|
|
214
397
|
return server;
|
|
215
398
|
}
|
|
216
399
|
export async function startMcpServer(options = {}) {
|