@bike4mind/cli 0.2.29-feat-cli-sandbox.18842 → 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-XPSG72HV.js";
5
2
 
6
3
  // src/utils/Logger.ts
7
4
  import fs from "fs/promises";
@@ -265,39 +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
- platform: SandboxPlatformSchema.default(DEFAULT_SANDBOX_CONFIG.platform)
289
- });
290
- var PartialSandboxConfigSchema = z.object({
291
- enabled: z.boolean().optional(),
292
- mode: z.enum(["disabled", "auto-allow", "permissions"]).optional(),
293
- filesystem: z.object({
294
- allowedReadPaths: z.array(z.string()).optional(),
295
- deniedPaths: z.array(z.string()).optional(),
296
- writeOnlyToWorkingDir: z.boolean().optional()
297
- }).optional(),
298
- excludedCommands: z.array(z.string()).optional(),
299
- platform: SandboxPlatformSchema.optional()
300
- }).optional();
301
265
  var AuthTokensSchema = z.object({
302
266
  accessToken: z.string(),
303
267
  refreshToken: z.string(),
@@ -372,8 +336,7 @@ var CliConfigSchema = z.object({
372
336
  disabled: z.array(z.string()),
373
337
  config: z.record(z.any())
374
338
  }),
375
- trustedTools: z.array(z.string()).optional().default([]),
376
- sandbox: SandboxConfigSchema.optional()
339
+ trustedTools: z.array(z.string()).optional().default([])
377
340
  });
378
341
  var ProjectConfigSchema = z.object({
379
342
  tools: z.object({
@@ -391,8 +354,7 @@ var ProjectConfigSchema = z.object({
391
354
  theme: z.enum(["light", "dark"]).optional(),
392
355
  exportFormat: z.enum(["markdown", "json"]).optional(),
393
356
  enableSkillTool: z.boolean().optional()
394
- }).optional(),
395
- sandbox: PartialSandboxConfigSchema
357
+ }).optional()
396
358
  });
397
359
  var ProjectLocalConfigSchema = z.object({
398
360
  trustedTools: z.array(z.string()).optional(),
@@ -409,8 +371,7 @@ var ProjectLocalConfigSchema = z.object({
409
371
  exportFormat: z.enum(["markdown", "json"]).optional(),
410
372
  enableSkillTool: z.boolean().optional()
411
373
  }).optional(),
412
- mcpServers: McpServersSchema.optional(),
413
- sandbox: PartialSandboxConfigSchema
374
+ mcpServers: McpServersSchema.optional()
414
375
  });
415
376
  var DEFAULT_CONFIG = {
416
377
  version: "0.1.0",
@@ -535,20 +496,6 @@ function mergeMcpServers(...serverArrays) {
535
496
  }
536
497
  return Array.from(serverMap.values());
537
498
  }
