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.
Files changed (59) hide show
  1. package/README.md +215 -136
  2. package/dist/cli.js +40 -0
  3. package/dist/commands/mcp.js +35 -0
  4. package/dist/commands/profile.js +35 -2
  5. package/dist/executor/resolver.js +1 -38
  6. package/dist/mcp/server.js +82 -2
  7. package/dist/services/agent-config-service.d.ts +20 -3
  8. package/dist/services/agent-config-service.js +84 -16
  9. package/dist/services/agent-converter-service.d.ts +21 -0
  10. package/dist/services/agent-converter-service.js +182 -0
  11. package/dist/services/credential-redaction-service.d.ts +13 -0
  12. package/dist/services/credential-redaction-service.js +89 -0
  13. package/dist/services/credential-resolution-service.d.ts +11 -0
  14. package/dist/services/credential-resolution-service.js +69 -0
  15. package/dist/services/mcp-preflight-service.d.ts +26 -0
  16. package/dist/services/mcp-preflight-service.js +238 -0
  17. package/dist/services/plugin-install-service.d.ts +135 -0
  18. package/dist/services/plugin-install-service.js +601 -0
  19. package/dist/services/portable-mcp-classifier.d.ts +12 -0
  20. package/dist/services/portable-mcp-classifier.js +116 -0
  21. package/dist/services/portable-profile-pack-service.d.ts +26 -0
  22. package/dist/services/portable-profile-pack-service.js +264 -0
  23. package/dist/services/profile-export-service.d.ts +15 -3
  24. package/dist/services/profile-export-service.js +10 -57
  25. package/dist/services/profile-import-service.d.ts +9 -1
  26. package/dist/services/profile-import-service.js +266 -11
  27. package/dist/services/profile-service.d.ts +1 -0
  28. package/dist/services/profile-service.js +128 -32
  29. package/dist/services/runtime-detector.d.ts +9 -0
  30. package/dist/services/runtime-detector.js +130 -0
  31. package/dist/services/skill-paths.d.ts +4 -0
  32. package/dist/services/skill-paths.js +26 -0
  33. package/dist/services/skill-preflight-service.d.ts +23 -0
  34. package/dist/services/skill-preflight-service.js +40 -0
  35. package/dist/services/sync/agent-reader.d.ts +14 -0
  36. package/dist/services/sync/agent-reader.js +198 -45
  37. package/dist/services/sync/claude-writer.js +4 -7
  38. package/dist/services/sync/codex-writer.d.ts +1 -0
  39. package/dist/services/sync/codex-writer.js +25 -8
  40. package/dist/services/sync/gemini-writer.js +9 -8
  41. package/dist/services/sync/managed-plugin-registry.d.ts +17 -0
  42. package/dist/services/sync/managed-plugin-registry.js +75 -0
  43. package/dist/services/sync/plugin-skill-reader.d.ts +7 -0
  44. package/dist/services/sync/plugin-skill-reader.js +174 -0
  45. package/dist/services/sync-service.js +6 -1
  46. package/dist/services/update-check-service.d.ts +33 -0
  47. package/dist/services/update-check-service.js +128 -0
  48. package/dist/system/executables.d.ts +1 -0
  49. package/dist/system/executables.js +38 -0
  50. package/dist/types.d.ts +62 -5
  51. package/dist/ui/routes.js +293 -8
  52. package/dist/web/assets/index-Cdb5hbxM.css +1 -0
  53. package/dist/web/assets/index-gN83hZYA.js +65 -0
  54. package/dist/web/favicon-light.svg +13 -0
  55. package/dist/web/favicon.svg +13 -0
  56. package/dist/web/index.html +7 -2
  57. package/package.json +9 -1
  58. package/dist/web/assets/index-364NYWPA.css +0 -1
  59. 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
