@bike4mind/cli 0.18.4 → 0.20.0

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 (41) hide show
  1. package/LICENSE +17 -3
  2. package/README.md +204 -35
  3. package/bin/bike4mind-cli.mjs +137 -24
  4. package/bin/hearth-hook.mjs +292 -0
  5. package/dist/AgentHistoryStore-C8uUKjjC.mjs +35512 -0
  6. package/dist/ApiClient-B_CQrUiF.mjs +277 -0
  7. package/dist/{ConfigStore-Cq20962p.mjs → ConfigStore-DD3DcC3-.mjs} +6256 -3911
  8. package/dist/{ImageStore-BVmEG1xc.mjs → ImageStore-kVo-oHoS.mjs} +2 -2
  9. package/dist/PluginStore-DwvOJ-G3.mjs +206 -0
  10. package/dist/ProxyManager-Bqr7Lmsd.mjs +3 -0
  11. package/dist/{ProxyManager-CV94yZUW.mjs → ProxyManager-C5H0pUyK.mjs} +2 -2
  12. package/dist/{SandboxOrchestrator-BS6gALNq.mjs → SandboxOrchestrator-BFPVpmB5.mjs} +1 -1
  13. package/dist/{SandboxOrchestrator-BoINxbX4.mjs → SandboxOrchestrator-C8uleDn2.mjs} +7 -7
  14. package/dist/ShellSessionManager-6o8KZzl1-vrbPAUTq.mjs +252 -0
  15. package/dist/{ViolationLogStore-B-plqJfn.mjs → ViolationLogStore-byEhxa2A.mjs} +1 -1
  16. package/dist/WorkItemsClient-Cow6nXx7.mjs +382 -0
  17. package/dist/{bashExecute-B1N1lMOS-TZVDbcQ4.mjs → bashExecute-CrdPpBqk-DCATrE-D.mjs} +116 -16
  18. package/dist/buildAgent-mVuXU_H4.mjs +824 -0
  19. package/dist/commands/acpCommand.mjs +798 -0
  20. package/dist/commands/apiCommand.mjs +14 -16
  21. package/dist/commands/doctorCommand.mjs +5 -5
  22. package/dist/commands/envCommand.mjs +1 -1
  23. package/dist/commands/headlessCommand.mjs +272 -76
  24. package/dist/commands/mcpCommand.mjs +14 -1
  25. package/dist/commands/pluginCommand.mjs +232 -0
  26. package/dist/commands/updateCommand.mjs +10 -9
  27. package/dist/{grepSearch-DJs-cubo-Bm0Y8oS3.mjs → grepSearch-BaYUfIYs-C-fxWc9G.mjs} +3 -3
  28. package/dist/index.mjs +3281 -2322
  29. package/dist/{package-CBaK53NX.mjs → package-BqKSCbso.mjs} +1 -1
  30. package/dist/serve-CuF0I5en.mjs +772 -0
  31. package/dist/store-BG3e54c8.mjs +3 -0
  32. package/dist/{store-DV5s-qni.mjs → store-CvjTpQPs.mjs} +70 -3
  33. package/dist/{terminalSetup-BbJt04ZG.mjs → terminalSetup-DjXAwpDy.mjs} +2 -3
  34. package/dist/{treeSitterEngine-BRbQ9b7I.mjs → treeSitterEngine-QBE3YkmG.mjs} +51 -1
  35. package/dist/{updateChecker-C8xsNY2L.mjs → updateChecker-CQW8bxo6.mjs} +10 -10
  36. package/package.json +48 -43
  37. package/dist/BackgroundAgentManager-DOesheMD.mjs +0 -27171
  38. package/dist/ProxyManager-ByuAHFMq.mjs +0 -3
  39. package/dist/store-DgzCTRkN.mjs +0 -3
  40. package/dist/utils-Cdktpk_k.mjs +0 -158
  41. package/dist/utils-DEizxshI.mjs +0 -3
package/LICENSE CHANGED
@@ -9,7 +9,7 @@ Parameters
9
9
 
10
10
  Licensor: Bike4Mind, Inc.
11
11
 
