@bike4mind/cli 0.13.0 → 0.14.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.
@@ -387,6 +387,19 @@ if (isDev) {
387
387
  } else {
388
388
  // Production: run compiled JavaScript
389
389
  try {
390
+ // Auto-update on launch (consent-first): when a newer version is available
391
+ // on a writable global prefix, install + re-exec into it BEFORE importing
392
+ // the code-split app — running an install while dist/index.mjs is loaded
393
+ // would crash it. On the default 'ask' preference this prompts the user
394
+ // (Update once / Always / Skip / Never); 'auto' installs silently, 'never'
395
+ // does nothing. Wrapped so the updater can never block launching the CLI.
396
+ try {
397
+ const { maybeAutoUpdateOnLaunch } = await import('../dist/commands/updateCommand.mjs');
398
+ await maybeAutoUpdateOnLaunch();
399
+ } catch {
400
+ // Updater is best-effort — fall through to the current version.
401
+ }
402
+
390
403
  await import(join(__dirname, '../dist/index.mjs'));
391
404
  } catch (error) {
392
405
  console.error('Failed to start CLI:', error);
@@ -1493,6 +1493,27 @@ let SupportedFabFileMimeTypes = /* @__PURE__ */ function(SupportedFabFileMimeTyp
1493
1493
  return SupportedFabFileMimeTypes;
1494
1494
  }({});
1495
1495
  /**
1496
+ * Canonical agent execution status tuple. Shared between the database model
1497
+ * (`AgentExecutionModel`), the client store (`useAgentExecutionStore`), and
1498
+ * wire schemas (`ChildExecutionSnapshotSchema`, etc.) so the value space can't
1499
+ * drift across boundaries. Previously declared in two places — moving it here
1500
+ * lets the Zod schemas type `status` as `z.enum(AGENT_EXECUTION_STATUSES)`
1501
+ * instead of `z.string()`, removing the runtime narrowing the client used to
1502
+ * compensate for the loose contract.
1503
+ */
1504
+ const AGENT_EXECUTION_STATUSES = [
1505
+ "pending",
1506
+ "running",
1507
+ "continuing",
1508
+ "awaiting_permission",
1509
+ "awaiting_subagent",
1510
+ "awaiting_dag_children",
1511
+ "paused",
1512
+ "completed",
1513
+ "failed",
1514
+ "aborted"
1515
+ ];
1516
+ /**
1496
1517
  * ========================================
1497
1518
  * Client to Server Actions
1498
1519
  * ========================================
@@ -2690,11 +2711,36 @@ const PermissionRequestAction = z.object({
2690
2711
  toolInput: z.unknown(),
2691
2712
  iteration: z.number()
2692
2713
  });
2714
+ /**
2715
+ * Persisted snapshot of a non-background child subagent execution. Replayed on
2716
+ * reconnect (#8695) so `SubagentStepNest` can re-render the nested iteration
2717
+ * trace under the parent's `delegate_to_agent` action step after a hard refresh
2718
+ * or once the parent run has terminated. Background children are excluded —
2719
+ * they surface via the header badge + completion toast (#8341), not inline
2720
+ * nesting.
2721
+ *
2722
+ * `agentName` is sourced from the child doc's `subagentConfig` (now persisted
2723
+ * at creation time for every subagent — see `agentExecutor.ts` `onStart`). A
2724
+ * generic fallback ("Subagent") is used for legacy docs without it. Empty
2725
+ * `steps` is valid for an in-flight in-process child whose terminal write
2726
+ * hasn't landed yet — the nest renders the agent header alone in that case.
2727
+ */
2728
+ const ChildExecutionSnapshotSchema = z.object({
2729
+ executionId: z.string(),
2730
+ agentName: z.string(),
2731
+ model: z.string().optional(),
2732
+ status: z.enum(AGENT_EXECUTION_STATUSES),
2733
+ steps: z.array(AgentStepSchema),
2734
+ totalCredits: z.number().optional(),
2735
+ finalAnswer: z.string().optional(),
2736
+ error: z.string().optional(),
2737
+ isTimeout: z.boolean().optional()
2738
+ });
2693
2739
  const ReconnectResultAction = z.object({
2694
2740
  action: z.literal("reconnect_result"),
2695
2741
  found: z.boolean(),
2696
2742
  executionId: z.string().optional(),
2697
- status: z.string().optional(),
2743
+ status: z.enum(AGENT_EXECUTION_STATUSES).optional(),
2698
2744
  pendingPermission: z.object({
2699
2745
  toolName: z.string(),
2700
2746
  toolInput: z.unknown(),
@@ -2703,7 +2749,9 @@ const ReconnectResultAction = z.object({
2703
2749
  totalCreditsUsed: z.number().optional(),
2704
2750
  iterationCount: z.number().optional(),
2705
2751
  steps: z.array(AgentStepSchema).optional(),
2706
- stepsTruncated: z.boolean().optional()
2752
+ stepsTruncated: z.boolean().optional(),
2753
+ children: z.array(ChildExecutionSnapshotSchema).optional(),
2754
+ childrenTruncated: z.boolean().optional()
2707
2755
  });
2708
2756
  z.discriminatedUnion("action", [
2709
2757
  DataSubscribeRequestAction,
@@ -10025,6 +10073,7 @@ const CliConfigSchema = z.object({
10025
10073
  temperature: z.number(),
10026
10074
  autoSave: z.boolean(),
10027
10075
  autoCompact: z.boolean().optional().prefault(true),
10076
+ autoUpdate: z.boolean().optional(),
10028
10077
  theme: z.enum(["light", "dark"]),
10029
10078
  exportFormat: z.enum(["markdown", "json"]),
10030
10079
  maxIterations: z.number().nullable().prefault(10),
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-C3tokQej.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-B9I7UHuG.mjs";
3
3
  //#region src/commands/apiCommand.ts
4
4
  /**
5
5
  * External API config command (--api-url / --reset-api)
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "../package-DNcd24qN.mjs";
3
- import { n as compareSemver, r as fetchLatestVersion } from "../updateChecker-D67NPlS5.mjs";
2
+ import { t as version } from "../package-uvIC6spW.mjs";
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";
6
- import { constants, existsSync, promises } from "fs";
6
+ import { existsSync } from "fs";
7
7
  import { homedir } from "os";
8
8
  import path from "path";
9
9
  //#region src/commands/doctorCommand.ts
@@ -55,20 +55,16 @@ async function handleDoctorCommand() {
55
55
  encoding: "utf-8",
56
56
  timeout: 1e4
57
57
  }).trim();
58
- try {
59
- await promises.access(npmPrefix, constants.W_OK);
60
- results.push({
61
- name: "Global npm path",
62
- status: "pass",
63
- message: `${npmPrefix} (writable)`
64
- });
65
- } catch {
66
- results.push({
67
- name: "Global npm path",
68
- status: "warn",
69
- message: `${npmPrefix} (not writable — may need sudo for updates)`
70
- });
71
- }
58
+ if (await isNpmPrefixWritable(npmPrefix)) results.push({
59
+ name: "Global npm path",
60
+ status: "pass",
61
+ message: `${npmPrefix} (writable)`
62
+ });
63
+ else results.push({
64
+ name: "Global npm path",
65
+ status: "warn",
66
+ message: `${npmPrefix} (not writable — may need sudo for updates)`
67
+ });
72
68
  } catch {
73
69
  results.push({
74
70
  name: "Global npm path",
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-C3tokQej.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-B9I7UHuG.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-BhPOnNo3.mjs";
3
- import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-C3tokQej.mjs";
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-BTPUXUNS.mjs";
3
+ import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-B9I7UHuG.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-C3tokQej.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-B9I7UHuG.mjs";
3
3
  //#region src/commands/mcpCommand.ts
4
4
  /**
5
5
  * External MCP commands (b4m mcp list, b4m mcp add, etc.)
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "../package-DNcd24qN.mjs";
3
- import { i as forceCheckForUpdate } from "../updateChecker-D67NPlS5.mjs";
2
+ import { t as version } from "../package-uvIC6spW.mjs";
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
- import { execSync } from "child_process";
5
+ import { execSync, spawnSync } from "child_process";
6
+ import { createInterface } from "readline";
6
7
  //#region src/commands/updateCommand.ts
7
8
  /**
8
9
  * External update command (b4m update)
9
10
  * Checks for and installs CLI updates.
10
11
  * Runs outside the interactive CLI session.
11
12
  */
12
- const INSTALL_CMD = "npm install -g @bike4mind/cli@latest";
13
13
  function runGlobalInstall() {
14
14
  try {
15
15
  execSync(INSTALL_CMD, {
@@ -25,6 +25,121 @@ function runGlobalInstall() {
25
25
  return false;
26
26
  }
27
27
  }
28
+ /**
29
+ * Ask the user how to handle an available update (the `'ask'` preference).
30
+ * Plain readline (not Ink) because this runs in the bin bootstrap before the
31
+ * code-split app loads — the only window in which it's safe to install.
32
+ *
33
+ * Maps the [U/a/s/n] keys to a choice; an empty line defaults to `'update'`
34
+ * (capital `U` in the prompt is the default). The interface is fully torn down
35
+ * before returning so a Skip/Never fall-through leaves stdin clean for Ink.
36
+ */
37
+ async function promptUpdateChoice(currentVersion, latestVersion) {
38
+ if (!process.stdin.isTTY) return "skip";
39
+ console.log(`\n\x1b[33m ⬆ Update available: v${currentVersion} → v${latestVersion}\x1b[0m\n`);
40
+ console.log(" How would you like to handle updates?");
41
+ console.log(" [U] Update once (install now)");
42
+ console.log(" [A] Always (auto-update silently from now on)");
43
+ console.log(" [S] Skip (not now — ask again next launch)");
44
+ console.log(" [N] Never (manual `b4m update` only)\n");
45
+ const rl = createInterface({
46
+ input: process.stdin,
47
+ output: process.stdout
48
+ });
49
+ let answer;
50
+ try {
51
+ answer = await new Promise((resolve) => {
52
+ rl.on("close", () => resolve(null));
53
+ rl.question(" Choose [U/a/s/n]: ", resolve);
54
+ });
55
+ } finally {
56
+ rl.close();
57
+ }
58
+ if (answer === null) return "skip";
59
+ switch (answer.trim().toLowerCase()) {
60
+ case "a": return "always";
61
+ case "s": return "skip";
62
+ case "n": return "never";
63
+ default: return "update";
64
+ }
65
+ }
66
+ /**
67
+ * Install the latest version and re-exec into it so the session the user just
68
+ * opened runs the new code — zero version skew, no mid-session file-swap risk.
69
+ *
70
+ * spawnSync inherits the TTY so Ink renders normally in the child; the guard
71
+ * env prevents an update loop. npm install overwrites the global package in
72
+ * place, so re-running the same argv[1] bin path picks up the new code. On a
73
+ * successful spawn this never returns (it calls process.exit); it only returns
74
+ * when the install itself fails, so the caller falls through to the current
75
+ * version.
76
+ */
77
+ function installAndReexec(currentVersion, latestVersion) {
78
+ console.log(`\x1b[33m ⬆ Updating v${currentVersion} → v${latestVersion}…\x1b[0m`);
79
+ if (!runGlobalInstall()) return;
80
+ const child = spawnSync(process.argv[0], process.argv.slice(1), {
81
+ stdio: "inherit",
82
+ env: {
83
+ ...process.env,
84
+ [REEXEC_GUARD_ENV]: "1"
85
+ }
86
+ });
87
+ if (child.error) {
88
+ console.error(`\n Failed to launch the updated version: ${child.error.message}`);
89
+ console.error(" Re-run b4m to use the newly installed version.");
90
+ process.exit(1);
91
+ }
92
+ process.exit(child.status ?? (child.signal ? 1 : 0));
93
+ }
94
+ /**
95
+ * Auto-update on launch (Claude-Code-style), consent-first.
96
+ *
97
+ * Called from the bin bootstrap on the interactive path *before* the
98
+ * code-split app (`dist/index.mjs`) is imported — the only safe install window
99
+ * (running an install while dist chunks are loaded would crash them).
100
+ *
101
+ * Behaviour by `autoUpdate` preference once an update is available on a
102
+ * writable prefix:
103
+ * - `'auto'` → install silently and re-exec into the new version.
104
+ * - `'never'` → do nothing (the startup notify banner still informs).
105
+ * - `'ask'` → prompt the user: Update once / Always (persist `auto`) /
106
+ * Skip (ask again next launch) / Never (persist `never`).
107
+ *
108
+ * It is a safe no-op (returns without installing) when: already re-exec'd this
109
+ * launch, disabled via `B4M_AUTO_UPDATE=0`, not attached to a TTY, no update is
110
+ * available, the prefix needs sudo (notify banner informs instead), or the
111
+ * user declines the prompt. Any install/network failure is swallowed so the
112
+ * user is never blocked from launching the current version.
113
+ */
114
+ async function maybeAutoUpdateOnLaunch() {
115
+ if (!shouldAttemptAutoUpdate({ isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY) })) return;
116
+ const preference = await getAutoUpdatePreference();
117
+ if (preference === "never") return;
118
+ const currentVersion = version;
119
+ let result;
120
+ let timeoutHandle;
121
+ try {
122
+ result = await Promise.race([checkForUpdate(currentVersion), new Promise((resolve) => {
123
+ timeoutHandle = setTimeout(() => resolve(null), 3e3);
124
+ })]);
125
+ } catch {
126
+ return;
127
+ } finally {
128
+ if (timeoutHandle) clearTimeout(timeoutHandle);
129
+ }
130
+ if (!result?.updateAvailable) return;
131
+ if (!await isNpmPrefixWritable()) return;
132
+ if (preference === "ask") {
133
+ const choice = await promptUpdateChoice(currentVersion, result.latestVersion);
134
+ if (choice === "skip") return;
135
+ if (choice === "never") {
136
+ await setAutoUpdatePreference(false);
137
+ return;
138
+ }
139
+ if (choice === "always") await setAutoUpdatePreference(true);
140
+ }
141
+ installAndReexec(currentVersion, result.latestVersion);
142
+ }
28
143
  async function handleUpdateCommand() {
29
144
  const currentVersion = version;
30
145
  console.log(`Current version: v${currentVersion}`);
@@ -56,4 +171,4 @@ async function handleUpdateCommand() {
56
171
  else console.log("\nRipgrep restored.");
57
172
  }
58
173
  //#endregion
59
- export { handleUpdateCommand };
174
+ export { handleUpdateCommand, maybeAutoUpdateOnLaunch, promptUpdateChoice };
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
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-BhPOnNo3.mjs";
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-BTPUXUNS.mjs";
3
3
  import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-DV5s-qni.mjs";
4
- import { Ut as validateJupyterKernelName, Wt as validateNotebookPath$1, g as CREDIT_DEDUCT_TRANSACTION_TYPES, i as getEnvironmentName, n as logger, r as getApiUrl, t as ConfigStore, v as ChatModels } from "./ConfigStore-C3tokQej.mjs";
5
- import { t as version } from "./package-DNcd24qN.mjs";
6
- import { t as checkForUpdate } from "./updateChecker-D67NPlS5.mjs";
4
+ import { Ut as validateJupyterKernelName, Wt as validateNotebookPath$1, g as CREDIT_DEDUCT_TRANSACTION_TYPES, i as getEnvironmentName, n as logger, r as getApiUrl, t as ConfigStore, v as ChatModels } from "./ConfigStore-B9I7UHuG.mjs";
5
+ import { t as version } from "./package-uvIC6spW.mjs";
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";
9
9
  import { execSync } from "child_process";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  //#region package.json
3
- var version = "0.13.0";
3
+ var version = "0.14.0";
4
4
  //#endregion
5
5
  export { version as t };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { $ as ProjectEvents, A as GenerateImageToolCallSchema, At as dayjsConfig_default, B as InviteEvents, Bt as sanitizeTelemetryError, C as ElabsEvents, Ct as UnauthorizedError, D as ForbiddenError, Dt as VideoModels, E as FileEvents, Et as VideoGenerationUsageTransaction, F as ImageEditUsageTransaction, Ft as isGPTImage2Model, G as ModalEvents, Gt as buildRateLimitLogEntry, H as KnowledgeType, Ht as settingsMap, I as ImageGenerationUsageTransaction, It as isGPTImageModel, J as OpenAIEmbeddingModel, Jt as parseRateLimitHeaders, K as ModelBackend, Kt as extractSnippetMeta, L as ImageModels, Lt as isZodError, M as GenericCreditDeductTransaction, Mt as getDataLakeTags, N as HTTPError, Nt as getMcpProviderMetadata, O as FriendshipEvents, Ot as XAI_IMAGE_MODELS, P as HttpStatus, Pt as getViewById, Q as ProfileEvents, R as InboxEvents, Rt as obfuscateApiKey, S as DashboardParamsSchema, St as UiNavigationEvents, T as FeedbackEvents, Tt as VIDEO_SIZE_CONSTRAINTS, U as LLMEvents, V as InviteType, Vt as secureParameters, W as MiscEvents, X as Permission, Y as OpenAIImageGenerationInput, Yt as CollectionType, Z as PermissionDeniedError, _ as ChatCompletionCreateInputSchema, _t as TaskScheduleHandler, a as ALERT_THRESHOLDS, at as ReceivedCreditTransaction, b as CompletionApiUsageTransaction, bt as ToolUsageTransaction, c as ApiKeyScope, ct as ResearchModeParamsSchema, d as ArtifactTypeSchema, dt as ResearchTaskType, et as PromptIntentSchema, f as AuthEvents, ft as SessionEvents, gt as TagType, h as BadRequestError, ht as SupportedFabFileMimeTypes, it as RealtimeVoiceUsageTransaction, j as GenericCreditAddTransaction, jt as getAccessibleDataLakes, k as GEMINI_IMAGE_MODELS, kt as b4mLLMTools, l as ApiKeyType, lt as ResearchTaskExecutionType, m as BFL_SAFETY_TOLERANCE, mt as SubscriptionCreditTransaction, n as logger, nt as PurchaseTransaction, o as AiEvents, ot as RechartsChartTypeList, p as BFL_IMAGE_MODELS, pt as SpeechToTextUsageTransaction, q as NotFoundError, qt as isNearLimit, rt as QuestMasterParamsSchema, s as ApiKeyEvents, st as RegInviteEvents, t as ConfigStore, tt as PromptMetaZodSchema, u as AppFileEvents, ut as ResearchTaskPeriodicFrequencyType, v as ChatModels, vt as TextGenerationUsageTransaction, w as FavoriteDocumentType, wt as UnprocessableEntityError, x as CorruptedFileError, xt as TransferCreditTransaction, y as ClaudeArtifactMimeTypes, yt as TooManyRequestsError, z as InternalServerError, zt as resolveNavigationIntents } from "./ConfigStore-C3tokQej.mjs";
2
+ import { $ as ProjectEvents, A as GenerateImageToolCallSchema, At as dayjsConfig_default, B as InviteEvents, Bt as sanitizeTelemetryError, C as ElabsEvents, Ct as UnauthorizedError, D as ForbiddenError, Dt as VideoModels, E as FileEvents, Et as VideoGenerationUsageTransaction, F as ImageEditUsageTransaction, Ft as isGPTImage2Model, G as ModalEvents, Gt as buildRateLimitLogEntry, H as KnowledgeType, Ht as settingsMap, I as ImageGenerationUsageTransaction, It as isGPTImageModel, J as OpenAIEmbeddingModel, Jt as parseRateLimitHeaders, K as ModelBackend, Kt as extractSnippetMeta, L as ImageModels, Lt as isZodError, M as GenericCreditDeductTransaction, Mt as getDataLakeTags, N as HTTPError, Nt as getMcpProviderMetadata, O as FriendshipEvents, Ot as XAI_IMAGE_MODELS, P as HttpStatus, Pt as getViewById, Q as ProfileEvents, R as InboxEvents, Rt as obfuscateApiKey, S as DashboardParamsSchema, St as UiNavigationEvents, T as FeedbackEvents, Tt as VIDEO_SIZE_CONSTRAINTS, U as LLMEvents, V as InviteType, Vt as secureParameters, W as MiscEvents, X as Permission, Y as OpenAIImageGenerationInput, Yt as CollectionType, Z as PermissionDeniedError, _ as ChatCompletionCreateInputSchema, _t as TaskScheduleHandler, a as ALERT_THRESHOLDS, at as ReceivedCreditTransaction, b as CompletionApiUsageTransaction, bt as ToolUsageTransaction, c as ApiKeyScope, ct as ResearchModeParamsSchema, d as ArtifactTypeSchema, dt as ResearchTaskType, et as PromptIntentSchema, f as AuthEvents, ft as SessionEvents, gt as TagType, h as BadRequestError, ht as SupportedFabFileMimeTypes, it as RealtimeVoiceUsageTransaction, j as GenericCreditAddTransaction, jt as getAccessibleDataLakes, k as GEMINI_IMAGE_MODELS, kt as b4mLLMTools, l as ApiKeyType, lt as ResearchTaskExecutionType, m as BFL_SAFETY_TOLERANCE, mt as SubscriptionCreditTransaction, n as logger, nt as PurchaseTransaction, o as AiEvents, ot as RechartsChartTypeList, p as BFL_IMAGE_MODELS, pt as SpeechToTextUsageTransaction, q as NotFoundError, qt as isNearLimit, rt as QuestMasterParamsSchema, s as ApiKeyEvents, st as RegInviteEvents, t as ConfigStore, tt as PromptMetaZodSchema, u as AppFileEvents, ut as ResearchTaskPeriodicFrequencyType, v as ChatModels, vt as TextGenerationUsageTransaction, w as FavoriteDocumentType, wt as UnprocessableEntityError, x as CorruptedFileError, xt as TransferCreditTransaction, y as ClaudeArtifactMimeTypes, yt as TooManyRequestsError, z as InternalServerError, zt as resolveNavigationIntents } from "./ConfigStore-B9I7UHuG.mjs";
3
3
  import { a as isUserLockedOut, c as userCanDisableMFA, d as userRequiresMFA, f as verifyBackupCode, i as getLockoutTimeRemaining, l as userEligibleForMFA, n as generateBackupCodes, o as recordFailedAttempt, p as verifyTOTPToken, r as generateTOTPSetup, s as shouldResetFailedAttempts, t as clearFailedAttempts, u as userHasMFAConfigured } from "./utils-PpNti-tY.mjs";
4
4
  import { n as isPathAllowed, t as assertPathAllowed } from "./pathValidation-D8tjkQXE-1HwvsuYT.mjs";
5
- import { t as version } from "./package-DNcd24qN.mjs";
5
+ import { t as version } from "./package-uvIC6spW.mjs";
6
6
  import { execFile, execFileSync, spawn } from "child_process";
7
7
  import crypto, { createHash, randomBytes } from "crypto";
8
8
  import { existsSync, promises, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -12233,8 +12233,8 @@ No markdown, no explanation, no code blocks — just the raw JSON object.`;
12233
12233
  "Minimize: sum(error^2)"
12234
12234
  ].join("\n");
12235
12235
  //#endregion
12236
- //#region ../../b4m-core/services/dist/tools-CtLkSQLQ.mjs
12237
- async function performDeepResearch(context, params, config) {
12236
+ //#region ../../b4m-core/services/dist/tools-CTdKqJPY.mjs
12237
+ async function performDeepResearch(context, params, config = {}) {
12238
12238
  const maxDepth = config.maxDepth || 7;
12239
12239
  const duration = config.duration || 4.5;
12240
12240
  const startTime = Date.now();
@@ -12562,7 +12562,7 @@ const deepResearchTool = {
12562
12562
  toolFn: async (value) => {
12563
12563
  const params = value;
12564
12564
  await context.onStart?.("deep_research", params);
12565
- const result = await performDeepResearch(context, { topic: params.topic }, config);
12565
+ const result = await performDeepResearch(context, { topic: params.topic }, config ?? {});
12566
12566
  return JSON.stringify(result);
12567
12567
  },
12568
12568
  toolSchema: {
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from "child_process";
3
+ import { constants, promises } from "fs";
4
+ import { homedir } from "os";
5
+ import path from "path";
6
+ import axios from "axios";
7
+ //#region src/utils/updateChecker.ts
8
+ /**
9
+ * Update checker utility for B4M CLI
10
+ * Checks the NPM registry for newer versions and caches results.
11
+ * Used by the startup banner, `b4m update`, and `b4m doctor` commands.
12
+ */
13
+ const CACHE_FILE = path.join(homedir(), ".bike4mind", "update-check.json");
14
+ const CACHE_TTL_MS = 1440 * 60 * 1e3;
15
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org/@bike4mind/cli/latest";
16
+ const FETCH_TIMEOUT_MS = 5e3;
17
+ /**
18
+ * Canonical global-install command for the CLI.
19
+ * Single source of truth shared by the manual `b4m update` path and the
20
+ * auto-update-on-launch bootstrap so they can never drift.
21
+ */
22
+ const INSTALL_CMD = "npm install -g @bike4mind/cli@latest";
23
+ /**
24
+ * Env var set on the re-exec'd child after an auto-update so it skips the
25
+ * update block and can't loop. Single source of truth for both the reader
26
+ * (shouldAttemptAutoUpdate) and the writer (maybeAutoUpdateOnLaunch).
27
+ */
28
+ const REEXEC_GUARD_ENV = "B4M_UPDATED_REEXEC";
29
+ /**
30
+ * Check whether the global npm prefix is writable by the current user.
31
+ * When it isn't (e.g. Homebrew/system node), `npm install -g` needs sudo —
32
+ * which an unattended auto-updater cannot provide — so callers should fall
33
+ * back to a manual-update notice instead of attempting a silent install.
34
+ * Non-throwing — returns false on any error.
35
+ *
36
+ * Pass `prefix` to reuse an already-resolved `npm config get prefix` (each
37
+ * `execSync` is ~50-200ms); omit it and the prefix is resolved internally.
38
+ */
39
+ async function isNpmPrefixWritable(prefix) {
40
+ try {
41
+ const resolved = prefix ?? execSync("npm config get prefix", {
42
+ encoding: "utf-8",
43
+ timeout: 1e4
44
+ }).trim();
45
+ if (!resolved) return false;
46
+ await promises.access(resolved, constants.W_OK);
47
+ return true;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+ /**
53
+ * Cheap, synchronous pre-checks gating auto-update-on-launch. Excludes the
54
+ * async config-file and network lookups so it stays easy to unit-test.
55
+ * Returns false (skip the update) when already re-exec'd this launch, opted
56
+ * out via env, or not attached to an interactive TTY.
57
+ */
58
+ function shouldAttemptAutoUpdate(opts) {
59
+ const env = opts.env ?? process.env;
60
+ if (env["B4M_UPDATED_REEXEC"] === "1") return false;
61
+ if (env.B4M_AUTO_UPDATE === "0") return false;
62
+ if (!opts.isTTY) return false;
63
+ return true;
64
+ }
65
+ const CONFIG_FILE = path.join(homedir(), ".bike4mind", "config.json");
66
+ /**
67
+ * Read the user's tri-state `autoUpdate` preference from the global config.
68
+ * Kept deliberately lightweight (no Zod / ConfigStore) because it runs in the
69
+ * bin bootstrap before the app loads. Defaults to `'ask'` (consent-first) when
70
+ * the file or flag is absent/unreadable.
71
+ */
72
+ async function getAutoUpdatePreference() {
73
+ try {
74
+ const raw = await promises.readFile(CONFIG_FILE, "utf-8");
75
+ const value = JSON.parse(raw)?.preferences?.autoUpdate;
76
+ if (value === true) return "auto";
77
+ if (value === false) return "never";
78
+ return "ask";
79
+ } catch {
80
+ return "ask";
81
+ }
82
+ }
83
+ /**
84
+ * Persist the user's `autoUpdate` choice (`true` = always, `false` = never).
85
+ *
86
+ * Writes the **global** config file directly — the mirror of how
87
+ * `getAutoUpdatePreference()` reads it — rather than routing through
88
+ * `ConfigStore.save()`. `ConfigStore.load()` merges global → project → local,
89
+ * and `ConfigStore.save()` writes that merged result back to the global path;
90
+ * answering "Always"/"Never" inside a project would therefore bake the
91
+ * project's other overrides (model, theme, temperature, …) into the user's
92
+ * global config as a silent side effect. This call fires during the bin
93
+ * bootstrap any time an update is available, so that blast radius is
94
+ * unacceptable. A direct read-merge-write of only `preferences.autoUpdate`
95
+ * avoids it. If the on-disk file is schema-incomplete, the next
96
+ * `ConfigStore.load()` backfills defaults harmlessly — exactly as
97
+ * `getAutoUpdatePreference` already tolerates a partial file. Best-effort:
98
+ * a failure is swallowed (we simply ask again next launch rather than blocking).
99
+ */
100
+ async function setAutoUpdatePreference(value) {
101
+ try {
102
+ let raw = {};
103
+ try {
104
+ const parsed = JSON.parse(await promises.readFile(CONFIG_FILE, "utf-8"));
105
+ if (parsed && typeof parsed === "object") raw = parsed;
106
+ } catch {}
107
+ const prefs = raw.preferences && typeof raw.preferences === "object" ? raw.preferences : {};
108
+ raw.preferences = {
109
+ ...prefs,
110
+ autoUpdate: value
111
+ };
112
+ await promises.mkdir(path.dirname(CONFIG_FILE), { recursive: true });
113
+ await promises.writeFile(CONFIG_FILE, JSON.stringify(raw, null, 2), "utf-8");
114
+ await promises.chmod(CONFIG_FILE, 384);
115
+ } catch {}
116
+ }
117
+ /**
118
+ * Compare two semver strings (MAJOR.MINOR.PATCH).
119
+ * Returns -1 if a < b, 0 if equal, 1 if a > b.
120
+ */
121
+ function compareSemver(a, b) {
122
+ const partsA = a.split(".").map(Number);
123
+ const partsB = b.split(".").map(Number);
124
+ for (let i = 0; i < 3; i++) {
125
+ const segA = partsA[i] ?? 0;
126
+ const segB = partsB[i] ?? 0;
127
+ if (segA < segB) return -1;
128
+ if (segA > segB) return 1;
129
+ }
130
+ return 0;
131
+ }
132
+ /**
133
+ * Fetch the latest published version from the NPM registry.
134
+ * Returns the version string or null on any error.
135
+ */
136
+ async function fetchLatestVersion() {
137
+ try {
138
+ const version = (await axios.get(NPM_REGISTRY_URL, {
139
+ timeout: FETCH_TIMEOUT_MS,
140
+ headers: { Accept: "application/json" }
141
+ })).data?.version;
142
+ return typeof version === "string" ? version : null;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+ /**
148
+ * Read the cached update check result.
149
+ */
150
+ async function readCache() {
151
+ try {
152
+ const data = await promises.readFile(CACHE_FILE, "utf-8");
153
+ const parsed = JSON.parse(data);
154
+ if (parsed && typeof parsed.lastChecked === "string" && typeof parsed.latestVersion === "string" && typeof parsed.currentVersion === "string") return parsed;
155
+ return null;
156
+ } catch {
157
+ return null;
158
+ }
159
+ }
160
+ /**
161
+ * Write the update check result to cache.
162
+ */
163
+ async function writeCache(cache) {
164
+ try {
165
+ await promises.mkdir(path.dirname(CACHE_FILE), { recursive: true });
166
+ await promises.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
167
+ } catch {}
168
+ }
169
+ /**
170
+ * Check for updates using cache when fresh.
171
+ * Non-throwing — returns null on any error.
172
+ */
173
+ async function checkForUpdate(currentVersion) {
174
+ try {
175
+ const cache = await readCache();
176
+ if (cache && cache.currentVersion === currentVersion) {
177
+ if (Date.now() - new Date(cache.lastChecked).getTime() < CACHE_TTL_MS) return {
178
+ currentVersion,
179
+ latestVersion: cache.latestVersion,
180
+ updateAvailable: compareSemver(cache.latestVersion, currentVersion) > 0
181
+ };
182
+ }
183
+ const latestVersion = await fetchLatestVersion();
184
+ if (!latestVersion) return null;
185
+ await writeCache({
186
+ lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
187
+ latestVersion,
188
+ currentVersion
189
+ });
190
+ return {
191
+ currentVersion,
192
+ latestVersion,
193
+ updateAvailable: compareSemver(latestVersion, currentVersion) > 0
194
+ };
195
+ } catch {
196
+ return null;
197
+ }
198
+ }
199
+ /**
200
+ * Force-check for updates (ignores cache).
201
+ * Used by `b4m update` and `b4m doctor`.
202
+ */
203
+ async function forceCheckForUpdate(currentVersion) {
204
+ const latestVersion = await fetchLatestVersion();
205
+ if (!latestVersion) return null;
206
+ await writeCache({
207
+ lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
208
+ latestVersion,
209
+ currentVersion
210
+ });
211
+ return {
212
+ currentVersion,
213
+ latestVersion,
214
+ updateAvailable: compareSemver(latestVersion, currentVersion) > 0
215
+ };
216
+ }
217
+ //#endregion
218
+ export { fetchLatestVersion as a, isNpmPrefixWritable as c, compareSemver as i, setAutoUpdatePreference as l, REEXEC_GUARD_ENV as n, forceCheckForUpdate as o, checkForUpdate as r, getAutoUpdatePreference as s, INSTALL_CMD as t, shouldAttemptAutoUpdate as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "description": "Interactive CLI tool for Bike4Mind with ReAct agents",
6
6
  "license": "UNLICENSED",
@@ -107,8 +107,8 @@
107
107
  "zod": "^4.4.3",
108
108
  "zod-validation-error": "^5.0.0",
109
109
  "zustand": "^5.0.13",
110
- "@bike4mind/fab-pipeline": "0.2.9",
111
- "@bike4mind/llm-adapters": "0.3.4",
110
+ "@bike4mind/fab-pipeline": "0.2.10",
111
+ "@bike4mind/llm-adapters": "0.3.5",
112
112
  "@bike4mind/observability": "0.1.0"
113
113
  },
114
114
  "devDependencies": {
@@ -124,11 +124,11 @@
124
124
  "tsx": "^4.22.3",
125
125
  "typescript": "^5.9.3",
126
126
  "vitest": "^4.1.7",
127
- "@bike4mind/agents": "0.12.0",
128
- "@bike4mind/common": "2.107.0",
129
- "@bike4mind/mcp": "1.37.22",
130
- "@bike4mind/utils": "2.23.10",
131
- "@bike4mind/services": "2.93.0"
127
+ "@bike4mind/agents": "0.13.0",
128
+ "@bike4mind/common": "2.107.1",
129
+ "@bike4mind/mcp": "1.37.23",
130
+ "@bike4mind/services": "0.0.0-changeset-release-main-20260609021710",
131
+ "@bike4mind/utils": "2.23.11"
132
132
  },
133
133
  "optionalDependencies": {
134
134
  "@vscode/ripgrep": "^1.18.0"
@@ -1,117 +0,0 @@
1
- #!/usr/bin/env node
2
- import { promises } from "fs";
3
- import { homedir } from "os";
4
- import path from "path";
5
- import axios from "axios";
6
- //#region src/utils/updateChecker.ts
7
- /**
8
- * Update checker utility for B4M CLI
9
- * Checks the NPM registry for newer versions and caches results.
10
- * Used by the startup banner, `b4m update`, and `b4m doctor` commands.
11
- */
12
- const CACHE_FILE = path.join(homedir(), ".bike4mind", "update-check.json");
13
- const CACHE_TTL_MS = 1440 * 60 * 1e3;
14
- const NPM_REGISTRY_URL = "https://registry.npmjs.org/@bike4mind/cli/latest";
15
- const FETCH_TIMEOUT_MS = 5e3;
16
- /**
17
- * Compare two semver strings (MAJOR.MINOR.PATCH).
18
- * Returns -1 if a < b, 0 if equal, 1 if a > b.
19
- */
20
- function compareSemver(a, b) {
21
- const partsA = a.split(".").map(Number);
22
- const partsB = b.split(".").map(Number);
23
- for (let i = 0; i < 3; i++) {
24
- const segA = partsA[i] ?? 0;
25
- const segB = partsB[i] ?? 0;
26
- if (segA < segB) return -1;
27
- if (segA > segB) return 1;
28
- }
29
- return 0;
30
- }
31
- /**
32
- * Fetch the latest published version from the NPM registry.
33
- * Returns the version string or null on any error.
34
- */
35
- async function fetchLatestVersion() {
36
- try {
37
- const version = (await axios.get(NPM_REGISTRY_URL, {
38
- timeout: FETCH_TIMEOUT_MS,
39
- headers: { Accept: "application/json" }
40
- })).data?.version;
41
- return typeof version === "string" ? version : null;
42
- } catch {
43
- return null;
44
- }
45
- }
46
- /**
47
- * Read the cached update check result.
48
- */
49
- async function readCache() {
50
- try {
51
- const data = await promises.readFile(CACHE_FILE, "utf-8");
52
- const parsed = JSON.parse(data);
53
- if (parsed && typeof parsed.lastChecked === "string" && typeof parsed.latestVersion === "string" && typeof parsed.currentVersion === "string") return parsed;
54
- return null;
55
- } catch {
56
- return null;
57
- }
58
- }
59
- /**
60
- * Write the update check result to cache.
61
- */
62
- async function writeCache(cache) {
63
- try {
64
- await promises.mkdir(path.dirname(CACHE_FILE), { recursive: true });
65
- await promises.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
66
- } catch {}
67
- }
68
- /**
69
- * Check for updates using cache when fresh.
70
- * Non-throwing — returns null on any error.
71
- */
72
- async function checkForUpdate(currentVersion) {
73
- try {
74
- const cache = await readCache();
75
- if (cache && cache.currentVersion === currentVersion) {
76
- if (Date.now() - new Date(cache.lastChecked).getTime() < CACHE_TTL_MS) return {
77
- currentVersion,
78
- latestVersion: cache.latestVersion,
79
- updateAvailable: compareSemver(cache.latestVersion, currentVersion) > 0
80
- };
81
- }
82
- const latestVersion = await fetchLatestVersion();
83
- if (!latestVersion) return null;
84
- await writeCache({
85
- lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
86
- latestVersion,
87
- currentVersion
88
- });
89
- return {
90
- currentVersion,
91
- latestVersion,
92
- updateAvailable: compareSemver(latestVersion, currentVersion) > 0
93
- };
94
- } catch {
95
- return null;
96
- }
97
- }
98
- /**
99
- * Force-check for updates (ignores cache).
100
- * Used by `b4m update` and `b4m doctor`.
101
- */
102
- async function forceCheckForUpdate(currentVersion) {
103
- const latestVersion = await fetchLatestVersion();
104
- if (!latestVersion) return null;
105
- await writeCache({
106
- lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
107
- latestVersion,
108
- currentVersion
109
- });
110
- return {
111
- currentVersion,
112
- latestVersion,
113
- updateAvailable: compareSemver(latestVersion, currentVersion) > 0
114
- };
115
- }
116
- //#endregion
117
- export { forceCheckForUpdate as i, compareSemver as n, fetchLatestVersion as r, checkForUpdate as t };