538
- function mergeSandboxConfig(base, override) {
539
- const resolved = base ?? DEFAULT_SANDBOX_CONFIG;
540
- if (!override) return resolved;
541
- return {
542
- enabled: override.enabled ?? resolved.enabled,
543
- mode: override.mode ?? resolved.mode,
544
- filesystem: {
545
- ...resolved.filesystem,
546
- ...override.filesystem ?? {}
547
- },
548
- excludedCommands: override.excludedCommands ?? resolved.excludedCommands,
549
- platform: override.platform ?? resolved.platform
550
- };
551
- }
552
499
  function mergeConfigs(global, project, local) {
553
500
  const merged = { ...global };
554
501
  if (project) {
@@ -575,9 +522,6 @@ function mergeConfigs(global, project, local) {
575
522
  if (project.mcpServers) {
576
523
  merged.mcpServers = mergeMcpServers(merged.mcpServers, project.mcpServers);
577
524
  }
578
- if (project.sandbox) {
579
- merged.sandbox = mergeSandboxConfig(merged.sandbox, project.sandbox);
580
- }
581
525
  }
582
526
  if (local) {
583
527
  if (local.trustedTools) {
@@ -599,9 +543,6 @@ function mergeConfigs(global, project, local) {
599
543
  if (local.mcpServers) {
600
544
  merged.mcpServers = mergeMcpServers(merged.mcpServers, local.mcpServers);
601
545
  }
602
- if (local.sandbox) {
603
- merged.sandbox = mergeSandboxConfig(merged.sandbox, local.sandbox);
604
- }
605
546
  }
606
547
  return merged;
607
548
  }
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigStore
4
- } from "../chunk-JJL2EFHA.js";
5
- import "../chunk-XPSG72HV.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-JJL2EFHA.js";
12
+ } from "./chunk-LBTTUQJM.js";
14
13
  import {
15
14
  selectActiveBackgroundAgents,
16
15
  useCliStore
17
16
  } from "./chunk-TVW4ZESU.js";
18
- import "./chunk-XPSG72HV.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,42 +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
- const result2 = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
11763
- cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
11721
+ if (!permissionManager.needsPermission(toolName)) {
11722
+ const result2 = await executeTool(toolName, args, apiClient, originalFn);
11764
11723
  agentContext.observationQueue.push({ toolName, result: result2 });
11765
11724
  return result2;
11766
11725
  }
11767
11726
  const { useCliStore: useCliStore2 } = await import("./store-FU6NDC2W.js");
11768
11727
  if (useCliStore2.getState().autoAcceptEdits) {
11769
- const result2 = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
11770
- cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
11728
+ const result2 = await executeTool(toolName, args, apiClient, originalFn);
11771
11729
  agentContext.observationQueue.push({ toolName, result: result2 });
11772
11730
  return result2;
11773
11731
  }
@@ -11802,13 +11760,15 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
11802
11760
  } else if (toolName === "bash_execute" && args?.command) {
11803
11761
  const cwd = args.cwd ? ` (in ${args.cwd})` : "";
11804
11762
  const timeout = args.timeout ? ` [timeout: ${args.timeout}ms]` : "";
11805
- const sandboxLabel = isSandboxed ? " [sandboxed]" : "";
11806
- preview = `$ ${args.command}${cwd}${timeout}${sandboxLabel}`;
11763
+ preview = `$ ${args.command}${cwd}${timeout}`;
11807
11764
  }
11808
- const response = await showPermissionPrompt(toolName, effectiveArgs, preview);
11765
+ const response = await showPermissionPrompt(toolName, args, preview);
11809
11766
  if (response.action === "deny") {
11810
11767
  throw new PermissionDeniedError(toolName, args);
11811
11768
  }
11769
+ if (response.action === "allow-session") {
11770
+ permissionManager.trustToolForSession(toolName);
11771
+ }
11812
11772
  if (response.action === "allow-always") {
11813
11773
  const canTrust = permissionManager.trustTool(toolName);
11814
11774
  if (canTrust) {
@@ -11830,22 +11790,12 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
11830
11790
  }
11831
11791
  }
11832
11792
  }
11833
- const result = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
11834
- cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
11793
+ const result = await executeTool(toolName, args, apiClient, originalFn);
11835
11794
  agentContext.observationQueue.push({ toolName, result });
11836
11795
  return result;
11837
11796
  }
11838
11797
  };
11839
11798
  }
