@bike4mind/cli 0.15.2 → 0.16.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 +49 -0
- package/bin/bike4mind-cli.mjs +66 -0
- package/dist/{ConfigStore-w0o_0xs0.mjs → ConfigStore-CtGc4fF2.mjs} +136 -9
- 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 +2 -2
- package/dist/commands/mcpCommand.mjs +1 -1
- package/dist/commands/updateCommand.mjs +1 -1
- package/dist/index.mjs +36 -10
- package/dist/{package-C9MYdYCP.mjs → package-3pouvthv.mjs} +1 -1
- package/dist/{tools-j5oGazTr.mjs → tools-D9eSRR7Q.mjs} +345 -152
- package/package.json +21 -21
package/README.md
CHANGED
|
@@ -578,6 +578,55 @@ Coming soon:
|
|
|
578
578
|
⏳ Session search and management
|
|
579
579
|
⏳ Tool execution monitoring
|
|
580
580
|
|
|
581
|
+
## Running as a `claude` engine inside a host app
|
|
582
|
+
|
|
583
|
+
The CLI can be driven by a host app (e.g. a kanban-style agent pipeline) as a
|
|
584
|
+
drop-in replacement for `claude` — running a pipeline **stage** or a **board AI /
|
|
585
|
+
YAML** pane with full parity (board tools over HTTP MCP, the 3-layer brief
|
|
586
|
+
injected, the spec auto-fired on turn 1, and the live-card `processing` /
|
|
587
|
+
`action_required` / `ready` signal).
|
|
588
|
+
|
|
589
|
+
A host that only launches an engine whose executable basename is exactly `claude`
|
|
590
|
+
can be pointed at a **symlink** named `claude`:
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
# Create a claude-named symlink to the CLI entry point somewhere on disk:
|
|
594
|
+
ln -s "$(pwd)/apps/cli/bin/bike4mind-cli.mjs" /usr/local/bin/claude-b4m/claude
|
|
595
|
+
|
|
596
|
+
# In the host, set the engine's `terminal_command` to that symlink path:
|
|
597
|
+
# /usr/local/bin/claude-b4m/claude
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
Use a **direct symlink** whose basename is `claude` — not a `package.json` bin
|
|
601
|
+
alias (it would shadow the real `claude`) and not a wrapper shell script (argv
|
|
602
|
+
gets mangled). No other setup is required; the CLI replicates claude's launch-flag
|
|
603
|
+
and lifecycle-hook contract.
|
|
604
|
+
|
|
605
|
+
### Supported claude-compatible flags
|
|
606
|
+
|
|
607
|
+
These are parsed in `bin/bike4mind-cli.mjs` into `B4M_*` env vars consumed by the
|
|
608
|
+
runtime. Unknown flags are tolerated (never hard-error) and the interactive TUI is
|
|
609
|
+
preserved (a positional prompt seeds **and** submits turn 1 without switching to
|
|
610
|
+
headless `-p`):
|
|
611
|
+
|
|
612
|
+
| Flag | Effect |
|
|
613
|
+
| --- | --- |
|
|
614
|
+
| `--mcp-config <file>` | Inject MCP servers from a claude-shape JSON (`{ "mcpServers": {...} }`). Supports HTTP transport (`type: "http"`, `url`, `headers`) with per-launch Bearer auth. |
|
|
615
|
+
| `--strict-mcp-config` | Use ONLY `--mcp-config` servers; ignore file-config and `.mcp.json` (board YAML pane). |
|
|
616
|
+
| `--append-system-prompt <text>` | Append text verbatim to the end of the system prompt (the 3-layer brief). |
|
|
617
|
+
| `--allowedTools <patterns>` | Auto-approve matching tools without a permission prompt (glob `mcp__manifold__*` or a space-separated explicit list). |
|
|
618
|
+
| `--settings <json>` | Inline JSON; the `hooks` subset drives lifecycle hooks (see below). Malformed JSON is ignored, never fatal. |
|
|
619
|
+
| `--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. |
|
|
620
|
+
| `<positional prompt>` | Seeds and auto-fires turn 1 while staying interactive. |
|
|
621
|
+
|
|
622
|
+
### Lifecycle hooks (`--settings.hooks`)
|
|
623
|
+
|
|
624
|
+
Mirrors claude's hook contract for the host's `action_required` signal: a
|
|
625
|
+
`Notification`/`permission_prompt` command writes a sentinel file while an
|
|
626
|
+
interactive permission prompt blocks; `PostToolUse` / `Stop` / `UserPromptSubmit`
|
|
627
|
+
commands remove it. Each hook command runs via a real shell with its stdin piped
|
|
628
|
+
then closed (EOF), so a blocking `cat > <file>` hook returns immediately.
|
|
629
|
+
|
|
581
630
|
## License
|
|
582
631
|
|
|
583
632
|
Private - Bike4Mind
|
package/bin/bike4mind-cli.mjs
CHANGED
|
@@ -111,6 +111,39 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
111
111
|
type: 'string',
|
|
112
112
|
description: 'Add local Ollama models to the model picker (e.g. http://localhost:11434)',
|
|
113
113
|
})
|
|
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 apps/cli README "host app".
|
|
117
|
+
.option('mcp-config', {
|
|
118
|
+
type: 'string',
|
|
119
|
+
description: 'Path to a JSON file of MCP servers to inject ({ "mcpServers": {...} })',
|
|
120
|
+
})
|
|
121
|
+
.option('strict-mcp-config', {
|
|
122
|
+
type: 'boolean',
|
|
123
|
+
description: 'Use ONLY --mcp-config servers; ignore file-config and .mcp.json',
|
|
124
|
+
default: false,
|
|
125
|
+
})
|
|
126
|
+
.option('append-system-prompt', {
|
|
127
|
+
type: 'string',
|
|
128
|
+
description: 'Text appended verbatim to the end of the composed system prompt',
|
|
129
|
+
})
|
|
130
|
+
.option('allowedTools', {
|
|
131
|
+
type: 'array',
|
|
132
|
+
string: true,
|
|
133
|
+
description: 'Tool names/globs auto-approved without a permission prompt (e.g. mcp__manifold__*)',
|
|
134
|
+
})
|
|
135
|
+
.option('settings', {
|
|
136
|
+
type: 'string',
|
|
137
|
+
description: 'Inline JSON settings (currently: lifecycle hooks) merged over user config',
|
|
138
|
+
})
|
|
139
|
+
.option('session-id', {
|
|
140
|
+
type: 'string',
|
|
141
|
+
description: 'Pin this session to a fixed uuid (first launch of a resumable pane)',
|
|
142
|
+
})
|
|
143
|
+
.option('resume', {
|
|
144
|
+
type: 'string',
|
|
145
|
+
description: 'Resume an existing session by uuid',
|
|
146
|
+
})
|
|
114
147
|
.option('api-url', {
|
|
115
148
|
type: 'string',
|
|
116
149
|
description: 'Set a custom API URL (self-hosted instance) and clear auth tokens, then exit',
|
|
@@ -197,6 +230,39 @@ if (argv['ollama-host']) {
|
|
|
197
230
|
process.env.B4M_OLLAMA_HOST = argv['ollama-host'];
|
|
198
231
|
}
|
|
199
232
|
|
|
233
|
+
// ─── claude-compatible flags → B4M_* env (read by src/index.tsx init) ─────────
|
|
234
|
+
// Mirrors the established bin→env→init channel (cf. --add-dir → B4M_ADDITIONAL_DIRS).
|
|
235
|
+
if (argv['mcp-config']) {
|
|
236
|
+
process.env.B4M_MCP_CONFIG_FILE = resolve(argv['mcp-config']);
|
|
237
|
+
}
|
|
238
|
+
if (argv['strict-mcp-config']) {
|
|
239
|
+
process.env.B4M_STRICT_MCP_CONFIG = '1';
|
|
240
|
+
}
|
|
241
|
+
if (argv['append-system-prompt']) {
|
|
242
|
+
process.env.B4M_APPEND_SYSTEM_PROMPT = argv['append-system-prompt'];
|
|
243
|
+
}
|
|
244
|
+
if (argv.allowedTools && argv.allowedTools.length > 0) {
|
|
245
|
+
// Each element may itself be a space-separated list (the host's board pane passes
|
|
246
|
+
// "a b c" as a single argv token); flatten on whitespace into discrete patterns.
|
|
247
|
+
const patterns = argv.allowedTools.flatMap((s) => String(s).split(/\s+/)).filter(Boolean);
|
|
248
|
+
process.env.B4M_ALLOWED_TOOLS = JSON.stringify(patterns);
|
|
249
|
+
}
|
|
250
|
+
if (argv.settings) {
|
|
251
|
+
process.env.B4M_SETTINGS_JSON = argv.settings;
|
|
252
|
+
}
|
|
253
|
+
if (argv['session-id']) {
|
|
254
|
+
process.env.B4M_SESSION_ID = argv['session-id'];
|
|
255
|
+
}
|
|
256
|
+
if (argv.resume) {
|
|
257
|
+
process.env.B4M_RESUME_ID = argv.resume;
|
|
258
|
+
}
|
|
259
|
+
// Positional task (claude `<prompt>` form): seeds AND submits turn 1, stays interactive.
|
|
260
|
+
// Only when it's not a known subcommand and headless -p wasn't used.
|
|
261
|
+
const KNOWN_SUBCOMMANDS = new Set(['mcp', 'update', 'doctor']);
|
|
262
|
+
if (argv.prompt === undefined && argv._.length > 0 && !KNOWN_SUBCOMMANDS.has(String(argv._[0]))) {
|
|
263
|
+
process.env.B4M_INITIAL_PROMPT = String(argv._[0]);
|
|
264
|
+
}
|
|
265
|
+
|
|
200
266
|
// Auto-detect environment: prefer production mode when dist exists
|
|
201
267
|
const distPath = join(__dirname, '../dist/index.mjs');
|
|
202
268
|
const srcPath = join(__dirname, '../src/index.tsx');
|
|
@@ -291,9 +291,6 @@ let ImageModels = /* @__PURE__ */ function(ImageModels) {
|
|
|
291
291
|
ImageModels["FLUX_PRO_1_1"] = "flux-pro-1.1";
|
|
292
292
|
ImageModels["FLUX_PRO_ULTRA"] = "flux-pro-1.1-ultra";
|
|
293
293
|
ImageModels["FLUX_PRO_FILL"] = "flux-pro-1.0-fill";
|
|
294
|
-
ImageModels["FLUX_PRO_CANNY"] = "flux-pro-1.0-canny";
|
|
295
|
-
ImageModels["FLUX_PRO_DEPTH"] = "flux-pro-1.0-depth";
|
|
296
|
-
ImageModels["FLUX_DEV"] = "flux-dev";
|
|
297
294
|
ImageModels["FLUX_KONTEXT_PRO"] = "flux-kontext-pro";
|
|
298
295
|
ImageModels["FLUX_KONTEXT_MAX"] = "flux-kontext-max";
|
|
299
296
|
ImageModels["GROK_IMAGINE_IMAGE_QUALITY"] = "grok-imagine-image-quality";
|
|
@@ -2663,7 +2660,8 @@ const AgentCompletedAction = z.object({
|
|
|
2663
2660
|
executionId: z.string(),
|
|
2664
2661
|
answer: z.string().optional(),
|
|
2665
2662
|
totalIterations: z.number(),
|
|
2666
|
-
totalCreditsUsed: z.number()
|
|
2663
|
+
totalCreditsUsed: z.number(),
|
|
2664
|
+
mementoIds: z.array(z.string()).optional()
|
|
2667
2665
|
});
|
|
2668
2666
|
const AgentFailedAction = z.object({
|
|
2669
2667
|
action: z.literal("failed"),
|
|
@@ -3615,10 +3613,26 @@ const OpenAIImageStyleSchema = z.enum(["vivid", "natural"]);
|
|
|
3615
3613
|
/**
|
|
3616
3614
|
* Maps legacy/removed image model IDs to their current replacements.
|
|
3617
3615
|
* Prevents Zod validation failures when clients send stale persisted model names.
|
|
3616
|
+
*
|
|
3617
|
+
* Values are constrained to `ALL_IMAGE_MODELS` so a remap target can never point at
|
|
3618
|
+
* a model that the schema would itself reject. If a target model is later retired,
|
|
3619
|
+
* re-point its entry to the current replacement rather than deleting it — clients may
|
|
3620
|
+
* still hold the legacy key in persisted state. When adding an entry, bump the
|
|
3621
|
+
* `llm-settings` persist `version` in LLMContext so existing clients re-run the remap.
|
|
3622
|
+
*
|
|
3623
|
+
* Only `flux-dev` is aliased among the recently removed Flux ids. It was a general
|
|
3624
|
+
* text-to-image model with a confirmed stale client (#8853), so remapping to the
|
|
3625
|
+
* current BFL standard is safe and faithful. `flux-pro-1.0-canny` and
|
|
3626
|
+
* `flux-pro-1.0-depth` are intentionally left as hard validation errors: they were
|
|
3627
|
+
* never UI-selectable (nothing persists them, and no alerts show callers sending them)
|
|
3628
|
+
* and they are structural-control models — silently remapping them to a plain
|
|
3629
|
+
* text-to-image model would drop the control image and return the wrong kind of result,
|
|
3630
|
+
* so a clear "unsupported model" error is the more honest response.
|
|
3618
3631
|
*/
|
|
3619
3632
|
const LEGACY_IMAGE_MODEL_MAP = {
|
|
3620
3633
|
"dall-e-3": "gpt-image-1",
|
|
3621
|
-
"dall-e-2": "gpt-image-1"
|
|
3634
|
+
"dall-e-2": "gpt-image-1",
|
|
3635
|
+
"flux-dev": "flux-pro-1.1"
|
|
3622
3636
|
};
|
|
3623
3637
|
const OpenAIImageGenerationInput = z.object({
|
|
3624
3638
|
prompt: z.string(),
|
|
@@ -3829,6 +3843,8 @@ z.enum([
|
|
|
3829
3843
|
"logoSettings",
|
|
3830
3844
|
"enforceCredits",
|
|
3831
3845
|
"enableTeamPlan",
|
|
3846
|
+
"allowOpenRegistration",
|
|
3847
|
+
"defaultFreeCredits",
|
|
3832
3848
|
"enableGoogleCalendar",
|
|
3833
3849
|
"googleCalendarServiceAccountEmail",
|
|
3834
3850
|
"googleCalendarServiceAccountSecret",
|
|
@@ -3885,6 +3901,22 @@ z.enum([
|
|
|
3885
3901
|
"overwatchRollupSync",
|
|
3886
3902
|
"orchestrationDefaults"
|
|
3887
3903
|
]);
|
|
3904
|
+
/**
|
|
3905
|
+
* Intent-classifier sub-config (#8924). Drives the LLM-based silent
|
|
3906
|
+
* auto-routing classifier that decides between quest_processor and
|
|
3907
|
+
* agent_executor for `contextual` queries. The cascade reuses
|
|
3908
|
+
* `getLlmWithFallback()` so a missing primary API key transparently degrades.
|
|
3909
|
+
*
|
|
3910
|
+
* `shadowMode: true` means the endpoint computes a decision and emits
|
|
3911
|
+
* telemetry but no client wires it into routing yet (M3 ships dark-launched;
|
|
3912
|
+
* M4 flips clients onto it).
|
|
3913
|
+
*/
|
|
3914
|
+
const IntentClassifierConfigSchema = z.object({
|
|
3915
|
+
enabled: z.boolean().default(true),
|
|
3916
|
+
shadowMode: z.boolean().default(true),
|
|
3917
|
+
primaryModel: z.string().default("claude-haiku-4-5-20251001"),
|
|
3918
|
+
fallbackModels: z.array(z.string()).default(["gemini-2.5-flash-lite", "gpt-4.1-nano-2025-04-14"])
|
|
3919
|
+
});
|
|
3888
3920
|
const OrchestrationDefaultsSchema = z.object({
|
|
3889
3921
|
/** Tool names the synthetic profile is allowed to invoke. */
|
|
3890
3922
|
allowedTools: z.array(z.string()).default([
|
|
@@ -3922,7 +3954,9 @@ const OrchestrationDefaultsSchema = z.object({
|
|
|
3922
3954
|
* synthetic profiles can decompose multi-step queries; admins can flip off
|
|
3923
3955
|
* org-wide if DAG behavior surprises end users.
|
|
3924
3956
|
*/
|
|
3925
|
-
dagEnabled: z.boolean().default(true)
|
|
3957
|
+
dagEnabled: z.boolean().default(true),
|
|
3958
|
+
/** LLM intent-classifier configuration (#8924). */
|
|
3959
|
+
intentClassifier: IntentClassifierConfigSchema.default(IntentClassifierConfigSchema.parse({}))
|
|
3926
3960
|
});
|
|
3927
3961
|
function makeStringSetting(config) {
|
|
3928
3962
|
return {
|
|
@@ -5747,6 +5781,22 @@ const settingsMap = {
|
|
|
5747
5781
|
group: API_SERVICE_GROUPS.CREDITS.id,
|
|
5748
5782
|
category: "Users"
|
|
5749
5783
|
}),
|
|
5784
|
+
allowOpenRegistration: makeBooleanSetting({
|
|
5785
|
+
key: "allowOpenRegistration",
|
|
5786
|
+
name: "Allow Open Registration",
|
|
5787
|
+
defaultValue: false,
|
|
5788
|
+
description: "Master switch for self-serve signup. When OFF (default), a valid invite code is required to register. When ON, users may register without an invite code; the Default Free Credits grant is then applied after they verify their email (anti-spam — see Default Free Credits). Safe to enable: the pre-request credit reservation caps every free user at the credits they are granted.",
|
|
5789
|
+
group: API_SERVICE_GROUPS.CREDITS.id,
|
|
5790
|
+
category: "Users"
|
|
5791
|
+
}),
|
|
5792
|
+
defaultFreeCredits: makeNumberSetting({
|
|
5793
|
+
key: "defaultFreeCredits",
|
|
5794
|
+
name: "Default Free Credits",
|
|
5795
|
+
defaultValue: 0,
|
|
5796
|
+
description: "Credits granted to a user who registers WITHOUT an invite code (only applies when Allow Open Registration is ON). Granted after the user verifies their email, NOT at signup — an unverified throwaway account gets 0 credits (anti-spam). A free user can never spend more than this — their hard ceiling of real model cost is roughly credits ÷ 1500 USD.",
|
|
5797
|
+
group: API_SERVICE_GROUPS.CREDITS.id,
|
|
5798
|
+
category: "Users"
|
|
5799
|
+
}),
|
|
5750
5800
|
FacebookLink: makeStringSetting({
|
|
5751
5801
|
key: "FacebookLink",
|
|
5752
5802
|
name: "Facebook Link",
|
|
@@ -8326,7 +8376,25 @@ z.object({
|
|
|
8326
8376
|
/** Persona-based sub-agent filter — only these agent names are available for delegation */
|
|
8327
8377
|
allowedAgents: z.array(z.string()).optional(),
|
|
8328
8378
|
/** When true, Quest Processor injects Slack-specific tool configs (help, notebooks, curated files) */
|
|
8329
|
-
enableSlackTools: z.boolean().optional()
|
|
8379
|
+
enableSlackTools: z.boolean().optional(),
|
|
8380
|
+
/**
|
|
8381
|
+
* Agent-mode toggle state forwarded from the composer (#8923). The chat
|
|
8382
|
+
* completion path itself does not branch on this — that decision happens
|
|
8383
|
+
* upstream in `routeQuery()` on the client (and, post-M4, on the server).
|
|
8384
|
+
* Plumbed through so telemetry and future per-decision routing logs can
|
|
8385
|
+
* attribute the choice to its source (manual toggle vs. classifier vs.
|
|
8386
|
+
* mention) without re-deriving it.
|
|
8387
|
+
*/
|
|
8388
|
+
agentMode: z.object({
|
|
8389
|
+
enabled: z.boolean(),
|
|
8390
|
+
source: z.enum([
|
|
8391
|
+
"toggle",
|
|
8392
|
+
"classifier",
|
|
8393
|
+
"mention",
|
|
8394
|
+
"user-default",
|
|
8395
|
+
"agent_literal"
|
|
8396
|
+
])
|
|
8397
|
+
}).optional()
|
|
8330
8398
|
}).extend({
|
|
8331
8399
|
/** Notebook session ID */
|
|
8332
8400
|
sessionId: z.string().optional(),
|
|
@@ -10202,11 +10270,32 @@ const ApiConfigSchema = z.object({ customUrl: z.url().optional() });
|
|
|
10202
10270
|
*/
|
|
10203
10271
|
const McpServerSchema = z.object({
|
|
10204
10272
|
name: z.string(),
|
|
10273
|
+
type: z.enum(["stdio", "http"]).optional(),
|
|
10205
10274
|
command: z.string().optional(),
|
|
10206
10275
|
args: z.array(z.string()).optional(),
|
|
10276
|
+
url: z.string().optional(),
|
|
10277
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
10207
10278
|
env: z.record(z.string(), z.string()).prefault({}),
|
|
10208
10279
|
enabled: z.boolean().prefault(true)
|
|
10209
|
-
});
|
|
10280
|
+
}).superRefine(mcpServerTransportRefine);
|
|
10281
|
+
/**
|
|
10282
|
+
* A valid MCP server is exactly one of:
|
|
10283
|
+
* - stdio: has `command` (spawned child process)
|
|
10284
|
+
* - http: has `url` (streamable-HTTP endpoint)
|
|
10285
|
+
* Rejecting malformed configs at parse time (not connect time) surfaces errors early.
|
|
10286
|
+
*/
|
|
10287
|
+
function mcpServerTransportRefine(server, ctx) {
|
|
10288
|
+
const hasCommand = typeof server.command === "string" && server.command.trim() !== "";
|
|
10289
|
+
const hasUrl = typeof server.url === "string" && server.url.trim() !== "";
|
|
10290
|
+
if (hasCommand && hasUrl) ctx.addIssue({
|
|
10291
|
+
code: "custom",
|
|
10292
|
+
message: "MCP server cannot set both \"command\" (stdio) and \"url\" (http)"
|
|
10293
|
+
});
|
|
10294
|
+
else if (!hasCommand && !hasUrl) ctx.addIssue({
|
|
10295
|
+
code: "custom",
|
|
10296
|
+
message: "MCP server must set either \"command\" (stdio) or \"url\" (http)"
|
|
10297
|
+
});
|
|
10298
|
+
}
|
|
10210
10299
|
/**
|
|
10211
10300
|
* MCP Servers can be specified in two formats:
|
|
10212
10301
|
* 1. Array format (B4M native): [{ "name": "...", "command": "...", ... }]
|
|
@@ -10215,11 +10304,14 @@ const McpServerSchema = z.object({
|
|
|
10215
10304
|
* Both formats are supported for maximum flexibility and compatibility.
|
|
10216
10305
|
*/
|
|
10217
10306
|
const McpServersSchema = z.union([z.array(McpServerSchema), z.record(z.string(), z.object({
|
|
10307
|
+
type: z.enum(["stdio", "http"]).optional(),
|
|
10218
10308
|
command: z.string().optional(),
|
|
10219
10309
|
args: z.array(z.string()).optional(),
|
|
10310
|
+
url: z.string().optional(),
|
|
10311
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
10220
10312
|
env: z.record(z.string(), z.string()).optional(),
|
|
10221
10313
|
enabled: z.boolean().optional()
|
|
10222
|
-
}))]);
|
|
10314
|
+
}).superRefine(mcpServerTransportRefine))]);
|
|
10223
10315
|
/**
|
|
10224
10316
|
* Normalize MCP servers to internal array format
|
|
10225
10317
|
* Accepts both array and object formats
|
|
@@ -10227,15 +10319,21 @@ const McpServersSchema = z.union([z.array(McpServerSchema), z.record(z.string(),
|
|
|
10227
10319
|
function normalizeMcpServers(servers) {
|
|
10228
10320
|
if (Array.isArray(servers)) return servers.map((server) => ({
|
|
10229
10321
|
name: server.name,
|
|
10322
|
+
type: server.type,
|
|
10230
10323
|
command: server.command,
|
|
10231
10324
|
args: server.args,
|
|
10325
|
+
url: server.url,
|
|
10326
|
+
headers: server.headers,
|
|
10232
10327
|
env: server.env || {},
|
|
10233
10328
|
enabled: server.enabled ?? true
|
|
10234
10329
|
}));
|
|
10235
10330
|
else return Object.entries(servers).map(([name, config]) => ({
|
|
10236
10331
|
name,
|
|
10332
|
+
type: config.type,
|
|
10237
10333
|
command: config.command,
|
|
10238
10334
|
args: config.args,
|
|
10335
|
+
url: config.url,
|
|
10336
|
+
headers: config.headers,
|
|
10239
10337
|
env: config.env || {},
|
|
10240
10338
|
enabled: config.enabled ?? true
|
|
10241
10339
|
}));
|
|
@@ -10467,6 +10565,29 @@ async function loadMcpJsonConfig(projectDir) {
|
|
|
10467
10565
|
}
|
|
10468
10566
|
}
|
|
10469
10567
|
/**
|
|
10568
|
+
* Load an explicit `--mcp-config <file>` (claude shape: `{ "mcpServers": {...} }`).
|
|
10569
|
+
* Unlike `.mcp.json` this is an absolute path passed at launch, not project-relative.
|
|
10570
|
+
* Returns null on missing/malformed file (a bad config must not brick the launch).
|
|
10571
|
+
*/
|
|
10572
|
+
async function loadMcpConfigFile(filePath) {
|
|
10573
|
+
try {
|
|
10574
|
+
const data = await promises.readFile(filePath, "utf-8");
|
|
10575
|
+
const rawConfig = JSON.parse(data);
|
|
10576
|
+
return normalizeMcpServers(McpJsonConfigSchema.parse(rawConfig).mcpServers);
|
|
10577
|
+
} catch (error) {
|
|
10578
|
+
if (error.code === "ENOENT") {
|
|
10579
|
+
console.error(`--mcp-config file not found: ${filePath}`);
|
|
10580
|
+
return null;
|
|
10581
|
+
}
|
|
10582
|
+
if (error instanceof z.ZodError) {
|
|
10583
|
+
console.error("--mcp-config validation error:", error.issues);
|
|
10584
|
+
return null;
|
|
10585
|
+
}
|
|
10586
|
+
console.error("Failed to load --mcp-config file:", error);
|
|
10587
|
+
return null;
|
|
10588
|
+
}
|
|
10589
|
+
}
|
|
10590
|
+
/**
|
|
10470
10591
|
* Merge MCP servers from multiple configs
|
|
10471
10592
|
* Later configs can override earlier ones by name
|
|
10472
10593
|
*/
|
|
@@ -10648,6 +10769,12 @@ var ConfigStore = class {
|
|
|
10648
10769
|
} else this.projectConfigDir = null;
|
|
10649
10770
|
const mergedConfig = mergeConfigs(globalConfig, projectConfig, projectLocalConfig);
|
|
10650
10771
|
if (mcpJsonServers && mcpJsonServers.length > 0) mergedConfig.mcpServers = mergeMcpServers(mcpJsonServers, mergedConfig.mcpServers);
|
|
10772
|
+
const mcpConfigFile = process.env.B4M_MCP_CONFIG_FILE;
|
|
10773
|
+
if (mcpConfigFile) {
|
|
10774
|
+
const injected = await loadMcpConfigFile(mcpConfigFile);
|
|
10775
|
+
if (process.env.B4M_STRICT_MCP_CONFIG === "1") mergedConfig.mcpServers = injected ?? [];
|
|
10776
|
+
else if (injected) mergedConfig.mcpServers = mergeMcpServers(mergedConfig.mcpServers, injected);
|
|
10777
|
+
}
|
|
10651
10778
|
this.config = mergedConfig;
|
|
10652
10779
|
return this.config;
|
|
10653
10780
|
} catch (error) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "../package-
|
|
2
|
+
import { t as version } from "../package-3pouvthv.mjs";
|
|
3
3
|
import { a as fetchLatestVersion, c as isNpmPrefixWritable, i as compareSemver } from "../updateChecker-C8xsNY2L.mjs";
|
|
4
4
|
import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
|
|
5
5
|
import { execSync } from "child_process";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools,
|
|
3
|
-
import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-
|
|
2
|
+
import { $ as SessionStore, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, J as isReadOnlyTool, K as buildSystemPrompt, N as loadContextFiles, P as PermissionManager, S as ApiClient, T as FallbackLlmBackend, W as setWebSocketToolExecutor, X as CustomCommandStore, Y as ReActAgent, Z as CheckpointStore, _ as createAgentDelegateTool, b as createSkillTool, d as createFindDefinitionTool, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, k as McpManager, m as createCoordinateTaskTool, p as createWriteTodosTool, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-D9eSRR7Q.mjs";
|
|
3
|
+
import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
|
|
4
4
|
import { t as DEFAULT_SANDBOX_CONFIG } from "../types-LyRNHOiS.mjs";
|
|
5
5
|
import { t as createSandboxRuntime } from "../SandboxRuntimeAdapter-ChGlxSGQ.mjs";
|
|
6
6
|
import { t as SandboxOrchestrator } from "../SandboxOrchestrator-BoINxbX4.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "../package-
|
|
2
|
+
import { t as version } from "../package-3pouvthv.mjs";
|
|
3
3
|
import { c as isNpmPrefixWritable, l as setAutoUpdatePreference, n as REEXEC_GUARD_ENV, o as forceCheckForUpdate, r as checkForUpdate, s as getAutoUpdatePreference, t as INSTALL_CMD, u as shouldAttemptAutoUpdate } from "../updateChecker-C8xsNY2L.mjs";
|
|
4
4
|
import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
|
|
5
5
|
import { execSync, spawnSync } from "child_process";
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as SessionStore, A as substituteArguments, B as DEFAULT_RETRY_CONFIG, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as getPlanModeFilePath, H as clearFeatureModuleTools, I as getProcessHooks, J as isReadOnlyTool, K as buildSystemPrompt, L as ALWAYS_DENIED_FOR_AGENTS, M as extractCompactInstructions, N as loadContextFiles, O as isTransientNetworkError, P as PermissionManager, Q as CommandHistoryStore, R as DEFAULT_AGENT_MODEL, S as ApiClient, T as FallbackLlmBackend, U as registerFeatureModuleTools, V as DEFAULT_THOROUGHNESS, W as setWebSocketToolExecutor, X as CustomCommandStore, Y as ReActAgent, Z as CheckpointStore, _ as createAgentDelegateTool, a as createBlockerTools, at as formatFileSize, b as createSkillTool, c as createDecisionStore, d as createFindDefinitionTool, et as OAuthClient, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, i as createBlockerStore, it as mergeCommands, j as formatStep, k as McpManager, l as formatDecisionsOutput, m as createCoordinateTaskTool, n as createReviewGateTool, nt as processFileReferences, o as formatBlockersOutput, ot as searchFiles, p as createWriteTodosTool, q as buildSkillsPromptSection, r as formatReviewGatesOutput, rt as searchCommands, s as createDecisionLogTool, st as warmFileCache, t as createReviewGateStore, tt as hasFileReferences, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, x as parseAgentConfig, y as SubagentOrchestrator, z as DEFAULT_MAX_ITERATIONS } from "./tools-D9eSRR7Q.mjs";
|
|
3
3
|
import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-DV5s-qni.mjs";
|
|
4
|
-
import { Gt as validateNotebookPath$1, Wt as validateJupyterKernelName, g as CREDIT_DEDUCT_TRANSACTION_TYPES, i as getEnvironmentName, n as logger, r as getApiUrl, t as ConfigStore, v as ChatModels } from "./ConfigStore-
|
|
5
|
-
import { t as version } from "./package-
|
|
4
|
+
import { Gt as validateNotebookPath$1, Wt as validateJupyterKernelName, g as CREDIT_DEDUCT_TRANSACTION_TYPES, i as getEnvironmentName, n as logger, r as getApiUrl, t as ConfigStore, v as ChatModels } from "./ConfigStore-CtGc4fF2.mjs";
|
|
5
|
+
import { t as version } from "./package-3pouvthv.mjs";
|
|
6
6
|
import { r as checkForUpdate } from "./updateChecker-C8xsNY2L.mjs";
|
|
7
7
|
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
8
8
|
import { Box, Static, Text, render, useApp, useInput, usePaste, useStdout } from "ink";
|
|
@@ -3083,7 +3083,7 @@ function buildCompactionPrompt(messages, options = {}) {
|
|
|
3083
3083
|
* @param preservedMessages - Recent messages to keep verbatim
|
|
3084
3084
|
* @returns A new session with compacted context
|
|
3085
3085
|
*/
|
|
3086
|
-
function createCompactedSession(originalSession, summary, preservedMessages) {
|
|
3086
|
+
function createCompactedSession(originalSession, summary, preservedMessages, preserveId = false) {
|
|
3087
3087
|
const summaryMessage = {
|
|
3088
3088
|
id: v4(),
|
|
3089
3089
|
role: "user",
|
|
@@ -3103,7 +3103,7 @@ function createCompactedSession(originalSession, summary, preservedMessages) {
|
|
|
3103
3103
|
...preservedMessages
|
|
3104
3104
|
] : [summaryMessage, ...preservedMessages];
|
|
3105
3105
|
return {
|
|
3106
|
-
id: v4(),
|
|
3106
|
+
id: preserveId ? originalSession.id : v4(),
|
|
3107
3107
|
name: `${originalSession.name} (compacted)`,
|
|
3108
3108
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3109
3109
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6327,8 +6327,18 @@ function CliApp() {
|
|
|
6327
6327
|
logger.warn(`🤖 Using fallback model: ${modelInfo.id}`);
|
|
6328
6328
|
}
|
|
6329
6329
|
llm.currentModel = modelInfo.id;
|
|
6330
|
-
const
|
|
6331
|
-
|
|
6330
|
+
const pinnedSessionId = process.env.B4M_SESSION_ID;
|
|
6331
|
+
const resumeSessionId = process.env.B4M_RESUME_ID;
|
|
6332
|
+
let newSession;
|
|
6333
|
+
if (resumeSessionId) {
|
|
6334
|
+
const resumed = await state.sessionStore.load(resumeSessionId);
|
|
6335
|
+
if (!resumed) {
|
|
6336
|
+
console.error(`No conversation found with session ID ${resumeSessionId}`);
|
|
6337
|
+
process.exit(1);
|
|
6338
|
+
}
|
|
6339
|
+
newSession = resumed;
|
|
6340
|
+
} else newSession = {
|
|
6341
|
+
id: pinnedSessionId || v4(),
|
|
6332
6342
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
6333
6343
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6334
6344
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6392,6 +6402,7 @@ function CliApp() {
|
|
|
6392
6402
|
let permissionPromptCounter = 0;
|
|
6393
6403
|
const promptFn = (toolName, args, preview) => {
|
|
6394
6404
|
return new Promise((resolve) => {
|
|
6405
|
+
getProcessHooks()?.fireNotificationPermissionPrompt(toolName);
|
|
6395
6406
|
const canBeTrusted = permissionManager.canBeTrusted(toolName);
|
|
6396
6407
|
const id = `perm-${++permissionPromptCounter}`;
|
|
6397
6408
|
const prompt = {
|
|
@@ -6600,6 +6611,7 @@ function CliApp() {
|
|
|
6600
6611
|
additionalDirectories,
|
|
6601
6612
|
featureModulePrompts: featureModulePrompts || void 0,
|
|
6602
6613
|
planModeFilePath: mode === "plan" ? getPlanModeFilePath(newSession.id) : void 0,
|
|
6614
|
+
appendSystemPrompt: process.env.B4M_APPEND_SYSTEM_PROMPT,
|
|
6603
6615
|
deferredToolNames: deferredToolRegistry.getDirectoryNames()
|
|
6604
6616
|
});
|
|
6605
6617
|
const cliSystemPrompt = buildPromptForMode(useCliStore.getState().interactionMode);
|
|
@@ -6778,6 +6790,16 @@ function CliApp() {
|
|
|
6778
6790
|
const handleMessageRef = useRef(null);
|
|
6779
6791
|
const abortControllerRef = useRef(state.abortController);
|
|
6780
6792
|
abortControllerRef.current = state.abortController;
|
|
6793
|
+
const autoFiredRef = useRef(false);
|
|
6794
|
+
useEffect(() => {
|
|
6795
|
+
if (!isInitialized || autoFiredRef.current) return;
|
|
6796
|
+
const initialPrompt = process.env.B4M_INITIAL_PROMPT;
|
|
6797
|
+
if (!initialPrompt) return;
|
|
6798
|
+
autoFiredRef.current = true;
|
|
6799
|
+
setImmediate(() => {
|
|
6800
|
+
handleMessageRef.current?.(initialPrompt);
|
|
6801
|
+
});
|
|
6802
|
+
}, [isInitialized]);
|
|
6781
6803
|
const emitNextAwaitingStatus = () => {
|
|
6782
6804
|
const s = useCliStore.getState();
|
|
6783
6805
|
if (s.permissionPrompt) {
|
|
@@ -7068,6 +7090,7 @@ function CliApp() {
|
|
|
7068
7090
|
console.error("❌ CLI failed to initialize. Try restarting b4m.\n");
|
|
7069
7091
|
return;
|
|
7070
7092
|
}
|
|
7093
|
+
getProcessHooks()?.fireUserPromptSubmit();
|
|
7071
7094
|
bridgePresence.emitEvent({
|
|
7072
7095
|
type: "message",
|
|
7073
7096
|
role: "user",
|
|
@@ -7102,7 +7125,7 @@ function CliApp() {
|
|
|
7102
7125
|
if (compactionPrompt) {
|
|
7103
7126
|
const result = await state.agent.run(compactionPrompt, { maxIterations: 1 });
|
|
7104
7127
|
await state.sessionStore.save(activeSession);
|
|
7105
|
-
const newSession = createCompactedSession(activeSession, result.finalAnswer, preservedMessages);
|
|
7128
|
+
const newSession = createCompactedSession(activeSession, result.finalAnswer, preservedMessages, !!(process.env.B4M_SESSION_ID || process.env.B4M_RESUME_ID));
|
|
7106
7129
|
await logger.initialize(newSession.id);
|
|
7107
7130
|
setState((prev) => ({
|
|
7108
7131
|
...prev,
|
|
@@ -7260,6 +7283,7 @@ function CliApp() {
|
|
|
7260
7283
|
abortController: null
|
|
7261
7284
|
}));
|
|
7262
7285
|
useCliStore.getState().setIsThinking(false);
|
|
7286
|
+
getProcessHooks()?.fireStop();
|
|
7263
7287
|
bridgePresence.emitEvent({
|
|
7264
7288
|
type: "status",
|
|
7265
7289
|
status: wasAborted ? "idle" : "awaiting_input"
|
|
@@ -8118,8 +8142,9 @@ Multi-line Input:
|
|
|
8118
8142
|
console.clear();
|
|
8119
8143
|
renderBanner();
|
|
8120
8144
|
const model = state.session?.model || state.config?.defaultModel || ChatModels.CLAUDE_4_5_SONNET;
|
|
8145
|
+
const clearPinnedId = process.env.B4M_SESSION_ID || process.env.B4M_RESUME_ID;
|
|
8121
8146
|
const newSession = {
|
|
8122
|
-
id: v4(),
|
|
8147
|
+
id: clearPinnedId ? state.session?.id ?? clearPinnedId : v4(),
|
|
8123
8148
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
8124
8149
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8125
8150
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8458,7 +8483,7 @@ Multi-line Input:
|
|
|
8458
8483
|
const summary = (await state.agent.run(compactionPrompt, { maxIterations: 1 })).finalAnswer;
|
|
8459
8484
|
await state.sessionStore.save(state.session);
|
|
8460
8485
|
const oldSessionName = state.session.name;
|
|
8461
|
-
const newSession = createCompactedSession(state.session, summary, preservedMessages);
|
|
8486
|
+
const newSession = createCompactedSession(state.session, summary, preservedMessages, !!(process.env.B4M_SESSION_ID || process.env.B4M_RESUME_ID));
|
|
8462
8487
|
await logger.initialize(newSession.id);
|
|
8463
8488
|
setState((prev) => ({
|
|
8464
8489
|
...prev,
|
|
@@ -8975,6 +9000,7 @@ Multi-line Input:
|
|
|
8975
9000
|
additionalDirectories: state.additionalDirectories,
|
|
8976
9001
|
featureModulePrompts: newFeaturePrompts || void 0,
|
|
8977
9002
|
planModeFilePath: planFilePathForRebuild,
|
|
9003
|
+
appendSystemPrompt: process.env.B4M_APPEND_SYSTEM_PROMPT,
|
|
8978
9004
|
deferredToolNames: deferredToolRegistry.getDirectoryNames()
|
|
8979
9005
|
}));
|
|
8980
9006
|
const moduleNames = newFeatureRegistry.getModuleNames();
|