- > One AI setup. Multiple agents. Zero reconfiguration.
5
+ **One AI setup. Three agents. Zero reconfiguration.**
4
6
 
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.
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
  [![npm version](https://img.shields.io/npm/v/brainctl)](https://www.npmjs.com/package/brainctl)
8
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
11
  [![Build](https://img.shields.io/github/actions/workflow/status/Rorogogogo/brainctl/deploy.yml?branch=main)](https://github.com/Rorogogogo/brainctl/actions)
12
+ [![Stars](https://img.shields.io/github/stars/Rorogogogo/brainctl?style=social)](https://github.com/Rorogogogo/brainctl)
13
+
14
+ </div>
10
15
 
11
16
  ---
12
17
 
13
- ## ✨ Features
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
- - šŸ”€ **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
35
+ > **Prerequisite:** at least one of `claude`, `codex`, or `gemini` installed and on your `PATH`.
24
36
 
25
37
  ---
26
38
 
27
- ## šŸ“ø Demo
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
- ### Web Dashboard — Drag MCPs between agents
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
- │ Claude │ │ Codex │ │ Gemini │
34
- │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │ │
35
- │ │ github ā”‚ā—„ā”œā”€ā”€ā”¤ā–ŗā”‚ github │ │ │ Drop here │
36
- │ │ brainctl │ │ │ │ brainctl │ │ │ to copy │
37
- │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ │
38
- │ Skills: 11 │ │ Skills: 3 │ │ Skills: 0 │
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
- 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.
101
+ Restart your Gemini session.
102
+
103
+ </details>
43
104
 
44
105
  ---
45
106
 
46
- ## šŸ“¦ Installation
107
+ ### 3. Launch the dashboard
47
108
 
48
109
  ```bash
49
- npm install -g brainctl
110
+ brainctl ui
111
+ # → http://127.0.0.1:3333
50
112
  ```
51
113
 
52
- Or from source:
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
- ```bash
55
- git clone https://github.com/Rorogogogo/brainctl.git
56
- cd brainctl
57
- npm install && npm run build && npm link
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
- > **Prerequisite:** At least one supported agent CLI must be installed and on your `PATH` (`claude`, `codex`, or `gemini`).
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
- # 1. Initialize a project
153
+ # Scaffold a project
68
154
  brainctl init
69
155
 
70
- # 2. Check your setup
156
+ # See what's wired up
71
157
  brainctl status
72
158
  brainctl doctor
73
159
 
74
- # 3. Run a skill
160
+ # Run a skill through an agent
75
161
  brainctl run summarize ./memory/notes.md --with claude
76
162
 
77
- # 4. Launch the web dashboard
163
+ # Open the dashboard
78
164
  brainctl ui
79
- # → http://127.0.0.1:3333
80
165
  ```
81
166
 
82
167
  ---
83
168
 
84
- ## šŸ“– Usage
85
-
86
- ### CLI Commands
87
-
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 |
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 Examples
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
- # With fallback agent
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
- # Using Gemini
112
- brainctl run review ./code.md --with gemini
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
- ## 🧠 Config: `ai-stack.yaml`
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 Context Assembly Works
249
+ ### How context is assembled
139
250
 
140
251
  ```
141
252
  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
142
- │ MEMORY │ ← Markdown files from configured paths
253
+ │ MEMORY │ ← markdown files from configured paths
143
254
  ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
144
- │ SKILL │ ← Prompt template from ai-stack.yaml
255
+ │ SKILL │ ← prompt template from ai-stack.yaml
145
256
  ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
146
- │ INPUT │ ← Your file
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 Server
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 Configs** | `read_agent_configs`, `add_agent_mcp`, `remove_agent_mcp` |
180
- | **System** | `status`, `doctor`, `sync` |
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
- ## šŸ–„ļø Web Dashboard
279
+ ## šŸ—‚ļø Agent config map
185
280
 
186
- ```bash
187
- brainctl ui
188
- ```
189
-
190
- Opens a local dashboard at `http://127.0.0.1:3333` with:
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
- - **Agent Profiles** — See live MCPs and skills for Claude, Codex, and Gemini side-by-side
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 # CLI entry point (Commander)
208
- │ ā”œā”€ā”€ commands/ # 8 command handlers
209
- │ ā”œā”€ā”€ services/ # 11 business logic services
210
- │ │ └── sync/ # Agent config readers/writers
211
- │ ā”œā”€ā”€ context/ # Memory loader, skill resolver, context builder
212
- │ ā”œā”€ā”€ executor/ # Agent spawning (Claude, Codex, Gemini)
213
- │ ā”œā”€ā”€ mcp/ # FastMCP server (20 tools)
214
- │ └── ui/ # HTTP server with SSE streaming
215
- ā”œā”€ā”€ web/src/ # React dashboard (Vite + dnd-kit)
216
- ā”œā”€ā”€ tests/ # Vitest test suite
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
- ## 🧪 Development
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
- npm test # Run all tests (Vitest)
235
- npm run build # Build server (tsc) + web (Vite)
236
- npm run dev -- <args> # Run CLI via tsx
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
- ## šŸ¤ Contributing
327
+ ## šŸ’” Philosophy
242
328
 
243
- Contributions are welcome! Please open an issue or submit a pull request.
329
+ brainctl doesn't replace your AI tools — it sits **between you and them** as a thin orchestration layer:
244
330
 
245
- ```bash
246
- git clone https://github.com/Rorogogogo/brainctl.git
247
- cd brainctl
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
- ## šŸ’” Philosophy
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
- - **You keep using** Claude Code, Codex, and Gemini CLI directly
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) — use it however you want.
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) {
@@ -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
+ }
@@ -43,13 +43,22 @@ export function registerProfileCommand(program, services) {
43
43
  });
44
44
  profileCmd
45
45
  .command('export')
46
- .argument('<name>', 'Profile name to export')
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
- name,
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
+ }