@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 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
@@ -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 ConfigStore } from "../ConfigStore-w0o_0xs0.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
3
3
  //#region src/commands/apiCommand.ts
4
4
  /**
5
5
  * External API config command (--api-url / --reset-api)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "../package-C9MYdYCP.mjs";
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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-w0o_0xs0.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
3
3
  //#region src/commands/envCommand.ts
4
4
  /**
5
5
  * Environment switching for the `--dev` / `--prod` launch flags.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as buildSystemPrompt, J as ReActAgent, N as loadContextFiles, P as PermissionManager, Q as SessionStore, S as ApiClient, T as FallbackLlmBackend, U as setWebSocketToolExecutor, X as CheckpointStore, Y as CustomCommandStore, _ 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, q as isReadOnlyTool, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-j5oGazTr.mjs";
3
- import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-w0o_0xs0.mjs";
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 ConfigStore } from "../ConfigStore-w0o_0xs0.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-CtGc4fF2.mjs";
3
3
  //#region src/commands/mcpCommand.ts
4
4
  /**
5
5
  * External MCP commands (b4m mcp list, b4m mcp add, etc.)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "../package-C9MYdYCP.mjs";
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 OAuthClient, A as substituteArguments, B as DEFAULT_THOROUGHNESS, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as buildSystemPrompt, H as registerFeatureModuleTools, I as ALWAYS_DENIED_FOR_AGENTS, J as ReActAgent, K as buildSkillsPromptSection, L as DEFAULT_AGENT_MODEL, M as extractCompactInstructions, N as loadContextFiles, O as isTransientNetworkError, P as PermissionManager, Q as SessionStore, R as DEFAULT_MAX_ITERATIONS, S as ApiClient, T as FallbackLlmBackend, U as setWebSocketToolExecutor, V as clearFeatureModuleTools, W as getPlanModeFilePath, X as CheckpointStore, Y as CustomCommandStore, Z as CommandHistoryStore, _ as createAgentDelegateTool, a as createBlockerTools, at as searchFiles, b as createSkillTool, c as createDecisionStore, d as createFindDefinitionTool, et as hasFileReferences, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, i as createBlockerStore, it as formatFileSize, j as formatStep, k as McpManager, l as formatDecisionsOutput, m as createCoordinateTaskTool, n as createReviewGateTool, nt as searchCommands, o as formatBlockersOutput, ot as warmFileCache, p as createWriteTodosTool, q as isReadOnlyTool, r as formatReviewGatesOutput, rt as mergeCommands, s as createDecisionLogTool, t as createReviewGateStore, tt as processFileReferences, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, x as parseAgentConfig, y as SubagentOrchestrator, z as DEFAULT_RETRY_CONFIG } from "./tools-j5oGazTr.mjs";
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-w0o_0xs0.mjs";
5
- import { t as version } from "./package-C9MYdYCP.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-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 newSession = {
6331
- id: v4(),
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();
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  //#region package.json
3
- var version = "0.15.2";
3
+ var version = "0.16.0";
4
4
  //#endregion
5
5
  export { version as t };