11840
- function cleanupSandboxFiles(paths) {
11841
- if (!paths || paths.length === 0) return;
11842
- for (const p of paths) {
11843
- try {
11844
- rmSync(p, { recursive: true, force: true });
11845
- } catch {
11846
- }
11847
- }
11848
- }
11849
11799
  function wrapToolWithHooks(tool, hooks, hookContext) {
11850
11800
  const hasToolHooks = hooks?.PreToolUse || hooks?.PostToolUse || hooks?.PostToolUseFailure;
11851
11801
  if (!hasToolHooks) {
@@ -11939,7 +11889,7 @@ function normalizeToolName(toolName) {
11939
11889
  }
11940
11890
  return TOOL_NAME_MAPPING[toolName] || toolName;
11941
11891
  }
11942
- function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter, sandboxOrchestrator) {
11892
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
11943
11893
  const logger2 = new CliLogger();
11944
11894
  const storage = new NoOpStorage();
11945
11895
  const user = {
@@ -11999,15 +11949,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
11999
11949
  tools_to_generate
12000
11950
  );
12001
11951
  let tools = Object.entries(toolsMap).map(
12002
- ([_, tool]) => wrapToolWithPermission(
12003
- tool,
12004
- permissionManager,
12005
- showPermissionPrompt,
12006
- agentContext,
12007
- configStore,
12008
- apiClient,
12009
- sandboxOrchestrator
12010
- )
11952
+ ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
12011
11953
  );
12012
11954
  if (toolFilter) {
12013
11955
  const { allowedTools, deniedTools } = toolFilter;
@@ -12031,22 +11973,12 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
12031
11973
  var PermissionManager = class {
12032
11974
  constructor(trustedTools = [], customCategories, deniedTools) {
12033
11975
  this.trustedTools = /* @__PURE__ */ new Set();
11976
+ this.sessionTrustedTools = /* @__PURE__ */ new Set();
12034
11977
  this.deniedTools = /* @__PURE__ */ new Set();
12035
- this._sandboxMode = "disabled";
12036
- this._sandboxActive = false;
12037
11978
  this.trustedTools = new Set(trustedTools);
12038
11979
  this.customCategories = new Map(Object.entries(customCategories || {}));
12039
11980
  this.deniedTools = new Set(deniedTools || []);
12040
11981
  }
12041
- /** Update sandbox state from the orchestrator */
12042
- setSandboxState(mode, active) {
12043
- this._sandboxMode = mode;
12044
- this._sandboxActive = active;
12045
- }
12046
- /** Check if sandbox auto-allow is active */
12047
- isSandboxAutoAllow() {
12048
- return this._sandboxMode === "auto-allow" && this._sandboxActive;
12049
- }
12050
11982
  /**
12051
11983
  * Check if a tool needs permission before execution
12052
11984
  *
@@ -12057,13 +11989,8 @@ var PermissionManager = class {
12057
11989
  *
12058
11990
  * Note: prompt_always tools CANNOT be trusted, so they always need permission
12059
11991
  * Note: denied tools from project config ALWAYS need permission (cannot be overridden locally)
12060
- *
12061
- * @param toolName - The tool being checked
12062
- * @param options.isSandboxed - If true, the command will run inside the OS-level sandbox.
12063
- * In auto-allow mode, sandboxed bash_execute commands skip the permission prompt
12064
- * because the sandbox provides the security boundary.
12065
11992
  */
12066
- needsPermission(toolName, options) {
11993
+ needsPermission(toolName) {
12067
11994
  const categoryMap = Object.fromEntries(this.customCategories);
12068
11995
  const category = getToolCategory(toolName, categoryMap);
12069
11996
  if (this.deniedTools.has(toolName)) {
@@ -12072,7 +11999,7 @@ var PermissionManager = class {
12072
11999
  if (category === "auto_approve") {
12073
12000
  return false;
12074
12001
  }
12075
- if (options?.isSandboxed && toolName === "bash_execute" && this.isSandboxAutoAllow()) {
12002
+ if (this.sessionTrustedTools.has(toolName)) {
12076
12003
  return false;
12077
12004
  }
12078
12005
  if (category === "prompt_always") {
@@ -12142,6 +12069,29 @@ var PermissionManager = class {
12142
12069
  getDeniedTools() {
12143
12070
  return Array.from(this.deniedTools).sort();
12144
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
+ }
12145
12095
  /**
12146
12096
  * Clear all trusted tools
12147
12097
  */
@@ -13643,7 +13593,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
13643
13593
  // package.json
13644
13594
  var package_default = {
13645
13595
  name: "@bike4mind/cli",
13646
- version: "0.2.29-feat-cli-sandbox.18842+fc55c56fc",
13596
+ version: "0.2.29-feat-session-permission.18841+6d9c4deb5",
13647
13597
  type: "module",
13648
13598
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
13649
13599
  license: "UNLICENSED",
@@ -13753,10 +13703,10 @@ var package_default = {
13753
13703
  },
13754
13704
  devDependencies: {
13755
13705
  "@bike4mind/agents": "0.1.0",
13756
- "@bike4mind/common": "2.50.1-feat-cli-sandbox.18842+fc55c56fc",
13757
- "@bike4mind/mcp": "1.29.1-feat-cli-sandbox.18842+fc55c56fc",
13758
- "@bike4mind/services": "2.48.1-feat-cli-sandbox.18842+fc55c56fc",
13759
- "@bike4mind/utils": "2.5.1-feat-cli-sandbox.18842+fc55c56fc",
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",
13760
13710
  "@types/better-sqlite3": "^7.6.13",
13761
13711
  "@types/diff": "^5.0.9",
13762
13712
  "@types/jsonwebtoken": "^9.0.4",
@@ -13773,7 +13723,7 @@ var package_default = {
13773
13723
  optionalDependencies: {
13774
13724
  "@vscode/ripgrep": "^1.17.0"
13775
13725
  },
13776
- gitHead: "fc55c56fc0acb53d861d98b5b5e902a3e38a4c37"
13726
+ gitHead: "6d9c4deb5a83cfc1087043d22f407d3444a91cca"
13777
13727
  };
13778
13728
 
13779
13729
  // src/config/constants.ts
@@ -15581,8 +15531,7 @@ function CliApp() {
15581
15531
  agentStore: null,
15582
15532
  abortController: null,
15583
15533
  contextContent: "",
15584
- backgroundManager: null,
15585
- sandboxOrchestrator: null
15534
+ backgroundManager: null
15586
15535
  });
15587
15536
  const [isInitialized, setIsInitialized] = useState9(false);
15588
15537
  const [initError, setInitError] = useState9(null);
@@ -15779,20 +15728,6 @@ function CliApp() {
15779
15728
  config.tools.disabled || []
15780
15729
  // denied tools from project config
15781
15730
  );
15782
- const { createSandboxRuntime } = await import("./SandboxRuntimeAdapter-SRX3IM2Q.js");
15783
- const { SandboxOrchestrator } = await import("./SandboxOrchestrator-AEOHC2BL.js");
15784
- const { DEFAULT_SANDBOX_CONFIG } = await import("./types-2T7ALTCA.js");
15785
- const sandboxRuntime = await createSandboxRuntime();
15786
- const sandboxConfig = config.sandbox ?? DEFAULT_SANDBOX_CONFIG;
15787
- const sandboxOrchestrator = new SandboxOrchestrator(sandboxConfig, sandboxRuntime);
15788
- permissionManager.setSandboxState(sandboxConfig.mode, sandboxOrchestrator.isActive());
15789
- if (sandboxConfig.enabled && sandboxConfig.mode !== "disabled") {
15790
- if (sandboxRuntime) {
15791
- console.log(`\u{1F512} Sandbox: ${sandboxConfig.mode} (${sandboxRuntime.name})`);
15792
- } else {
15793
- console.log("\u26A0\uFE0F Sandbox: enabled but runtime not available on this platform");
15794
- }
15795
- }
15796
15731
  let permissionPromptCounter = 0;
15797
15732
  const promptFn = (toolName, args, preview) => {
15798
15733
  return new Promise((resolve3) => {
@@ -15818,10 +15753,7 @@ function CliApp() {
15818
15753
  promptFn,
15819
15754
  agentContext,
15820
15755
  state.configStore,
15821
- apiClient,
15822
- void 0,
15823
- // toolFilter
15824
- sandboxOrchestrator
15756
+ apiClient
15825
15757
  );
15826
15758
  startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
15827
15759
  const mcpManager = new McpManager(config);
@@ -15979,10 +15911,8 @@ ${contextResult.mergedContent}` : "";
15979
15911
  // Store agent store for agent management commands
15980
15912
  contextContent: contextResult.mergedContent,
15981
15913
  // Store raw context for compact instructions
15982
- backgroundManager,
15914
+ backgroundManager
15983
15915
  // Store for grouped notification turn tracking
15984
- sandboxOrchestrator
15985
- // Store sandbox orchestrator for /sandbox commands
15986
15916
  }));
15987
15917
  setStoreSession(newSession);
15988
15918
  const bannerLines = [
@@ -17451,86 +17381,6 @@ No usage data available for the last ${USAGE_DAYS} days.`);
17451
17381
  }
17452
17382
  break;
17453
17383
  }
17454
- // --- Sandbox commands ---
17455
- case "sandbox": {
17456
- if (!state.sandboxOrchestrator) {
17457
- console.log("\nSandbox: Not initialized");
17458
- break;
17459
- }
17460
- const status = state.sandboxOrchestrator.getStatus();
17461
- console.log("\nSandbox Status:");
17462
- console.log(` Enabled: ${status.enabled ? "Yes" : "No"}`);
17463
- console.log(` Mode: ${status.mode}`);
17464
- console.log(` Platform: ${status.platform ?? "N/A"}`);
17465
- console.log(` Runtime: ${status.runtimeName ?? "N/A"}`);
17466
- console.log(` Available: ${status.runtimeAvailable ? "Yes" : "No"}`);
17467
- console.log("");
17468
- console.log("Filesystem Config:");
17469
- console.log(` Write only to CWD: ${status.config.filesystem.writeOnlyToWorkingDir}`);
17470
- console.log(` Allowed reads: ${status.config.filesystem.allowedReadPaths.join(", ") || "(none)"}`);
17471
- console.log(` Denied paths: ${status.config.filesystem.deniedPaths.join(", ") || "(none)"}`);
17472
- console.log(` Excluded cmds: ${status.config.excludedCommands.join(", ") || "(none)"}`);
17473
- console.log("");
17474
- break;
17475
- }
17476
- case "sandbox:enable": {
17477
- if (!state.sandboxOrchestrator) {
17478
- console.log("Sandbox not initialized");
17479
- break;
17480
- }
17481
- if (!state.sandboxOrchestrator.isAvailable()) {
17482
- console.log("Sandbox runtime not available on this platform");
17483
- break;
17484
- }
17485
- state.sandboxOrchestrator.setMode("auto-allow");
17486
- state.permissionManager?.setSandboxState("auto-allow", true);
17487
- const config = await state.configStore.get();
17488
- await state.configStore.save({
17489
- ...config,
17490
- sandbox: { ...state.sandboxOrchestrator.getConfig() }
17491
- });
17492
- console.log("Sandbox enabled (auto-allow mode)");
17493
- break;
17494
- }
17495
- case "sandbox:disable": {
17496
- if (!state.sandboxOrchestrator) {
17497
- console.log("Sandbox not initialized");
17498
- break;
17499
- }
17500
- state.sandboxOrchestrator.setMode("disabled");
17501
- state.permissionManager?.setSandboxState("disabled", false);
17502
- const disableConfig = await state.configStore.get();
17503
- await state.configStore.save({
17504
- ...disableConfig,
17505
- sandbox: { ...state.sandboxOrchestrator.getConfig() }
17506
- });
17507
- console.log("Sandbox disabled");
17508
- break;
17509
- }
17510
- case "sandbox:mode": {
17511
- if (!state.sandboxOrchestrator) {
17512
- console.log("Sandbox not initialized");
17513
- break;
17514
- }
17515
- const modeArg = args[0];
17516
- if (modeArg !== "auto-allow" && modeArg !== "permissions") {
17517
- console.log("Usage: /sandbox:mode <auto-allow|permissions>");
17518
- break;
17519
- }
17520
- if (!state.sandboxOrchestrator.isAvailable()) {
17521
- console.log("Sandbox runtime not available on this platform");
17522
- break;
17523
- }
17524
- state.sandboxOrchestrator.setMode(modeArg);
17525
- state.permissionManager?.setSandboxState(modeArg, state.sandboxOrchestrator.isActive());
17526
- const modeConfig = await state.configStore.get();
17527
- await state.configStore.save({
17528
- ...modeConfig,
17529
- sandbox: { ...state.sandboxOrchestrator.getConfig() }
17530
- });
17531
- console.log(`Sandbox mode set to: ${modeArg}`);
17532
- break;
17533
- }
17534
17384
  default:
17535
17385
  console.log(`Unknown command: /${command}`);
17536
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.18842+fc55c56fc",
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.18842+fc55c56fc",
114
- "@bike4mind/mcp": "1.29.1-feat-cli-sandbox.18842+fc55c56fc",
115
- "@bike4mind/services": "2.48.1-feat-cli-sandbox.18842+fc55c56fc",
116
- "@bike4mind/utils": "2.5.1-feat-cli-sandbox.18842+fc55c56fc",
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": "fc55c56fc0acb53d861d98b5b5e902a3e38a4c37"
133
+ "gitHead": "6d9c4deb5a83cfc1087043d22f407d3444a91cca"
134
134
  }
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- expandPath,
4
- isBinaryAvailable
5
- } from "./chunk-PRJKJWD6.js";
6
-
7
- // src/sandbox/runtime/BubblewrapRuntime.ts
8
- import os from "os";
9
- import { existsSync } from "fs";
10
- var SYSTEM_RO_BINDS = ["/usr", "/bin", "/lib", "/lib64", "/sbin", "/etc"];
11
- var SPECIAL_MOUNTS = {
12
- dev: "/dev",
13
- proc: "/proc",
14
- tmp: "/tmp"
15
- };
16
- var BubblewrapRuntime = class {
17
- constructor() {
18
- this.platform = "linux";
19
- this.name = "bubblewrap";
20
- }
21
- isAvailable() {
22
- return isBinaryAvailable("bwrap");
23
- }
24
- wrapCommand(options) {
25
- const { command, cwd, filesystemConfig, env } = options;
26
- const expandedDenied = filesystemConfig.deniedPaths.map(expandPath);
27
- const expandedAllowed = filesystemConfig.allowedReadPaths.map(expandPath);
28
- const args = [];
29
- for (const sysPath of SYSTEM_RO_BINDS) {
30
- if (existsSync(sysPath)) {
31
- args.push("--ro-bind", sysPath, sysPath);
32
- }
33
- }
34
- args.push("--dev", SPECIAL_MOUNTS.dev);
35
- args.push("--proc", SPECIAL_MOUNTS.proc);
36
- args.push("--tmpfs", SPECIAL_MOUNTS.tmp);
37
- args.push("--bind", cwd, cwd);
38
- const homeDir = os.homedir();
39
- args.push("--ro-bind", homeDir, homeDir);
40
- for (const allowedPath of expandedAllowed) {
41
- if (!allowedPath.startsWith(homeDir) && existsSync(allowedPath)) {
42
- args.push("--ro-bind-try", allowedPath, allowedPath);
43
- }
44
- }
45
- for (const deniedPath of expandedDenied) {
46
- args.push("--tmpfs", deniedPath);
47
- }
48
- args.push("--unshare-all");
49
- args.push("--share-net");
50
- args.push("--die-with-parent");
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,101 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- DEFAULT_SANDBOX_CONFIG
4
- } from "./chunk-XPSG72HV.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
- return {
28
- type: "unsandboxed",
29
- requiresPermission: true,
30
- reason: `Command '${baseCommand}' is excluded from sandboxing`
31
- };
32
- }
33
- if (!this.runtime) {
34
- return {
35
- type: "unsandboxed",
36
- requiresPermission: true,
37
- reason: "Sandbox runtime not available on this platform"
38
- };
39
- }
40
- const wrappedCommand = this.runtime.wrapCommand({
41
- command,
42
- cwd,
43
- filesystemConfig: this.config.filesystem
44
- });
45
- return { type: "sandbox", wrappedCommand };
46
- }
47
- /** Get the current sandbox mode */
48
- getMode() {
49
- return this.config.mode;
50
- }
51
- /** Set the sandbox mode (does not persist — caller must save config) */
52
- setMode(mode) {
53
- this.config.mode = mode;
54
- this.config.enabled = mode !== "disabled";
55
- }
56
- /** Check if sandbox is enabled and runtime is available */
57
- isAvailable() {
58
- return this.runtime !== null && this.runtime.isAvailable();
59
- }
60
- /** Check if sandbox is currently active (enabled + available) */
61
- isActive() {
62
- return this.config.enabled && this.config.mode !== "disabled" && this.isAvailable();
63
- }
64
- /** Get the current sandbox configuration */
65
- getConfig() {
66
- return this.config;
67
- }
68
- /** Update config (does not persist — caller must save) */
69
- updateConfig(config) {
70
- this.config = config;
71
- }
72
- /** Get full status information for display */
73
- getStatus() {
74
- return {
75
- mode: this.config.mode,
76
- enabled: this.config.enabled,
77
- platform: this.runtime?.platform ?? null,
78
- runtimeAvailable: this.runtime?.isAvailable() ?? false,
79
- runtimeName: this.runtime?.name ?? null,
80
- config: this.config
81
- };
82
- }
83
- /**
84
- * Extract the base command name from a full command string.
85
- * e.g., "docker compose up -d" → "docker"
86
- */
87
- getBaseCommand(command) {
88
- const trimmed = command.trim();
89
- const withoutEnv = trimmed.replace(/^(\w+=\S+\s+)*/, "");
90
- const parts = withoutEnv.split(/\s+/);
91
- const first = parts[0] || "";
92
- return first.split("/").pop() || first;
93
- }
94
- /** Check if a command is in the excluded list */
95
- isExcludedCommand(baseCommand) {
96
- return this.config.excludedCommands.some((excluded) => baseCommand === excluded);
97
- }
98
- };
99
- export {
100
- SandboxOrchestrator
101
- };
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- createSandboxRuntime,
4
- detectPlatform,
5
- expandPath,
6
- isBinaryAvailable
7
- } from "./chunk-PRJKJWD6.js";
8
- export {
9
- createSandboxRuntime,
10
- detectPlatform,
11
- expandPath,
12
- isBinaryAvailable
13
- };
@@ -1,92 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- expandPath,
4
- isBinaryAvailable
5
- } from "./chunk-PRJKJWD6.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
- const profile = this.generateProfile(options);
74
- const tmpDir = mkdtempSync(path.join(os.tmpdir(), "b4m-sandbox-"));
75
- const profilePath = path.join(tmpDir, "sandbox.sb");
76
- writeFileSync(profilePath, profile, "utf-8");
77
- const args = ["-f", profilePath, "bash", "-c", options.command];
78
- return {
79
- executable: "sandbox-exec",
80
- args,
81
- env: options.env ?? {},
82
- commandString: `sandbox-exec -f ${profilePath} bash -c ${shellEscape(options.command)}`,
83
- cleanupPaths: [profilePath, tmpDir]
84
- };
85
- }
86
- };
87
- function shellEscape(str) {
88
- return `'${str.replace(/'/g, "'\\''")}'`;
89
- }
90
- export {
91
- SeatbeltRuntime
92
- };
@@ -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-DZMEIRPM.js");
28
- const runtime = new SeatbeltRuntime();
29
- return runtime.isAvailable() ? runtime : null;
30
- }
31
- if (platform === "linux") {
32
- const { BubblewrapRuntime } = await import("./BubblewrapRuntime-EJM4QF7U.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,26 +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
- platform: {
14
- linux: {
15
- runtime: "bubblewrap"
16
- },
17
- macos: {
18
- runtime: "seatbelt",
19
- profileTemplate: "default"
20
- }
21
- }
22
- };
23
-
24
- export {
25
- DEFAULT_SANDBOX_CONFIG
26
- };
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- DEFAULT_SANDBOX_CONFIG
4
- } from "./chunk-XPSG72HV.js";
5
- export {
6
- DEFAULT_SANDBOX_CONFIG
7
- };