@bike4mind/cli 0.10.3 → 0.12.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
@@ -74,6 +74,8 @@ b4m [options]
74
74
  ```
75
75
 
76
76
  **Available flags:**
77
+ - `--dev` - Point the CLI at the local dev server (`http://localhost:3001`) and remember it
78
+ - `--prod` - Point the CLI at Bike4Mind production and remember it
77
79
  - `--verbose`, `-v` - Show debug logs in console (useful for troubleshooting)
78
80
  - `--help`, `-h` - Show help information
79
81
  - `--version`, `-V` - Show CLI version
@@ -90,6 +92,28 @@ b4m --version
90
92
  b4m --help
91
93
  ```
92
94
 
95
+ ### Switching environments (`--dev` / `--prod`)
96
+
97
+ Flip which backend the CLI talks to without editing config by hand:
98
+
99
+ ```bash
100
+ b4m --dev # local dev server (http://localhost:3001)
101
+ b4m --prod # Bike4Mind production
102
+ b4m # reuses whichever environment you last selected (sticky)
103
+ ```
104
+
105
+ The choice is persisted to `~/.bike4mind/config.json`, so a bare `b4m` always
106
+ reopens the environment you last chose. Both single- and double-dash forms work
107
+ (`-dev`/`--dev`, `-prod`/`--prod`); `--local` is an alias for `--dev`.
108
+
109
+ Auth tokens are cached **per environment**, so switching back and forth does not
110
+ force a re-login — each environment remembers its own session. The first time you
111
+ visit a new environment you'll be prompted to `/login`. The active environment is
112
+ shown in the startup banner (`🌍 API Environment: …`).
113
+
114
+ > For a one-off custom/self-hosted URL, use the in-session `/set-api <url>` command
115
+ > (see [API Configuration](#api-configuration) below).
116
+
93
117
  ## Commands
94
118
 
95
119
  While in interactive mode:
@@ -137,6 +161,11 @@ Authentication tokens are securely stored in your config file with restricted pe
137
161
 
138
162
  By default, the CLI connects to the main Bike4Mind service at `https://app.bike4mind.com`.
139
163
 
