@bike4mind/cli 3.3.0 → 4.0.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.
- package/README.md +139 -37
- package/bin/bike4mind-cli.mjs +55 -1
- package/bin/hearth-hook.mjs +53 -7
- package/dist/{AgentHistoryStore-CLidbOHG.mjs → AgentHistoryStore-DgqmcVLN.mjs} +5597 -5568
- package/dist/ApiClient-_JrM_xWw.mjs +133 -0
- package/dist/{ConfigStore-DUATzLxB.mjs → ConfigStore-BwqsQ0U9.mjs} +1409 -189
- package/dist/RemoteSkillSource-qTvRkaNA.mjs +455 -0
- package/dist/{WorkItemsClient-BSoOblGp.mjs → WorkItemsClient-CERTDrhT.mjs} +1 -1
- package/dist/{buildAgent-DyZWK0LW.mjs → buildToolSearch-D9GEK91u.mjs} +406 -47
- package/dist/commands/acpCommand.mjs +13 -13
- package/dist/commands/apiCommand.mjs +1 -1
- package/dist/commands/doctorCommand.mjs +1 -1
- package/dist/commands/envCommand.mjs +1 -1
- package/dist/commands/headlessCommand.mjs +5 -4
- package/dist/commands/mcpCommand.mjs +4 -3
- package/dist/commands/pluginCommand.mjs +1 -1
- package/dist/commands/skillsCommand.mjs +259 -0
- package/dist/commands/updateCommand.mjs +1 -1
- package/dist/{dist-DPP7UmK9.mjs → dist-Ul2qwn5x.mjs} +17 -132
- package/dist/index.mjs +171 -48
- package/dist/{package-DYPVrada.mjs → package-CQaam4Dq.mjs} +1 -1
- package/dist/{serve-w6v3rZiR.mjs → serve-CFxUhjml.mjs} +657 -27
- package/dist/store-BYeE0shc.mjs +2 -0
- package/dist/{store-BxmPEI3v.mjs → store-BbvSi3KK.mjs} +26 -2
- package/package.json +11 -10
- package/dist/store-BkMur6uk.mjs +0 -2
package/README.md
CHANGED
|
@@ -43,10 +43,12 @@ pnpm install
|
|
|
43
43
|
The CLI uses native dependencies (`better-sqlite3` for image caching, `sharp` for image processing) that require compilation. These should build automatically during installation, but if you encounter errors:
|
|
44
44
|
|
|
45
45
|
**Prerequisites:**
|
|
46
|
+
|
|
46
47
|
- Python 3 (required by node-gyp)
|
|
47
48
|
- C++ compiler (Xcode Command Line Tools on macOS, build-essential on Linux, Visual Studio on Windows)
|
|
48
49
|
|
|
49
50
|
**Manual rebuild if needed:**
|
|
51
|
+
|
|
50
52
|
```bash
|
|
51
53
|
# If you see "Could not locate the bindings file" errors
|
|
52
54
|
pnpm rebuild better-sqlite3
|
|
@@ -77,42 +79,43 @@ A bare positional argument seeds **and** submits the first turn while keeping th
|
|
|
77
79
|
|
|
78
80
|
**Environment / API** (see [API Configuration](#api-configuration)):
|
|
79
81
|
|
|
80
|
-
| Flag
|
|
81
|
-
|
|
|
82
|
-
| `--dev`
|
|
83
|
-
| `--prod`
|
|
82
|
+
| Flag | Effect |
|
|
83
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `--dev` | Point the CLI at the local dev server (`http://localhost:3001`) and remember it. Aliases: `--local`, single-dash forms. |
|
|
85
|
+
| `--prod` | Point the CLI at Bike4Mind production and remember it. Alias: `--production`. |
|
|
84
86
|
| `--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`
|
|
87
|
+
| `--reset-api` | Reset the API URL to the built-in default and clear auth, then exit. |
|
|
86
88
|
|
|
87
89
|
**Session / behavior:**
|
|
88
90
|
|
|
89
|
-
| Flag
|
|
90
|
-
|
|
|
91
|
-
| `--verbose`, `-v`
|
|
92
|
-
| `--debug-stream`
|
|
93
|
-
| `--no-project-config` | Skip loading project-specific config (`.bike4mind/`).
|
|
94
|
-
| `--add-dir <dir>`
|
|
95
|
-
| `--ollama-host <url>` | Add a local Ollama endpoint's models to the picker (e.g. `http://localhost:11434`).
|
|
96
|
-
| `--no-remote-skills`
|
|
91
|
+
| Flag | Effect |
|
|
92
|
+
| --------------------- | --------------------------------------------------------------------------------------------- |
|
|
93
|
+
| `--verbose`, `-v` | Show debug logs in the console (also always written to file — see [Debug Logs](#debug-logs)). |
|
|
94
|
+
| `--debug-stream` | Ultra-verbose: log every SSE event (stream-parser debugging). Implies `--verbose`. |
|
|
95
|
+
| `--no-project-config` | Skip loading project-specific config (`.bike4mind/`). |
|
|
96
|
+
| `--add-dir <dir>` | Grant file access to an additional directory. Repeatable. |
|
|
97
|
+
| `--ollama-host <url>` | Add a local Ollama endpoint's models to the picker (e.g. `http://localhost:11434`). |
|
|
98
|
+
| `--no-remote-skills` | Skip fetching remote B4M-web skills for this run (local files only). |
|
|
97
99
|
|
|
98
100
|
**Headless / non-interactive** (for scripts and CI):
|
|
99
101
|
|
|
100
|
-
| Flag
|
|
101
|
-
|
|
|
102
|
-
| `-p`, `--prompt <query>`
|
|
103
|
-
| `--output-format <fmt>`
|
|
104
|
-
| `--dangerously-skip-permissions` | With `-p`, auto-allow all tool permission prompts. Use with caution in CI/CD.
|
|
102
|
+
| Flag | Effect |
|
|
103
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| `-p`, `--prompt <query>` | Run a single query non-interactively and exit. |
|
|
105
|
+
| `--output-format <fmt>` | Output when using `-p`: `text` (default), `json`, or `stream-json` (NDJSON of thoughts/actions/observations). |
|
|
106
|
+
| `--dangerously-skip-permissions` | With `-p`, auto-allow all tool permission prompts. Use with caution in CI/CD. |
|
|
105
107
|
|
|
106
108
|
**Info:**
|
|
107
109
|
|
|
108
|
-
| Flag
|
|
109
|
-
|
|
|
110
|
-
| `--help`, `-h`
|
|
110
|
+
| Flag | Effect |
|
|
111
|
+
| ----------------- | ----------------- |
|
|
112
|
+
| `--help`, `-h` | Show help. |
|
|
111
113
|
| `--version`, `-V` | Show CLI version. |
|
|
112
114
|
|
|
113
115
|
> **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).
|
|
114
116
|
|
|
115
117
|
**Examples:**
|
|
118
|
+
|
|
116
119
|
```bash
|
|
117
120
|
# Point at a self-hosted stack, then sign in
|
|
118
121
|
b4m --api-url http://localhost:3000
|
|
@@ -138,8 +141,31 @@ b4m mcp enable <name> | disable <name> # Toggle a server on/off
|
|
|
138
141
|
b4m mcp serve [--http] [--port <n>] # Expose Bike4Mind itself as an MCP server
|
|
139
142
|
b4m update # Check for and install CLI updates
|
|
140
143
|
b4m doctor # Diagnose the CLI installation (Node, registry, ripgrep, native modules)
|
|
144
|
+
b4m skills push <path> # Create a skill from a folder, SKILL.md, or commands/*.md
|
|
145
|
+
b4m skills push <path> --update # Update your skill with the same name without a prompt
|
|
141
146
|
```
|
|
142
147
|
|
|
148
|
+
### Pushing a local skill
|
|
149
|
+
|
|
150
|
+
Log in with `b4m /login`, then run `b4m skills push ~/.claude/skills/my-skill`.
|
|
151
|
+
Only the Markdown instructions are uploaded; supporting files and unsupported
|
|
152
|
+
frontmatter (including `allowed-tools`) are reported and skipped. If no description
|
|
153
|
+
is present, the first non-empty body line supplies it.
|
|
154
|
+
|
|
155
|
+
An existing skill prompts before updating. For scripts without a terminal, pass
|
|
156
|
+
`--update` explicitly. Removing `argument-hint` from the file clears the stored hint
|
|
157
|
+
on update. A Skill Market import cannot be overwritten; rename the local skill or
|
|
158
|
+
delete the import first.
|
|
159
|
+
|
|
160
|
+
An export from another Skill Market author requires separate consent because the
|
|
161
|
+
copy becomes your own trusted skill. Prefer importing it from the market to retain
|
|
162
|
+
attribution and updates; `--yes` acknowledges this warning when pushing a copy.
|
|
163
|
+
|
|
164
|
+
If push reports missing or expired authentication, run `b4m /login` again. If a
|
|
165
|
+
non-interactive push stops at a name conflict, use `--update` only when you intend
|
|
166
|
+
to replace that skill. Server validation errors are printed without creating or
|
|
167
|
+
updating the skill.
|
|
168
|
+
|
|
143
169
|
### Switching environments (`--dev` / `--prod`)
|
|
144
170
|
|
|
145
171
|
Flip which backend the CLI talks to without editing config by hand:
|
|
@@ -162,16 +188,28 @@ shown in the startup banner (`🌍 API Environment: …`).
|
|
|
162
188
|
> For a one-off custom/self-hosted URL, use the in-session `/set-api <url>` command
|
|
163
189
|
> (see [API Configuration](#api-configuration) below).
|
|
164
190
|
|
|
191
|
+
### Typing while the agent works
|
|
192
|
+
|
|
193
|
+
You can keep typing while a turn is running. Nothing you send is dropped, and a second turn never starts in parallel with the first.
|
|
194
|
+
|
|
195
|
+
- **Enter** steers the running turn. The message is added to the agent's next model request, which is sent after the current batch of tool calls finishes. If the agent has just answered and has iterations left, it reopens to take the message into account. A message the turn never reaches runs as the next turn.
|
|
196
|
+
- **Tab** queues the message as a follow-up. It runs as a new turn once the current one ends. Everything still queued at that point runs together as a single turn, in the order you sent it.
|
|
197
|
+
- **Esc** cancels the turn and moves any queued messages back into the input, where you can edit and resend them or clear them with Ctrl+C. Any message you send while the cancelled turn is still shutting down runs once the turn has stopped.
|
|
198
|
+
|
|
199
|
+
Queued messages appear above the input. Follow-ups are labelled `[next turn]`.
|
|
200
|
+
|
|
165
201
|
## Commands
|
|
166
202
|
|
|
167
203
|
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).
|
|
168
204
|
|
|
169
205
|
**Authentication:**
|
|
206
|
+
|
|
170
207
|
- `/login` - Authenticate with your B4M account
|
|
171
208
|
- `/logout` - Clear authentication and sign out
|
|
172
209
|
- `/whoami` - Show current authenticated user
|
|
173
210
|
|
|
174
211
|
**Session management:**
|
|
212
|
+
|
|
175
213
|
- `/save <name>` - Save current session
|
|
176
214
|
- `/resume` (alias `/sessions`) - List and resume saved sessions
|
|
177
215
|
- `/clear` (alias `/new`) - Start a new session
|
|
@@ -180,11 +218,13 @@ While in interactive mode. This mirrors the registry in `src/config/commands.ts`
|
|
|
180
218
|
- `/usage` - Show credit usage and balance
|
|
181
219
|
|
|
182
220
|
**API configuration:**
|
|
221
|
+
|
|
183
222
|
- `/set-api <url>` - Connect to a self-hosted / custom Bike4Mind instance
|
|
184
223
|
- `/reset-api` - Reset to the Bike4Mind main service
|
|
185
224
|
- `/api-info` - Show current API configuration
|
|
186
225
|
|
|
187
226
|
**Files & checkpoints:**
|
|
227
|
+
|
|
188
228
|
- `/add-dir <path>` / `/remove-dir <path>` / `/dirs` - Manage accessible directories
|
|
189
229
|
- `/undo` - Undo the last file change
|
|
190
230
|
- `/checkpoints` - List file restore points
|
|
@@ -193,9 +233,11 @@ While in interactive mode. This mirrors the registry in `src/config/commands.ts`
|
|
|
193
233
|
- `/rewind` - Rewind the conversation to a previous point
|
|
194
234
|
|
|
195
235
|
**Tool permissions:**
|
|
236
|
+
|
|
196
237
|
- `/trust <tool-name>` / `/untrust <tool-name>` / `/trusted` - Manage auto-approved tools
|
|
197
238
|
|
|
198
239
|
**Sandbox** (OS-level isolation for `bash`):
|
|
240
|
+
|
|
199
241
|
- `/sandbox` - Show sandbox status and configuration
|
|
200
242
|
- `/sandbox:enable` / `/sandbox:disable` - Toggle the sandbox
|
|
201
243
|
- `/sandbox:mode <auto-allow|permissions>` - Set enforcement mode
|
|
@@ -204,20 +246,24 @@ While in interactive mode. This mirrors the registry in `src/config/commands.ts`
|
|
|
204
246
|
- `/sandbox:violations [count]` / `/sandbox:violations:clear` - Inspect / clear violations
|
|
205
247
|
|
|
206
248
|
**MCP & agents:**
|
|
249
|
+
|
|
207
250
|
- `/mcp` (alias `/mcp:list`) - Show MCP server status and connected tools
|
|
208
251
|
- `/agents` (alias `/agents:list`) - List available agents
|
|
209
252
|
- `/agents:new <name>` / `/agents:reload` - Create / reload agent definitions
|
|
210
253
|
|
|
211
254
|
**Custom commands:**
|
|
255
|
+
|
|
212
256
|
- `/commands` - List all custom commands
|
|
213
257
|
- `/commands:new <name>` / `/commands:reload` - Create / reload custom commands
|
|
214
258
|
|
|
215
259
|
**Durable workflow** (cross-session continuity):
|
|
260
|
+
|
|
216
261
|
- `/workflow [decisions|blockers|handoff|review-gates]` - Workflow overview or a section
|
|
217
262
|
- `/decisions` / `/blockers` / `/review-gates` - Shortcuts for the sections above
|
|
218
263
|
- `/handoff [generate|--local]` - Show or generate a session handoff (`--local` = LLM-free snapshot)
|
|
219
264
|
|
|
220
265
|
**General:**
|
|
266
|
+
|
|
221
267
|
- `/help` - Show help
|
|
222
268
|
- `/config` - Open the interactive configuration editor
|
|
223
269
|
- `/project-config` - Show merged project configuration
|
|
@@ -304,6 +350,7 @@ export SERPER_API_KEY="your-key-here"
|
|
|
304
350
|
```
|
|
305
351
|
|
|
306
352
|
**Available Tools:**
|
|
353
|
+
|
|
307
354
|
- ✅ `dice_roll` - No API key needed
|
|
308
355
|
- ✅ `math_evaluate` - No API key needed
|
|
309
356
|
- ✅ `current_datetime` - No API key needed
|
|
@@ -315,6 +362,7 @@ export SERPER_API_KEY="your-key-here"
|
|
|
315
362
|
- 🔑 `deep_research` - Requires `toolApiKeys.serper`
|
|
316
363
|
|
|
317
364
|
**Get API Keys:**
|
|
365
|
+
|
|
318
366
|
- OpenWeather API: https://openweathermap.org/api
|
|
319
367
|
- Serper API: https://serper.dev/
|
|
320
368
|
|
|
@@ -361,6 +409,7 @@ You can run MCP servers in Docker containers for isolation and portability:
|
|
|
361
409
|
```
|
|
362
410
|
|
|
363
411
|
**Docker Configuration Notes:**
|
|
412
|
+
|
|
364
413
|
- The `-i` flag is required for stdin communication with the MCP server
|
|
365
414
|
- Use `-e VAR_NAME` (without value) to pass a variable through to the container. It must be listed in this server's `env` block: the MCP child is started with those variables plus the MCP SDK's own defaults (`PATH`, `HOME`, `SHELL`, `TERM`, `USER`), never with your whole shell environment
|
|
366
415
|
- Keys that would have the runtime load code before the server starts - `NODE_*`, `LD_*`, `DYLD_*`, `ELECTRON_RUN_AS_NODE` - are refused and logged. Everything else in `env` is passed through, so `HTTPS_PROXY`, `NO_PROXY` and a `PATH` your wrapper needs all work here
|
|
@@ -387,6 +436,7 @@ You can also run any MCP server via npx or custom executables:
|
|
|
387
436
|
```
|
|
388
437
|
|
|
389
438
|
**Available Built-in MCP Servers:**
|
|
439
|
+
|
|
390
440
|
- 🔧 **GitHub** - Repository management, issues, PRs (requires `GITHUB_ACCESS_TOKEN`)
|
|
391
441
|
- 🔧 **LinkedIn** - Post management, analytics (requires `LINKEDIN_ACCESS_TOKEN`, `COMPANY_NAME`)
|
|
392
442
|
- 🔧 **Atlassian** - Jira/Confluence integration (requires `ATLASSIAN_ACCESS_TOKEN`, `ATLASSIAN_CLOUD_ID`, `ATLASSIAN_SITE_URL`)
|
|
@@ -395,16 +445,32 @@ You can also run any MCP server via npx or custom executables:
|
|
|
395
445
|
|
|
396
446
|
#### Serve Bike4Mind itself as an MCP server (`b4m mcp serve`)
|
|
397
447
|
|
|
398
|
-
The reverse of adding a server: `b4m mcp serve` exposes your Bike4Mind backend to any MCP client (Claude Desktop, editors, other agents). It advertises
|
|
448
|
+
The reverse of adding a server: `b4m mcp serve` exposes your Bike4Mind backend to any MCP client (Claude Desktop, editors, other agents). It advertises twenty-six tools (`list_notebooks`, `get_notebook`, `create_notebook`, `rename_notebook`, `clone_notebook`, `delete_notebook`, `list_projects`, `get_project`, `create_project`, `send_message`, `search_knowledge_base`, `list_lakes`, `list_files`, `get_file`, `generate_sound_effect`, `text_to_speech`, `generate_image`, `create_artifact`, `publish_artifact`, `bob_read_site`, `bob_compare_sites`, `bob_recheck`, `get_bob_report`, `get_bob_comparison`, `list_bob_reports`, `bob_runs_left`) 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.
|
|
399
449
|
|
|
400
450
|
To get answers grounded in a data lake, call `list_lakes`, then `create_notebook { dataLakeId }` with a lake's id, then `send_message { notebookId }` on that notebook: the result carries the `reply` plus the `citables` it was grounded in. `list_lakes` needs a key with `datalake:read` (and data lakes enabled on the instance).
|
|
401
451
|
|
|
452
|
+
The seven Bob tools read websites with Bob on an instance that serves it. `bob_read_site` and `bob_compare_sites` spend the key owner's credits and publish each report, as a run started in the app does; they wait up to `waitSeconds` (default 600, at most 900) with progress, then return the report as Markdown with its in-app and published links, or the id to read back with `get_bob_report` / `get_bob_comparison` when Bob is still reading. `bob_recheck` re-scans one finding against the live site; it costs no credits and adds the reading to the existing report. Bob's routes enforce their scopes: starting work needs `bob:run`, which can also read back, and reading needs `bob:read`.
|
|
453
|
+
|
|
402
454
|
```bash
|
|
403
455
|
b4m mcp serve # stdio (default) - for local clients that spawn the process
|
|
404
456
|
b4m mcp serve --http --port 7000 # stateless streamable HTTP at http://127.0.0.1:7000/mcp
|
|
405
457
|
```
|
|
406
458
|
|
|
407
|
-
|
|
459
|
+
Over stdio only, when the caller is entitled to OptiHashi and the key carries `optihashi:read` and `optihashi:compute`, the server also offers `optihashi_solvers` and `optihashi_solve` (a free, bounded classical solver race). With an API key, the CLI first checks the key's own scopes and offers them only when it holds both. The solve input schema comes from the server at startup, with a 5 second discovery budget; restart the session to pick up changed family ids, solver ids or limits. `optihashi_solvers` fetches the current roster on every call. With no entitlement, scopes or feature, they are simply not listed. No paid compute is exposed, and HTTP mode never offers them.
|
|
460
|
+
|
|
461
|
+
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, apart from the optional stdio-only tools above, which are fetched once at startup; 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 requires an `Authorization: Bearer <token>` header on every request. The token is `--token`, else `B4M_MCP_TOKEN`, else a random one generated at startup and printed to stderr (a token you set yourself is printed masked). Prefer `B4M_MCP_TOKEN` over `--token`, which shows up in process listings. The advertised scopes are the recommended key configuration, not per-route hard gates, apart from Bob's, which its routes enforce. 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. Point an HTTP client at it like this:
|
|
462
|
+
|
|
463
|
+
```json
|
|
464
|
+
{
|
|
465
|
+
"mcpServers": {
|
|
466
|
+
"bike4mind": {
|
|
467
|
+
"type": "http",
|
|
468
|
+
"url": "http://127.0.0.1:7000/mcp",
|
|
469
|
+
"headers": { "Authorization": "Bearer <token>" }
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
```
|
|
408
474
|
|
|
409
475
|
## Plugins
|
|
410
476
|
|
|
@@ -438,22 +504,22 @@ The entry default-exports a factory. It receives a context (currently `{ logger
|
|
|
438
504
|
|
|
439
505
|
```js
|
|
440
506
|
export default ctx => ({
|
|
441
|
-
name: 'foo',
|
|
507
|
+
name: 'foo', // unique module name
|
|
442
508
|
description: 'What this plugin does',
|
|
443
509
|
getTools: () => [
|
|
444
510
|
{
|
|
445
|
-
toolFn: async args => 'result',
|
|
511
|
+
toolFn: async args => 'result', // invoked when the agent calls the tool
|
|
446
512
|
toolSchema: {
|
|
447
513
|
name: 'foo_tool',
|
|
448
514
|
description: 'What the tool does',
|
|
449
|
-
parameters: { type: 'object', properties: {} },
|
|
515
|
+
parameters: { type: 'object', properties: {} }, // JSON Schema
|
|
450
516
|
},
|
|
451
517
|
},
|
|
452
518
|
],
|
|
453
519
|
getSystemPromptSection: () => 'Extra system-prompt text (or an empty string)',
|
|
454
|
-
getCommands: () => [{ name: 'foo', description: '...', execute: args => {} }],
|
|
455
|
-
registerWsHandlers: wsManager => {},
|
|
456
|
-
dispose: () => {},
|
|
520
|
+
getCommands: () => [{ name: 'foo', description: '...', execute: args => {} }], // optional
|
|
521
|
+
registerWsHandlers: wsManager => {}, // optional
|
|
522
|
+
dispose: () => {}, // optional, runs on exit/disable
|
|
457
523
|
});
|
|
458
524
|
```
|
|
459
525
|
|
|
@@ -489,6 +555,7 @@ The CLI includes a `recent_changes` tool that uses git history to find recently
|
|
|
489
555
|
### How It Works
|
|
490
556
|
|
|
491
557
|
The tool uses `git log` to track file modifications and returns:
|
|
558
|
+
|
|
492
559
|
- Files sorted by activity (most commits first)
|
|
493
560
|
- Commit messages for context
|
|
494
561
|
- Optional statistics showing lines changed
|
|
@@ -503,6 +570,7 @@ The CLI supports loading project-specific instructions from context files, simil
|
|
|
503
570
|
### Supported Files
|
|
504
571
|
|
|
505
572
|
**Project-level** (in your project directory, checked in priority order):
|
|
573
|
+
|
|
506
574
|
1. `CLAUDE.local.md` - Personal/gitignored instructions (highest priority)
|
|
507
575
|
2. `CLAUDE.md` - Project instructions
|
|
508
576
|
3. `AGENTS.md` - Cross-tool standard
|
|
@@ -511,6 +579,7 @@ The CLI supports loading project-specific instructions from context files, simil
|
|
|
511
579
|
6. `INSTRUCTIONS.md` - Project instructions (lowest priority)
|
|
512
580
|
|
|
513
581
|
**Global** (in `~/.bike4mind/`):
|
|
582
|
+
|
|
514
583
|
1. `AI.local.md` - Personal global instructions
|
|
515
584
|
2. `AI.md` - Global instructions
|
|
516
585
|
|
|
@@ -535,6 +604,7 @@ Create a `CLAUDE.md` in your project:
|
|
|
535
604
|
```
|
|
536
605
|
|
|
537
606
|
On startup, you'll see:
|
|
607
|
+
|
|
538
608
|
```
|
|
539
609
|
📄 Project context: CLAUDE.md
|
|
540
610
|
```
|
|
@@ -546,6 +616,7 @@ The agent will follow these instructions for all interactions in that project.
|
|
|
546
616
|
Sessions are saved to `~/.bike4mind/sessions/`
|
|
547
617
|
|
|
548
618
|
Each session includes:
|
|
619
|
+
|
|
549
620
|
- Full conversation history
|
|
550
621
|
- Token usage tracking
|
|
551
622
|
- Agent reasoning steps
|
|
@@ -556,6 +627,7 @@ Each session includes:
|
|
|
556
627
|
Debug logs are automatically written to `~/.bike4mind/debug/[session-id].txt` for every session, regardless of the `--verbose` flag.
|
|
557
628
|
|
|
558
629
|
**What's logged:**
|
|
630
|
+
|
|
559
631
|
- Session configuration (API endpoint, environment, model)
|
|
560
632
|
- HTTP requests and responses (with size and preview)
|
|
561
633
|
- SSE streaming events from LLM
|
|
@@ -564,6 +636,7 @@ Debug logs are automatically written to `~/.bike4mind/debug/[session-id].txt` fo
|
|
|
564
636
|
- Authentication flow events
|
|
565
637
|
|
|
566
638
|
**Accessing logs:**
|
|
639
|
+
|
|
567
640
|
```bash
|
|
568
641
|
# List all debug logs
|
|
569
642
|
ls -lh ~/.bike4mind/debug/
|
|
@@ -576,13 +649,41 @@ tail -f ~/.bike4mind/debug/[session-id].txt
|
|
|
576
649
|
```
|
|
577
650
|
|
|
578
651
|
**Log retention:**
|
|
652
|
+
|
|
579
653
|
- Debug logs older than 30 days are automatically cleaned up on CLI startup
|
|
580
654
|
- Each log file is named by session ID for easy correlation
|
|
581
655
|
|
|
582
656
|
**Verbose mode:**
|
|
657
|
+
|
|
583
658
|
- Without `--verbose`: Debug logs only written to file (clean console output)
|
|
584
659
|
- With `--verbose`: Debug logs shown in console AND written to file
|
|
585
660
|
|
|
661
|
+
### Hearth hook troubleshooting
|
|
662
|
+
|
|
663
|
+
If the standalone `bin/hearth-hook.mjs` reporter is not reaching Hearth, enable its diagnostic:
|
|
664
|
+
|
|
665
|
+
```bash
|
|
666
|
+
export B4M_HEARTH_DEBUG=1
|
|
667
|
+
echo '{}' | node packages/cli/bin/hearth-hook.mjs
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
Set `B4M_API_URL` and `B4M_API_KEY` first. With missing credentials the hook makes no request.
|
|
671
|
+
Diagnostics are off by default; an empty `B4M_HEARTH_DEBUG` or exactly `0` also disables them.
|
|
672
|
+
Other nonempty values enable one stderr line on failure:
|
|
673
|
+
|
|
674
|
+
- `POST /api/hearth/events failed: HTTP 401`: check whether the API key is valid.
|
|
675
|
+
- `POST /api/hearth/events error: ECONNREFUSED`: check the endpoint and server availability.
|
|
676
|
+
- `POST /api/hearth/events error: TimeoutError`: check connectivity and server responsiveness.
|
|
677
|
+
- `stdin payload error: SyntaxError`: the hook received malformed JSON.
|
|
678
|
+
- `configuration error: ERR_INVALID_URL`: check that `B4M_API_URL` is a valid absolute URL.
|
|
679
|
+
- `configuration error: missing B4M_API_URL or B4M_API_KEY`: set both variables before running the hook.
|
|
680
|
+
|
|
681
|
+
Each line starts with `hearth-hook:`. Errors expose a native error code or name, never the error
|
|
682
|
+
message, API key, URL, or hook payload. The hook still exits `0` on reporting failures. With debugging
|
|
683
|
+
enabled, the request gets 2.75 seconds and the diagnostic flush gets up to 250 ms within the 3-second
|
|
684
|
+
request/diagnostic budget. A closed or stalled stderr can lose the diagnostic; reporting remains
|
|
685
|
+
best-effort. Unset `B4M_HEARTH_DEBUG` after troubleshooting to keep routine hook events quiet.
|
|
686
|
+
|
|
586
687
|
## Development
|
|
587
688
|
|
|
588
689
|
This section covers the contributor workflow for hacking on the CLI from a checkout of the monorepo. For end-user installation see [Installation](#installation) above.
|
|
@@ -679,6 +780,7 @@ pnpm --filter @bike4mind/agents dev
|
|
|
679
780
|
`dist/` rebuilds on save, so the next `b4m` invocation sees changes without a manual `build`.
|
|
680
781
|
|
|
681
782
|
Caveats:
|
|
783
|
+
|
|
682
784
|
- This does **not** hot-reload an already-running interactive `b4m` session — you still exit and re-run.
|
|
683
785
|
- Running multiple watchers in parallel (one per package you're touching) hasn't been load-tested in this repo. If watch seems to miss changes or feels unreliable, fall back to manual `pnpm --filter <pkg> build`.
|
|
684
786
|
|
|
@@ -786,15 +888,15 @@ runtime. Unknown flags are tolerated (never hard-error) and the interactive TUI
|
|
|
786
888
|
preserved (a positional prompt seeds **and** submits turn 1 without switching to
|
|
787
889
|
headless `-p`):
|
|
788
890
|
|
|
789
|
-
| Flag
|
|
790
|
-
|
|
|
791
|
-
| `--mcp-config <file>`
|
|
792
|
-
| `--strict-mcp-config`
|
|
793
|
-
| `--append-system-prompt <text>`
|
|
794
|
-
| `--allowedTools <patterns>`
|
|
795
|
-
| `--settings <json>`
|
|
891
|
+
| Flag | Effect |
|
|
892
|
+
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
893
|
+
| `--mcp-config <file>` | Inject MCP servers from a claude-shape JSON (`{ "mcpServers": {...} }`). Supports HTTP transport (`type: "http"`, `url`, `headers`) with per-launch Bearer auth. |
|
|
894
|
+
| `--strict-mcp-config` | Use ONLY `--mcp-config` servers; ignore file-config and `.mcp.json` (board YAML pane). |
|
|
895
|
+
| `--append-system-prompt <text>` | Append text verbatim to the end of the system prompt (the 3-layer brief). |
|
|
896
|
+
| `--allowedTools <patterns>` | Auto-approve matching tools without a permission prompt (glob `mcp__manifold__*` or a space-separated explicit list). |
|
|
897
|
+
| `--settings <json>` | Inline JSON; the `hooks` subset drives lifecycle hooks (see below). Malformed JSON is ignored, never fatal. |
|
|
796
898
|
| `--session-id <uuid>` / `--resume <uuid>` | Pin / resume a session (board pane). `/clear` and `/compact` keep the pinned uuid; a bad `--resume` prints `No conversation found with session ID <uuid>` and exits non-zero so the host can self-heal the pane. |
|
|
797
|
-
| `<positional prompt>`
|
|
899
|
+
| `<positional prompt>` | Seeds and auto-fires turn 1 while staying interactive. |
|
|
798
900
|
|
|
799
901
|
### Lifecycle hooks (`--settings.hooks`)
|
|
800
902
|
|
package/bin/bike4mind-cli.mjs
CHANGED
|
@@ -217,6 +217,10 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
217
217
|
type: 'string',
|
|
218
218
|
description: 'Bike4Mind API URL (overrides B4M_API_URL and the configured backend)',
|
|
219
219
|
})
|
|
220
|
+
.option('token', {
|
|
221
|
+
type: 'string',
|
|
222
|
+
description: 'Bearer token required by --http mode (overrides B4M_MCP_TOKEN; generated and printed when unset). Visible in process listings; prefer B4M_MCP_TOKEN',
|
|
223
|
+
})
|
|
220
224
|
.example('b4m mcp serve', 'Serve over stdio for Claude Desktop and other local clients')
|
|
221
225
|
.example('b4m mcp serve --http --port 7000', 'Serve over streamable HTTP on port 7000');
|
|
222
226
|
}, async () => {
|
|
@@ -250,6 +254,31 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
250
254
|
})
|
|
251
255
|
.demandCommand(1, 'You must provide a subcommand (list, add, remove)');
|
|
252
256
|
})
|
|
257
|
+
.command('skills', 'Manage your Bike4Mind skills', (yargs) => {
|
|
258
|
+
return yargs
|
|
259
|
+
.command('push <path>', 'Create or update a skill from a Claude Code SKILL.md', (yargs) => {
|
|
260
|
+
return yargs
|
|
261
|
+
.positional('path', {
|
|
262
|
+
type: 'string',
|
|
263
|
+
describe: 'Skill folder, its SKILL.md, or a commands/*.md file',
|
|
264
|
+
})
|
|
265
|
+
.option('update', {
|
|
266
|
+
type: 'boolean',
|
|
267
|
+
description: 'Overwrite your existing skill with the same name without asking',
|
|
268
|
+
default: false,
|
|
269
|
+
})
|
|
270
|
+
.option('yes', {
|
|
271
|
+
type: 'boolean',
|
|
272
|
+
description: "Push another author's Skill Market skill as your own without asking",
|
|
273
|
+
default: false,
|
|
274
|
+
})
|
|
275
|
+
.example('b4m skills push ~/.claude/skills/my-skill', 'Create a skill from a Claude Code skill folder')
|
|
276
|
+
.example('b4m skills push ./SKILL.md --update', 'Update the existing skill of the same name');
|
|
277
|
+
}, async () => {
|
|
278
|
+
// Handled by external command handler
|
|
279
|
+
})
|
|
280
|
+
.demandCommand(1, 'You must provide a subcommand (push)');
|
|
281
|
+
})
|
|
253
282
|
.command('update', 'Check for and install CLI updates')
|
|
254
283
|
.command('doctor', 'Run diagnostic checks on CLI installation')
|
|
255
284
|
.command('acp', 'Run as an Agent Client Protocol (ACP) stdio server for editors like Zed')
|
|
@@ -312,7 +341,7 @@ if (argv.resume) {
|
|
|
312
341
|
}
|
|
313
342
|
// Positional task (claude `<prompt>` form): seeds AND submits turn 1, stays interactive.
|
|
314
343
|
// Only when it's not a known subcommand and headless -p wasn't used.
|
|
315
|
-
const KNOWN_SUBCOMMANDS = new Set(['mcp', 'plugin', 'update', 'doctor', 'acp']);
|
|
344
|
+
const KNOWN_SUBCOMMANDS = new Set(['mcp', 'plugin', 'skills', 'update', 'doctor', 'acp']);
|
|
316
345
|
if (argv.prompt === undefined && argv._.length > 0 && !KNOWN_SUBCOMMANDS.has(String(argv._[0]))) {
|
|
317
346
|
process.env.B4M_INITIAL_PROMPT = String(argv._[0]);
|
|
318
347
|
}
|
|
@@ -480,6 +509,31 @@ if (argv._[0] === 'plugin') {
|
|
|
480
509
|
}
|
|
481
510
|
}
|
|
482
511
|
|
|
512
|
+
// Handle skills subcommands (external commands)
|
|
513
|
+
if (argv._[0] === 'skills') {
|
|
514
|
+
const skillsSubcommand = argv._[1];
|
|
515
|
+
|
|
516
|
+
try {
|
|
517
|
+
let handleSkillsCommand;
|
|
518
|
+
|
|
519
|
+
if (isDev) {
|
|
520
|
+
const { register } = require('tsx/esm/api');
|
|
521
|
+
register();
|
|
522
|
+
const module = await import('../src/commands/skillsCommand.ts');
|
|
523
|
+
handleSkillsCommand = module.handleSkillsCommand;
|
|
524
|
+
} else {
|
|
525
|
+
const module = await import('../dist/commands/skillsCommand.mjs');
|
|
526
|
+
handleSkillsCommand = module.handleSkillsCommand;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
await handleSkillsCommand(skillsSubcommand, argv);
|
|
530
|
+
process.exit(0);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
console.error('Error:', error.message);
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
483
537
|
// Handle update command (external command)
|
|
484
538
|
if (argv._[0] === 'update') {
|
|
485
539
|
try {
|
package/bin/hearth-hook.mjs
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
// shared default channel by NAME and the server find-or-creates it, so a fresh
|
|
10
10
|
// install needs no per-user setup and lands in the same channel the cc-bridge
|
|
11
11
|
// reports into (one roster, not two half-rosters).
|
|
12
|
+
// Optional: B4M_HEARTH_DEBUG - any value but empty or 0 writes one stderr line
|
|
13
|
+
// when reporting fails or stdin errors. Off by default: hook stderr can reach the
|
|
14
|
+
// user's terminal on every event, so a stale key must not nag.
|
|
12
15
|
// Always exits 0 - a reporting hook must never block the session.
|
|
13
16
|
//
|
|
14
17
|
// DISCLOSURE. Everything sent here lands in an append-only log that other
|
|
@@ -39,7 +42,36 @@
|
|
|
39
42
|
import { basename, resolve } from 'node:path';
|
|
40
43
|
import { homedir } from 'node:os';
|
|
41
44
|
|
|
42
|
-
const { B4M_API_URL, B4M_API_KEY, B4M_HEARTH_CHANNEL, B4M_HEARTH_DISCLOSURE } = process.env;
|
|
45
|
+
const { B4M_API_URL, B4M_API_KEY, B4M_HEARTH_CHANNEL, B4M_HEARTH_DISCLOSURE, B4M_HEARTH_DEBUG } = process.env;
|
|
46
|
+
|
|
47
|
+
const DEBUG = Boolean(B4M_HEARTH_DEBUG) && B4M_HEARTH_DEBUG !== '0';
|
|
48
|
+
|
|
49
|
+
// The diagnostic is best-effort: with the reader gone, an unhandled EPIPE here
|
|
50
|
+
// would kill the process and decide the exit code, which this hook must never do.
|
|
51
|
+
process.stderr.on('error', () => {});
|
|
52
|
+
|
|
53
|
+
// Resolves once the line is flushed: stderr to a pipe is async on macOS, so
|
|
54
|
+
// exiting straight after a bare write can drop it. Bounded, because a full pipe
|
|
55
|
+
// with a stalled reader never calls back - and a diagnostic must not be the
|
|
56
|
+
// thing that holds the session open.
|
|
57
|
+
const FLUSH_DEADLINE_MS = 250;
|
|
58
|
+
const REQUEST_DEADLINE_MS = 3000;
|
|
59
|
+
const debug = line =>
|
|
60
|
+
DEBUG
|
|
61
|
+
? new Promise(done => {
|
|
62
|
+
const deadline = setTimeout(done, FLUSH_DEADLINE_MS).unref();
|
|
63
|
+
process.stderr.write(`hearth-hook: ${line}\n`, () => {
|
|
64
|
+
clearTimeout(deadline);
|
|
65
|
+
done();
|
|
66
|
+
});
|
|
67
|
+
})
|
|
68
|
+
: Promise.resolve();
|
|
69
|
+
|
|
70
|
+
function errorCode(error) {
|
|
71
|
+
const code = error?.cause?.code ?? error?.code;
|
|
72
|
+
if (typeof code === 'string') return code;
|
|
73
|
+
return typeof error?.name === 'string' ? error.name : 'unknown';
|
|
74
|
+
}
|
|
43
75
|
|
|
44
76
|
const DEFAULT_DISCLOSURE = 2;
|
|
45
77
|
const MAX_DISCLOSURE = 2;
|
|
@@ -238,10 +270,18 @@ function describe({ label, tier, workspace, activity }) {
|
|
|
238
270
|
}
|
|
239
271
|
|
|
240
272
|
const chunks = [];
|
|
273
|
+
process.stdin.on('error', err => {
|
|
274
|
+
debug(`stdin error: ${errorCode(err)}`).finally(() => process.exit(0));
|
|
275
|
+
});
|
|
241
276
|
process.stdin.on('data', c => chunks.push(c));
|
|
242
277
|
process.stdin.on('end', async () => {
|
|
278
|
+
let pending = Promise.resolve();
|
|
279
|
+
let operation = 'stdin payload';
|
|
243
280
|
try {
|
|
244
|
-
if (!B4M_API_URL || !B4M_API_KEY)
|
|
281
|
+
if (!B4M_API_URL || !B4M_API_KEY) {
|
|
282
|
+
pending = debug('configuration error: missing B4M_API_URL or B4M_API_KEY');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
245
285
|
const hook = JSON.parse(Buffer.concat(chunks).toString() || '{}');
|
|
246
286
|
|
|
247
287
|
const tier = disclosureTier();
|
|
@@ -267,7 +307,10 @@ process.stdin.on('end', async () => {
|
|
|
267
307
|
const activity = activityOf(hook);
|
|
268
308
|
if (tier >= 2) payload.activity = activity;
|
|
269
309
|
|
|
270
|
-
|
|
310
|
+
operation = 'configuration';
|
|
311
|
+
const endpoint = new URL('/api/hearth/events', B4M_API_URL);
|
|
312
|
+
operation = 'POST /api/hearth/events';
|
|
313
|
+
const res = await fetch(endpoint, {
|
|
271
314
|
method: 'POST',
|
|
272
315
|
headers: { 'content-type': 'application/json', 'x-api-key': B4M_API_KEY },
|
|
273
316
|
body: JSON.stringify({
|
|
@@ -281,12 +324,15 @@ process.stdin.on('end', async () => {
|
|
|
281
324
|
// had posted it - the log could not distinguish operator from tooling.
|
|
282
325
|
actor: { kind: 'agent', displayName: label },
|
|
283
326
|
}),
|
|
284
|
-
//
|
|
285
|
-
signal: AbortSignal.timeout(
|
|
327
|
+
// Reserve the debug flush inside the same 3s request/diagnostic budget.
|
|
328
|
+
signal: AbortSignal.timeout(DEBUG ? REQUEST_DEADLINE_MS - FLUSH_DEADLINE_MS : REQUEST_DEADLINE_MS),
|
|
286
329
|
});
|
|
287
|
-
|
|
288
|
-
|
|
330
|
+
if (!res.ok) pending = debug(`POST /api/hearth/events failed: HTTP ${res.status}`);
|
|
331
|
+
} catch (err) {
|
|
332
|
+
// Never print error messages: they can contain URLs or input content.
|
|
333
|
+
pending = debug(`${operation} error: ${errorCode(err)}`);
|
|
289
334
|
} finally {
|
|
335
|
+
await pending;
|
|
290
336
|
process.exit(0);
|
|
291
337
|
}
|
|
292
338
|
});
|