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.
Files changed (51) hide show
  1. package/README.md +210 -157
  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/mcp/server.js +51 -5
  6. package/dist/services/agent-config-service.d.ts +4 -2
  7. package/dist/services/agent-config-service.js +50 -15
  8. package/dist/services/agent-converter-service.d.ts +21 -0
  9. package/dist/services/agent-converter-service.js +182 -0
  10. package/dist/services/credential-redaction-service.d.ts +13 -0
  11. package/dist/services/credential-redaction-service.js +89 -0
  12. package/dist/services/credential-resolution-service.d.ts +11 -0
  13. package/dist/services/credential-resolution-service.js +69 -0
  14. package/dist/services/mcp-preflight-service.d.ts +3 -2
  15. package/dist/services/mcp-preflight-service.js +159 -5
  16. package/dist/services/plugin-install-service.d.ts +43 -0
  17. package/dist/services/plugin-install-service.js +379 -21
  18. package/dist/services/portable-mcp-classifier.d.ts +12 -0
  19. package/dist/services/portable-mcp-classifier.js +116 -0
  20. package/dist/services/portable-profile-pack-service.d.ts +26 -0
  21. package/dist/services/portable-profile-pack-service.js +264 -0
  22. package/dist/services/profile-export-service.d.ts +15 -3
  23. package/dist/services/profile-export-service.js +10 -57
  24. package/dist/services/profile-import-service.d.ts +9 -1
  25. package/dist/services/profile-import-service.js +265 -10
  26. package/dist/services/profile-service.js +11 -0
  27. package/dist/services/runtime-detector.d.ts +9 -0
  28. package/dist/services/runtime-detector.js +130 -0
  29. package/dist/services/skill-paths.d.ts +2 -0
  30. package/dist/services/skill-paths.js +14 -0
  31. package/dist/services/sync/agent-reader.d.ts +9 -0
  32. package/dist/services/sync/agent-reader.js +177 -35
  33. package/dist/services/sync/claude-writer.js +0 -6
  34. package/dist/services/sync/codex-writer.d.ts +1 -0
  35. package/dist/services/sync/codex-writer.js +21 -8
  36. package/dist/services/sync/gemini-writer.js +5 -7
  37. package/dist/services/sync/plugin-skill-reader.d.ts +5 -0
  38. package/dist/services/sync/plugin-skill-reader.js +142 -1
  39. package/dist/services/sync-service.js +1 -1
  40. package/dist/services/update-check-service.d.ts +33 -0
  41. package/dist/services/update-check-service.js +128 -0
  42. package/dist/types.d.ts +47 -0
  43. package/dist/ui/routes.js +35 -8
  44. package/dist/web/assets/index-Cdb5hbxM.css +1 -0
  45. package/dist/web/assets/index-gN83hZYA.js +65 -0
  46. package/dist/web/favicon-light.svg +13 -0
  47. package/dist/web/favicon.svg +13 -0
  48. package/dist/web/index.html +7 -2
  49. package/package.json +5 -1
  50. package/dist/web/assets/index-BCkorugl.css +0 -1
  51. 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
- > 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.
14
21
 
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
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
- ## šŸ“ø 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.
42
+
43
+ <details open>
44
+ <summary><b>🟣 Claude Code</b> — <code>~/.claude.json</code></summary>
28
45
 
29
- ### Web Dashboard — Drag MCPs between agents
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
- │ 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
+ }
40
64
  ```
41
65
 
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.
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
- ## šŸ“¦ 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.
53
115
 
54
- ```bash
55
- git clone https://github.com/Rorogogogo/brainctl.git
56
- cd brainctl
57
- npm install && npm run build && npm link
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
- > **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
+ ---
107
193
 
108
- # With fallback agent
109
- brainctl run analyze ./report.md --with codex --fallback claude
194
+ ## šŸ“¦ Portable profiles
110
195
 
111
- # Using Gemini
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
- ### Profile MCP Format
198
+ ```bash
199
+ # Pack a saved profile
200
+ brainctl profile export starter
116
201
 
117
- Packed/published profiles should classify every MCP as either `local` or `remote`.
202
+ # Pack a live agent's state (plugins + skills + MCPs)
203
+ brainctl profile export --agent claude
118
204
 
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"
205
+ # Restore on another machine
206
+ brainctl profile import ./claude.tar.gz \
207
+ --credential github_token=ghp_xxx
130
208
  ```
131
209
 
132
- Rules:
210
+ **Supported MCP runtimes when packing bundled servers:**
133
211
 
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
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
- ## 🧠 Config: `ai-stack.yaml`
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 Context Assembly Works
249
+ ### How context is assembled
165
250
 
166
251
  ```
167
252
  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
168
- │ MEMORY │ ← Markdown files from configured paths
253
+ │ MEMORY │ ← markdown files from configured paths
169
254
  ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
170
- │ SKILL │ ← Prompt template from ai-stack.yaml
255
+ │ SKILL │ ← prompt template from ai-stack.yaml
171
256
  ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
172
- │ INPUT │ ← Your file
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 Server
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 Configs** | `read_agent_configs`, `add_agent_mcp`, `remove_agent_mcp` |
206
- | **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` |
207
276
 
208
277
  ---
209
278
 
210
- ## šŸ–„ļø Web Dashboard
279
+ ## šŸ—‚ļø Agent config map
211
280
 
212
- ```bash
213
- brainctl ui
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
- 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
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 # 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
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
- ## 🧪 Development
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
- npm test # Run all tests (Vitest)
261
- npm run build # Build server (tsc) + web (Vite)
262
- 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
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
- ## šŸ¤ Contributing
327
+ ## šŸ’” Philosophy
268
328
 
269
- 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:
270
330
 
271
- ```bash
272
- git clone https://github.com/Rorogogogo/brainctl.git
273
- cd brainctl
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
- ## šŸ’” Philosophy
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
- - **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
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) — 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
+ }