12
- Licensed Work: Bike4Mind (the lumina5 repository and the components
12
+ Licensed Work: Bike4Mind (this repository and the components
13
13
  distributed under this License).
14
14
  The Licensed Work is (c) 2026 Bike4Mind, Inc.
15
15
 
@@ -33,7 +33,21 @@ Additional Use Grant: You may make production use of the Licensed Work,
33
33
  provider — is expressly permitted and is not a Bike4Mind
34
34
  Service.
35
35
 
36
- Change Date: Four years from the date a given version of the Licensed
36
+ Nominative name use (an additional grant): notwithstanding
37
+ the trademark reservation in the Terms below, you may use
38
+ the name "Bike4Mind" solely to state accurately that your
39
+ product or service is built on, forked from, or derived
40
+ from the Licensed Work. This permission does not extend to
41
+ using "Bike4Mind" as the name of your own product or
42
+ service, or in any manner that implies sponsorship or
43
+ endorsement by Bike4Mind, Inc. Retaining the NOTICE file
44
+ provided with the Licensed Work is encouraged; and when you
45
+ distribute a version on or after its Change Date, retaining
46
+ it is required by the Change License (Apache-2.0, Section
47
+ 4(d)). That obligation attaches to distribution, not to
48
+ hosted use.
49
+
50
+ Change Date: Two years from the date a given version of the Licensed
37
51
  Work is first made publicly available. Each version
38
52
  carries its own Change Date (see Terms). Bike4Mind, Inc.
39
53
  may, at its discretion, designate an earlier Change Date
@@ -57,7 +71,7 @@ works, redistribute, and make non-production use of the Licensed Work. The
57
71
  Licensor may make an Additional Use Grant, above, permitting limited production
58
72
  use.
59
73
 
60
- Effective on the Change Date, or the fourth anniversary of the first publicly
74
+ Effective on the Change Date, or the second anniversary of the first publicly
61
75
  available distribution of a specific version of the Licensed Work under this
62
76
  License, whichever comes first, the Licensor hereby grants you rights under
63
77
  the terms of the Change License, and the rights granted in the paragraph
package/README.md CHANGED
@@ -70,26 +70,74 @@ The CLI will prompt you to authenticate with your Bike4Mind account on first run
70
70
  ### CLI Flags
71
71
 
72
72
  ```bash
73
- b4m [options]
73
+ b4m [options] ["initial prompt"]
74
74
  ```
75
75
 
76
- **Available flags:**
77
- - `--dev` - Point the CLI at the local dev server (`http://localhost:3001`) and remember it
78
- - `--prod` - Point the CLI at Bike4Mind production and remember it
79
- - `--verbose`, `-v` - Show debug logs in console (useful for troubleshooting)
80
- - `--help`, `-h` - Show help information
81
- - `--version`, `-V` - Show CLI version
76
+ A bare positional argument seeds **and** submits the first turn while keeping the interactive UI open (e.g. `b4m "summarize the git log"`).
77
+
78
+ **Environment / API** (see [API Configuration](#api-configuration)):
79
+
80
+ | Flag | Effect |
81
+ | --- | --- |
82
+ | `--dev` | Point the CLI at the local dev server (`http://localhost:3001`) and remember it. Aliases: `--local`, single-dash forms. |
83
+ | `--prod` | Point the CLI at Bike4Mind production and remember it. Alias: `--production`. |
84
+ | `--api-url <url>` | Point the CLI at **any** instance — a self-hosted stack, an AWS deployment, `http://localhost:3000`, etc. Clears cached auth (bound to the old origin) and exits; run `b4m` again to sign in. |
85
+ | `--reset-api` | Reset the API URL to the built-in default and clear auth, then exit. |
86
+
87
+ **Session / behavior:**
88
+
89
+ | Flag | Effect |
90
+ | --- | --- |
91
+ | `--verbose`, `-v` | Show debug logs in the console (also always written to file — see [Debug Logs](#debug-logs)). |
92
+ | `--debug-stream` | Ultra-verbose: log every SSE event (stream-parser debugging). Implies `--verbose`. |
93
+ | `--no-project-config` | Skip loading project-specific config (`.bike4mind/`). |
94
+ | `--add-dir <dir>` | Grant file access to an additional directory. Repeatable. |
95
+ | `--ollama-host <url>` | Add a local Ollama endpoint's models to the picker (e.g. `http://localhost:11434`). |
96
+ | `--no-remote-skills` | Skip fetching remote B4M-web skills for this run (local files only). |
97
+
98
+ **Headless / non-interactive** (for scripts and CI):
99
+
100
+ | Flag | Effect |
101
+ | --- | --- |
102
+ | `-p`, `--prompt <query>` | Run a single query non-interactively and exit. |
103
+ | `--output-format <fmt>` | Output when using `-p`: `text` (default), `json`, or `stream-json` (NDJSON of thoughts/actions/observations). |
104
+ | `--dangerously-skip-permissions` | With `-p`, auto-allow all tool permission prompts. Use with caution in CI/CD. |
105
+
106
+ **Info:**
107
+
108
+ | Flag | Effect |
109
+ | --- | --- |
110
+ | `--help`, `-h` | Show help. |
111
+ | `--version`, `-V` | Show CLI version. |
112
+
113
+ > **Host / `claude`-drop-in flags** (`--mcp-config`, `--strict-mcp-config`, `--append-system-prompt`, `--allowedTools`, `--settings`, `--session-id`, `--resume`) are documented under [Running as a `claude` engine inside a host app](#running-as-a-claude-engine-inside-a-host-app).
82
114
 
83
115
  **Examples:**
84
116
  ```bash
117
+ # Point at a self-hosted stack, then sign in
118
+ b4m --api-url http://localhost:3000
119
+ b4m
120
+
121
+ # One-shot headless query, JSON output
122
+ b4m -p "What is 2+2?" --output-format json
123
+
85
124
  # Run with debug logs visible
86
125
  b4m --verbose
87
126
 
88
- # Check version
89
- b4m --version
127
+ # Seed and submit the first turn, stay interactive
128
+ b4m "review the recent changes"
129
+ ```
90
130
 
91
- # Show help
92
- b4m --help
131
+ ### Subcommands
132
+
133
+ ```bash
134
+ b4m mcp list # List configured MCP servers
135
+ b4m mcp add <name> -- <command...> # Add an MCP server
136
+ b4m mcp remove <name> # Remove an MCP server
137
+ b4m mcp enable <name> | disable <name> # Toggle a server on/off
138
+ b4m mcp serve [--http] [--port <n>] # Expose Bike4Mind itself as an MCP server
139
+ b4m update # Check for and install CLI updates
140
+ b4m doctor # Diagnose the CLI installation (Node, registry, ripgrep, native modules)
93
141
  ```
94
142
 
95
143
  ### Switching environments (`--dev` / `--prod`)
@@ -116,31 +164,64 @@ shown in the startup banner (`🌍 API Environment: …`).
116
164
 
117
165
  ## Commands
118
166
 
119
- While in interactive mode:
167
+ While in interactive mode. This mirrors the registry in `src/config/commands.ts` — run `/help` in a session for the live list (which also includes any custom and project commands).
120
168
 
121
169
  **Authentication:**
122
170
  - `/login` - Authenticate with your B4M account
123
171
  - `/logout` - Clear authentication and sign out
124
172
  - `/whoami` - Show current authenticated user
125
173
 
126
- **Session Management:**
174
+ **Session management:**
127
175
  - `/save <name>` - Save current session
128
- - `/resume` - List and resume saved sessions
129
-
130
- **API Configuration:**
131
- - `/set-api <url>` - Connect to self-hosted Bike4Mind instance
132
- - `/reset-api` - Reset to Bike4Mind main service
176
+ - `/resume` (alias `/sessions`) - List and resume saved sessions
177
+ - `/clear` (alias `/new`) - Start a new session
178
+ - `/compact [instructions]` - Compact the conversation into a new session
179
+ - `/context` - Show context-window usage
180
+ - `/usage` - Show credit usage and balance
181
+
182
+ **API configuration:**
183
+ - `/set-api <url>` - Connect to a self-hosted / custom Bike4Mind instance
184
+ - `/reset-api` - Reset to the Bike4Mind main service
133
185
  - `/api-info` - Show current API configuration
134
186
 
135
- **Tool Permissions:**
136
- - `/trust <tool-name>` - Trust a tool (won't ask permission again)
137
- - `/untrust <tool-name>` - Remove tool from trusted list
138
- - `/trusted` - List all trusted tools
187
+ **Files & checkpoints:**
188
+ - `/add-dir <path>` / `/remove-dir <path>` / `/dirs` - Manage accessible directories
189
+ - `/undo` - Undo the last file change
190
+ - `/checkpoints` - List file restore points
191
+ - `/restore <number>` - Restore files to a checkpoint
192
+ - `/diff [number]` - Diff current state against a checkpoint
193
+ - `/rewind` - Rewind the conversation to a previous point
194
+
195
+ **Tool permissions:**
196
+ - `/trust <tool-name>` / `/untrust <tool-name>` / `/trusted` - Manage auto-approved tools
197
+
198
+ **Sandbox** (OS-level isolation for `bash`):
199
+ - `/sandbox` - Show sandbox status and configuration
200
+ - `/sandbox:enable` / `/sandbox:disable` - Toggle the sandbox
201
+ - `/sandbox:mode <auto-allow|permissions>` - Set enforcement mode
202
+ - `/sandbox:trust-domain <domain> [...]` / `/sandbox:domains` - Manage the network allowlist
203
+ - `/sandbox:violations [count]` / `/sandbox:violations:clear` - Inspect / clear violations
204
+
205
+ **MCP & agents:**
206
+ - `/mcp` (alias `/mcp:list`) - Show MCP server status and connected tools
207
+ - `/agents` (alias `/agents:list`) - List available agents
208
+ - `/agents:new <name>` / `/agents:reload` - Create / reload agent definitions
209
+
210
+ **Custom commands:**
211
+ - `/commands` - List all custom commands
212
+ - `/commands:new <name>` / `/commands:reload` - Create / reload custom commands
213
+
214
+ **Durable workflow** (cross-session continuity):
215
+ - `/workflow [decisions|blockers|handoff|review-gates]` - Workflow overview or a section
216
+ - `/decisions` / `/blockers` / `/review-gates` - Shortcuts for the sections above
217
+ - `/handoff [generate|--local]` - Show or generate a session handoff (`--local` = LLM-free snapshot)
139
218
 
140
219
  **General:**
141
220
  - `/help` - Show help
142
- - `/exit` - Exit CLI
143
- - `/config` - Show configuration
221
+ - `/config` - Open the interactive configuration editor
222
+ - `/project-config` - Show merged project configuration
223
+ - `/terminal-setup` - Configure Shift+Enter for multi-line input
224
+ - `/exit` (alias `/quit`) - Exit the CLI
144
225
 
145
226
  ## Configuration
146
227
 
@@ -164,31 +245,43 @@ The default is baked in at build time from the hosted build environment — ther
164
245
  fallback in source** (open-core, #9306/#9392), so a fresh clone ships empty. A fork publishes its
165
246
  own CLI by setting `B4M_DEFAULT_API_URL` when building — see `tsdown.config.ts`.
166
247
 
248
+ Because that default is injected only at build time, the endpoint is resolved like this:
249
+
250
+ 1. A **custom URL** you set (`--api-url` / `--dev` / `--prod` / `/set-api`) always wins.
251
+ 2. Otherwise, the **build-time default** baked into a published binary.
252
+ 3. Otherwise, when running **from source** (a `pnpm link --global` checkout, `pnpm dev` — no
253
+ `dist/` built), the CLI defaults to the local dev server `http://localhost:3001`. Use
254
+ `--prod` / `--api-url` to point it elsewhere.
255
+ 4. Otherwise (a published, unbranded fork with no baked default), the first `b4m` **prompts you
256
+ to choose a backend**.
257
+
167
258
  **Quick switch between local dev and production:** use the `b4m --dev` / `b4m --prod`
168
259
  launch flags (see [Switching environments](#switching-environments---dev----prod)).
169
260
  They persist your choice and cache auth per-environment. The `/set-api`, `/reset-api`,
170
261
  and `/api-info` commands below operate on the same setting from inside a session.
171
262
 
172
- **For Self-Hosted Instances:**
173
-
174
- If your organization runs a self-hosted Bike4Mind instance, connect to it using:
263
+ **For self-hosted / custom instances:**
175
264
 
176
- ```bash
177
- /set-api https://app.your-instance.example.com
178
- ```
265
+ The CLI can point at **any** Bike4Mind deployment — a self-hosted Docker stack, an AWS deployment, or `http://localhost:3000` — because auth (the OAuth device flow) and the chat API ship in the open core and are served by that instance directly.
179
266
 
180
- To return to the main Bike4Mind service:
267
+ From the launch line (clears auth, then exit — run `b4m` again to sign in):
181
268
 
182
269
  ```bash
183
- /reset-api
270
+ b4m --api-url http://localhost:3000 # local self-host Docker stack
271
+ b4m --api-url https://app.your-instance.example.com
272
+ b4m --reset-api # back to the built-in default
184
273
  ```
185
274
 
186
- Check your current API configuration:
275
+ Or from inside a session:
187
276
 
188
277
  ```bash
189
- /api-info
278
+ /set-api https://app.your-instance.example.com
279
+ /reset-api # return to the default service
280
+ /api-info # show the current API configuration
190
281
  ```
191
282
 
283
+ Auth tokens are cached **per environment**, so switching between hosted and self-host does not force a re-login. For an end-to-end walkthrough (hosted **and** self-host, sign-in, credits, troubleshooting), see the top-level [**BIKE4MIND_CLI.md**](../../BIKE4MIND_CLI.md).
284
+
192
285
  ### Tool API Keys
193
286
 
194
287
  Some built-in tools require API keys to function. Add them to `~/.bike4mind/config.json`:
@@ -297,6 +390,77 @@ You can also run any MCP server via npx or custom executables:
297
390
 
298
391
  **Note:** Internal MCP servers must be built and available in the `b4m-core/packages/mcp/dist/src/` directory. The CLI will automatically find them if you're running from the monorepo. For Docker-based servers, ensure Docker is installed and the image is accessible.
299
392
 
393
+ #### Serve Bike4Mind itself as an MCP server (`b4m mcp serve`)
394
+
395
+ The reverse of adding a server: `b4m mcp serve` exposes your Bike4Mind backend to any MCP client (Claude Desktop, editors, other agents). It advertises eight tools (`list_notebooks`, `get_notebook`, `create_notebook`, `send_message`, `search_knowledge_base`, `list_files`, `get_file`, `generate_sound_effect`) plus four resource templates (`b4m://notebook/{id}`, `b4m://file/{id}`, `b4m://project/{id}`, `b4m://artifact/{id}`), each listable and readable as JSON, and `b4m://agent-quest` - the credential-free manifest for The Open Door, the onboarding quest for agents.
396
+
397
+ ```bash
398
+ b4m mcp serve # stdio (default) - for local clients that spawn the process
399
+ b4m mcp serve --http --port 7000 # stateless streamable HTTP at http://127.0.0.1:7000/mcp
400
+ ```
401
+
402
+ Auth precedence is `--api-key` > `B4M_API_KEY` > the stored login; the endpoint is `B4M_API_URL` > `--api-url` > the configured backend. Tool *listing* needs no credentials; tool *calls* authenticate. Prefer `B4M_API_KEY` over `--api-key` (a flag value shows up in process listings). HTTP mode binds loopback only (`127.0.0.1`, `/mcp` path) and has no per-request auth, so do not expose it off-host. The advertised scopes are the recommended key configuration, not per-route hard gates. See [Serve Bike4Mind as an MCP server](../../BIKE4MIND_CLI.md#serve-bike4mind-as-an-mcp-server-b4m-mcp-serve) for the Claude Desktop config and details.
403
+
404
+ ## Plugins
405
+
406
+ The CLI can load external feature modules at runtime. A plugin is an npm package that adds tools, a system-prompt section, slash commands, and (optionally) WebSocket handlers to the interactive session, without any change to the CLI itself.
407
+
408
+ ```bash
409
+ b4m plugin add @someone/b4m-plugin-foo # install from npm
410
+ b4m plugin add github:user/b4m-plugin-foo # install from GitHub
411
+ b4m plugin add file:/path/to/local/plugin # install a local directory (development/QA)
412
+ b4m plugin list # show installed plugins and their state
413
+ b4m plugin remove foo # uninstall and disable
414
+ ```
415
+
416
+ Plugins install to `~/.bike4mind/plugins/` and are gated by a `features` toggle in your config: `add` enables it for you, and every installed plugin also shows up automatically in the `/config` editor. A newly installed plugin loads the next time you start `b4m`; toggling an already-installed plugin in `/config` hot-reloads it in place.
417
+
418
+ ### Writing a plugin
419
+
420
+ A plugin declares itself with a `b4m-plugin` field in its `package.json`:
421
+
422
+ ```json
423
+ {
424
+ "name": "@someone/b4m-plugin-foo",
425
+ "type": "module",
426
+ "b4m-plugin": { "entry": "./dist/index.js", "configKey": "foo" }
427
+ }
428
+ ```
429
+
430
+ `entry` (required) is the module the CLI imports, resolved inside the package. `configKey` (optional, defaults to the package name) is the `features` key that gates the plugin.
431
+
432
+ The entry default-exports a factory. It receives a context (currently `{ logger }`, with `debug/info/warn/error` methods that write to the CLI debug log) and returns the module object:
433
+
434
+ ```js
435
+ export default ctx => ({
436
+ name: 'foo', // unique module name
437
+ description: 'What this plugin does',
438
+ getTools: () => [
439
+ {
440
+ toolFn: async args => 'result', // invoked when the agent calls the tool
441
+ toolSchema: {
442
+ name: 'foo_tool',
443
+ description: 'What the tool does',
444
+ parameters: { type: 'object', properties: {} }, // JSON Schema
445
+ },
446
+ },
447
+ ],
448
+ getSystemPromptSection: () => 'Extra system-prompt text (or an empty string)',
449
+ getCommands: () => [{ name: 'foo', description: '...', execute: args => {} }], // optional
450
+ registerWsHandlers: wsManager => {}, // optional
451
+ dispose: () => {}, // optional, runs on exit/disable
452
+ });
453
+ ```
454
+
455
+ Tool objects are written out structurally as shown - plugin packages cannot import `@bike4mind/*` packages at runtime (those are bundled into the CLI binary and are not on npm in a compatible form). See `test/fixtures/b4m-plugin-example/` in this package for a complete working plugin.
456
+
457
+ ### Limitations and trust
458
+
459
+ - **Plugins run in-process with your privileges.** Installing one runs its npm install scripts, and enabling it executes its code inside the CLI, same as configuring an MCP server command. Only install plugins you trust.
460
+ - **Interactive mode only.** Headless (`b4m -p`) and ACP sessions do not load feature modules or plugins today.
461
+ - **Downgrade caveat:** CLI versions without plugin support strip plugin keys from the config `features` map on their next save; re-enable after upgrading again.
462
+ - A plugin that fails to load (bad manifest, import error, malformed tools) is skipped with a warning at startup - it never blocks the CLI. `b4m plugin list` shows the reason.
463
+
300
464
  ## Git-Aware Code Search
301
465
 
302
466
  The CLI includes a `recent_changes` tool that uses git history to find recently modified files. This significantly speeds up debugging by narrowing the search space to recently changed code.
@@ -459,6 +623,11 @@ b4m --version
459
623
 
460
624
  After step 4, running `b4m` anywhere on your system executes this working tree. Re-run `pnpm link --global` if you move or rename the repo.
461
625
 
626
+ > **Which backend does a linked checkout talk to?** A source/linked run has no build-time
627
+ > default baked in, so `b4m` defaults to the local dev server (`http://localhost:3001`) — start
628
+ > your local stack, or point it elsewhere with `b4m --prod` / `b4m --api-url <url>`. See
629
+ > [API Configuration](#api-configuration) for the full resolution order.
630
+
462
631
  ### Editing CLI source (`packages/cli/src/`)
463
632
 
464
633
  Just run `b4m`. The bin's `tsx` fallback picks up your edits on the next invocation — no build step needed.
@@ -547,7 +716,7 @@ packages/cli/
547
716
  │ │ └── ConfigStore.ts
548
717
  │ └── index.tsx # Main entry point
549
718
  └── bin/
550
- └── bike4mind-cli.js # Executable
719
+ └── bike4mind-cli.mjs # Executable (source/dist auto-detect + flag parsing)
551
720
  ```
552
721
 
553
722
  ## Dependencies
@@ -1,19 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
 
4
- // This is the executable entry point for the CLI
5
- // In development, it will use tsx to run the TypeScript directly
6
- // In production, it will run the compiled JavaScript
4
+ // Executable entry point for the CLI: tsx runs the TypeScript source in dev,
5
+ // the compiled JavaScript in production.
7
6
 
8
- // Suppress punycode deprecation warning from dependencies
9
- // This is a known issue with older npm packages using Node's built-in punycode
7
+ // Suppress punycode deprecation warning from older deps using Node's built-in punycode.
10
8
  process.removeAllListeners('warning');
11
9
  process.on('warning', (warning) => {
12
- // Only suppress punycode deprecation warnings
13
10
  if (warning.name === 'DeprecationWarning' && warning.message.includes('punycode')) {
14
11
  return;
15
12
  }
16
- // Show all other warnings
17
13
  console.warn(warning);
18
14
  });
19
15
 
@@ -37,7 +33,7 @@ const { version: cliVersion } = require('../package.json');
37
33
  // Intercept these BEFORE yargs parses argv. They're accepted with either a
38
34
  // single or double dash (e.g. `b4m -prod` and `b4m --prod` both work), but
39
35
  // single-dash multi-char tokens would otherwise be split into clustered short
40
- // flags by yargs (`-prod` `-p -r -o -d`, colliding with -p/--prompt). We pull
36
+ // flags by yargs (`-prod` -> `-p -r -o -d`, colliding with -p/--prompt). We pull
41
37
  // them out here, record the target, and strip them so yargs sees a clean argv.
42
38
  const ENV_FLAG_MAP = {
43
39
  '--dev': 'dev', '-dev': 'dev', '--local': 'dev', '-local': 'dev',
@@ -57,14 +53,13 @@ let envTarget = null;
57
53
  process.argv = [process.argv[0], process.argv[1], ...cleaned];
58
54
  }
59
55
 
60
- // Parse CLI arguments
61
56
  const argv = await yargs(hideBin(process.argv))
62
57
  // --dev / --prod are declared here ONLY so they appear in `--help`. The actual
63
- // handling is the pre-yargs argv interception above these yargs-side values
58
+ // handling is the pre-yargs argv interception above - these yargs-side values
64
59
  // (`argv.dev` / `argv.prod`) are never read.
65
60
  .option('dev', {
66
61
  type: 'boolean',
67
- description: 'Point the CLI at the local dev server (http://localhost:3001) and remember it',
62
+ description: 'Point the CLI at the local dev server (http://localhost:3000) and remember it',
68
63
  })
69
64
  .option('prod', {
70
65
  type: 'boolean',
@@ -107,13 +102,17 @@ const argv = await yargs(hideBin(process.argv))
107
102
  description: 'When using -p, auto-allow all tool permission prompts (use with caution in CI/CD)',
108
103
  default: false,
109
104
  })
105
+ .option('permission-policy', {
106
+ type: 'string',
107
+ description: 'When using -p, path to a JSON permission policy for unattended runs (allow/deny/risk rules)',
108
+ })
110
109
  .option('ollama-host', {
111
110
  type: 'string',
112
111
  description: 'Add local Ollama models to the model picker (e.g. http://localhost:11434)',
113
112
  })
114
- // ─── claude-compatible flags (host drop-in masquerade) ──────────────────────
115
- // Declared so a host's claude launch flags parse into named options instead of
116
- // leaking into the positional `argv._` task. See packages/cli README "host app".
113
+ // claude-compatible flags (host drop-in masquerade). Declared so a host's
114
+ // claude launch flags parse into named options instead of leaking into the
115
+ // positional `argv._` task. See packages/cli README "host app".
117
116
  .option('mcp-config', {
118
117
  type: 'string',
119
118
  description: 'Path to a JSON file of MCP servers to inject ({ "mcpServers": {...} })',
@@ -198,10 +197,62 @@ const argv = await yargs(hideBin(process.argv))
198
197
  }, async () => {
199
198
  // Handled by external command handler
200
199
  })
201
- .demandCommand(1, 'You must provide a subcommand (list, add, remove, enable, disable)');
200
+ .command('serve', 'Run Bike4Mind as an MCP server (stdio by default, or --http)', (yargs) => {
201
+ return yargs
202
+ .option('http', {
203
+ type: 'boolean',
204
+ description: 'Serve over streamable HTTP instead of stdio',
205
+ default: false,
206
+ })
207
+ .option('port', {
208
+ type: 'number',
209
+ description: 'Port for --http mode',
210
+ default: 7000,
211
+ })
212
+ .option('api-key', {
213
+ type: 'string',
214
+ description: 'Bike4Mind API key (overrides B4M_API_KEY and the stored login). Visible in process listings; prefer B4M_API_KEY',
215
+ })
216
+ .option('api-url', {
217
+ type: 'string',
218
+ description: 'Bike4Mind API URL (overrides B4M_API_URL and the configured backend)',
219
+ })
220
+ .example('b4m mcp serve', 'Serve over stdio for Claude Desktop and other local clients')
221
+ .example('b4m mcp serve --http --port 7000', 'Serve over streamable HTTP on port 7000');
222
+ }, async () => {
223
+ // Handled by external command handler
224
+ })
225
+ .demandCommand(1, 'You must provide a subcommand (list, add, remove, enable, disable, serve)');
226
+ })
227
+ .command('plugin', 'Manage B4M feature-module plugins', (yargs) => {
228
+ return yargs
229
+ .command('list', 'List installed plugins', {}, async () => {
230
+ // Handled by external command handler
231
+ })
232
+ .command('add <spec>', 'Install a plugin package', (yargs) => {
233
+ return yargs
234
+ .positional('spec', {
235
+ type: 'string',
236
+ describe: 'npm package, @scope/package, github:user/repo, or file:<path>',
237
+ })
238
+ .example('b4m plugin add @someone/b4m-plugin-foo', 'Install from npm')
239
+ .example('b4m plugin add github:user/b4m-plugin-foo', 'Install from GitHub');
240
+ }, async () => {
241
+ // Handled by external command handler
242
+ })
243
+ .command('remove <name>', 'Uninstall a plugin', (yargs) => {
244
+ return yargs.positional('name', {
245
+ type: 'string',
246
+ describe: 'Plugin name (config key, package name, or short name)',
247
+ });
248
+ }, async () => {
249
+ // Handled by external command handler
250
+ })
251
+ .demandCommand(1, 'You must provide a subcommand (list, add, remove)');
202
252
  })
203
253
  .command('update', 'Check for and install CLI updates')
204
254
  .command('doctor', 'Run diagnostic checks on CLI installation')
255
+ .command('acp', 'Run as an Agent Client Protocol (ACP) stdio server for editors like Zed')
205
256
  .help()
206
257
  .alias('help', 'h')
207
258
  .version(cliVersion)
@@ -238,8 +289,8 @@ if (argv['no-remote-skills']) {
238
289
  process.env.B4M_NO_REMOTE_SKILLS = '1';
239
290
  }
240
291
 
241
- // ─── claude-compatible flags B4M_* env (read by src/index.tsx init) ─────────
242
- // Mirrors the established binenvinit channel (cf. --add-dir B4M_ADDITIONAL_DIRS).
292
+ // claude-compatible flags -> B4M_* env (read by src/index.tsx init).
293
+ // Mirrors the established bin->env->init channel (cf. --add-dir -> B4M_ADDITIONAL_DIRS).
243
294
  if (argv['mcp-config']) {
244
295
  process.env.B4M_MCP_CONFIG_FILE = resolve(argv['mcp-config']);
245
296
  }
@@ -266,7 +317,7 @@ if (argv.resume) {
266
317
  }
267
318
  // Positional task (claude `<prompt>` form): seeds AND submits turn 1, stays interactive.
268
319
  // Only when it's not a known subcommand and headless -p wasn't used.
269
- const KNOWN_SUBCOMMANDS = new Set(['mcp', 'update', 'doctor']);
320
+ const KNOWN_SUBCOMMANDS = new Set(['mcp', 'plugin', 'update', 'doctor', 'acp']);
270
321
  if (argv.prompt === undefined && argv._.length > 0 && !KNOWN_SUBCOMMANDS.has(String(argv._[0]))) {
271
322
  process.env.B4M_INITIAL_PROMPT = String(argv._[0]);
272
323
  }
@@ -284,10 +335,21 @@ const hasDist = existsSync(distPath);
284
335
  const isDev = process.env.NODE_ENV === 'development' ||
285
336
  (!hasDist && hasSource);
286
337
 
338
+ // Signal source mode to the app so endpoint resolution can default an
339
+ // otherwise-unconfigured run to the local dev server (build-time brand
340
+ // defaults are never injected into a source run). Set before any command
341
+ // dispatch below so --reset-api messaging and the app both observe it.
342
+ // See resolveApiEndpoint() / isSourceMode() in src/utils/apiUrl.ts.
343
+ if (isDev) {
344
+ process.env.B4M_SOURCE_MODE = '1';
345
+ }
346
+
287
347
  // Handle --api-url / --reset-api flags
288
348
  // These mutate ~/.bike4mind/config.json and exit before any auth flow runs,
289
349
  // so devs can recover from a misconfigured customUrl without editing JSON.
290
- if (argv['reset-api'] || argv['api-url'] !== undefined) {
350
+ // `mcp serve` has its own ephemeral --api-url (points that one server at a
351
+ // backend without touching config), so skip the mutate-and-exit path for it.
352
+ if ((argv['reset-api'] || argv['api-url'] !== undefined) && argv._[0] !== 'mcp') {
291
353
  try {
292
354
  let handleApiCommand;
293
355
 
@@ -360,6 +422,7 @@ if (argv.prompt !== undefined) {
360
422
  dangerouslySkipPermissions: argv['dangerously-skip-permissions'] || false,
361
423
  verbose: argv.verbose || false,
362
424
  addDirs: rawAddDirs.map(d => resolve(d)),
425
+ permissionPolicyPath: argv['permission-policy'] ? resolve(argv['permission-policy']) : undefined,
363
426
  });
364
427
  // handleHeadlessCommand calls process.exit internally, but handle the case it doesn't
365
428
  process.exit(0);
@@ -397,6 +460,31 @@ if (argv._[0] === 'mcp') {
397
460
  }
398
461
  }
399
462
 
463
+ // Handle plugin subcommands (external commands)
464
+ if (argv._[0] === 'plugin') {
465
+ const pluginSubcommand = argv._[1];
466
+
467
+ try {
468
+ let handlePluginCommand;
469
+
470
+ if (isDev) {
471
+ const { register } = require('tsx/esm/api');
472
+ register();
473
+ const module = await import('../src/commands/pluginCommand.ts');
474
+ handlePluginCommand = module.handlePluginCommand;
475
+ } else {
476
+ const module = await import('../dist/commands/pluginCommand.mjs');
477
+ handlePluginCommand = module.handlePluginCommand;
478
+ }
479
+
480
+ await handlePluginCommand(pluginSubcommand, argv);
481
+ process.exit(0);
482
+ } catch (error) {
483
+ console.error('Error:', error.message);
484
+ process.exit(1);
485
+ }
486
+ }
487
+
400
488
  // Handle update command (external command)
401
489
  if (argv._[0] === 'update') {
402
490
  try {
@@ -420,6 +508,31 @@ if (argv._[0] === 'update') {
420
508
  }
421
509
  }
422
510
 
511
+ // Handle ACP server command (external command)
512
+ // stdio is the JSON-RPC transport, so this never returns until the client
513
+ // disconnects. Errors go to stderr to keep stdout as a clean protocol stream.
514
+ if (argv._[0] === 'acp') {
515
+ try {
516
+ let handleAcpCommand;
517
+
518
+ if (isDev) {
519
+ const { register } = require('tsx/esm/api');
520
+ register();
521
+ const module = await import('../src/commands/acpCommand.ts');
522
+ handleAcpCommand = module.handleAcpCommand;
523
+ } else {
524
+ const module = await import('../dist/commands/acpCommand.mjs');
525
+ handleAcpCommand = module.handleAcpCommand;
526
+ }
527
+
528
+ await handleAcpCommand({ verbose: argv.verbose || false, version: cliVersion });
529
+ process.exit(0);
530
+ } catch (error) {
531
+ process.stderr.write(`Error: ${error.message}\n`);
532
+ process.exit(1);
533
+ }
534
+ }
535
+
423
536
  // Handle doctor command (external command)
424
537
  if (argv._[0] === 'doctor') {
425
538
  try {
@@ -444,14 +557,14 @@ if (argv._[0] === 'doctor') {
444
557
  }
445
558
 
446
559
  if (isDev) {
447
- // Show dev mode indicator for developers
448
- console.log('🔧 Running in development mode (using TypeScript source)\n');
560
+ // Note: this is about *how the CLI runs* (unbuilt TypeScript source), which is
561
+ // distinct from the `--dev` flag (which selects the local dev *backend*).
562
+ console.log('🔧 Running from TypeScript source (no dist build)\n');
449
563
  // Development: use tsx to run TypeScript
450
564
  try {
451
565
  const { register } = require('tsx/esm/api');
452
566
  register();
453
567
 
454
- // Import and run the main app
455
568
  await import(join(__dirname, '../src/index.tsx'));
456
569
  } catch (error) {
457
570
  console.error('Failed to start CLI in development mode:', error);
@@ -463,7 +576,7 @@ if (isDev) {
463
576
  try {
464
577
  // Auto-update on launch (consent-first): when a newer version is available
465
578
  // on a writable global prefix, install + re-exec into it BEFORE importing
466
- // the code-split app running an install while dist/index.mjs is loaded
579
+ // the code-split app - running an install while dist/index.mjs is loaded
467
580
  // would crash it. On the default 'ask' preference this prompts the user
468
581
  // (Update once / Always / Skip / Never); 'auto' installs silently, 'never'
469
582
  // does nothing. Wrapped so the updater can never block launching the CLI.
@@ -471,7 +584,7 @@ if (isDev) {
471
584
  const { maybeAutoUpdateOnLaunch } = await import('../dist/commands/updateCommand.mjs');
472
585
  await maybeAutoUpdateOnLaunch();
473
586
  } catch {
474
- // Updater is best-effort fall through to the current version.
587
+ // Updater is best-effort - fall through to the current version.
475
588
  }
476
589
 
477
590
  await import(join(__dirname, '../dist/index.mjs'));