brainctl 0.1.7 ā 0.1.10
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 +210 -157
- package/dist/cli.js +40 -0
- package/dist/commands/mcp.js +35 -0
- package/dist/commands/profile.js +35 -2
- package/dist/mcp/server.js +51 -5
- package/dist/services/agent-config-service.d.ts +4 -2
- package/dist/services/agent-config-service.js +50 -15
- 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 +3 -2
- package/dist/services/mcp-preflight-service.js +159 -5
- package/dist/services/plugin-install-service.d.ts +43 -0
- package/dist/services/plugin-install-service.js +379 -21
- 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 +265 -10
- package/dist/services/profile-service.js +11 -0
- package/dist/services/runtime-detector.d.ts +9 -0
- package/dist/services/runtime-detector.js +130 -0
- package/dist/services/skill-paths.d.ts +2 -0
- package/dist/services/skill-paths.js +14 -0
- package/dist/services/sync/agent-reader.d.ts +9 -0
- package/dist/services/sync/agent-reader.js +177 -35
- package/dist/services/sync/claude-writer.js +0 -6
- package/dist/services/sync/codex-writer.d.ts +1 -0
- package/dist/services/sync/codex-writer.js +21 -8
- package/dist/services/sync/gemini-writer.js +5 -7
- package/dist/services/sync/plugin-skill-reader.d.ts +5 -0
- package/dist/services/sync/plugin-skill-reader.js +142 -1
- package/dist/services/sync-service.js +1 -1
- package/dist/services/update-check-service.d.ts +33 -0
- package/dist/services/update-check-service.js +128 -0
- package/dist/types.d.ts +47 -0
- package/dist/ui/routes.js +35 -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 +5 -1
- package/dist/web/assets/index-BCkorugl.css +0 -1
- package/dist/web/assets/index-sGnTMhkX.js +0 -16
package/README.md
CHANGED
|
@@ -1,146 +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.
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
+
```
|
|
34
|
+
|
|
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.
|
|
42
|
+
|
|
43
|
+
<details open>
|
|
44
|
+
<summary><b>š£ Claude Code</b> ā <code>~/.claude.json</code></summary>
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
Easiest: use the `claude` CLI.
|
|
30
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
|
+
}
|
|
40
64
|
```
|
|
41
65
|
|
|
42
|
-
|
|
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
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
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.
|
|
53
115
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
134
|
+
|
|
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
|
+
---
|
|
107
193
|
|
|
108
|
-
|
|
109
|
-
brainctl run analyze ./report.md --with codex --fallback claude
|
|
194
|
+
## š¦ Portable profiles
|
|
110
195
|
|
|
111
|
-
|
|
112
|
-
brainctl run review ./code.md --with gemini
|
|
113
|
-
```
|
|
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.
|
|
114
197
|
|
|
115
|
-
|
|
198
|
+
```bash
|
|
199
|
+
# Pack a saved profile
|
|
200
|
+
brainctl profile export starter
|
|
116
201
|
|
|
117
|
-
|
|
202
|
+
# Pack a live agent's state (plugins + skills + MCPs)
|
|
203
|
+
brainctl profile export --agent claude
|
|
118
204
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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"
|
|
205
|
+
# Restore on another machine
|
|
206
|
+
brainctl profile import ./claude.tar.gz \
|
|
207
|
+
--credential github_token=ghp_xxx
|
|
130
208
|
```
|
|
131
209
|
|
|
132
|
-
|
|
210
|
+
**Supported MCP runtimes when packing bundled servers:**
|
|
133
211
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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.
|
|
140
225
|
|
|
141
226
|
---
|
|
142
227
|
|
|
143
|
-
## š§
|
|
228
|
+
## š§ `ai-stack.yaml`
|
|
144
229
|
|
|
145
230
|
```yaml
|
|
146
231
|
memory:
|
|
@@ -161,15 +246,15 @@ skills:
|
|
|
161
246
|
mcps: {}
|
|
162
247
|
```
|
|
163
248
|
|
|
164
|
-
### How
|
|
249
|
+
### How context is assembled
|
|
165
250
|
|
|
166
251
|
```
|
|
167
252
|
āāāāāāāāāāāāāāā
|
|
168
|
-
ā MEMORY ā ā
|
|
253
|
+
ā MEMORY ā ā markdown files from configured paths
|
|
169
254
|
āāāāāāāāāāāāāāā¤
|
|
170
|
-
ā SKILL ā ā
|
|
255
|
+
ā SKILL ā ā prompt template from ai-stack.yaml
|
|
171
256
|
āāāāāāāāāāāāāāā¤
|
|
172
|
-
ā INPUT ā ā
|
|
257
|
+
ā INPUT ā ā your file
|
|
173
258
|
āāāāāāāāāāāāāāā
|
|
174
259
|
ā
|
|
175
260
|
Agent CLI (claude / codex / gemini)
|
|
@@ -177,51 +262,29 @@ mcps: {}
|
|
|
177
262
|
|
|
178
263
|
---
|
|
179
264
|
|
|
180
|
-
## š MCP
|
|
181
|
-
|
|
182
|
-
brainctl exposes **20 MCP tools** that any compatible agent can call:
|
|
183
|
-
|
|
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
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
**Available tools:**
|
|
265
|
+
## š MCP tools
|
|
199
266
|
|
|
200
267
|
| Category | Tools |
|
|
201
|
-
|
|
268
|
+
|---|---|
|
|
202
269
|
| **Skills** | `list_skills`, `get_skill`, `run` |
|
|
203
270
|
| **Memory** | `read_memory`, `write_memory` |
|
|
204
271
|
| **Profiles** | `list_profiles`, `get_profile`, `create_profile`, `update_profile`, `delete_profile`, `switch_profile`, `copy_profile_items`, `export_profile`, `import_profile` |
|
|
205
|
-
| **Agent
|
|
206
|
-
| **
|
|
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` |
|
|
207
276
|
|
|
208
277
|
---
|
|
209
278
|
|
|
210
|
-
##
|
|
279
|
+
## šļø Agent config map
|
|
211
280
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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/` |
|
|
215
286
|
|
|
216
|
-
|
|
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
|
|
287
|
+
Writes are atomic (temp file ā rename) and always leave a timestamped `.bak.*` behind.
|
|
225
288
|
|
|
226
289
|
---
|
|
227
290
|
|
|
@@ -230,63 +293,53 @@ Opens a local dashboard at `http://127.0.0.1:3333` with:
|
|
|
230
293
|
```
|
|
231
294
|
brainctl/
|
|
232
295
|
āāā src/
|
|
233
|
-
ā āāā cli.ts #
|
|
234
|
-
ā āāā commands/
|
|
235
|
-
ā āāā services/
|
|
236
|
-
ā ā āāā sync/
|
|
237
|
-
ā āāā context/
|
|
238
|
-
ā āāā executor/
|
|
239
|
-
ā āāā mcp/
|
|
240
|
-
ā āāā ui/
|
|
241
|
-
āāā web/src/
|
|
242
|
-
|
|
243
|
-
āāā 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
|
|
244
306
|
```
|
|
245
307
|
|
|
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
|
-
|
|
254
308
|
---
|
|
255
309
|
|
|
256
|
-
## š§Ŗ
|
|
310
|
+
## š§Ŗ Develop locally
|
|
257
311
|
|
|
258
312
|
```bash
|
|
313
|
+
git clone https://github.com/Rorogogogo/brainctl.git
|
|
314
|
+
cd brainctl
|
|
259
315
|
npm install
|
|
260
|
-
|
|
261
|
-
npm
|
|
262
|
-
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
|
|
263
321
|
```
|
|
264
322
|
|
|
323
|
+
Point your agent's MCP config at `node <repo>/dist/cli.js mcp` to iterate without publishing.
|
|
324
|
+
|
|
265
325
|
---
|
|
266
326
|
|
|
267
|
-
##
|
|
327
|
+
## š” Philosophy
|
|
268
328
|
|
|
269
|
-
|
|
329
|
+
brainctl doesn't replace your AI tools ā it sits **between you and them** as a thin orchestration layer:
|
|
270
330
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
npm install
|
|
275
|
-
npm test
|
|
276
|
-
```
|
|
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.
|
|
277
334
|
|
|
278
335
|
---
|
|
279
336
|
|
|
280
|
-
##
|
|
281
|
-
|
|
282
|
-
brainctl doesn't replace your AI tools. It sits between you and them as a thin orchestration layer:
|
|
337
|
+
## š¤ Contributing
|
|
283
338
|
|
|
284
|
-
|
|
285
|
-
- **brainctl keeps** the environment consistent across all of them
|
|
286
|
-
- **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.
|
|
287
340
|
|
|
288
341
|
---
|
|
289
342
|
|
|
290
343
|
## š License
|
|
291
344
|
|
|
292
|
-
[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
|
+
}
|