164
+ **Quick switch between local dev and production:** use the `b4m --dev` / `b4m --prod`
165
+ launch flags (see [Switching environments](#switching-environments---dev----prod)).
166
+ They persist your choice and cache auth per-environment. The `/set-api`, `/reset-api`,
167
+ and `/api-info` commands below operate on the same setting from inside a session.
168
+
140
169
  **For Self-Hosted Instances:**
141
170
 
142
171
  If your organization runs a self-hosted Bike4Mind instance, connect to it using:
@@ -33,8 +33,43 @@ const require = createRequire(import.meta.url);
33
33
  // whichever package.json it discovers first, which is not necessarily ours.
34
34
  const { version: cliVersion } = require('../package.json');
35
35
 
36
+ // --- API environment flags (--dev / --prod) ---
37
+ // Intercept these BEFORE yargs parses argv. They're accepted with either a
38
+ // single or double dash (e.g. `b4m -prod` and `b4m --prod` both work), but
39
+ // single-dash multi-char tokens would otherwise be split into clustered short
40
+ // flags by yargs (`-prod` → `-p -r -o -d`, colliding with -p/--prompt). We pull
41
+ // them out here, record the target, and strip them so yargs sees a clean argv.
42
+ const ENV_FLAG_MAP = {
43
+ '--dev': 'dev', '-dev': 'dev', '--local': 'dev', '-local': 'dev',
44
+ '--prod': 'prod', '-prod': 'prod', '--production': 'prod', '-production': 'prod',
45
+ };
46
+ let envTarget = null;
47
+ {
48
+ const cleaned = [];
49
+ for (const token of process.argv.slice(2)) {
50
+ if (Object.prototype.hasOwnProperty.call(ENV_FLAG_MAP, token)) {
51
+ envTarget = ENV_FLAG_MAP[token]; // last one wins
52
+ } else {
53
+ cleaned.push(token);
54
+ }
55
+ }
56
+ // Rebuild argv without the env tokens so yargs doesn't choke on them.
57
+ process.argv = [process.argv[0], process.argv[1], ...cleaned];
58
+ }
59
+
36
60
  // Parse CLI arguments
37
61
  const argv = await yargs(hideBin(process.argv))
62
+ // --dev / --prod are declared here ONLY so they appear in `--help`. The actual
63
+ // handling is the pre-yargs argv interception above — these yargs-side values
64
+ // (`argv.dev` / `argv.prod`) are never read.
65
+ .option('dev', {
66
+ type: 'boolean',
67
+ description: 'Point the CLI at the local dev server (http://localhost:3001) and remember it',
68
+ })
69
+ .option('prod', {
70
+ type: 'boolean',
71
+ description: 'Point the CLI at Bike4Mind production and remember it',
72
+ })
38
73
  .option('verbose', {
39
74
  alias: 'v',
40
75
  type: 'boolean',
@@ -204,6 +239,28 @@ if (argv['reset-api'] || argv['api-url'] !== undefined) {
204
239
  }
205
240
  }
206
241
 
242
+ // Apply --dev / --prod environment switch before anything connects to a server.
243
+ // This persists the choice (sticky: a bare `b4m` reuses the last selection) and
244
+ // swaps in that environment's cached auth token.
245
+ if (envTarget) {
246
+ try {
247
+ let applyEnvironmentFlag;
248
+
249
+ if (isDev) {
250
+ const { register } = require('tsx/esm/api');
251
+ register();
252
+ ({ applyEnvironmentFlag } = await import('../src/commands/envCommand.ts'));
253
+ } else {
254
+ ({ applyEnvironmentFlag } = await import('../dist/commands/envCommand.mjs'));
255
+ }
256
+
257
+ await applyEnvironmentFlag(envTarget);
258
+ } catch (error) {
259
+ console.error('Failed to switch API environment:', error.message);
260
+ process.exit(1);
261
+ }
262
+ }
263
+
207
264
  // Handle headless mode (-p / --prompt flag)
208
265
  // Must be done after isDev detection to use correct import path
209
266
  if (argv.prompt !== undefined) {
@@ -987,6 +987,13 @@ let CreditHolderType = /* @__PURE__ */ function(CreditHolderType) {
987
987
  CreditHolderType["Agent"] = "Agent";
988
988
  return CreditHolderType;
989
989
  }({});
990
+ const COMPLETION_SOURCES = [
991
+ "web",
992
+ "cli",
993
+ "api",
994
+ "agent",
995
+ "system"
996
+ ];
990
997
  let CreditPurchaseStatus = /* @__PURE__ */ function(CreditPurchaseStatus) {
991
998
  CreditPurchaseStatus["Completed"] = "completed";
992
999
  CreditPurchaseStatus["Pending"] = "pending";
@@ -1003,6 +1010,12 @@ const BaseCreditTransaction = z.object({
1003
1010
  credits: z.number(),
1004
1011
  description: z.string().optional(),
1005
1012
  metadata: z.record(z.string(), z.any()).optional(),
1013
+ /**
1014
+ * Where this transaction originated — used to break down usage in reports.
1015
+ * Optional because legacy rows (and non-completion transactions like
1016
+ * purchases/refunds) may not have it set. See CompletionSource in analytics.ts.
1017
+ */
1018
+ source: z.enum(COMPLETION_SOURCES).optional(),
1006
1019
  createdAt: z.date(),
1007
1020
  updatedAt: z.date()
1008
1021
  });
@@ -2584,7 +2597,8 @@ const AgentStepSchema = z.object({
2584
2597
  });
2585
2598
  const ExecutionStartedAction = z.object({
2586
2599
  action: z.literal("execution_started"),
2587
- executionId: z.string()
2600
+ executionId: z.string(),
2601
+ questId: z.string().optional()
2588
2602
  });
2589
2603
  const IterationStepAction = z.object({
2590
2604
  action: z.literal("iteration_step"),
@@ -3644,6 +3658,8 @@ z.enum([
3644
3658
  "EnableLatticeDefault",
3645
3659
  "EnableDataLakes",
3646
3660
  "EnableDataLakesDefault",
3661
+ "EnableBriefcase",
3662
+ "EnableBriefcaseDefault",
3647
3663
  "RapidReplySettings",
3648
3664
  "EnableResearchEngine",
3649
3665
  "EnableResearchEngineDefault",
@@ -4615,6 +4631,14 @@ const API_SERVICE_GROUPS = {
4615
4631
  key: "EnableArtifactsDefault",
4616
4632
  order: 21
4617
4633
  },
4634
+ {
4635
+ key: "EnableBriefcase",
4636
+ order: 25
4637
+ },
4638
+ {
4639
+ key: "EnableBriefcaseDefault",
4640
+ order: 26
4641
+ },
4618
4642
  {
4619
4643
  key: "EnableBmPi",
4620
4644
  order: 30
@@ -5035,6 +5059,25 @@ const settingsMap = {
5035
5059
  order: 89,
5036
5060
  dependsOn: "EnableDataLakes"
5037
5061
  }),
5062
+ EnableBriefcase: makeBooleanSetting({
5063
+ key: "EnableBriefcase",
5064
+ name: "Enable Briefcase",
5065
+ defaultValue: false,
5066
+ description: "Server-side gate for the Briefcase capability (one-click AI prompt catalog). Off by default — turn on to expose the briefcase APIs and launcher panel.",
5067
+ category: "Experimental",
5068
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
5069
+ order: 86
5070
+ }),
5071
+ EnableBriefcaseDefault: makeBooleanSetting({
5072
+ key: "EnableBriefcaseDefault",
5073
+ name: "Briefcase: On by default for users",
5074
+ defaultValue: false,
5075
+ description: "When enabled, Briefcase is active for users who have never explicitly toggled it.",
5076
+ category: "Experimental",
5077
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
5078
+ order: 87,
5079
+ dependsOn: "EnableBriefcase"
5080
+ }),
5038
5081
  EnableQuestMaster: makeBooleanSetting({
5039
5082
  key: "EnableQuestMaster",
5040
5083
  name: "Enable Quest Master",
@@ -7755,6 +7798,55 @@ z$1.object({
7755
7798
  skip: z$1.array(z$1.string())
7756
7799
  })
7757
7800
  });
7801
+ z.enum([
7802
+ "inject",
7803
+ "auto-fire",
7804
+ "hidden"
7805
+ ]);
7806
+ /**
7807
+ * Modes acceptable at AUTHORING time. 'hidden' is intentionally excluded until
7808
+ * the host has true hidden-send support — accepting it would persist a value
7809
+ * that silently behaves as 'auto-fire' (a surprising downgrade). It stays in
7810
+ * ExecutionModeSchema/the stored enum for forward-compat.
7811
+ */
7812
+ const AuthorableExecutionModeSchema = z.enum(["inject", "auto-fire"]);
7813
+ /**
7814
+ * Tools a prompt may require — constrained to the host's closed tool set, MINUS
7815
+ * integration-gated tools that act on the caller's own credentials/account. A
7816
+ * shared system prompt must not be able to inject e.g. blog-publishing into a
7817
+ * non-author's session via requiredTools. (Per-user entitlement of the remaining
7818
+ * tools is still the chat pipeline's responsibility — see follow-up note in the
7819
+ * briefcase blueprint; this allowlist is the storage-layer floor.)
7820
+ */
7821
+ const BRIEFCASE_DISALLOWED_TOOLS = [
7822
+ "blog_publish",
7823
+ "blog_edit",
7824
+ "blog_draft"
7825
+ ];
7826
+ const BriefcaseRequiredToolsSchema = z.array(b4mLLMTools.refine((t) => !BRIEFCASE_DISALLOWED_TOOLS.includes(t), "This tool is not permitted in a briefcase prompt")).max(16);
7827
+ z.string().regex(/^[a-f0-9]{24}$/i, "Invalid prompt id");
7828
+ const PROMPT_TEXT_MAX = 16e3;
7829
+ const TAGS_MAX = 20;
7830
+ z.object({
7831
+ type: z.string().min(1).max(100),
7832
+ name: z.string().min(1).max(200),
7833
+ description: z.string().max(500).optional(),
7834
+ promptText: z.string().min(1).max(PROMPT_TEXT_MAX),
7835
+ tags: z.array(z.string().min(1).max(50)).max(TAGS_MAX).optional(),
7836
+ executionMode: AuthorableExecutionModeSchema.optional(),
7837
+ requiredTools: BriefcaseRequiredToolsSchema.optional()
7838
+ }).partial();
7839
+ /**
7840
+ * One catalog sub-query. Exactly one selector is used, in precedence order:
7841
+ * `personal` (resolved to the caller server-side) > `tags` > `type`.
7842
+ */
7843
+ const PromptBatchQuerySchema = z.object({
7844
+ key: z.string().min(1).max(100),
7845
+ tags: z.array(z.string().min(1).max(50)).max(TAGS_MAX).optional(),
7846
+ type: z.string().max(100).optional(),
7847
+ personal: z.boolean().optional()
7848
+ });
7849
+ z.object({ queries: z.array(PromptBatchQuerySchema).min(1).max(32).refine((qs) => new Set(qs.map((q) => q.key)).size === qs.length, { message: "Batch query keys must be unique" }) });
7758
7850
  const DATA_LAKES = [{
7759
7851
  id: "ionq-sales",
7760
7852
  name: "IonQ Sales Intelligence",
@@ -8197,6 +8289,175 @@ BaseArtifactSchema.extend({
8197
8289
  lastExecutionTime: z.number().optional()
8198
8290
  })
8199
8291
  });
8292
+ /**
8293
+ * Published-artifact schemas — the B4M instantiation of the `artifact-publishing`
8294
+ * blueprint (MillionOnMars/blueprints, extracted from Polaris Publish v1).
8295
+ *
8296
+ * The blueprint expresses scope/visibility as open string vocabularies; B4M
8297
+ * binds them to its own enums:
8298
+ * scope tier ∈ { user, project, organization }
8299
+ * visibility ∈ { private, project, organization, public } (ordered ladder)
8300
+ *
8301
+ * Unlike Polaris (bundles only), B4M publishes three SOURCE kinds through one
8302
+ * record: a rich HTML `bundle`, a chat `reply`, or a `fabfile`. The `source`
8303
+ * discriminator records provenance; `bundle` artifacts get the full
8304
+ * upload→validate→serve pipeline, while `reply`/`fabfile` are server-rendered
8305
+ * viewer pages.
8306
+ */
8307
+ const PublishScopeTierSchema = z.enum([
8308
+ "user",
8309
+ "project",
8310
+ "organization"
8311
+ ]);
8312
+ const PublishSourceKindSchema = z.enum([
8313
+ "bundle",
8314
+ "reply",
8315
+ "fabfile"
8316
+ ]);
8317
+ const PublishSourceSchema = z.object({
8318
+ kind: PublishSourceKindSchema,
8319
+ /** Set when kind === 'bundle' and the bundle was generated from a B4M artifact. */
8320
+ artifactId: z.string().optional(),
8321
+ /** Set when kind === 'reply'. */
8322
+ sessionId: z.string().optional(),
8323
+ messageId: z.string().optional(),
8324
+ /** Set when kind === 'fabfile'. */
8325
+ fabFileId: z.string().optional()
8326
+ });
8327
+ const ArtifactFileSchema = z.object({
8328
+ path: z.string(),
8329
+ size: z.int().nonnegative(),
8330
+ mimeType: z.string(),
8331
+ sha256: z.string()
8332
+ });
8333
+ const ArtifactVersionMetaSchema = z.object({
8334
+ publishedAt: z.date(),
8335
+ publishedBy: z.string(),
8336
+ size: z.object({
8337
+ totalBytes: z.int().nonnegative(),
8338
+ fileCount: z.int().nonnegative()
8339
+ }),
8340
+ sha256Index: z.string()
8341
+ });
8342
+ /** Reserved slugs — must include every tier URL token so a slug can't shadow routing. */
8343
+ const RESERVED_SLUGS = [
8344
+ "api",
8345
+ "admin",
8346
+ "static",
8347
+ "p",
8348
+ "u",
8349
+ "o",
8350
+ "pj",
8351
+ "r",
8352
+ "f",
8353
+ "a",
8354
+ "_next",
8355
+ "health"
8356
+ ];
8357
+ const SlugSchema = z.string().min(3).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "slug must be lowercase kebab-case").refine((s) => !RESERVED_SLUGS.includes(s), { message: "slug is reserved" });
8358
+ const PUBLISH_LIMITS = {
8359
+ maxFiles: 50,
8360
+ maxBundleBytes: 50 * 1024 * 1024,
8361
+ maxFileBytes: 10 * 1024 * 1024
8362
+ };
8363
+ z.object({
8364
+ /** Short opaque id for short URLs (`/p/r/{publicId}`, `/p/f/{publicId}`) and lookups. */
8365
+ publicId: z.string(),
8366
+ tier: PublishScopeTierSchema,
8367
+ scopeId: z.string(),
8368
+ slug: SlugSchema,
8369
+ title: z.string().min(1).max(200),
8370
+ description: z.string().max(1e3).optional(),
8371
+ visibility: VisibilitySchema.prefault("private"),
8372
+ /** Group id a viewer must belong to when gated cross-scope. */
8373
+ gatedToGroupId: z.string().optional(),
8374
+ ownerId: z.string(),
8375
+ lastPublishedBy: z.string().optional(),
8376
+ source: PublishSourceSchema,
8377
+ /** Canonical blob prefix '{tier}/{scopeId}/{slug}/'. Empty for non-bundle sources. */
8378
+ storageKeyPrefix: z.string(),
8379
+ size: z.object({
8380
+ totalBytes: z.int().nonnegative(),
8381
+ fileCount: z.int().nonnegative()
8382
+ }),
8383
+ sha256Index: z.string().optional(),
8384
+ manifest: z.array(ArtifactFileSchema).prefault([]),
8385
+ declaredApiEndpoints: z.array(z.string()).prefault([]),
8386
+ /** Rendered body snapshot for reply/fabfile viewer pages (markdown or text). */
8387
+ renderedBody: z.string().optional(),
8388
+ publishedAt: z.date(),
8389
+ previousVersionMeta: ArtifactVersionMetaSchema.optional(),
8390
+ viewCount: z.int().nonnegative().prefault(0),
8391
+ createdAt: z.date(),
8392
+ updatedAt: z.date(),
8393
+ deletedAt: z.date().nullish(),
8394
+ deletedBy: z.string().nullish()
8395
+ });
8396
+ const ValidationViolationTypeSchema = z.enum([
8397
+ "csp_violation",
8398
+ "forbidden_pattern",
8399
+ "forbidden_iframe",
8400
+ "invalid_asset_url",
8401
+ "missing_index",
8402
+ "size_exceeded",
8403
+ "invalid_mime_type",
8404
+ "invalid_path"
8405
+ ]);
8406
+ z.object({
8407
+ type: ValidationViolationTypeSchema,
8408
+ message: z.string(),
8409
+ file: z.string().optional(),
8410
+ line: z.int().optional()
8411
+ });
8412
+ const FileDescriptorSchema = z.object({
8413
+ path: z.string(),
8414
+ size: z.int().nonnegative(),
8415
+ mimeType: z.string()
8416
+ });
8417
+ z.object({
8418
+ tier: PublishScopeTierSchema,
8419
+ scopeId: z.string(),
8420
+ slug: SlugSchema,
8421
+ title: z.string().min(1).max(200),
8422
+ description: z.string().max(1e3).optional(),
8423
+ visibility: VisibilitySchema.optional(),
8424
+ gatedToGroupId: z.string().optional(),
8425
+ source: PublishSourceSchema.optional(),
8426
+ files: z.array(FileDescriptorSchema).min(1).max(PUBLISH_LIMITS.maxFiles)
8427
+ });
8428
+ z.object({
8429
+ draftId: z.uuid(),
8430
+ uploadUrls: z.array(z.object({
8431
+ path: z.string(),
8432
+ url: z.string(),
8433
+ expiresAt: z.string()
8434
+ }))
8435
+ });
8436
+ z.object({ draftId: z.uuid() });
8437
+ z.object({
8438
+ sessionId: z.string(),
8439
+ messageId: z.string(),
8440
+ title: z.string().min(1).max(200).optional(),
8441
+ visibility: VisibilitySchema.optional(),
8442
+ tier: PublishScopeTierSchema.prefault("user"),
8443
+ scopeId: z.string().optional()
8444
+ });
8445
+ z.object({
8446
+ fabFileId: z.string(),
8447
+ title: z.string().min(1).max(200).optional(),
8448
+ visibility: VisibilitySchema.optional(),
8449
+ tier: PublishScopeTierSchema.prefault("user"),
8450
+ scopeId: z.string().optional()
8451
+ });
8452
+ z.object({
8453
+ publicId: z.string(),
8454
+ url: z.string(),
8455
+ tier: PublishScopeTierSchema,
8456
+ scopeId: z.string(),
8457
+ slug: z.string(),
8458
+ visibility: VisibilitySchema,
8459
+ publishedAt: z.string()
8460
+ });
8200
8461
  const QuestStatusSchema = z.enum([
8201
8462
  "pending",
8202
8463
  "in-progress",
@@ -9385,6 +9646,29 @@ dayjs.extend(timezone);
9385
9646
  dayjs.extend(relativeTime);
9386
9647
  dayjs.extend(localizedFormat);
9387
9648
  var dayjsConfig_default = dayjs;
9649
+ //#endregion
9650
+ //#region src/utils/apiUrl.ts
9651
+ /** Bike4Mind production service (used when no customUrl is configured). */
9652
+ const BIKE4MIND_URL = "https://app.bike4mind.com";
9653
+ /** Local development server the `--dev` flag points the CLI at. */
9654
+ const LOCAL_DEV_URL = "http://localhost:3001";
9655
+ /**
9656
+ * Resolve API URL based on configuration
9657
+ * Returns custom URL if set, otherwise Bike4Mind main service
9658
+ */
9659
+ function getApiUrl(configApiConfig) {
9660
+ if (configApiConfig?.customUrl) return configApiConfig.customUrl;
9661
+ return BIKE4MIND_URL;
9662
+ }
9663
+ /**
9664
+ * Get human-readable API type name
9665
+ */
9666
+ function getEnvironmentName(configApiConfig) {
9667
+ const url = configApiConfig?.customUrl;
9668
+ if (!url) return "Production";
9669
+ if (/^https?:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/i.test(url)) return "Local Dev";
9670
+ return "Self-Hosted";
9671
+ }
9388
9672
  const logger = class Logger {
9389
9673
  static {
9390
9674
  this.instance = null;
@@ -9726,6 +10010,7 @@ const CliConfigSchema = z.object({
9726
10010
  version: z.string(),
9727
10011
  userId: z.string(),
9728
10012
  auth: AuthTokensSchema.optional(),
10013
+ authByEnv: z.record(z.string(), AuthTokensSchema).optional(),
9729
10014
  defaultModel: z.string(),
9730
10015
  apiConfig: ApiConfigSchema.optional(),
9731
10016
  toolApiKeys: z.object({
@@ -10017,6 +10302,26 @@ function mergeConfigs(global, project, local) {
10017
10302
  return merged;
10018
10303
  }
10019
10304
  /**
10305
+ * Normalize an API URL for use as an `authByEnv` cache key.
10306
+ *
10307
+ * Without normalization, `/set-api https://x.com` and `/set-api https://x.com/`
10308
+ * (or `HTTPS://X.com`) would create separate cache entries, defeating the
10309
+ * per-environment token reuse on a later `--dev` / `--prod` switch.
10310
+ */
10311
+ function normalizeEnvKey(url) {
10312
+ return url.toLowerCase().replace(/\/+$/, "");
10313
+ }
10314
+ /**
10315
+ * Treat an auth token as "authenticated" only when it has an `expiresAt` in
10316
+ * the future. The startup flow auto-refreshes expired tokens anyway, but
10317
+ * without this check the launch banner would briefly claim a saved login is
10318
+ * being reused when it's actually about to trigger a re-auth.
10319
+ */
10320
+ function hasValidAuth(auth) {
10321
+ if (!auth) return false;
10322
+ return new Date(auth.expiresAt) > /* @__PURE__ */ new Date();
10323
+ }
10324
+ /**
10020
10325
  * Manages CLI configuration stored as JSON
10021
10326
  */
10022
10327
  var ConfigStore = class {
@@ -10284,6 +10589,58 @@ var ConfigStore = class {
10284
10589
  await this.save(config);
10285
10590
  }
10286
10591
  /**
10592
+ * Switch the active API environment, caching auth tokens per-environment so
10593
+ * flipping between `--dev` and `--prod` doesn't force a re-login each time you
10594
+ * return to an environment you've already authenticated.
10595
+ *
10596
+ * Targets:
10597
+ * - 'prod' → Bike4Mind production (clears customUrl)
10598
+ * - 'dev' → local dev server (http://localhost:3001)
10599
+ * - { customUrl: '…' } → arbitrary self-hosted URL
10600
+ *
10601
+ * Mutates the cached config in place and persists via `save()` (no argument)
10602
+ * so the write bypasses save()'s field-merge — `save(config)` would otherwise
10603
+ * preserve the previous `auth` and defeat the per-env swap.
10604
+ */
10605
+ async switchApiEnvironment(target) {
10606
+ const config = await this.load();
10607
+ const prevKey = normalizeEnvKey(config.apiConfig?.customUrl || "https://app.bike4mind.com");
10608
+ let newUrl;
10609
+ let newApiConfig;
10610
+ if (target === "prod") {
10611
+ newUrl = BIKE4MIND_URL;
10612
+ newApiConfig = void 0;
10613
+ } else if (target === "dev") {
10614
+ newUrl = LOCAL_DEV_URL;
10615
+ newApiConfig = { customUrl: LOCAL_DEV_URL };
10616
+ } else {
10617
+ newUrl = target.customUrl;
10618
+ newApiConfig = { customUrl: target.customUrl };
10619
+ }
10620
+ const newKey = normalizeEnvKey(newUrl);
10621
+ const envName = getEnvironmentName(newApiConfig);
10622
+ if (prevKey === newKey) return {
10623
+ url: newUrl,
10624
+ envName,
10625
+ changed: false,
10626
+ authenticated: hasValidAuth(config.auth)
10627
+ };
10628
+ const authByEnv = { ...config.authByEnv || {} };
10629
+ if (config.auth) authByEnv[prevKey] = config.auth;
10630
+ else delete authByEnv[prevKey];
10631
+ const restored = authByEnv[newKey];
10632
+ config.apiConfig = newApiConfig;
10633
+ config.authByEnv = authByEnv;
10634
+ config.auth = restored;
10635
+ await this.save();
10636
+ return {
10637
+ url: newUrl,
10638
+ envName,
10639
+ changed: true,
10640
+ authenticated: hasValidAuth(restored)
10641
+ };
10642
+ }
10643
+ /**
10287
10644
  * Get project config directory (if any)
10288
10645
  */
10289
10646
  getProjectConfigDir() {
@@ -10398,4 +10755,4 @@ var ConfigStore = class {
10398
10755
  }
10399
10756
  };
10400
10757
  //#endregion
10401
- export { PromptMetaZodSchema as $, GenericCreditDeductTransaction as A, getDataLakeTags as At, KnowledgeType as B, settingsMap as Bt, FeedbackEvents as C, VIDEO_SIZE_CONSTRAINTS as Ct, GEMINI_IMAGE_MODELS as D, b4mLLMTools as Dt, FriendshipEvents as E, XAI_IMAGE_MODELS as Et, ImageModels as F, isZodError as Ft, NotFoundError as G, isNearLimit as Gt, MiscEvents as H, validateNotebookPath as Ht, InboxEvents as I, obfuscateApiKey as It, Permission as J, OpenAIEmbeddingModel as K, parseRateLimitHeaders as Kt, InternalServerError as L, resolveNavigationIntents as Lt, HttpStatus as M, getViewById as Mt, ImageEditUsageTransaction as N, isGPTImage2Model as Nt, GenerateImageToolCallSchema as O, dayjsConfig_default as Ot, ImageGenerationUsageTransaction as P, isGPTImageModel as Pt, PromptIntentSchema as Q, InviteEvents as R, sanitizeTelemetryError as Rt, FavoriteDocumentType as S, UnprocessableEntityError as St, ForbiddenError as T, VideoModels as Tt, ModalEvents as U, buildRateLimitLogEntry as Ut, LLMEvents as V, validateJupyterKernelName as Vt, ModelBackend as W, extractSnippetMeta as Wt, ProfileEvents as X, PermissionDeniedError as Y, ProjectEvents as Z, ClaudeArtifactMimeTypes as _, TooManyRequestsError as _t, ApiKeyEvents as a, RegInviteEvents as at, DashboardParamsSchema as b, UiNavigationEvents as bt, AppFileEvents as c, ResearchTaskPeriodicFrequencyType as ct, BFL_IMAGE_MODELS as d, SpeechToTextUsageTransaction as dt, PurchaseTransaction as et, BFL_SAFETY_TOLERANCE as f, SubscriptionCreditTransaction as ft, ChatModels as g, TextGenerationUsageTransaction as gt, ChatCompletionCreateInputSchema as h, TaskScheduleHandler as ht, AiEvents as i, RechartsChartTypeList as it, HTTPError as j, getMcpProviderMetadata as jt, GenericCreditAddTransaction as k, getAccessibleDataLakes as kt, ArtifactTypeSchema as l, ResearchTaskType as lt, CREDIT_DEDUCT_TRANSACTION_TYPES as m, TagType as mt, logger as n, RealtimeVoiceUsageTransaction as nt, ApiKeyScope as o, ResearchModeParamsSchema as ot, BadRequestError as p, SupportedFabFileMimeTypes as pt, OpenAIImageGenerationInput as q, CollectionType as qt, ALERT_THRESHOLDS as r, ReceivedCreditTransaction as rt, ApiKeyType as s, ResearchTaskExecutionType as st, ConfigStore as t, QuestMasterParamsSchema as tt, AuthEvents as u, SessionEvents as ut, CompletionApiUsageTransaction as v, ToolUsageTransaction as vt, FileEvents as w, VideoGenerationUsageTransaction as wt, ElabsEvents as x, UnauthorizedError as xt, CorruptedFileError as y, TransferCreditTransaction as yt, InviteType as z, secureParameters as zt };
10758
+ export { ProjectEvents as $, GenerateImageToolCallSchema as A, dayjsConfig_default as At, InviteEvents as B, sanitizeTelemetryError as Bt, ElabsEvents as C, UnauthorizedError as Ct, ForbiddenError as D, VideoModels as Dt, FileEvents as E, VideoGenerationUsageTransaction as Et, ImageEditUsageTransaction as F, isGPTImage2Model as Ft, ModalEvents as G, buildRateLimitLogEntry as Gt, KnowledgeType as H, settingsMap as Ht, ImageGenerationUsageTransaction as I, isGPTImageModel as It, OpenAIEmbeddingModel as J, parseRateLimitHeaders as Jt, ModelBackend as K, extractSnippetMeta as Kt, ImageModels as L, isZodError as Lt, GenericCreditDeductTransaction as M, getDataLakeTags as Mt, HTTPError as N, getMcpProviderMetadata as Nt, FriendshipEvents as O, XAI_IMAGE_MODELS as Ot, HttpStatus as P, getViewById as Pt, ProfileEvents as Q, InboxEvents as R, obfuscateApiKey as Rt, DashboardParamsSchema as S, UiNavigationEvents as St, FeedbackEvents as T, VIDEO_SIZE_CONSTRAINTS as Tt, LLMEvents as U, validateJupyterKernelName as Ut, InviteType as V, secureParameters as Vt, MiscEvents as W, validateNotebookPath as Wt, Permission as X, OpenAIImageGenerationInput as Y, CollectionType as Yt, PermissionDeniedError as Z, ChatCompletionCreateInputSchema as _, TaskScheduleHandler as _t, ALERT_THRESHOLDS as a, ReceivedCreditTransaction as at, CompletionApiUsageTransaction as b, ToolUsageTransaction as bt, ApiKeyScope as c, ResearchModeParamsSchema as ct, ArtifactTypeSchema as d, ResearchTaskType as dt, PromptIntentSchema as et, AuthEvents as f, SessionEvents as ft, CREDIT_DEDUCT_TRANSACTION_TYPES as g, TagType as gt, BadRequestError as h, SupportedFabFileMimeTypes as ht, getEnvironmentName as i, RealtimeVoiceUsageTransaction as it, GenericCreditAddTransaction as j, getAccessibleDataLakes as jt, GEMINI_IMAGE_MODELS as k, b4mLLMTools as kt, ApiKeyType as l, ResearchTaskExecutionType as lt, BFL_SAFETY_TOLERANCE as m, SubscriptionCreditTransaction as mt, logger as n, PurchaseTransaction as nt, AiEvents as o, RechartsChartTypeList as ot, BFL_IMAGE_MODELS as p, SpeechToTextUsageTransaction as pt, NotFoundError as q, isNearLimit as qt, getApiUrl as r, QuestMasterParamsSchema as rt, ApiKeyEvents as s, RegInviteEvents as st, ConfigStore as t, PromptMetaZodSchema as tt, AppFileEvents as u, ResearchTaskPeriodicFrequencyType as ut, ChatModels as v, TextGenerationUsageTransaction as vt, FavoriteDocumentType as w, UnprocessableEntityError as wt, CorruptedFileError as x, TransferCreditTransaction as xt, ClaudeArtifactMimeTypes as y, TooManyRequestsError as yt, InternalServerError as z, resolveNavigationIntents as zt };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-tjtx9TU6.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-CtBZ2p4M.mjs";
3
3
  //#region src/commands/apiCommand.ts
4
4
  /**
5
5
  * External API config command (--api-url / --reset-api)
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as version, n as compareSemver, r as fetchLatestVersion } from "../updateChecker-CS84T-KX.mjs";
2
+ import { t as version } from "../package-8UMmdI7b.mjs";
3
+ import { n as compareSemver, r as fetchLatestVersion } from "../updateChecker-D67NPlS5.mjs";
3
4
  import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
4
5
  import { execSync } from "child_process";
5
6
  import { constants, existsSync, promises } from "fs";
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { t as ConfigStore } from "../ConfigStore-CtBZ2p4M.mjs";
3
+ //#region src/commands/envCommand.ts
4
+ /**
5
+ * Environment switching for the `--dev` / `--prod` launch flags.
6
+ *
7
+ * Flipping the target persists the choice to ~/.bike4mind/config.json, so a
8
+ * bare `b4m` always reuses whichever environment you last selected. Auth tokens
9
+ * are cached per-environment, so flipping back and forth doesn't force a
10
+ * re-login (see ConfigStore.switchApiEnvironment).
11
+ */
12
+ /**
13
+ * Apply a `--dev` / `--prod` launch flag: switch the persisted API environment
14
+ * and print a concise banner describing the result. Runs before the app boots.
15
+ */
16
+ async function applyEnvironmentFlag(target) {
17
+ const result = await new ConfigStore().switchApiEnvironment(target);
18
+ if (result.changed) console.log(`🔀 Switched API environment → ${result.envName} (${result.url})`);
19
+ else console.log(`🌍 Already on ${result.envName} (${result.url})`);
20
+ if (result.authenticated) console.log(" ✅ Reusing your saved login for this environment.");
21
+ else console.log(" 🔓 Not logged in here yet — run /login once the CLI starts.");
22
+ console.log("");
23
+ }
24
+ //#endregion
25
+ export { applyEnvironmentFlag };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { $ as SessionStore, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as PermissionManager, I as generateCliTools, J as isReadOnlyTool, K as buildSystemPrompt, M as loadContextFiles, N as getApiUrl, O as McpManager, 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, m as createCoordinateTaskTool, p as createWriteTodosTool, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-DyWEu4_d.mjs";
3
- import { n as logger, t as ConfigStore } from "../ConfigStore-tjtx9TU6.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-BuaOUcTS.mjs";
3
+ import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-CtBZ2p4M.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-tjtx9TU6.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-CtBZ2p4M.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,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as version, i as forceCheckForUpdate } from "../updateChecker-CS84T-KX.mjs";
2
+ import { t as version } from "../package-8UMmdI7b.mjs";
3
+ import { i as forceCheckForUpdate } from "../updateChecker-D67NPlS5.mjs";
3
4
  import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
4
5
  import { execSync } from "child_process";
5
6
  //#region src/commands/updateCommand.ts
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { $ as SessionStore, A as formatStep, B as DEFAULT_RETRY_CONFIG, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as PermissionManager, G as getPlanModeFilePath, H as clearFeatureModuleTools, I as generateCliTools, J as isReadOnlyTool, K as buildSystemPrompt, L as ALWAYS_DENIED_FOR_AGENTS, M as loadContextFiles, N as getApiUrl, O as McpManager, P as getEnvironmentName, 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 extractCompactInstructions, k as substituteArguments, 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-DyWEu4_d.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-BuaOUcTS.mjs";
3
3
  import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-DV5s-qni.mjs";
4
- import { Ht as validateNotebookPath$1, Vt as validateJupyterKernelName, g as ChatModels, m as CREDIT_DEDUCT_TRANSACTION_TYPES, n as logger, t as ConfigStore } from "./ConfigStore-tjtx9TU6.mjs";
5
- import { a as version, t as checkForUpdate } from "./updateChecker-CS84T-KX.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-CtBZ2p4M.mjs";
5
+ import { t as version } from "./package-8UMmdI7b.mjs";
6
+ import { t as checkForUpdate } from "./updateChecker-D67NPlS5.mjs";
6
7
  import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
7
8
  import { Box, Static, Text, render, useApp, useInput, usePaste, useStdout } from "ink";
8
9
  import { execSync } from "child_process";
@@ -5440,6 +5441,7 @@ var BridgePresence = class {
5440
5441
  */
5441
5442
  async start(opts) {
5442
5443
  if (this.started) return this.instanceId !== null;
5444
+ this.stopped = false;
5443
5445
  this.started = true;
5444
5446
  const config = await readBridgeConfig();
5445
5447
  if (!config) {
@@ -5503,9 +5505,19 @@ var BridgePresence = class {
5503
5505
  this.emitQueue = this.emitQueue.then(task, task);
5504
5506
  return this.emitQueue;
5505
5507
  }
5506
- /** Tear down the tavern presence cleanly. */
5508
+ /**
5509
+ * Tear down the tavern presence cleanly. Halts the announce-retry and
5510
+ * command-WS reconnect loops, closes the socket, and best-effort signals
5511
+ * disconnect to the bridge.
5512
+ *
5513
+ * After this resolves the instance is fully reset, so a later `start()`
5514
+ * re-announces — the same singleton can be toggled off (Tavern feature
5515
+ * disabled at runtime) and back on without restarting the CLI. The
5516
+ * `stopped` latch is left true here purely so any straggler retry callback
5517
+ * already queued short-circuits; `start()` clears it.
5518
+ */
5507
5519
  async stop(reason = "cli_exit") {
5508
- if (this.stopped) return;
5520
+ if (this.stopped || !this.started) return;
5509
5521
  this.stopped = true;
5510
5522
  if (this.reconnectTimer) {
5511
5523
  clearTimeout(this.reconnectTimer);
@@ -5525,6 +5537,16 @@ var BridgePresence = class {
5525
5537
  instanceId: this.instanceId,
5526
5538
  reason
5527
5539
  }).catch(() => {});
5540
+ this.started = false;
5541
+ this.instanceId = null;
5542
+ this.config = null;
5543
+ this.startOpts = null;
5544
+ this.pendingWorkspaceName = null;
5545
+ this.pendingCapabilities = null;
5546
+ this.pendingSource = null;
5547
+ this.announceAttempts = 0;
5548
+ this.reconnectAttempts = 0;
5549
+ this.emitQueue = Promise.resolve();
5528
5550
  }
5529
5551
  async announce(body) {
5530
5552
  try {
@@ -5863,7 +5885,7 @@ function CliApp() {
5863
5885
  startupLog.push(`✅ Authenticated (expires in ${daysUntilExpiry} day${daysUntilExpiry !== 1 ? "s" : ""})`);
5864
5886
  const apiBaseURL = getApiUrl(config.apiConfig);
5865
5887
  const envName = getEnvironmentName(config.apiConfig);
5866
- if (import.meta.url.includes("/src/") || process.env.NODE_ENV === "development" || envName !== "Bike4Mind") startupLog.unshift(`🌍 API Environment: ${envName} (${apiBaseURL})`);
5888
+ startupLog.unshift(`🌍 API Environment: ${envName} (${apiBaseURL})`);
5867
5889
  const apiClient = new ApiClient(apiBaseURL, state.configStore);
5868
5890
  const tokenGetter = async () => {
5869
5891
  return (await state.configStore.getAuthTokens())?.accessToken ?? null;
@@ -6498,8 +6520,13 @@ function CliApp() {
6498
6520
  status: "running"
6499
6521
  });
6500
6522
  };
6523
+ const tavernPresenceEnabled = state.config?.features?.tavern ?? false;
6501
6524
  useEffect(() => {
6502
6525
  if (!isInitialized) return;
6526
+ if (!tavernPresenceEnabled) {
6527
+ bridgePresence.stop("tavern_disabled");
6528
+ return;
6529
+ }
6503
6530
  let cancelled = false;
6504
6531
  bridgePresence.setCallbacks({
6505
6532
  onSendPrompt: (text) => handleMessageRef.current?.(text),
@@ -6523,7 +6550,7 @@ function CliApp() {
6523
6550
  return () => {
6524
6551
  cancelled = true;
6525
6552
  };
6526
- }, [isInitialized]);
6553
+ }, [isInitialized, tavernPresenceEnabled]);
6527
6554
  /**
6528
6555
  * Handle custom command execution with proper display
6529
6556
  * Shows concise user message but sends full template to agent
@@ -6927,8 +6954,13 @@ function CliApp() {
6927
6954
  return;
6928
6955
  }
6929
6956
  }
6930
- const errorMessage = error instanceof Error ? error.message : String(error);
6931
- console.error(`\n❌ ${errorMessage}\n`);
6957
+ const rawMessage = error instanceof Error ? error.message : String(error);
6958
+ if (error instanceof Error && isTransientNetworkError(error)) {
6959
+ console.error("\n❌ The connection to the server dropped mid-response. Type \"continue\" to resume.\n");
6960
+ logger.debug(`Full error details: ${error.stack || error.message}`);
6961
+ return;
6962
+ }
6963
+ console.error(`\n❌ ${rawMessage}\n`);
6932
6964
  logger.debug(`Full error details: ${error instanceof Error ? error.stack || error.message : String(error)}`);
6933
6965
  } finally {
6934
6966
  const wasAborted = abortController.signal.aborted;
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ //#region package.json
3
+ var version = "0.12.0";
4
+ //#endregion
5
+ export { version as t };
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { $ as PromptMetaZodSchema, A as GenericCreditDeductTransaction, At as getDataLakeTags, B as KnowledgeType, Bt as settingsMap, C as FeedbackEvents, Ct as VIDEO_SIZE_CONSTRAINTS, D as GEMINI_IMAGE_MODELS, Dt as b4mLLMTools, E as FriendshipEvents, Et as XAI_IMAGE_MODELS, F as ImageModels, Ft as isZodError, G as NotFoundError, Gt as isNearLimit, H as MiscEvents, I as InboxEvents, It as obfuscateApiKey, J as Permission, K as OpenAIEmbeddingModel, Kt as parseRateLimitHeaders, L as InternalServerError, Lt as resolveNavigationIntents, M as HttpStatus, Mt as getViewById, N as ImageEditUsageTransaction, Nt as isGPTImage2Model, O as GenerateImageToolCallSchema, Ot as dayjsConfig_default, P as ImageGenerationUsageTransaction, Pt as isGPTImageModel, Q as PromptIntentSchema, R as InviteEvents, Rt as sanitizeTelemetryError, S as FavoriteDocumentType, St as UnprocessableEntityError, T as ForbiddenError, Tt as VideoModels, U as ModalEvents, Ut as buildRateLimitLogEntry, V as LLMEvents, W as ModelBackend, Wt as extractSnippetMeta, X as ProfileEvents, Y as PermissionDeniedError, Z as ProjectEvents, _ as ClaudeArtifactMimeTypes, _t as TooManyRequestsError, a as ApiKeyEvents, at as RegInviteEvents, b as DashboardParamsSchema, bt as UiNavigationEvents, c as AppFileEvents, ct as ResearchTaskPeriodicFrequencyType, d as BFL_IMAGE_MODELS, dt as SpeechToTextUsageTransaction, et as PurchaseTransaction, f as BFL_SAFETY_TOLERANCE, ft as SubscriptionCreditTransaction, g as ChatModels, gt as TextGenerationUsageTransaction, h as ChatCompletionCreateInputSchema, ht as TaskScheduleHandler, i as AiEvents, it as RechartsChartTypeList, j as HTTPError, jt as getMcpProviderMetadata, k as GenericCreditAddTransaction, kt as getAccessibleDataLakes, l as ArtifactTypeSchema, lt as ResearchTaskType, mt as TagType, n as logger, nt as RealtimeVoiceUsageTransaction, o as ApiKeyScope, ot as ResearchModeParamsSchema, p as BadRequestError, pt as SupportedFabFileMimeTypes, q as OpenAIImageGenerationInput, qt as CollectionType, r as ALERT_THRESHOLDS, rt as ReceivedCreditTransaction, s as ApiKeyType, st as ResearchTaskExecutionType, t as ConfigStore, tt as QuestMasterParamsSchema, u as AuthEvents, ut as SessionEvents, v as CompletionApiUsageTransaction, vt as ToolUsageTransaction, w as FileEvents, wt as VideoGenerationUsageTransaction, x as ElabsEvents, xt as UnauthorizedError, y as CorruptedFileError, yt as TransferCreditTransaction, z as InviteType, zt as secureParameters } from "./ConfigStore-tjtx9TU6.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-CtBZ2p4M.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-8UMmdI7b.mjs";
5
6
  import { execFile, execFileSync, spawn } from "child_process";
6
7
  import crypto, { createHash, randomBytes } from "crypto";
7
8
  import { existsSync, promises, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -22289,7 +22290,7 @@ var CliLogger = class extends Logger {
22289
22290
  * 5. Route to server or execute locally via ToolRouter
22290
22291
  * 6. Record observation in agent's step history
22291
22292
  */
22292
- function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator) {
22293
+ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator, allowedDirectories) {
22293
22294
  const originalFn = tool.toolFn;
22294
22295
  const toolName = tool.toolSchema.name;
22295
22296
  return {
@@ -22328,10 +22329,18 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
22328
22329
  * offer retry on sandbox failure, and record observation.
22329
22330
  */
22330
22331
  async function executeAndRecord() {
22331
- let result = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
22332
+ let result;
22333
+ try {
22334
+ result = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
22335
+ } catch (err) {
22336
+ const msg = err instanceof Error ? err.message : String(err);
22337
+ if (!isPathAccessDenial(msg)) throw err;
22338
+ result = msg;
22339
+ }
22332
22340
  cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
22333
22341
  await captureViolations(isSandboxed, result, args?.command, sandboxOrchestrator);
22334
22342
  result = await retrySandboxFailure(isSandboxed, result, toolName, args, apiClient, originalFn, showPermissionPrompt);
22343
+ result = await retryPathAccessDenial(result, toolName, effectiveArgs, allowedDirectories, configStore, apiClient, originalFn, showPermissionPrompt);
22335
22344
  agentContext.observationQueue.push({
22336
22345
  toolName,
22337
22346
  result
@@ -22376,6 +22385,64 @@ async function retrySandboxFailure(isSandboxed, result, toolName, originalArgs,
22376
22385
  return result;
22377
22386
  }
22378
22387
  /**
22388
+ * Matches the three shapes of filesystem allow-list denial emitted by core:
22389
+ * - file tools: `Access denied: Cannot <op> files outside allowed directories.`
22390
+ * - glob_files: `Access denied: Cannot search outside allowed directories.` (no "files")
22391
+ * - grep_search: `Path validation failed: "<p>" resolves outside allowed directories.`
22392
+ * The `files` token is optional so glob_files' wording is covered too.
22393
+ */
22394
+ const PATH_ACCESS_DENIAL_RE = /Access denied: Cannot \w+ (?:files )?outside allowed directories|Path validation failed: .* resolves outside allowed directories/;
22395
+ function isPathAccessDenial(text) {
22396
+ return PATH_ACCESS_DENIAL_RE.test(text);
22397
+ }
22398
+ /**
22399
+ * Derive the directory to grant so the blocked operation can succeed on retry.
22400
+ * File tools (`file_read`, `create_file`, `edit_local_file`, `delete_file`)
22401
+ * target a file → grant its containing directory. `grep_search` / `glob_files`
22402
+ * target a directory → grant it directly. Returns an absolute path, or null if
22403
+ * no path argument is present.
22404
+ */
22405
+ function deriveGrantDirectory(toolName, args) {
22406
+ const raw = args?.path ?? args?.dir_path;
22407
+ if (typeof raw !== "string" || raw.length === 0) return null;
22408
+ const resolved = path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), raw);
22409
+ return new Set(["grep_search", "glob_files"]).has(toolName) ? resolved : path.dirname(resolved);
22410
+ }
22411
+ /**
22412
+ * If a tool result indicates the operation was blocked by the filesystem
22413
+ * allow-list, prompt the user to grant access to the relevant directory.
22414
+ * On approval the directory is pushed onto the live allow-list (so the very
22415
+ * next core tool call sees it — same array reference held by the tool
22416
+ * context), persisted to config on "Always allow", and the tool is retried.
22417
+ *
22418
+ * This is the runtime, on-demand equivalent of `/add-dir`: instead of failing
22419
+ * hard when the agent reaches outside the workspace, it asks — like Claude
22420
+ * Code does — and continues once the user says yes.
22421
+ */
22422
+ async function retryPathAccessDenial(result, toolName, args, allowedDirectories, configStore, apiClient, originalFn, showPermissionPrompt) {
22423
+ if (!allowedDirectories || !isPathAccessDenial(result)) return result;
22424
+ const grantDir = deriveGrantDirectory(toolName, args);
22425
+ if (!grantDir) return result;
22426
+ if (allowedDirectories.includes(grantDir)) return result;
22427
+ const response = await showPermissionPrompt(toolName, args, `🔒 DIRECTORY ACCESS — "${toolName}" needs a path outside the current workspace.\n\n- Grant access to this directory:\n ${grantDir}\n- "Allow for this session" grants access until the CLI exits.\n- "Always allow" also saves it to your config so it persists across sessions.`);
22428
+ if (response.action === "deny") return result;
22429
+ const oneShot = response.action === "allow-once";
22430
+ allowedDirectories.push(grantDir);
22431
+ if (response.action === "allow-always") try {
22432
+ await configStore.addDirectory(grantDir);
22433
+ } catch {}
22434
+ try {
22435
+ return await executeTool(toolName, args, apiClient, originalFn);
22436
+ } catch (err) {
22437
+ return err instanceof Error ? err.message : String(err);
22438
+ } finally {
22439
+ if (oneShot) {
22440
+ const idx = allowedDirectories.lastIndexOf(grantDir);
22441
+ if (idx !== -1) allowedDirectories.splice(idx, 1);
22442
+ }
22443
+ }
22444
+ }
22445
+ /**
22379
22446
  * Clean up temporary sandbox files (e.g., Seatbelt profiles).
22380
22447
  * Fails silently — cleanup is best-effort.
22381
22448
  */
@@ -22604,12 +22671,14 @@ async function generateCliTools(userId, llm, model, permissionManager, showPermi
22604
22671
  "web_fetch"
22605
22672
  ].filter((name) => name in b4mTools).map((name) => [name, b4mTools[name]]));
22606
22673
  const cliOnlyTools = await getCliOnlyTools();
22607
- const toolsMap = generateTools(userId, user, logger, dbAdapters, storage, storage, statusUpdate, onStart, onFinish, llm, {}, model, void 0, {
22674
+ const tools_to_generate = {
22608
22675
  ...filteredB4mTools,
22609
22676
  ...cliOnlyTools
22610
- }, allowedDirectories);
22677
+ };
22678
+ const liveAllowedDirectories = allowedDirectories ?? [];
22679
+ const toolsMap = generateTools(userId, user, logger, dbAdapters, storage, storage, statusUpdate, onStart, onFinish, llm, {}, model, void 0, tools_to_generate, liveAllowedDirectories);
22611
22680
  let tools = Object.entries(toolsMap).map(([_, tool]) => {
22612
- return wrapToolWithCheckpointing(wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator), checkpointStore ?? null);
22681
+ return wrapToolWithCheckpointing(wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator, liveAllowedDirectories), checkpointStore ?? null);
22613
22682
  });
22614
22683
  if (toolFilter) {
22615
22684
  const { allowedTools, deniedTools } = toolFilter;
@@ -22778,24 +22847,6 @@ var PermissionManager = class {
22778
22847
  };
22779
22848
  }
22780
22849
  };
22781
- //#endregion
22782
- //#region src/utils/apiUrl.ts
22783
- const BIKE4MIND_URL = "https://app.bike4mind.com";
22784
- /**
22785
- * Resolve API URL based on configuration
22786
- * Returns custom URL if set, otherwise Bike4Mind main service
22787
- */
22788
- function getApiUrl(configApiConfig) {
22789
- if (configApiConfig?.customUrl) return configApiConfig.customUrl;
22790
- return BIKE4MIND_URL;
22791
- }
22792
- /**
22793
- * Get human-readable API type name
22794
- */
22795
- function getEnvironmentName(configApiConfig) {
22796
- if (configApiConfig?.customUrl) return "Self-Hosted";
22797
- return "Bike4Mind";
22798
- }
22799
22850
  const PROJECT_CONTEXT_FILES = [
22800
22851
  "CLAUDE.local.md",
22801
22852
  "CLAUDE.md",
@@ -23629,6 +23680,35 @@ var StreamAccumulator = class {
23629
23680
  //#endregion
23630
23681
  //#region src/llm/ServerLlmBackend.ts
23631
23682
  /**
23683
+ * Connection-level failures that should be retried rather than surfaced to the
23684
+ * user. Mirrors the canonical retryable-error list in `@bike4mind/llm-adapters`
23685
+ * (retry.ts, issues #6936 / #6892): the most common offender is a TLS socket
23686
+ * close mid-stream, which Node surfaces as `Error: aborted` thrown from
23687
+ * `node:_http_client` `socketCloseListener`. This happens when the SSE
23688
+ * connection sits idle during a long extended-thinking step and an intermediary
23689
+ * (or the socket itself) times out the idle connection.
23690
+ *
23691
+ * Crucially this is NOT a user cancel — those are detected separately via
23692
+ * `options.abortSignal` before this classifier is consulted. Matching is on the
23693
+ * lowercased message so we catch the various wordings undici/Node emit.
23694
+ */
23695
+ const TRANSIENT_NETWORK_ERROR_PATTERNS = [
23696
+ "aborted",
23697
+ "socket closed",
23698
+ "socket hang up",
23699
+ "connection closed",
23700
+ "econnreset",
23701
+ "etimedout",
23702
+ "terminated",
23703
+ "network error",
23704
+ "fetch failed",
23705
+ "und_err_socket"
23706
+ ];
23707
+ function isTransientNetworkError(error) {
23708
+ const message = error.message?.toLowerCase() ?? "";
23709
+ return TRANSIENT_NETWORK_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
23710
+ }
23711
+ /**
23632
23712
  * Server-side LLM backend that proxies requests through Bike4Mind API
23633
23713
  * Uses Server-Sent Events (SSE) for streaming responses
23634
23714
  * API keys remain secure on server - never exposed to CLI
@@ -23653,19 +23733,35 @@ var ServerLlmBackend = class ServerLlmBackend {
23653
23733
  */
23654
23734
  async complete(model, messages, options, callback) {
23655
23735
  let lastError;
23736
+ let delivered = false;
23737
+ const trackingCallback = (text, info) => {
23738
+ delivered = true;
23739
+ return callback(text, info);
23740
+ };
23656
23741
  for (let attempt = 0; attempt <= ServerLlmBackend.MAX_STREAM_RETRIES; attempt++) {
23657
23742
  if (attempt > 0) logger.warn(`[ServerLlmBackend] Retrying stream (attempt ${attempt + 1}/${ServerLlmBackend.MAX_STREAM_RETRIES + 1})...`);
23658
23743
  try {
23659
- await this.completeOnce(model, messages, options, callback);
23744
+ await this.completeOnce(model, messages, options, trackingCallback);
23660
23745
  return;
23661
23746
  } catch (error) {
23662
23747
  lastError = error instanceof Error ? error : new Error(String(error));
23663
- const isTransientStreamError = lastError.message.includes("Stream ended prematurely");
23664
- const isAborted = options.abortSignal?.aborted;
23665
- if (!isTransientStreamError || isAborted) throw lastError;
23666
- logger.warn(`[ServerLlmBackend] Transient stream failure: ${lastError.message}`);
23748
+ if (options.abortSignal?.aborted) throw lastError;
23749
+ if (delivered) {
23750
+ if (isTransientNetworkError(lastError)) {
23751
+ logger.warn(`[ServerLlmBackend] Ignoring post-delivery transient stream error: ${lastError.message}`);
23752
+ return;
23753
+ }
23754
+ throw lastError;
23755
+ }
23756
+ if (!(lastError.message.includes("Stream ended prematurely") || isTransientNetworkError(lastError))) throw lastError;
23757
+ logger.warn(`[ServerLlmBackend] Transient stream failure (attempt ${attempt + 1}/${ServerLlmBackend.MAX_STREAM_RETRIES + 1}): ${lastError.message}`);
23758
+ if (attempt < ServerLlmBackend.MAX_STREAM_RETRIES) await new Promise((resolve) => setTimeout(resolve, 500 * (attempt + 1)));
23667
23759
  }
23668
23760
  }
23761
+ if (lastError && isTransientNetworkError(lastError) && !options.abortSignal?.aborted) {
23762
+ logger.error("[ServerLlmBackend] Stream failed after all retries due to a network drop", lastError);
23763
+ throw new Error("The connection to the Bike4Mind server dropped mid-response (likely a network timeout during a long thinking step). It was retried automatically but kept failing — type \"continue\" to resume.");
23764
+ }
23669
23765
  throw lastError ?? /* @__PURE__ */ new Error("Stream failed after all retry attempts");
23670
23766
  }
23671
23767
  /**
@@ -24436,6 +24532,7 @@ var WebSocketToolExecutor = class {
24436
24532
  };
24437
24533
  //#endregion
24438
24534
  //#region src/auth/ApiClient.ts
24535
+ const USER_AGENT = `b4m-cli/${version}`;
24439
24536
  /**
24440
24537
  * Authenticated API client for B4M services
24441
24538
  * Automatically injects access tokens from ConfigStore
@@ -24446,7 +24543,11 @@ var ApiClient = class {
24446
24543
  this.oauthClient = new OAuthClient(baseURL);
24447
24544
  this.client = axios.create({
24448
24545
  baseURL,
24449
- headers: { "Content-Type": "application/json" }
24546
+ headers: {
24547
+ "Content-Type": "application/json",
24548
+ "User-Agent": USER_AGENT,
24549
+ "X-B4M-Client": USER_AGENT
24550
+ }
24450
24551
  });
24451
24552
  this.client.interceptors.request.use(async (config) => {
24452
24553
  const tokens = await this.configStore.getAuthTokens();
@@ -27518,4 +27619,4 @@ function createReviewGateStore(onUpdate) {
27518
27619
  };
27519
27620
  }
27520
27621
  //#endregion
27521
- export { SessionStore as $, formatStep as A, DEFAULT_RETRY_CONFIG as B, WebSocketToolExecutor as C, ServerLlmBackend as D, WebSocketLlmBackend as E, PermissionManager as F, getPlanModeFilePath as G, clearFeatureModuleTools as H, generateCliTools as I, isReadOnlyTool as J, buildSystemPrompt as K, ALWAYS_DENIED_FOR_AGENTS as L, loadContextFiles as M, getApiUrl as N, McpManager as O, getEnvironmentName as P, CommandHistoryStore as Q, DEFAULT_AGENT_MODEL as R, ApiClient as S, FallbackLlmBackend as T, registerFeatureModuleTools as U, DEFAULT_THOROUGHNESS as V, setWebSocketToolExecutor as W, CustomCommandStore as X, ReActAgent as Y, CheckpointStore as Z, createAgentDelegateTool as _, createBlockerTools as a, formatFileSize$1 as at, createSkillTool as b, createDecisionStore as c, createFindDefinitionTool as d, OAuthClient as et, createTodoStore as f, BackgroundAgentManager as g, createBackgroundAgentTools as h, createBlockerStore as i, mergeCommands as it, extractCompactInstructions as j, substituteArguments as k, formatDecisionsOutput as l, createCoordinateTaskTool as m, createReviewGateTool as n, processFileReferences as nt, formatBlockersOutput as o, searchFiles as ot, createWriteTodosTool as p, buildSkillsPromptSection as q, formatReviewGatesOutput as r, searchCommands as rt, createDecisionLogTool as s, warmFileCache as st, createReviewGateStore as t, hasFileReferences as tt, createGetFileStructureTool as u, AgentStore as v, WebSocketConnectionManager as w, parseAgentConfig as x, SubagentOrchestrator as y, DEFAULT_MAX_ITERATIONS as z };
27622
+ export { OAuthClient as $, substituteArguments as A, DEFAULT_THOROUGHNESS as B, WebSocketToolExecutor as C, ServerLlmBackend as D, WebSocketLlmBackend as E, generateCliTools as F, buildSystemPrompt as G, registerFeatureModuleTools as H, ALWAYS_DENIED_FOR_AGENTS as I, ReActAgent as J, buildSkillsPromptSection as K, DEFAULT_AGENT_MODEL as L, extractCompactInstructions as M, loadContextFiles as N, isTransientNetworkError as O, PermissionManager as P, SessionStore as Q, DEFAULT_MAX_ITERATIONS as R, ApiClient as S, FallbackLlmBackend as T, setWebSocketToolExecutor as U, clearFeatureModuleTools as V, getPlanModeFilePath as W, CheckpointStore as X, CustomCommandStore as Y, CommandHistoryStore as Z, createAgentDelegateTool as _, createBlockerTools as a, searchFiles as at, createSkillTool as b, createDecisionStore as c, createFindDefinitionTool as d, hasFileReferences as et, createTodoStore as f, BackgroundAgentManager as g, createBackgroundAgentTools as h, createBlockerStore as i, formatFileSize$1 as it, formatStep as j, McpManager as k, formatDecisionsOutput as l, createCoordinateTaskTool as m, createReviewGateTool as n, searchCommands as nt, formatBlockersOutput as o, warmFileCache as ot, createWriteTodosTool as p, isReadOnlyTool as q, formatReviewGatesOutput as r, mergeCommands as rt, createDecisionLogTool as s, createReviewGateStore as t, processFileReferences as tt, createGetFileStructureTool as u, AgentStore as v, WebSocketConnectionManager as w, parseAgentConfig as x, SubagentOrchestrator as y, DEFAULT_RETRY_CONFIG as z };
@@ -3,9 +3,6 @@ import { promises } from "fs";
3
3
  import { homedir } from "os";
4
4
  import path from "path";
5
5
  import axios from "axios";
6
- //#region package.json
7
- var version = "0.10.3";
8
- //#endregion
9
6
  //#region src/utils/updateChecker.ts
10
7
  /**
11
8
  * Update checker utility for B4M CLI
@@ -117,4 +114,4 @@ async function forceCheckForUpdate(currentVersion) {
117
114
  };
118
115
  }
119
116
  //#endregion
120
- export { version as a, forceCheckForUpdate as i, compareSemver as n, fetchLatestVersion as r, checkForUpdate as t };
117
+ export { forceCheckForUpdate as i, compareSemver as n, fetchLatestVersion as r, checkForUpdate as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "0.10.3",
3
+ "version": "0.12.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.6",
111
- "@bike4mind/llm-adapters": "0.3.1",
110
+ "@bike4mind/fab-pipeline": "0.2.8",
111
+ "@bike4mind/llm-adapters": "0.3.3",
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.11.5",
128
- "@bike4mind/common": "2.104.1",
129
- "@bike4mind/mcp": "1.37.19",
130
- "@bike4mind/services": "2.91.1",
131
- "@bike4mind/utils": "2.23.7"
127
+ "@bike4mind/agents": "0.11.7",
128
+ "@bike4mind/common": "2.106.0",
129
+ "@bike4mind/mcp": "1.37.21",
130
+ "@bike4mind/services": "2.92.1",
131
+ "@bike4mind/utils": "2.23.9"
132
132
  },
133
133
  "optionalDependencies": {
134
134
  "@vscode/ripgrep": "^1.18.0"