@bike4mind/cli 0.2.29-feat-cli-sandbox.18843 → 0.2.29-feat-session-permission.18841

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.
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- DEFAULT_SANDBOX_CONFIG
4
- } from "./chunk-W4TCH4ZT.js";
5
2
 
6
3
  // src/utils/Logger.ts
7
4
  import fs from "fs/promises";
@@ -265,41 +262,6 @@ import path2 from "path";
265
262
  import { homedir } from "os";
266
263
  import { v4 as uuidv4 } from "uuid";
267
264
  import { z } from "zod";
268
- var SandboxFilesystemSchema = z.object({
269
- allowedReadPaths: z.array(z.string()).default(DEFAULT_SANDBOX_CONFIG.filesystem.allowedReadPaths),
270
- deniedPaths: z.array(z.string()).default(DEFAULT_SANDBOX_CONFIG.filesystem.deniedPaths),
271
- writeOnlyToWorkingDir: z.boolean().default(true)
272
- });
273
- var SandboxPlatformSchema = z.object({
274
- linux: z.object({
275
- runtime: z.literal("bubblewrap").default("bubblewrap"),
276
- seccompProfile: z.string().optional()
277
- }).default({ runtime: "bubblewrap" }),
278
- macos: z.object({
279
- runtime: z.literal("seatbelt").default("seatbelt"),
280
- profileTemplate: z.string().default("default")
281
- }).default({ runtime: "seatbelt", profileTemplate: "default" })
282
- });
283
- var SandboxConfigSchema = z.object({
284
- enabled: z.boolean().default(false),
285
- mode: z.enum(["disabled", "auto-allow", "permissions"]).default("disabled"),
286
- filesystem: SandboxFilesystemSchema.default(DEFAULT_SANDBOX_CONFIG.filesystem),
287
- excludedCommands: z.array(z.string()).default(DEFAULT_SANDBOX_CONFIG.excludedCommands),
288
- allowUnsandboxedCommands: z.boolean().default(true),
289
- platform: SandboxPlatformSchema.default(DEFAULT_SANDBOX_CONFIG.platform)
290
- });
291
- var PartialSandboxConfigSchema = z.object({
292
- enabled: z.boolean().optional(),
293
- mode: z.enum(["disabled", "auto-allow", "permissions"]).optional(),
294
- filesystem: z.object({
295
- allowedReadPaths: z.array(z.string()).optional(),
296
- deniedPaths: z.array(z.string()).optional(),
297
- writeOnlyToWorkingDir: z.boolean().optional()
298
- }).optional(),
299
- excludedCommands: z.array(z.string()).optional(),
300
- allowUnsandboxedCommands: z.boolean().optional(),
301
- platform: SandboxPlatformSchema.optional()
302
- }).optional();
303
265
  var AuthTokensSchema = z.object({
304
266
  accessToken: z.string(),
305
267
  refreshToken: z.string(),
@@ -374,8 +336,7 @@ var CliConfigSchema = z.object({
374
336
  disabled: z.array(z.string()),
375
337
  config: z.record(z.any())
376
338
  }),
377
- trustedTools: z.array(z.string()).optional().default([]),
378
- sandbox: SandboxConfigSchema.optional()
339
+ trustedTools: z.array(z.string()).optional().default([])
379
340
  });
380
341
  var ProjectConfigSchema = z.object({
381
342
  tools: z.object({
@@ -393,8 +354,7 @@ var ProjectConfigSchema = z.object({
393
354
  theme: z.enum(["light", "dark"]).optional(),
394
355
  exportFormat: z.enum(["markdown", "json"]).optional(),
395
356
  enableSkillTool: z.boolean().optional()
396
- }).optional(),
397
- sandbox: PartialSandboxConfigSchema
357
+ }).optional()
398
358
  });
399
359
  var ProjectLocalConfigSchema = z.object({
400
360
  trustedTools: z.array(z.string()).optional(),
@@ -411,8 +371,7 @@ var ProjectLocalConfigSchema = z.object({
411
371
  exportFormat: z.enum(["markdown", "json"]).optional(),
412
372
  enableSkillTool: z.boolean().optional()
413
373
  }).optional(),
414
- mcpServers: McpServersSchema.optional(),
415
- sandbox: PartialSandboxConfigSchema
374
+ mcpServers: McpServersSchema.optional()
416
375
  });
417
376
  var DEFAULT_CONFIG = {
418
377
  version: "0.1.0",
@@ -537,21 +496,6 @@ function mergeMcpServers(...serverArrays) {
537
496
  }
538
497
  return Array.from(serverMap.values());
539
498
  }
