brainctl 0.1.6 ā 0.1.9
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 +215 -136
- package/dist/cli.js +40 -0
- package/dist/commands/mcp.js +35 -0
- package/dist/commands/profile.js +35 -2
- package/dist/executor/resolver.js +1 -38
- package/dist/mcp/server.js +82 -2
- package/dist/services/agent-config-service.d.ts +20 -3
- package/dist/services/agent-config-service.js +84 -16
- package/dist/services/agent-converter-service.d.ts +21 -0
- package/dist/services/agent-converter-service.js +182 -0
- package/dist/services/credential-redaction-service.d.ts +13 -0
- package/dist/services/credential-redaction-service.js +89 -0
- package/dist/services/credential-resolution-service.d.ts +11 -0
- package/dist/services/credential-resolution-service.js +69 -0
- package/dist/services/mcp-preflight-service.d.ts +26 -0
- package/dist/services/mcp-preflight-service.js +238 -0
- package/dist/services/plugin-install-service.d.ts +135 -0
- package/dist/services/plugin-install-service.js +601 -0
- package/dist/services/portable-mcp-classifier.d.ts +12 -0
- package/dist/services/portable-mcp-classifier.js +116 -0
- package/dist/services/portable-profile-pack-service.d.ts +26 -0
- package/dist/services/portable-profile-pack-service.js +264 -0
- package/dist/services/profile-export-service.d.ts +15 -3
- package/dist/services/profile-export-service.js +10 -57
- package/dist/services/profile-import-service.d.ts +9 -1
- package/dist/services/profile-import-service.js +266 -11
- package/dist/services/profile-service.d.ts +1 -0
- package/dist/services/profile-service.js +128 -32
- package/dist/services/runtime-detector.d.ts +9 -0
- package/dist/services/runtime-detector.js +130 -0
- package/dist/services/skill-paths.d.ts +4 -0
- package/dist/services/skill-paths.js +26 -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 +14 -0
- package/dist/services/sync/agent-reader.js +198 -45
- package/dist/services/sync/claude-writer.js +4 -7
- package/dist/services/sync/codex-writer.d.ts +1 -0
- package/dist/services/sync/codex-writer.js +25 -8
- package/dist/services/sync/gemini-writer.js +9 -8
- 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 +7 -0
- package/dist/services/sync/plugin-skill-reader.js +174 -0
- package/dist/services/sync-service.js +6 -1
- package/dist/services/update-check-service.d.ts +33 -0
- package/dist/services/update-check-service.js +128 -0
- package/dist/system/executables.d.ts +1 -0
- package/dist/system/executables.js +38 -0
- package/dist/types.d.ts +62 -5
- package/dist/ui/routes.js +293 -8
- package/dist/web/assets/index-Cdb5hbxM.css +1 -0
- package/dist/web/assets/index-gN83hZYA.js +65 -0
- package/dist/web/favicon-light.svg +13 -0
- package/dist/web/favicon.svg +13 -0
- package/dist/web/index.html +7 -2
- package/package.json +9 -1
- package/dist/web/assets/index-364NYWPA.css +0 -1
- package/dist/web/assets/index-BmfE7rus.js +0 -16
package/README.md
CHANGED
|
@@ -1,120 +1,231 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# š§ brainctl
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
**One AI setup. Three agents. Zero reconfiguration.**
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
*A cross-agent command centre for Claude Code, Codex, and Gemini CLI ā CLI + MCP server + drag-and-drop web dashboard.*
|
|
6
8
|
|
|
7
9
|
[](https://www.npmjs.com/package/brainctl)
|
|
8
10
|
[](https://opensource.org/licenses/MIT)
|
|
9
11
|
[](https://github.com/Rorogogogo/brainctl/actions)
|
|
12
|
+
[](https://github.com/Rorogogogo/brainctl)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
10
15
|
|
|
11
16
|
---
|
|
12
17
|
|
|
13
|
-
##
|
|
18
|
+
## š¦ Install & Set Up
|
|
19
|
+
|
|
20
|
+
> **Install once, wire into each agent.** The CLI is the same for everyone ā only the MCP registration differs per agent.
|
|
21
|
+
|
|
22
|
+
### 1. Install the CLI
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g brainctl
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Verify:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
brainctl --version
|
|
32
|
+
brainctl doctor # checks which agents are on your PATH
|
|
33
|
+
```
|
|
14
34
|
|
|
15
|
-
|
|
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
|
|
35
|
+
> **Prerequisite:** at least one of `claude`, `codex`, or `gemini` installed and on your `PATH`.
|
|
24
36
|
|
|
25
37
|
---
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
### 2. Register `brainctl` as an MCP server (pick your agents)
|
|
40
|
+
|
|
41
|
+
Each agent has its own config file. Brainctl exposes **22 MCP tools** (profiles, sync, skills, run, web UI launch, etc.) ā once you register it, any of them can call those tools.
|
|
28
42
|
|
|
29
|
-
|
|
43
|
+
<details open>
|
|
44
|
+
<summary><b>š£ Claude Code</b> ā <code>~/.claude.json</code></summary>
|
|
30
45
|
|
|
46
|
+
Easiest: use the `claude` CLI.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
claude mcp add brainctl -s user -- npx -y brainctl mcp
|
|
31
50
|
```
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
|
|
52
|
+
Or edit `~/.claude.json` directly:
|
|
53
|
+
|
|
54
|
+
```jsonc
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"brainctl": {
|
|
58
|
+
"type": "stdio",
|
|
59
|
+
"command": "npx",
|
|
60
|
+
"args": ["-y", "brainctl", "mcp"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Reload the MCP in Claude Code: `/mcp` ā `brainctl` ā **Reconnect**.
|
|
67
|
+
|
|
68
|
+
</details>
|
|
69
|
+
|
|
70
|
+
<details open>
|
|
71
|
+
<summary><b>š¢ Codex</b> ā <code>~/.codex/config.toml</code></summary>
|
|
72
|
+
|
|
73
|
+
Append this block:
|
|
74
|
+
|
|
75
|
+
```toml
|
|
76
|
+
[mcp_servers.brainctl]
|
|
77
|
+
command = "npx"
|
|
78
|
+
args = ["-y", "brainctl", "mcp"]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Restart your Codex session to pick it up.
|
|
82
|
+
|
|
83
|
+
</details>
|
|
84
|
+
|
|
85
|
+
<details open>
|
|
86
|
+
<summary><b>šµ Gemini CLI</b> ā <code>~/.gemini/settings.json</code></summary>
|
|
87
|
+
|
|
88
|
+
Merge into the top-level `mcpServers` object:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"brainctl": {
|
|
94
|
+
"command": "npx",
|
|
95
|
+
"args": ["-y", "brainctl", "mcp"]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
40
99
|
```
|
|
41
100
|
|
|
42
|
-
|
|
101
|
+
Restart your Gemini session.
|
|
102
|
+
|
|
103
|
+
</details>
|
|
43
104
|
|
|
44
105
|
---
|
|
45
106
|
|
|
46
|
-
|
|
107
|
+
### 3. Launch the dashboard
|
|
47
108
|
|
|
48
109
|
```bash
|
|
49
|
-
|
|
110
|
+
brainctl ui
|
|
111
|
+
# ā http://127.0.0.1:3333
|
|
50
112
|
```
|
|
51
113
|
|
|
52
|
-
Or from
|
|
114
|
+
Or from any MCP-connected agent: _"Open the brainctl UI"_ ā it'll start the server **and** open your browser automatically.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## ⨠Features
|
|
119
|
+
|
|
120
|
+
- š **Multi-agent** ā Claude Code, Codex, Gemini CLI from one config
|
|
121
|
+
- š±ļø **Drag-and-drop dashboard** ā shuffle MCPs and skills between agents, staged + previewed before save
|
|
122
|
+
- š **MCP server** ā 22 tools any compatible agent can call
|
|
123
|
+
- š¦ **Portable profiles** _(preview)_ ā pack an entire agent setup (plugins + skills + MCPs) into a self-contained tarball
|
|
124
|
+
- š **Multi-runtime packing** ā Node, Python, Java, Go, Rust, or plain binaries ā auto-detected
|
|
125
|
+
- š§ **Shared memory** ā markdown files injected into every run
|
|
126
|
+
- š§© **Reusable skills** ā prompt templates in `ai-stack.yaml`
|
|
127
|
+
- š **One-shot sync** ā push the active profile to all agents in one command
|
|
128
|
+
- 𩺠**Health checks** ā `status` and `doctor` surface drift and broken config
|
|
129
|
+
- š **Fallback agents** ā automatic failover if the primary agent is unavailable
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## šø Dashboard at a glance
|
|
53
134
|
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
135
|
+
```
|
|
136
|
+
āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā
|
|
137
|
+
ā Claude ā ā Codex ā ā Gemini ā
|
|
138
|
+
ā āāāāāāāāāāāā ā ā āāāāāāāāāāāā ā ā ā
|
|
139
|
+
ā ā github āāāāāā¤āŗā github ā ā ā drop here ā
|
|
140
|
+
ā ā brainctl ā ā ā ā brainctl ā ā ā to copy ā
|
|
141
|
+
ā āāāāāāāāāāāā ā ā āāāāāāāāāāāā ā ā ā
|
|
142
|
+
ā Skills: 11 ā ā Skills: 3 ā ā Skills: 0 ā
|
|
143
|
+
āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā
|
|
58
144
|
```
|
|
59
145
|
|
|
60
|
-
|
|
146
|
+
The dashboard reads **live config files** (`~/.claude.json`, `~/.codex/config.toml`, `~/.gemini/settings.json`) and writes back atomically with timestamped backups.
|
|
61
147
|
|
|
62
148
|
---
|
|
63
149
|
|
|
64
150
|
## š Quick Start
|
|
65
151
|
|
|
66
152
|
```bash
|
|
67
|
-
#
|
|
153
|
+
# Scaffold a project
|
|
68
154
|
brainctl init
|
|
69
155
|
|
|
70
|
-
#
|
|
156
|
+
# See what's wired up
|
|
71
157
|
brainctl status
|
|
72
158
|
brainctl doctor
|
|
73
159
|
|
|
74
|
-
#
|
|
160
|
+
# Run a skill through an agent
|
|
75
161
|
brainctl run summarize ./memory/notes.md --with claude
|
|
76
162
|
|
|
77
|
-
#
|
|
163
|
+
# Open the dashboard
|
|
78
164
|
brainctl ui
|
|
79
|
-
# ā http://127.0.0.1:3333
|
|
80
165
|
```
|
|
81
166
|
|
|
82
167
|
---
|
|
83
168
|
|
|
84
|
-
## š
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
|
89
|
-
|
|
90
|
-
| `brainctl
|
|
91
|
-
| `brainctl
|
|
92
|
-
| `brainctl
|
|
93
|
-
| `brainctl
|
|
94
|
-
| `brainctl profile
|
|
95
|
-
| `brainctl
|
|
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 |
|
|
169
|
+
## š CLI reference
|
|
170
|
+
|
|
171
|
+
| Command | What it does |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `brainctl init` | Scaffold `ai-stack.yaml` and `memory/` |
|
|
174
|
+
| `brainctl status` | Memory, skills, MCPs, and agent availability |
|
|
175
|
+
| `brainctl doctor` | Validate config, paths, and agent CLIs |
|
|
176
|
+
| `brainctl run <skill> <file> --with <agent>` | Execute a skill |
|
|
177
|
+
| `brainctl profile list / create / use / delete` | Manage saved profiles |
|
|
178
|
+
| `brainctl profile export [name] [--agent <agent>]` | Pack a profile or live agent as a portable tarball |
|
|
179
|
+
| `brainctl profile import <archive> [--credential key=value]` | Restore a portable tarball |
|
|
180
|
+
| `brainctl sync` | Push the active profile to every agent |
|
|
100
181
|
| `brainctl ui` | Start the web dashboard |
|
|
182
|
+
| `brainctl mcp` | Run as an MCP server over stdio |
|
|
101
183
|
|
|
102
|
-
### Run
|
|
184
|
+
### Run examples
|
|
103
185
|
|
|
104
186
|
```bash
|
|
105
|
-
# Basic execution
|
|
106
187
|
brainctl run summarize ./notes.md --with claude
|
|
188
|
+
brainctl run analyze ./report.md --with codex --fallback claude
|
|
189
|
+
brainctl run review ./code.md --with gemini
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## š¦ Portable profiles
|
|
107
195
|
|
|
108
|
-
|
|
109
|
-
brainctl run analyze ./report.md --with codex --fallback claude
|
|
196
|
+
Pack your complete agent setup ā **plugins, user-authored skills, and MCPs** ā into a single `.tar.gz`. The receiver imports it with one command; no marketplace, no network lookups.
|
|
110
197
|
|
|
111
|
-
|
|
112
|
-
|
|
198
|
+
```bash
|
|
199
|
+
# Pack a saved profile
|
|
200
|
+
brainctl profile export starter
|
|
201
|
+
|
|
202
|
+
# Pack a live agent's state (plugins + skills + MCPs)
|
|
203
|
+
brainctl profile export --agent claude
|
|
204
|
+
|
|
205
|
+
# Restore on another machine
|
|
206
|
+
brainctl profile import ./claude.tar.gz \
|
|
207
|
+
--credential github_token=ghp_xxx
|
|
113
208
|
```
|
|
114
209
|
|
|
210
|
+
**Supported MCP runtimes when packing bundled servers:**
|
|
211
|
+
|
|
212
|
+
| Runtime | Detected via | Install on unpack | Excluded |
|
|
213
|
+
|---|---|---|---|
|
|
214
|
+
| Node.js | `node`, `npx` | `npm install` | `node_modules/` |
|
|
215
|
+
| Python | `python`, `python3` | `pip install` / `uv sync` | `.venv/`, `__pycache__/` |
|
|
216
|
+
| Java | `java -jar` | ā | ā |
|
|
217
|
+
| Java (project) | `pom.xml`, `build.gradle` | `mvn package` / `gradle build` | `target/`, `build/` |
|
|
218
|
+
| Go | `go run` | `go build ./...` | ā |
|
|
219
|
+
| Rust | `cargo run` | `cargo build --release` | `target/` |
|
|
220
|
+
| Binary | `./server` | ā | ā |
|
|
221
|
+
|
|
222
|
+
Credentials are redacted to `${credentials.<key>}` placeholders on export and resolved on import.
|
|
223
|
+
|
|
224
|
+
> ā ļø Pack / Install via the web UI is currently disabled while we grind the last rough edges. The CLI commands above work today.
|
|
225
|
+
|
|
115
226
|
---
|
|
116
227
|
|
|
117
|
-
## š§
|
|
228
|
+
## š§ `ai-stack.yaml`
|
|
118
229
|
|
|
119
230
|
```yaml
|
|
120
231
|
memory:
|
|
@@ -135,15 +246,15 @@ skills:
|
|
|
135
246
|
mcps: {}
|
|
136
247
|
```
|
|
137
248
|
|
|
138
|
-
### How
|
|
249
|
+
### How context is assembled
|
|
139
250
|
|
|
140
251
|
```
|
|
141
252
|
āāāāāāāāāāāāāāā
|
|
142
|
-
ā MEMORY ā ā
|
|
253
|
+
ā MEMORY ā ā markdown files from configured paths
|
|
143
254
|
āāāāāāāāāāāāāāā¤
|
|
144
|
-
ā SKILL ā ā
|
|
255
|
+
ā SKILL ā ā prompt template from ai-stack.yaml
|
|
145
256
|
āāāāāāāāāāāāāāā¤
|
|
146
|
-
ā INPUT ā ā
|
|
257
|
+
ā INPUT ā ā your file
|
|
147
258
|
āāāāāāāāāāāāāāā
|
|
148
259
|
ā
|
|
149
260
|
Agent CLI (claude / codex / gemini)
|
|
@@ -151,51 +262,29 @@ mcps: {}
|
|
|
151
262
|
|
|
152
263
|
---
|
|
153
264
|
|
|
154
|
-
## š MCP
|
|
155
|
-
|
|
156
|
-
brainctl exposes **20 MCP tools** that any compatible agent can call:
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
# Add brainctl as an MCP server in your agent config
|
|
160
|
-
# Claude (~/.claude.json):
|
|
161
|
-
{
|
|
162
|
-
"mcpServers": {
|
|
163
|
-
"brainctl": {
|
|
164
|
-
"type": "stdio",
|
|
165
|
-
"command": "npx",
|
|
166
|
-
"args": ["-y", "brainctl", "mcp"]
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
**Available tools:**
|
|
265
|
+
## š MCP tools
|
|
173
266
|
|
|
174
267
|
| Category | Tools |
|
|
175
|
-
|
|
268
|
+
|---|---|
|
|
176
269
|
| **Skills** | `list_skills`, `get_skill`, `run` |
|
|
177
270
|
| **Memory** | `read_memory`, `write_memory` |
|
|
178
271
|
| **Profiles** | `list_profiles`, `get_profile`, `create_profile`, `update_profile`, `delete_profile`, `switch_profile`, `copy_profile_items`, `export_profile`, `import_profile` |
|
|
179
|
-
| **Agent
|
|
180
|
-
| **
|
|
272
|
+
| **Agent configs** | `read_agent_configs`, `add_agent_mcp`, `remove_agent_mcp` |
|
|
273
|
+
| **Sync** | `sync` |
|
|
274
|
+
| **System** | `status`, `doctor` |
|
|
275
|
+
| **UI** | `open_ui`, `close_ui` |
|
|
181
276
|
|
|
182
277
|
---
|
|
183
278
|
|
|
184
|
-
##
|
|
279
|
+
## šļø Agent config map
|
|
185
280
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
281
|
+
| Agent | Config file | MCP location | Skills / plugins |
|
|
282
|
+
|---|---|---|---|
|
|
283
|
+
| Claude | `~/.claude.json` | `mcpServers` + `projects[cwd].mcpServers` | `~/.claude/plugins/` + `~/.claude/skills/` |
|
|
284
|
+
| Codex | `~/.codex/config.toml` | `[mcp_servers.*]` | `~/.codex/plugins/` + `~/.codex/skills/` |
|
|
285
|
+
| Gemini | `~/.gemini/settings.json` | `mcpServers` | `~/.gemini/skills/` |
|
|
191
286
|
|
|
192
|
-
|
|
193
|
-
- **Drag & Drop** ā Copy MCPs between agents by dragging cards
|
|
194
|
-
- **Staged Changes** ā Preview adds/removes before applying, with undo support
|
|
195
|
-
- **Skills Editor** ā Edit skill prompts with live preview
|
|
196
|
-
- **MCP Manager** ā View and edit MCP configurations
|
|
197
|
-
- **Memory Viewer** ā Browse shared markdown memory files
|
|
198
|
-
- **Run Console** ā Execute skills with real-time streaming output
|
|
287
|
+
Writes are atomic (temp file ā rename) and always leave a timestamped `.bak.*` behind.
|
|
199
288
|
|
|
200
289
|
---
|
|
201
290
|
|
|
@@ -204,63 +293,53 @@ Opens a local dashboard at `http://127.0.0.1:3333` with:
|
|
|
204
293
|
```
|
|
205
294
|
brainctl/
|
|
206
295
|
āāā src/
|
|
207
|
-
ā āāā cli.ts #
|
|
208
|
-
ā āāā commands/
|
|
209
|
-
ā āāā services/
|
|
210
|
-
ā ā āāā sync/
|
|
211
|
-
ā āāā context/
|
|
212
|
-
ā āāā executor/
|
|
213
|
-
ā āāā mcp/
|
|
214
|
-
ā āāā ui/
|
|
215
|
-
āāā web/src/
|
|
216
|
-
|
|
217
|
-
āāā ai-stack.yaml # Project config
|
|
296
|
+
ā āāā cli.ts # Commander entry
|
|
297
|
+
ā āāā commands/ # CLI handlers
|
|
298
|
+
ā āāā services/ # Business logic (factory pattern)
|
|
299
|
+
ā ā āāā sync/ # Per-agent readers + writers
|
|
300
|
+
ā āāā context/ # Memory + skill + context builder
|
|
301
|
+
ā āāā executor/ # Agent subprocess spawning
|
|
302
|
+
ā āāā mcp/ # FastMCP server
|
|
303
|
+
ā āāā ui/ # HTTP server (SSE streaming)
|
|
304
|
+
āāā web/src/ # React dashboard (Vite + dnd-kit)
|
|
305
|
+
āāā tests/ # Vitest suite
|
|
218
306
|
```
|
|
219
307
|
|
|
220
|
-
### Agent Config Locations
|
|
221
|
-
|
|
222
|
-
| Agent | Config Path | MCP Location |
|
|
223
|
-
|-------|-------------|-------------|
|
|
224
|
-
| Claude | `~/.claude.json` | `projects[cwd].mcpServers` |
|
|
225
|
-
| Codex | `~/.codex/config.toml` | `[mcp_servers.*]` |
|
|
226
|
-
| Gemini | `~/.gemini/settings.json` | `mcpServers` |
|
|
227
|
-
|
|
228
308
|
---
|
|
229
309
|
|
|
230
|
-
## š§Ŗ
|
|
310
|
+
## š§Ŗ Develop locally
|
|
231
311
|
|
|
232
312
|
```bash
|
|
313
|
+
git clone https://github.com/Rorogogogo/brainctl.git
|
|
314
|
+
cd brainctl
|
|
233
315
|
npm install
|
|
234
|
-
|
|
235
|
-
npm
|
|
236
|
-
npm run
|
|
316
|
+
|
|
317
|
+
npm test # vitest
|
|
318
|
+
npm run build # server (tsc) + web (vite)
|
|
319
|
+
npm run dev -- status # run CLI via tsx
|
|
320
|
+
npx tsx src/cli.ts ui # dashboard from source
|
|
237
321
|
```
|
|
238
322
|
|
|
323
|
+
Point your agent's MCP config at `node <repo>/dist/cli.js mcp` to iterate without publishing.
|
|
324
|
+
|
|
239
325
|
---
|
|
240
326
|
|
|
241
|
-
##
|
|
327
|
+
## š” Philosophy
|
|
242
328
|
|
|
243
|
-
|
|
329
|
+
brainctl doesn't replace your AI tools ā it sits **between you and them** as a thin orchestration layer:
|
|
244
330
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
npm install
|
|
249
|
-
npm test
|
|
250
|
-
```
|
|
331
|
+
- You keep using Claude Code, Codex, and Gemini CLI directly.
|
|
332
|
+
- brainctl keeps the environment consistent across all of them.
|
|
333
|
+
- Portable profiles make your setup shareable in one file.
|
|
251
334
|
|
|
252
335
|
---
|
|
253
336
|
|
|
254
|
-
##
|
|
255
|
-
|
|
256
|
-
brainctl doesn't replace your AI tools. It sits between you and them as a thin orchestration layer:
|
|
337
|
+
## š¤ Contributing
|
|
257
338
|
|
|
258
|
-
|
|
259
|
-
- **brainctl keeps** the environment consistent across all of them
|
|
260
|
-
- **Profiles make it portable** ā share your setup with your team
|
|
339
|
+
Issues and PRs welcome. See [`CLAUDE.md`](CLAUDE.md) for the codebase map an agent-friendly contribution flow.
|
|
261
340
|
|
|
262
341
|
---
|
|
263
342
|
|
|
264
343
|
## š License
|
|
265
344
|
|
|
266
|
-
[MIT](https://opensource.org/licenses/MIT) ā
|
|
345
|
+
[MIT](https://opensource.org/licenses/MIT) ā do whatever you want.
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { realpathSync, readFileSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { Command } from 'commander';
|
|
6
7
|
import { registerDoctorCommand } from './commands/doctor.js';
|
|
@@ -12,6 +13,7 @@ import { registerStatusCommand } from './commands/status.js';
|
|
|
12
13
|
import { registerSyncCommand } from './commands/sync.js';
|
|
13
14
|
import { registerUiCommand } from './commands/ui.js';
|
|
14
15
|
import { printError } from './output.js';
|
|
16
|
+
import { createUpdateCheckService } from './services/update-check-service.js';
|
|
15
17
|
import { createDoctorService } from './services/doctor-service.js';
|
|
16
18
|
import { createInitService } from './services/init-service.js';
|
|
17
19
|
import { createProfileExportService } from './services/profile-export-service.js';
|
|
@@ -52,6 +54,44 @@ export async function main(argv = process.argv) {
|
|
|
52
54
|
printError(error);
|
|
53
55
|
process.exitCode = 1;
|
|
54
56
|
}
|
|
57
|
+
// Skip update check for MCP mode (handled in mcp command), non-TTY, opt-out, or local dev
|
|
58
|
+
const isMcpCommand = argv.includes('mcp');
|
|
59
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
60
|
+
const isOptedOut = !!process.env.BRAINCTL_NO_UPDATE_CHECK;
|
|
61
|
+
const isLocalDev = import.meta.url.includes('/src/');
|
|
62
|
+
if (isMcpCommand || !isInteractive || isOptedOut || isLocalDev)
|
|
63
|
+
return;
|
|
64
|
+
try {
|
|
65
|
+
const service = createUpdateCheckService();
|
|
66
|
+
const check = await service.check();
|
|
67
|
+
if (!check.isOutdated)
|
|
68
|
+
return;
|
|
69
|
+
const answer = await promptYesNo(`\nā brainctl ${check.latest} is available (you have ${check.current}).\n Update now? [Y/n] `);
|
|
70
|
+
if (answer) {
|
|
71
|
+
process.stderr.write(' Updating...\n');
|
|
72
|
+
const result = await service.selfUpdate();
|
|
73
|
+
if (result.success) {
|
|
74
|
+
process.stderr.write(` Updated to brainctl@${check.latest}\n`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
process.stderr.write(` Update failed: ${result.error ?? 'unknown error'}\n`);
|
|
78
|
+
process.stderr.write(' Run manually: npm install -g brainctl\n');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Update check failed ā don't interrupt the user
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function promptYesNo(question) {
|
|
87
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
rl.question(question, (answer) => {
|
|
90
|
+
rl.close();
|
|
91
|
+
const trimmed = answer.trim().toLowerCase();
|
|
92
|
+
resolve(trimmed === '' || trimmed === 'y' || trimmed === 'yes');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
55
95
|
}
|
|
56
96
|
export function shouldRunMain(entryPointPath, moduleUrl) {
|
|
57
97
|
if (!entryPointPath) {
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,9 +1,44 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
1
2
|
import { startMcpServer } from '../mcp/server.js';
|
|
3
|
+
import { createUpdateCheckService } from '../services/update-check-service.js';
|
|
2
4
|
export function registerMcpCommand(program) {
|
|
3
5
|
program
|
|
4
6
|
.command('mcp')
|
|
5
7
|
.description('Start the brainctl MCP server (stdio transport)')
|
|
6
8
|
.action(async () => {
|
|
9
|
+
if (!process.env.BRAINCTL_NO_UPDATE_CHECK) {
|
|
10
|
+
await autoUpdateIfNeeded();
|
|
11
|
+
}
|
|
7
12
|
await startMcpServer({ cwd: process.cwd() });
|
|
8
13
|
});
|
|
9
14
|
}
|
|
15
|
+
async function autoUpdateIfNeeded() {
|
|
16
|
+
try {
|
|
17
|
+
const service = createUpdateCheckService();
|
|
18
|
+
const check = await service.check();
|
|
19
|
+
if (!check.isOutdated)
|
|
20
|
+
return;
|
|
21
|
+
const result = await service.selfUpdate();
|
|
22
|
+
if (result.success) {
|
|
23
|
+
// Re-exec with the updated binary
|
|
24
|
+
const child = spawn(process.execPath, process.argv.slice(1), {
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
});
|
|
27
|
+
await new Promise((resolve) => {
|
|
28
|
+
child.on('exit', (code) => {
|
|
29
|
+
process.exit(code ?? 0);
|
|
30
|
+
});
|
|
31
|
+
child.on('error', () => {
|
|
32
|
+
resolve(); // fall through to current version
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (result.error) {
|
|
38
|
+
process.stderr.write(`brainctl: auto-update failed: ${result.error}\n`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Update check failed entirely ā continue silently
|
|
43
|
+
}
|
|
44
|
+
}
|
package/dist/commands/profile.js
CHANGED
|
@@ -43,13 +43,22 @@ export function registerProfileCommand(program, services) {
|
|
|
43
43
|
});
|
|
44
44
|
profileCmd
|
|
45
45
|
.command('export')
|
|
46
|
-
.argument('
|
|
46
|
+
.argument('[name]', 'Profile name to export')
|
|
47
|
+
.option('-a, --agent <name>', 'Pack a live agent config instead (claude, codex, gemini)')
|
|
47
48
|
.option('-o, --output <path>', 'Output file path')
|
|
48
49
|
.description('Export a profile as a portable tarball')
|
|
49
50
|
.action(async (name, options) => {
|
|
51
|
+
const agent = options.agent === 'claude' || options.agent === 'codex' || options.agent === 'gemini'
|
|
52
|
+
? options.agent
|
|
53
|
+
: undefined;
|
|
54
|
+
if (!agent && !name) {
|
|
55
|
+
throw new Error('Provide a profile name or --agent <name>.');
|
|
56
|
+
}
|
|
50
57
|
const result = await profileExportService.execute({
|
|
51
58
|
cwd: process.cwd(),
|
|
52
|
-
|
|
59
|
+
source: agent
|
|
60
|
+
? { source: 'agent', agent, cwd: process.cwd() }
|
|
61
|
+
: { source: 'profile', name: name },
|
|
53
62
|
outputPath: options.output,
|
|
54
63
|
});
|
|
55
64
|
console.log(`Exported profile to ${result.archivePath}`);
|
|
@@ -58,12 +67,14 @@ export function registerProfileCommand(program, services) {
|
|
|
58
67
|
.command('import')
|
|
59
68
|
.argument('<archive>', 'Path to profile tarball')
|
|
60
69
|
.option('--force', 'Overwrite existing profile', false)
|
|
70
|
+
.option('-c, --credential <key=value>', 'Provide a credential value for import', collectCredentialOption, [])
|
|
61
71
|
.description('Import a profile from a tarball')
|
|
62
72
|
.action(async (archive, options) => {
|
|
63
73
|
const result = await profileImportService.execute({
|
|
64
74
|
cwd: process.cwd(),
|
|
65
75
|
archivePath: archive,
|
|
66
76
|
force: options.force,
|
|
77
|
+
credentials: toCredentialMap(options.credential),
|
|
67
78
|
});
|
|
68
79
|
console.log(`Imported profile "${result.profileName}"`);
|
|
69
80
|
if (result.installedMcps.length > 0) {
|
|
@@ -71,3 +82,25 @@ export function registerProfileCommand(program, services) {
|
|
|
71
82
|
}
|
|
72
83
|
});
|
|
73
84
|
}
|
|
85
|
+
function collectCredentialOption(value, previous) {
|
|
86
|
+
return [...previous, value];
|
|
87
|
+
}
|
|
88
|
+
function toCredentialMap(values) {
|
|
89
|
+
if (values.length === 0) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const credentials = {};
|
|
93
|
+
for (const value of values) {
|
|
94
|
+
const separatorIndex = value.indexOf('=');
|
|
95
|
+
if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
|
|
96
|
+
throw new Error(`Invalid credential "${value}". Use key=value.`);
|
|
97
|
+
}
|
|
98
|
+
const key = value.slice(0, separatorIndex).trim();
|
|
99
|
+
const credentialValue = value.slice(separatorIndex + 1).trim();
|
|
100
|
+
if (!key || !credentialValue) {
|
|
101
|
+
throw new Error(`Invalid credential "${value}". Use key=value.`);
|
|
102
|
+
}
|
|
103
|
+
credentials[key] = credentialValue;
|
|
104
|
+
}
|
|
105
|
+
return credentials;
|
|
106
|
+
}
|