540
- function mergeSandboxConfig(base, override) {
541
- const resolved = base ?? DEFAULT_SANDBOX_CONFIG;
542
- if (!override) return resolved;
543
- return {
544
- enabled: override.enabled ?? resolved.enabled,
545
- mode: override.mode ?? resolved.mode,
546
- filesystem: {
547
- ...resolved.filesystem,
548
- ...override.filesystem ?? {}
549
- },
550
- excludedCommands: override.excludedCommands ?? resolved.excludedCommands,
551
- allowUnsandboxedCommands: override.allowUnsandboxedCommands ?? resolved.allowUnsandboxedCommands,
552
- platform: override.platform ?? resolved.platform
553
- };
554
- }
555
499
  function mergeConfigs(global, project, local) {
556
500
  const merged = { ...global };
557
501
  if (project) {
@@ -578,9 +522,6 @@ function mergeConfigs(global, project, local) {
578
522
  if (project.mcpServers) {
579
523
  merged.mcpServers = mergeMcpServers(merged.mcpServers, project.mcpServers);
580
524
  }
581
- if (project.sandbox) {
582
- merged.sandbox = mergeSandboxConfig(merged.sandbox, project.sandbox);
583
- }
584
525
  }
585
526
  if (local) {
586
527
  if (local.trustedTools) {
@@ -602,9 +543,6 @@ function mergeConfigs(global, project, local) {
602
543
  if (local.mcpServers) {
603
544
  merged.mcpServers = mergeMcpServers(merged.mcpServers, local.mcpServers);
604
545
  }
605
- if (local.sandbox) {
606
- merged.sandbox = mergeSandboxConfig(merged.sandbox, local.sandbox);
607
- }
608
546
  }
609
547
  return merged;
610
548
  }
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigStore
4
- } from "../chunk-JWEG53NG.js";
5
- import "../chunk-W4TCH4ZT.js";
4
+ } from "../chunk-LBTTUQJM.js";
6
5
 
7
6
  // src/commands/mcpCommand.ts
8
7
  async function handleMcpCommand(subcommand, argv) {
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-ELTTBWAQ.js";
3
2
  import "./chunk-BPFEGDC7.js";
4
3
  import "./chunk-BDQBOLYG.js";
5
4
  import {
@@ -10,13 +9,13 @@ import {
10
9
  import {
11
10
  ConfigStore,
12
11
  logger
13
- } from "./chunk-JWEG53NG.js";
12
+ } from "./chunk-LBTTUQJM.js";
14
13
  import {
15
14
  selectActiveBackgroundAgents,
16
15
  useCliStore
17
16
  } from "./chunk-TVW4ZESU.js";
18
- import "./chunk-W4TCH4ZT.js";
19
17
  import "./chunk-PKKJ44C5.js";
18
+ import "./chunk-ELTTBWAQ.js";
20
19
  import {
21
20
  BFLImageService,
22
21
  BaseStorage,
@@ -646,24 +645,6 @@ var COMMANDS = [
646
645
  name: "compact",
647
646
  description: "Compact conversation into new session",
648
647
  args: "[instructions]"
649
- },
650
- // Sandbox commands
651
- {
652
- name: "sandbox",
653
- description: "Show sandbox status and configuration"
654
- },
655
- {
656
- name: "sandbox:enable",
657
- description: "Enable OS-level sandbox for bash commands"
658
- },
659
- {
660
- name: "sandbox:disable",
661
- description: "Disable sandbox mode"
662
- },
663
- {
664
- name: "sandbox:mode",
665
- description: "Set sandbox mode (auto-allow or permissions)",
666
- args: "<auto-allow|permissions>"
667
648
  }
668
649
  ];
669
650
  function getAllCommandNames() {
@@ -1279,10 +1260,12 @@ function PermissionPrompt({
1279
1260
  }) {
1280
1261
  const items = canBeTrusted ? [
1281
1262
  { label: "\u2713 Allow once", value: "allow-once" },
1263
+ { label: "\u2713 Allow for this session", value: "allow-session" },
1282
1264
  { label: "\u2713 Always allow (trust this tool)", value: "allow-always" },
1283
1265
  { label: "\u2717 Deny", value: "deny" }
1284
1266
  ] : [
1285
1267
  { label: "\u2713 Allow once", value: "allow-once" },
1268
+ { label: "\u2713 Allow for this session", value: "allow-session" },
1286
1269
  { label: "\u2717 Deny", value: "deny" }
1287
1270
  ];
1288
1271
  const [selectedIndex, setSelectedIndex] = useState3(0);
@@ -3711,9 +3694,6 @@ EXAMPLES:
3711
3694
  Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
3712
3695
  }
3713
3696
 
3714
- // src/utils/toolsAdapter.ts
3715
- import { rmSync } from "fs";
3716
-
3717
3697
  // ../../b4m-core/packages/services/dist/src/referService/generateCodes.js
3718
3698
  import { randomBytes } from "crypto";
3719
3699
  import range from "lodash/range.js";
@@ -11732,60 +11712,20 @@ var CliLogger = class extends Logger {
11732
11712
  log(...args) {
11733
11713
  }
11734
11714
  };
11735
- function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator) {
11715
+ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient) {
11736
11716
  const originalFn = tool.toolFn;
11737
11717
  const toolName = tool.toolSchema.name;
11738
11718
  return {
11739
11719
  ...tool,
11740
11720
  toolFn: async (args) => {
11741
- let isSandboxed = false;
11742
- let sandboxedArgs = args;
11743
- if (toolName === "bash_execute" && args?.command && sandboxOrchestrator) {
11744
- const pathMod = await import("path");
11745
- const cwd = args.cwd ? pathMod.resolve(process.cwd(), args.cwd) : process.cwd();
11746
- const decision = sandboxOrchestrator.shouldSandbox(args.command, cwd);
11747
- if (decision.type === "blocked") {
11748
- return `Command blocked by sandbox: ${decision.reason}`;
11749
- }
11750
- if (decision.type === "sandbox") {
11751
- isSandboxed = true;
11752
- sandboxedArgs = {
11753
- ...args,
11754
- command: decision.wrappedCommand.commandString,
11755
- // Store cleanup paths for post-execution cleanup
11756
- _sandboxCleanup: decision.wrappedCommand.cleanupPaths
11757
- };
11758
- }
11759
- }
11760
- const effectiveArgs = isSandboxed ? sandboxedArgs : args;
11761
- if (!permissionManager.needsPermission(toolName, { isSandboxed })) {
11762
- let result2 = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
11763
- cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
11764
- result2 = await retrySandboxFailure(
11765
- isSandboxed,
11766
- result2,
11767
- toolName,
11768
- args,
11769
- apiClient,
11770
- originalFn,
11771
- showPermissionPrompt
11772
- );
11721
+ if (!permissionManager.needsPermission(toolName)) {
11722
+ const result2 = await executeTool(toolName, args, apiClient, originalFn);
11773
11723
  agentContext.observationQueue.push({ toolName, result: result2 });
11774
11724
  return result2;
11775
11725
  }
11776
11726
  const { useCliStore: useCliStore2 } = await import("./store-FU6NDC2W.js");
11777
11727
  if (useCliStore2.getState().autoAcceptEdits) {
11778
- let result2 = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
11779
- cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
11780
- result2 = await retrySandboxFailure(
11781
- isSandboxed,
11782
- result2,
11783
- toolName,
11784
- args,
11785
- apiClient,
11786
- originalFn,
11787
- showPermissionPrompt
11788
- );
11728
+ const result2 = await executeTool(toolName, args, apiClient, originalFn);
11789
11729
  agentContext.observationQueue.push({ toolName, result: result2 });
11790
11730
  return result2;
11791
11731
  }
@@ -11820,13 +11760,15 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
11820
11760
  } else if (toolName === "bash_execute" && args?.command) {
11821
11761
  const cwd = args.cwd ? ` (in ${args.cwd})` : "";
11822
11762
  const timeout = args.timeout ? ` [timeout: ${args.timeout}ms]` : "";
11823
- const sandboxLabel = isSandboxed ? " [sandboxed]" : "";
11824
- preview = `$ ${args.command}${cwd}${timeout}${sandboxLabel}`;
11763
+ preview = `$ ${args.command}${cwd}${timeout}`;
11825
11764
  }
11826
- const response = await showPermissionPrompt(toolName, effectiveArgs, preview);
11765
+ const response = await showPermissionPrompt(toolName, args, preview);
11827
11766
  if (response.action === "deny") {
11828
11767
  throw new PermissionDeniedError(toolName, args);
11829
11768
  }
11769
+ if (response.action === "allow-session") {
11770
+ permissionManager.trustToolForSession(toolName);
11771
+ }
11830
11772
  if (response.action === "allow-always") {
11831
11773
  const canTrust = permissionManager.trustTool(toolName);
11832
11774
  if (canTrust) {
@@ -11848,50 +11790,12 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
11848
11790
  }
11849
11791
  }
11850
11792
  }
11851
- let result = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
11852
- cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
11853
- result = await retrySandboxFailure(
11854
- isSandboxed,
11855
- result,
11856
- toolName,
11857
- args,
11858
- apiClient,
11859
- originalFn,
11860
- showPermissionPrompt
11861
- );
11793
+ const result = await executeTool(toolName, args, apiClient, originalFn);
11862
11794
  agentContext.observationQueue.push({ toolName, result });
11863
11795
  return result;
11864
11796
  }
11865
11797
  };
11866
11798
  }
11867
- function isSandboxFailure(isSandboxed, result) {
11868
- if (!isSandboxed) return false;
11869
- return result.includes("sandbox-exec:") || result.includes("bwrap:") || result.includes("Operation not permitted");
11870
- }
11871
- async function retrySandboxFailure(isSandboxed, result, toolName, originalArgs, apiClient, originalFn, showPermissionPrompt) {
11872
- if (!isSandboxFailure(isSandboxed, result)) return result;
11873
- const errorSnippet = result.slice(0, 200);
11874
- const retryResponse = await showPermissionPrompt(
11875
- toolName,
11876
- originalArgs,
11877
- `Sandbox execution failed. Retry without sandbox?
11878
-
11879
- Error: ${errorSnippet}`
11880
- );
11881
- if (retryResponse.action !== "deny") {
11882
- return executeTool(toolName, originalArgs, apiClient, originalFn);
11883
- }
11884
- return result;
11885
- }
11886
- function cleanupSandboxFiles(paths) {
11887
- if (!paths || paths.length === 0) return;
11888
- for (const p of paths) {
11889
- try {
11890
- rmSync(p, { recursive: true, force: true });
11891
- } catch {
11892
- }
11893
- }
11894
- }
11895
11799
  function wrapToolWithHooks(tool, hooks, hookContext) {
11896
11800
  const hasToolHooks = hooks?.PreToolUse || hooks?.PostToolUse || hooks?.PostToolUseFailure;
11897
11801
  if (!hasToolHooks) {
@@ -11985,7 +11889,7 @@ function normalizeToolName(toolName) {
11985
11889
  }
11986
11890
  return TOOL_NAME_MAPPING[toolName] || toolName;
11987
11891
  }
11988
- function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter, sandboxOrchestrator) {
11892
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
11989
11893
  const logger2 = new CliLogger();
11990
11894
  const storage = new NoOpStorage();
11991
11895
  const user = {
@@ -12045,15 +11949,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
12045
11949
  tools_to_generate
12046
11950
  );
12047
11951
  let tools = Object.entries(toolsMap).map(
12048
- ([_, tool]) => wrapToolWithPermission(
12049
- tool,
12050
- permissionManager,
12051
- showPermissionPrompt,
12052
- agentContext,
12053
- configStore,
12054
- apiClient,
12055
- sandboxOrchestrator
12056
- )
11952
+ ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
12057
11953
  );
12058
11954
  if (toolFilter) {
12059
11955
  const { allowedTools, deniedTools } = toolFilter;
@@ -12077,22 +11973,12 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
12077
11973
  var PermissionManager = class {
12078
11974
  constructor(trustedTools = [], customCategories, deniedTools) {
12079
11975
  this.trustedTools = /* @__PURE__ */ new Set();
11976
+ this.sessionTrustedTools = /* @__PURE__ */ new Set();
12080
11977
  this.deniedTools = /* @__PURE__ */ new Set();
12081
- this._sandboxMode = "disabled";
12082
- this._sandboxActive = false;
12083
11978
  this.trustedTools = new Set(trustedTools);
12084
11979
  this.customCategories = new Map(Object.entries(customCategories || {}));
12085
11980
  this.deniedTools = new Set(deniedTools || []);
12086
11981
  }
12087
- /** Update sandbox state from the orchestrator */
12088
- setSandboxState(mode, active) {
12089
- this._sandboxMode = mode;
12090
- this._sandboxActive = active;
12091
- }
12092
- /** Check if sandbox auto-allow is active */
12093
- isSandboxAutoAllow() {
12094
- return this._sandboxMode === "auto-allow" && this._sandboxActive;
12095
- }
12096
11982
  /**
12097
11983
  * Check if a tool needs permission before execution
12098
11984
  *
@@ -12103,13 +11989,8 @@ var PermissionManager = class {
12103
11989
  *
12104
11990
  * Note: prompt_always tools CANNOT be trusted, so they always need permission
12105
11991
  * Note: denied tools from project config ALWAYS need permission (cannot be overridden locally)
12106
- *
12107
- * @param toolName - The tool being checked
12108
- * @param options.isSandboxed - If true, the command will run inside the OS-level sandbox.
12109
- * In auto-allow mode, sandboxed bash_execute commands skip the permission prompt
12110
- * because the sandbox provides the security boundary.
12111
11992
  */
12112
- needsPermission(toolName, options) {
11993
+ needsPermission(toolName) {
12113
11994
  const categoryMap = Object.fromEntries(this.customCategories);
12114
11995
  const category = getToolCategory(toolName, categoryMap);
12115
11996
  if (this.deniedTools.has(toolName)) {
@@ -12118,7 +11999,7 @@ var PermissionManager = class {
12118
11999
  if (category === "auto_approve") {
12119
12000
  return false;
12120
12001
  }
12121
- if (options?.isSandboxed && toolName === "bash_execute" && this.isSandboxAutoAllow()) {
12002
+ if (this.sessionTrustedTools.has(toolName)) {
12122
12003
  return false;
12123
12004
  }
12124
12005
  if (category === "prompt_always") {
@@ -12188,6 +12069,29 @@ var PermissionManager = class {
12188
12069
  getDeniedTools() {
12189
12070
  return Array.from(this.deniedTools).sort();
12190
12071
  }
12072
+ /**
12073
+ * Trust a tool for the current session only (in-memory, no persistence)
12074
+ * Works for all tool categories including prompt_always, but NOT project-denied tools
12075
+ */
12076
+ trustToolForSession(toolName) {
12077
+ if (this.deniedTools.has(toolName)) {
12078
+ return false;
12079
+ }
12080
+ this.sessionTrustedTools.add(toolName);
12081
+ return true;
12082
+ }
12083
+ /**
12084
+ * Check if a tool is trusted for the current session
12085
+ */
12086
+ isSessionTrusted(toolName) {
12087
+ return this.sessionTrustedTools.has(toolName);
12088
+ }
12089
+ /**
12090
+ * Clear all session-scoped trust (called on exit or session reset)
12091
+ */
12092
+ clearSessionTrust() {
12093
+ this.sessionTrustedTools.clear();
12094
+ }
12191
12095
  /**
12192
12096
  * Clear all trusted tools
12193
12097
  */
@@ -13689,7 +13593,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
13689
13593
  // package.json
13690
13594
  var package_default = {
13691
13595
  name: "@bike4mind/cli",
13692
- version: "0.2.29-feat-cli-sandbox.18843+467f137bc",
13596
+ version: "0.2.29-feat-session-permission.18841+6d9c4deb5",
13693
13597
  type: "module",
13694
13598
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
13695
13599
  license: "UNLICENSED",
@@ -13799,10 +13703,10 @@ var package_default = {
13799
13703
  },
13800
13704
  devDependencies: {
13801
13705
  "@bike4mind/agents": "0.1.0",
13802
- "@bike4mind/common": "2.50.1-feat-cli-sandbox.18843+467f137bc",
13803
- "@bike4mind/mcp": "1.29.1-feat-cli-sandbox.18843+467f137bc",
13804
- "@bike4mind/services": "2.48.1-feat-cli-sandbox.18843+467f137bc",
13805
- "@bike4mind/utils": "2.5.1-feat-cli-sandbox.18843+467f137bc",
13706
+ "@bike4mind/common": "2.50.1-feat-session-permission.18841+6d9c4deb5",
13707
+ "@bike4mind/mcp": "1.29.1-feat-session-permission.18841+6d9c4deb5",
13708
+ "@bike4mind/services": "2.48.1-feat-session-permission.18841+6d9c4deb5",
13709
+ "@bike4mind/utils": "2.5.1-feat-session-permission.18841+6d9c4deb5",
13806
13710
  "@types/better-sqlite3": "^7.6.13",
13807
13711
  "@types/diff": "^5.0.9",
13808
13712
  "@types/jsonwebtoken": "^9.0.4",
@@ -13819,7 +13723,7 @@ var package_default = {
13819
13723
  optionalDependencies: {
13820
13724
  "@vscode/ripgrep": "^1.17.0"
13821
13725
  },
13822
- gitHead: "467f137bcb738020d1665b78bc7d34de7d846df5"
13726
+ gitHead: "6d9c4deb5a83cfc1087043d22f407d3444a91cca"
13823
13727
  };
13824
13728
 
13825
13729
  // src/config/constants.ts
@@ -15627,8 +15531,7 @@ function CliApp() {
15627
15531
  agentStore: null,
15628
15532
  abortController: null,
15629
15533
  contextContent: "",
15630
- backgroundManager: null,
15631
- sandboxOrchestrator: null
15534
+ backgroundManager: null
15632
15535
  });
15633
15536
  const [isInitialized, setIsInitialized] = useState9(false);
15634
15537
  const [initError, setInitError] = useState9(null);
@@ -15825,20 +15728,6 @@ function CliApp() {
15825
15728
  config.tools.disabled || []
15826
15729
  // denied tools from project config
15827
15730
  );
15828
- const { createSandboxRuntime } = await import("./SandboxRuntimeAdapter-5ZMU25Q3.js");
15829
- const { SandboxOrchestrator } = await import("./SandboxOrchestrator-Z36SFJ7X.js");
15830
- const { DEFAULT_SANDBOX_CONFIG } = await import("./types-SMQKWHQK.js");
15831
- const sandboxRuntime = await createSandboxRuntime();
15832
- const sandboxConfig = config.sandbox ?? DEFAULT_SANDBOX_CONFIG;
15833
- const sandboxOrchestrator = new SandboxOrchestrator(sandboxConfig, sandboxRuntime);
15834
- permissionManager.setSandboxState(sandboxConfig.mode, sandboxOrchestrator.isActive());
15835
- if (sandboxConfig.enabled && sandboxConfig.mode !== "disabled") {
15836
- if (sandboxRuntime) {
15837
- console.log(`\u{1F512} Sandbox: ${sandboxConfig.mode} (${sandboxRuntime.name})`);
15838
- } else {
15839
- console.log("\u26A0\uFE0F Sandbox: enabled but runtime not available on this platform");
15840
- }
15841
- }
15842
15731
  let permissionPromptCounter = 0;
15843
15732
  const promptFn = (toolName, args, preview) => {
15844
15733
  return new Promise((resolve3) => {
@@ -15864,10 +15753,7 @@ function CliApp() {
15864
15753
  promptFn,
15865
15754
  agentContext,
15866
15755
  state.configStore,
15867
- apiClient,
15868
- void 0,
15869
- // toolFilter
15870
- sandboxOrchestrator
15756
+ apiClient
15871
15757
  );
15872
15758
  startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
15873
15759
  const mcpManager = new McpManager(config);
@@ -16025,10 +15911,8 @@ ${contextResult.mergedContent}` : "";
16025
15911
  // Store agent store for agent management commands
16026
15912
  contextContent: contextResult.mergedContent,
16027
15913
  // Store raw context for compact instructions
16028
- backgroundManager,
15914
+ backgroundManager
16029
15915
  // Store for grouped notification turn tracking
16030
- sandboxOrchestrator
16031
- // Store sandbox orchestrator for /sandbox commands
16032
15916
  }));
16033
15917
  setStoreSession(newSession);
16034
15918
  const bannerLines = [
@@ -17497,86 +17381,6 @@ No usage data available for the last ${USAGE_DAYS} days.`);
17497
17381
  }
17498
17382
  break;
17499
17383
  }
17500
- // --- Sandbox commands ---
17501
- case "sandbox": {
17502
- if (!state.sandboxOrchestrator) {
17503
- console.log("\nSandbox: Not initialized");
17504
- break;
17505
- }
17506
- const status = state.sandboxOrchestrator.getStatus();
17507
- console.log("\nSandbox Status:");
17508
- console.log(` Enabled: ${status.enabled ? "Yes" : "No"}`);
17509
- console.log(` Mode: ${status.mode}`);
17510
- console.log(` Platform: ${status.platform ?? "N/A"}`);
17511
- console.log(` Runtime: ${status.runtimeName ?? "N/A"}`);
17512
- console.log(` Available: ${status.runtimeAvailable ? "Yes" : "No"}`);
17513
- console.log("");
17514
- console.log("Filesystem Config:");
17515
- console.log(` Write only to CWD: ${status.config.filesystem.writeOnlyToWorkingDir}`);
17516
- console.log(` Allowed reads: ${status.config.filesystem.allowedReadPaths.join(", ") || "(none)"}`);
17517
- console.log(` Denied paths: ${status.config.filesystem.deniedPaths.join(", ") || "(none)"}`);
17518
- console.log(` Excluded cmds: ${status.config.excludedCommands.join(", ") || "(none)"}`);
17519
- console.log("");
17520
- break;
17521
- }
17522
- case "sandbox:enable": {
17523
- if (!state.sandboxOrchestrator) {
17524
- console.log("Sandbox not initialized");
17525
- break;
17526
- }
17527
- if (!state.sandboxOrchestrator.isAvailable()) {
17528
- console.log("Sandbox runtime not available on this platform");
17529
- break;
17530
- }
17531
- state.sandboxOrchestrator.setMode("auto-allow");
17532
- state.permissionManager?.setSandboxState("auto-allow", true);
17533
- const config = await state.configStore.get();
17534
- await state.configStore.save({
17535
- ...config,
17536
- sandbox: { ...state.sandboxOrchestrator.getConfig() }
17537
- });
17538
- console.log("Sandbox enabled (auto-allow mode)");
17539
- break;
17540
- }
17541
- case "sandbox:disable": {
17542
- if (!state.sandboxOrchestrator) {
17543
- console.log("Sandbox not initialized");
17544
- break;
17545
- }
17546
- state.sandboxOrchestrator.setMode("disabled");
17547
- state.permissionManager?.setSandboxState("disabled", false);
17548
- const disableConfig = await state.configStore.get();
17549
- await state.configStore.save({
17550
- ...disableConfig,
17551
- sandbox: { ...state.sandboxOrchestrator.getConfig() }
17552
- });
17553
- console.log("Sandbox disabled");
17554
- break;
17555
- }
17556
- case "sandbox:mode": {
17557
- if (!state.sandboxOrchestrator) {
17558
- console.log("Sandbox not initialized");
17559
- break;
17560
- }
17561
- const modeArg = args[0];
17562
- if (modeArg !== "auto-allow" && modeArg !== "permissions") {
17563
- console.log("Usage: /sandbox:mode <auto-allow|permissions>");
17564
- break;
17565
- }
17566
- if (!state.sandboxOrchestrator.isAvailable()) {
17567
- console.log("Sandbox runtime not available on this platform");
17568
- break;
17569
- }
17570
- state.sandboxOrchestrator.setMode(modeArg);
17571
- state.permissionManager?.setSandboxState(modeArg, state.sandboxOrchestrator.isActive());
17572
- const modeConfig = await state.configStore.get();
17573
- await state.configStore.save({
17574
- ...modeConfig,
17575
- sandbox: { ...state.sandboxOrchestrator.getConfig() }
17576
- });
17577
- console.log(`Sandbox mode set to: ${modeArg}`);
17578
- break;
17579
- }
17580
17384
  default:
17581
17385
  console.log(`Unknown command: /${command}`);
17582
17386
  console.log("Type /help for available commands");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "0.2.29-feat-cli-sandbox.18843+467f137bc",
3
+ "version": "0.2.29-feat-session-permission.18841+6d9c4deb5",
4
4
  "type": "module",
5
5
  "description": "Interactive CLI tool for Bike4Mind with ReAct agents",
6
6
  "license": "UNLICENSED",
@@ -110,10 +110,10 @@
110
110
  },
111
111
  "devDependencies": {
112
112
  "@bike4mind/agents": "0.1.0",
113
- "@bike4mind/common": "2.50.1-feat-cli-sandbox.18843+467f137bc",
114
- "@bike4mind/mcp": "1.29.1-feat-cli-sandbox.18843+467f137bc",
115
- "@bike4mind/services": "2.48.1-feat-cli-sandbox.18843+467f137bc",
116
- "@bike4mind/utils": "2.5.1-feat-cli-sandbox.18843+467f137bc",
113
+ "@bike4mind/common": "2.50.1-feat-session-permission.18841+6d9c4deb5",
114
+ "@bike4mind/mcp": "1.29.1-feat-session-permission.18841+6d9c4deb5",
115
+ "@bike4mind/services": "2.48.1-feat-session-permission.18841+6d9c4deb5",
116
+ "@bike4mind/utils": "2.5.1-feat-session-permission.18841+6d9c4deb5",
117
117
  "@types/better-sqlite3": "^7.6.13",
118
118
  "@types/diff": "^5.0.9",
119
119
  "@types/jsonwebtoken": "^9.0.4",
@@ -130,5 +130,5 @@
130
130
  "optionalDependencies": {
131
131
  "@vscode/ripgrep": "^1.17.0"
132
132
  },
133
- "gitHead": "467f137bcb738020d1665b78bc7d34de7d846df5"
133
+ "gitHead": "6d9c4deb5a83cfc1087043d22f407d3444a91cca"
134
134
  }
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- expandPath,
4
- isBinaryAvailable
5
- } from "./chunk-AVAD2PPK.js";
6
-
7
- // src/sandbox/runtime/BubblewrapRuntime.ts
8
- import os from "os";
9
- var SYSTEM_RO_BINDS = ["/usr", "/bin", "/lib", "/lib64", "/sbin", "/etc"];
10
- var SPECIAL_MOUNTS = {
11
- dev: "/dev",
12
- proc: "/proc",
13
- tmp: "/tmp"
14
- };
15
- var BubblewrapRuntime = class {
16
- constructor() {
17
- this.platform = "linux";
18
- this.name = "bubblewrap";
19
- }
20
- isAvailable() {
21
- return isBinaryAvailable("bwrap");
22
- }
23
- wrapCommand(options) {
24
- const { command, cwd, filesystemConfig, env } = options;
25
- const expandedDenied = filesystemConfig.deniedPaths.map(expandPath);
26
- const expandedAllowed = filesystemConfig.allowedReadPaths.map(expandPath);
27
- const args = [];
28
- for (const sysPath of SYSTEM_RO_BINDS) {
29
- args.push("--ro-bind-try", sysPath, sysPath);
30
- }
31
- args.push("--dev", SPECIAL_MOUNTS.dev);
32
- args.push("--proc", SPECIAL_MOUNTS.proc);
33
- args.push("--tmpfs", SPECIAL_MOUNTS.tmp);
34
- args.push("--bind", cwd, cwd);
35
- const homeDir = os.homedir();
36
- args.push("--ro-bind", homeDir, homeDir);
37
- for (const allowedPath of expandedAllowed) {
38
- if (!allowedPath.startsWith(homeDir)) {
39
- args.push("--ro-bind-try", allowedPath, allowedPath);
40
- }
41
- }
42
- for (const deniedPath of expandedDenied) {
43
- args.push("--tmpfs", deniedPath);
44
- }
45
- args.push("--unshare-all");
46
- args.push("--share-net");
47
- args.push("--die-with-parent");
48
- if (options.seccompProfile) {
49
- args.push("--seccomp", options.seccompProfile);
50
- }
51
- args.push("--chdir", cwd);
52
- args.push("bash", "-c", command);
53
- const commandString = ["bwrap", ...args.map(shellEscape)].join(" ");
54
- return {
55
- executable: "bwrap",
56
- args,
57
- env: env ?? {},
58
- commandString
59
- };
60
- }
61
- };
62
- function shellEscape(str) {
63
- if (/^[a-zA-Z0-9._/=:-]+$/.test(str)) return str;
64
- return `'${str.replace(/'/g, "'\\''")}'`;
65
- }
66
- export {
67
- BubblewrapRuntime
68
- };
@@ -1,110 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- DEFAULT_SANDBOX_CONFIG
4
- } from "./chunk-W4TCH4ZT.js";
5
-
6
- // src/sandbox/SandboxOrchestrator.ts
7
- var SandboxOrchestrator = class {
8
- constructor(config, runtime) {
9
- this.config = config ?? DEFAULT_SANDBOX_CONFIG;
10
- this.runtime = runtime ?? null;
11
- }
12
- /**
13
- * Determine whether a command should be sandboxed.
14
- *
15
- * Decision logic:
16
- * 1. If sandbox is disabled → unsandboxed (no permission change)
17
- * 2. If command matches an excluded command → unsandboxed (requires permission)
18
- * 3. If runtime is not available → unsandboxed with warning
19
- * 4. Otherwise → sandbox the command
20
- */
21
- shouldSandbox(command, cwd) {
22
- if (!this.config.enabled || this.config.mode === "disabled") {
23
- return { type: "unsandboxed", requiresPermission: true };
24
- }
25
- const baseCommand = this.getBaseCommand(command);
26
- if (this.isExcludedCommand(baseCommand)) {
27
- if (!this.config.allowUnsandboxedCommands) {
28
- return {
29
- type: "blocked",
30
- reason: `Command '${baseCommand}' is excluded from sandboxing and unsandboxed commands are not allowed`
31
- };
32
- }
33
- return {
34
- type: "unsandboxed",
35
- requiresPermission: true,
36
- reason: `Command '${baseCommand}' is excluded from sandboxing`
37
- };
38
- }
39
- if (!this.runtime) {
40
- return {
41
- type: "unsandboxed",
42
- requiresPermission: true,
43
- reason: "Sandbox runtime not available on this platform"
44
- };
45
- }
46
- const wrappedCommand = this.runtime.wrapCommand({
47
- command,
48
- cwd,
49
- filesystemConfig: this.config.filesystem,
50
- ...this.runtime.platform === "linux" && this.config.platform.linux.seccompProfile && {
51
- seccompProfile: this.config.platform.linux.seccompProfile
52
- }
53
- });
54
- return { type: "sandbox", wrappedCommand };
55
- }
56
- /** Get the current sandbox mode */
57
- getMode() {
58
- return this.config.mode;
59
- }
60
- /** Set the sandbox mode (does not persist — caller must save config) */
61
- setMode(mode) {
62
- this.config.mode = mode;
63
- this.config.enabled = mode !== "disabled";
64
- }
65
- /** Check if sandbox is enabled and runtime is available */
66
- isAvailable() {
67
- return this.runtime !== null && this.runtime.isAvailable();
68
- }
69
- /** Check if sandbox is currently active (enabled + available) */
70
- isActive() {
71
- return this.config.enabled && this.config.mode !== "disabled" && this.isAvailable();
72
- }
73
- /** Get the current sandbox configuration */
74
- getConfig() {
75
- return this.config;
76
- }
77
- /** Update config (does not persist — caller must save) */
78
- updateConfig(config) {
79
- this.config = config;
80
- }
81
- /** Get full status information for display */
82
- getStatus() {
83
- return {
84
- mode: this.config.mode,
85
- enabled: this.config.enabled,
86
- platform: this.runtime?.platform ?? null,
87
- runtimeAvailable: this.runtime?.isAvailable() ?? false,
88
- runtimeName: this.runtime?.name ?? null,
89
- config: this.config
90
- };
91
- }
92
- /**
93
- * Extract the base command name from a full command string.
94
- * e.g., "docker compose up -d" → "docker"
95
- */
96
- getBaseCommand(command) {
97
- const trimmed = command.trim();
98
- const withoutEnv = trimmed.replace(/^(\w+=\S+\s+)*/, "");
99
- const parts = withoutEnv.split(/\s+/);
100
- const first = parts[0] || "";
101
- return first.split("/").pop() || first;
102
- }
103
- /** Check if a command is in the excluded list */
104
- isExcludedCommand(baseCommand) {
105
- return this.config.excludedCommands.some((excluded) => baseCommand === excluded);
106
- }
107
- };
108
- export {
109
- SandboxOrchestrator
110
- };
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- createSandboxRuntime,
4
- detectPlatform,
5
- expandPath,
6
- isBinaryAvailable
7
- } from "./chunk-AVAD2PPK.js";
8
- export {
9
- createSandboxRuntime,
10
- detectPlatform,
11
- expandPath,
12
- isBinaryAvailable
13
- };
@@ -1,96 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- expandPath,
4
- isBinaryAvailable
5
- } from "./chunk-AVAD2PPK.js";
6
-
7
- // src/sandbox/runtime/SeatbeltRuntime.ts
8
- import { writeFileSync, mkdtempSync } from "fs";
9
- import path from "path";
10
- import os from "os";
11
- function escapeSeatbeltPath(p) {
12
- return p.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
13
- }
14
- var SeatbeltRuntime = class {
15
- constructor() {
16
- this.platform = "darwin";
17
- this.name = "seatbelt";
18
- }
19
- isAvailable() {
20
- return isBinaryAvailable("sandbox-exec");
21
- }
22
- /**
23
- * Generate a Seatbelt profile string from the filesystem config.
24
- *
25
- * Strategy:
26
- * - Start with (allow default) to permit most operations
27
- * - Deny all file writes globally
28
- * - Allow file writes only to the working directory
29
- * - Deny all access to explicitly denied paths
30
- * - Allow read access to explicitly allowed paths
31
- */
32
- generateProfile(options) {
33
- const { cwd, filesystemConfig } = options;
34
- const expandedDenied = filesystemConfig.deniedPaths.map(expandPath);
35
- const expandedAllowed = filesystemConfig.allowedReadPaths.map(expandPath);
36
- const lines = [
37
- "(version 1)",
38
- "",
39
- "; Start with permissive defaults (process exec, network, sysctl, etc.)",
40
- "(allow default)",
41
- ""
42
- ];
43
- if (filesystemConfig.writeOnlyToWorkingDir) {
44
- lines.push("; Deny all file writes globally");
45
- lines.push("(deny file-write*)");
46
- lines.push("");
47
- lines.push("; Allow writes to working directory");
48
- lines.push(`(allow file-write* (subpath "${escapeSeatbeltPath(cwd)}"))`);
49
- lines.push("");
50
- lines.push("; Allow writes to temp directories");
51
- lines.push(`(allow file-write* (subpath "/tmp"))`);
52
- lines.push(`(allow file-write* (subpath "/private/tmp"))`);
53
- lines.push(`(allow file-write* (subpath "${escapeSeatbeltPath(os.tmpdir())}"))`);
54
- lines.push("");
55
- }
56
- if (expandedDenied.length > 0) {
57
- lines.push("; Deny access to sensitive paths");
58
- for (const deniedPath of expandedDenied) {
59
- lines.push(`(deny file-read* file-write* (subpath "${escapeSeatbeltPath(deniedPath)}"))`);
60
- }
61
- lines.push("");
62
- }
63
- if (expandedAllowed.length > 0) {
64
- lines.push("; Explicitly allowed read paths");
65
- for (const allowedPath of expandedAllowed) {
66
- lines.push(`(allow file-read* (subpath "${escapeSeatbeltPath(allowedPath)}"))`);
67
- }
68
- lines.push("");
69
- }
70
- return lines.join("\n");
71
- }
72
- wrapCommand(options) {
73
- try {
74
- const profile = this.generateProfile(options);
75
- const tmpDir = mkdtempSync(path.join(os.tmpdir(), "b4m-sandbox-"));
76
- const profilePath = path.join(tmpDir, "sandbox.sb");
77
- writeFileSync(profilePath, profile, "utf-8");
78
- const args = ["-f", profilePath, "bash", "-c", options.command];
79
- return {
80
- executable: "sandbox-exec",
81
- args,
82
- env: options.env ?? {},
83
- commandString: `sandbox-exec -f ${profilePath} bash -c ${shellEscape(options.command)}`,
84
- cleanupPaths: [profilePath, tmpDir]
85
- };
86
- } catch (err) {
87
- throw new Error(`Failed to create sandbox profile: ${err instanceof Error ? err.message : String(err)}`);
88
- }
89
- }
90
- };
91
- function shellEscape(str) {
92
- return `'${str.replace(/'/g, "'\\''")}'`;
93
- }
94
- export {
95
- SeatbeltRuntime
96
- };
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/sandbox/runtime/SandboxRuntimeAdapter.ts
4
- import os from "os";
5
- import { execSync } from "child_process";
6
- function detectPlatform() {
7
- const platform = os.platform();
8
- if (platform === "darwin") return "darwin";
9
- if (platform === "linux") return "linux";
10
- return null;
11
- }
12
- function isBinaryAvailable(binary) {
13
- try {
14
- execSync(`which ${binary}`, { stdio: "ignore" });
15
- return true;
16
- } catch {
17
- return false;
18
- }
19
- }
20
- function expandPath(pathStr) {
21
- return pathStr.replace(/\$HOME/g, os.homedir()).replace(/\$USER/g, os.userInfo().username).replace(/~\//g, `${os.homedir()}/`);
22
- }
23
- async function createSandboxRuntime() {
24
- const platform = detectPlatform();
25
- if (!platform) return null;
26
- if (platform === "darwin") {
27
- const { SeatbeltRuntime } = await import("./SeatbeltRuntime-SL7HVS3J.js");
28
- const runtime = new SeatbeltRuntime();
29
- return runtime.isAvailable() ? runtime : null;
30
- }
31
- if (platform === "linux") {
32
- const { BubblewrapRuntime } = await import("./BubblewrapRuntime-HXTR3NQG.js");
33
- const runtime = new BubblewrapRuntime();
34
- return runtime.isAvailable() ? runtime : null;
35
- }
36
- return null;
37
- }
38
-
39
- export {
40
- detectPlatform,
41
- isBinaryAvailable,
42
- expandPath,
43
- createSandboxRuntime
44
- };
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/sandbox/types.ts
4
- var DEFAULT_SANDBOX_CONFIG = {
5
- enabled: false,
6
- mode: "disabled",
7
- filesystem: {
8
- writeOnlyToWorkingDir: true,
9
- allowedReadPaths: ["$HOME/.gitconfig", "$HOME/.npmrc", "$HOME/.node_modules"],
10
- deniedPaths: ["$HOME/.ssh", "$HOME/.aws", "$HOME/.gnupg", "$HOME/.env", "/etc/shadow", "/etc/passwd"]
11
- },
12
- excludedCommands: ["docker", "watchman", "podman"],
13
- allowUnsandboxedCommands: true,
14
- platform: {
15
- linux: {
16
- runtime: "bubblewrap"
17
- },
18
- macos: {
19
- runtime: "seatbelt",
20
- profileTemplate: "default"
21
- }
22
- }
23
- };
24
-
25
- export {
26
- DEFAULT_SANDBOX_CONFIG
27
- };
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- DEFAULT_SANDBOX_CONFIG
4
- } from "./chunk-W4TCH4ZT.js";
5
- export {
6
- DEFAULT_SANDBOX_CONFIG
7
- };