@bike4mind/cli 0.2.29-feat-cli-websocket-streaming.18881 → 0.2.29-feat-cli-sandbox.18927

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.
Files changed (27) hide show
  1. package/dist/BubblewrapRuntime-PMIOLWKR.js +71 -0
  2. package/dist/ProxyManager-4ZZEYTD6.js +249 -0
  3. package/dist/SandboxOrchestrator-MYBNKKWL.js +162 -0
  4. package/dist/SandboxRuntimeAdapter-FQ56MAB2.js +13 -0
  5. package/dist/SeatbeltRuntime-EE3TTLEP.js +98 -0
  6. package/dist/StderrViolationParser-7OYPM2DJ.js +59 -0
  7. package/dist/ViolationLogStore-RIIUVURH.js +104 -0
  8. package/dist/{artifactExtractor-SNWW3EVH.js → artifactExtractor-5KWVJRK7.js} +1 -1
  9. package/dist/{chunk-QQL3OBKL.js → chunk-2OADXKGK.js} +2 -2
  10. package/dist/chunk-4BIBE3J7.js +48 -0
  11. package/dist/{chunk-EC2VEDD2.js → chunk-5JTIPGUF.js} +2 -2
  12. package/dist/{chunk-BXCARBUQ.js → chunk-CLA2MUX4.js} +2 -2
  13. package/dist/{chunk-KPOK6V6E.js → chunk-FKJTU7KU.js} +8 -67
  14. package/dist/{chunk-LBTTUQJM.js → chunk-M2AJGZSX.js} +87 -3
  15. package/dist/{chunk-TY6Z2UV2.js → chunk-Q7CVAY4M.js} +6 -15
  16. package/dist/chunk-QWB6ZYY4.js +48 -0
  17. package/dist/commands/mcpCommand.js +2 -1
  18. package/dist/{create-QOJPAIOR.js → create-BDV4MI3R.js} +3 -3
  19. package/dist/index.js +965 -839
  20. package/dist/{llmMarkdownGenerator-JPDNDGE4.js → llmMarkdownGenerator-UIJD4IKA.js} +1 -1
  21. package/dist/{markdownGenerator-NHN4MNEA.js → markdownGenerator-UBZZJ3YM.js} +1 -1
  22. package/dist/{mementoService-EN7FZK7B.js → mementoService-UP7777VB.js} +3 -3
  23. package/dist/{src-36AL3BJR.js → src-DOG6MRCB.js} +1 -13
  24. package/dist/{src-JZN5O6F3.js → src-G5MW2D7G.js} +2 -2
  25. package/dist/{subtractCredits-FCBMFPIP.js → subtractCredits-XGBHF6DI.js} +3 -3
  26. package/dist/types-KB5NP6T4.js +7 -0
  27. package/package.json +11 -6
package/dist/index.js CHANGED
@@ -1,21 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import "./chunk-5JTIPGUF.js";
2
3
  import "./chunk-BPFEGDC7.js";
3
4
  import "./chunk-BDQBOLYG.js";
4
5
  import {
5
6
  getEffectiveApiKey,
6
7
  getOpenWeatherKey,
7
8
  getSerperKey
8
- } from "./chunk-QQL3OBKL.js";
9
+ } from "./chunk-2OADXKGK.js";
9
10
  import {
10
11
  ConfigStore,
11
12
  logger
12
- } from "./chunk-LBTTUQJM.js";
13
+ } from "./chunk-M2AJGZSX.js";
13
14
  import {
14
15
  selectActiveBackgroundAgents,
15
16
  useCliStore
16
17
  } from "./chunk-TVW4ZESU.js";
17
- import "./chunk-BXCARBUQ.js";
18
- import "./chunk-EC2VEDD2.js";
18
+ import "./chunk-4BIBE3J7.js";
19
+ import "./chunk-CLA2MUX4.js";
19
20
  import {
20
21
  BFLImageService,
21
22
  BaseStorage,
@@ -27,7 +28,7 @@ import {
27
28
  OpenAIBackend,
28
29
  OpenAIImageService,
29
30
  XAIImageService
30
- } from "./chunk-TY6Z2UV2.js";
31
+ } from "./chunk-Q7CVAY4M.js";
31
32
  import {
32
33
  AiEvents,
33
34
  ApiKeyEvents,
@@ -83,32 +84,37 @@ import {
83
84
  XAI_IMAGE_MODELS,
84
85
  b4mLLMTools,
85
86
  getMcpProviderMetadata
86
- } from "./chunk-KPOK6V6E.js";
87
+ } from "./chunk-FKJTU7KU.js";
87
88
  import {
88
89
  Logger
89
90
  } from "./chunk-OCYRD7D6.js";
90
91
 
91
92
  // src/index.tsx
92
- import React21, { useState as useState9, useEffect as useEffect6, useCallback as useCallback2, useRef as useRef3 } from "react";
93
+ import React21, { useState as useState10, useEffect as useEffect7, useCallback as useCallback2, useRef as useRef3 } from "react";
93
94
  import { render, Box as Box20, Text as Text20, useApp, useInput as useInput9 } from "ink";
94
95
  import { execSync } from "child_process";
95
96
  import { randomBytes as randomBytes5 } from "crypto";
96
- import { v4 as uuidv413 } from "uuid";
97
+ import { v4 as uuidv411 } from "uuid";
97
98
 
98
99
  // src/components/App.tsx
99
- import React15, { useState as useState5, useEffect as useEffect4 } from "react";
100
+ import React15, { useState as useState6, useEffect as useEffect5 } from "react";
100
101
  import { Box as Box14, Text as Text14, Static, useInput as useInput6 } from "ink";
101
102
 
102
103
  // src/components/StatusBar.tsx
103
104
  import React from "react";
104
105
  import { Box, Text } from "ink";
105
- var StatusBar = React.memo(function StatusBar2({ sessionName, model, tokenUsage }) {
106
+ var StatusBar = React.memo(function StatusBar2({
107
+ sessionName,
108
+ model,
109
+ tokenUsage,
110
+ creditsUsage
111
+ }) {
106
112
  const autoAcceptEdits = useCliStore((state) => state.autoAcceptEdits);
107
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "row", justifyContent: "space-between", width: "100%", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, autoAcceptEdits && /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, "AUTO ACCEPT: Edits"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
113
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "row", justifyContent: "space-between", width: "100%", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, autoAcceptEdits && /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, "AUTO ACCEPT: Edits"), tokenUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), creditsUsage !== void 0 && creditsUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, creditsUsage.toLocaleString(), " ", creditsUsage === 1 ? "credit" : "credits"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
108
114
  });
109
115
 
110
116
  // src/components/InputPrompt.tsx
111
- import React5, { useState as useState2, useMemo, useEffect as useEffect2, useRef as useRef2 } from "react";
117
+ import React5, { useState as useState3, useMemo, useEffect as useEffect3, useRef as useRef2 } from "react";
112
118
  import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
113
119
 
114
120
  // src/components/CustomTextInput.tsx
@@ -321,65 +327,53 @@ function CommandAutocomplete({ commands, selectedIndex }) {
321
327
  // src/components/FileAutocomplete.tsx
322
328
  import React4 from "react";
323
329
  import { Box as Box3, Text as Text4 } from "ink";
330
+ import * as path2 from "path";
324
331
 
325
332
  // src/utils/fileSearch.ts
326
333
  import * as fs from "fs";
327
334
  import * as path from "path";
328
- import Fuse from "fuse.js";
329
- var DEFAULT_IGNORE_PATTERNS = [
330
- "node_modules",
331
- ".git",
332
- ".next",
333
- ".turbo",
334
- "dist",
335
- "build",
336
- ".cache",
337
- "coverage",
338
- ".DS_Store",
339
- ".env",
340
- ".env.local",
341
- ".env.secrets"
342
- ];
343
- function walkDirectory(basePath = process.cwd(), maxDepth = 5, ignorePatterns = DEFAULT_IGNORE_PATTERNS) {
344
- const results = [];
345
- const normalizedBase = path.resolve(basePath);
346
- function walk(currentPath, depth) {
347
- if (depth > maxDepth) return;
335
+ import { AsyncFzf } from "fzf";
336
+ import { fdir } from "fdir";
337
+ import ignore from "ignore";
338
+ function loadIgnoreRules(projectRoot) {
339
+ const ig = ignore();
340
+ ig.add(".git/");
341
+ const gitignorePath = path.join(projectRoot, ".gitignore");
342
+ if (fs.existsSync(gitignorePath)) {
348
343
  try {
349
- const entries = fs.readdirSync(currentPath, { withFileTypes: true });
350
- for (const entry of entries) {
351
- if (ignorePatterns.some((pattern) => entry.name === pattern || entry.name.startsWith(pattern))) {
352
- continue;
353
- }
354
- const fullPath = path.join(currentPath, entry.name);
355
- const relativePath = path.relative(normalizedBase, fullPath);
356
- if (entry.isDirectory()) {
357
- results.push({
358
- path: relativePath,
359
- isDirectory: true
360
- });
361
- walk(fullPath, depth + 1);
362
- } else if (entry.isFile()) {
363
- try {
364
- const stats = fs.statSync(fullPath);
365
- results.push({
366
- path: relativePath,
367
- isDirectory: false,
368
- size: stats.size
369
- });
370
- } catch {
371
- results.push({
372
- path: relativePath,
373
- isDirectory: false
374
- });
375
- }
376
- }
377
- }
344
+ const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
345
+ ig.add(gitignoreContent);
378
346
  } catch {
379
347
  }
380
348
  }
381
- walk(normalizedBase, 0);
382
- return results;
349
+ return ig;
350
+ }
351
+ function crawlDirectory(projectRoot, maxDepth = 10, maxFiles = 2e4, ig) {
352
+ let fileCount = 0;
353
+ const crawler = new fdir().withRelativePaths().withDirs().withPathSeparator("/").exclude((dirPath) => {
354
+ const relativePath = path.posix.relative(projectRoot, dirPath);
355
+ if (!relativePath || relativePath === ".") {
356
+ return false;
357
+ }
358
+ const pathWithSlash = `${relativePath}/`;
359
+ return ig.ignores(pathWithSlash);
360
+ }).filter(() => {
361
+ if (fileCount >= maxFiles) {
362
+ return false;
363
+ }
364
+ fileCount++;
365
+ return true;
366
+ });
367
+ if (maxDepth !== void 0) {
368
+ crawler.withMaxDepth(maxDepth);
369
+ }
370
+ const paths = crawler.crawl(projectRoot).sync();
371
+ return paths.filter((p) => {
372
+ if (!p || p === ".") {
373
+ return true;
374
+ }
375
+ return !ig.ignores(p);
376
+ });
383
377
  }
384
378
  function formatFileSize(bytes) {
385
379
  if (bytes < 1024) return `${bytes} B`;
@@ -390,42 +384,112 @@ function formatFileSize(bytes) {
390
384
  var cachedFiles = null;
391
385
  var cacheTimestamp = 0;
392
386
  var CACHE_TTL_MS = 3e4;
393
- function getCachedFiles() {
387
+ function getCachedFiles(projectRoot = process.cwd()) {
394
388
  const now = Date.now();
395
389
  if (!cachedFiles || now - cacheTimestamp > CACHE_TTL_MS) {
396
- cachedFiles = walkDirectory();
390
+ const normalizedBase = path.resolve(projectRoot);
391
+ const ig = loadIgnoreRules(normalizedBase);
392
+ cachedFiles = crawlDirectory(normalizedBase, 10, 2e4, ig);
397
393
  cacheTimestamp = now;
398
394
  }
399
395
  return cachedFiles;
400
396
  }
401
- function searchFiles(query) {
402
- const files = getCachedFiles();
397
+ function warmFileCache() {
398
+ if (!cachedFiles || Date.now() - cacheTimestamp > CACHE_TTL_MS) {
399
+ setImmediate(() => {
400
+ getCachedFiles();
401
+ });
402
+ }
403
+ }
404
+ function listAbsoluteDirectory(absolutePath, filterQuery) {
405
+ try {
406
+ const normalizedPath = path.normalize(absolutePath);
407
+ if (!fs.existsSync(normalizedPath)) {
408
+ return [];
409
+ }
410
+ const stats = fs.statSync(normalizedPath);
411
+ if (!stats.isDirectory()) {
412
+ return [];
413
+ }
414
+ const entries = fs.readdirSync(normalizedPath, { withFileTypes: true });
415
+ let filteredEntries = entries;
416
+ if (filterQuery && filterQuery.length > 0) {
417
+ const lowerQuery = filterQuery.toLowerCase();
418
+ filteredEntries = entries.filter((entry) => entry.name.toLowerCase().includes(lowerQuery));
419
+ }
420
+ return filteredEntries.slice(0, 15).map((entry) => {
421
+ const fullPath = path.join(normalizedPath, entry.name);
422
+ const result = {
423
+ path: fullPath,
424
+ isDirectory: entry.isDirectory()
425
+ };
426
+ if (entry.isFile()) {
427
+ try {
428
+ const fileStats = fs.statSync(fullPath);
429
+ result.size = fileStats.size;
430
+ } catch {
431
+ }
432
+ }
433
+ return result;
434
+ });
435
+ } catch {
436
+ return [];
437
+ }
438
+ }
439
+ async function searchFiles(query, maxResults = 20) {
440
+ const projectRoot = process.cwd();
441
+ if (path.isAbsolute(query)) {
442
+ try {
443
+ const stats = fs.statSync(query);
444
+ if (stats.isDirectory()) {
445
+ return listAbsoluteDirectory(query);
446
+ }
447
+ } catch {
448
+ }
449
+ let dirToList;
450
+ let filterQuery;
451
+ if (query.endsWith("/") || query.endsWith(path.sep)) {
452
+ dirToList = query;
453
+ filterQuery = void 0;
454
+ } else {
455
+ dirToList = path.dirname(query);
456
+ filterQuery = path.basename(query);
457
+ }
458
+ return listAbsoluteDirectory(dirToList, filterQuery);
459
+ }
460
+ const files = getCachedFiles(projectRoot);
403
461
  if (!query || query.trim() === "") {
404
- return files.filter((f) => !f.path.includes(path.sep)).slice(0, 20);
405
- }
406
- const queryDir = path.dirname(query);
407
- let searchPool = files;
408
- if (queryDir && queryDir !== ".") {
409
- const normalizedDir = queryDir.replace(/\\/g, "/");
410
- searchPool = files.filter((f) => {
411
- const normalizedPath = f.path.replace(/\\/g, "/");
412
- return normalizedPath.startsWith(normalizedDir + "/") || normalizedPath === normalizedDir;
462
+ const rootFiles = files.filter((f) => !f.includes("/")).slice(0, maxResults);
463
+ return rootFiles.map((p) => {
464
+ const fullPath = path.join(projectRoot, p);
465
+ try {
466
+ const stats = fs.statSync(fullPath);
467
+ return {
468
+ path: p,
469
+ isDirectory: stats.isDirectory(),
470
+ size: stats.isFile() ? stats.size : void 0
471
+ };
472
+ } catch {
473
+ return { path: p, isDirectory: false };
474
+ }
413
475
  });
414
- if (searchPool.length === 0) {
415
- searchPool = files;
416
- }
417
- }
418
- const fuse = new Fuse(searchPool, {
419
- keys: [{ name: "path", weight: 1 }],
420
- threshold: 0.4,
421
- // Allow some fuzzy matching
422
- includeScore: true,
423
- minMatchCharLength: 1,
424
- // Use extended search for better path matching
425
- useExtendedSearch: false
476
+ }
477
+ const fzf = new AsyncFzf(files);
478
+ const results = await fzf.find(query);
479
+ return results.slice(0, maxResults).map((result) => {
480
+ const p = result.item;
481
+ const fullPath = path.join(projectRoot, p);
482
+ try {
483
+ const stats = fs.statSync(fullPath);
484
+ return {
485
+ path: p,
486
+ isDirectory: stats.isDirectory(),
487
+ size: stats.isFile() ? stats.size : void 0
488
+ };
489
+ } catch {
490
+ return { path: p, isDirectory: false };
491
+ }
426
492
  });
427
- const results = fuse.search(query);
428
- return results.slice(0, 20).map((result) => result.item);
429
493
  }
430
494
  function isPathWithinCwd(filePath) {
431
495
  const cwd = path.resolve(process.cwd());
@@ -509,10 +573,27 @@ var MAX_FILE_SIZE = 10 * 1024 * 1024;
509
573
  // src/components/FileAutocomplete.tsx
510
574
  function FileAutocomplete({ files, selectedIndex, query }) {
511
575
  if (files.length === 0) {
512
- return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No matching files", query ? ` for "${query}"` : ""));
513
- }
514
- return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, files.length === 1 ? "1 match" : `${files.length} matches`, " - Use up/down to navigate, Tab to select")), files.map((file, index) => {
515
- const isSelected = index === selectedIndex;
576
+ if (path2.isAbsolute(query)) {
577
+ return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No items in "), /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, query), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " (or path does not exist)"));
578
+ }
579
+ return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No matching files", query ? ` for "${query}"` : " in root directory"));
580
+ }
581
+ const VIEWPORT_SIZE = 6;
582
+ const totalFiles = files.length;
583
+ let startIndex = 0;
584
+ let endIndex = Math.min(VIEWPORT_SIZE, totalFiles);
585
+ if (totalFiles > VIEWPORT_SIZE) {
586
+ const halfViewport = Math.floor(VIEWPORT_SIZE / 2);
587
+ startIndex = Math.max(0, selectedIndex - halfViewport);
588
+ endIndex = Math.min(totalFiles, startIndex + VIEWPORT_SIZE);
589
+ if (endIndex === totalFiles) {
590
+ startIndex = Math.max(0, totalFiles - VIEWPORT_SIZE);
591
+ }
592
+ }
593
+ const visibleFiles = files.slice(startIndex, endIndex);
594
+ return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, totalFiles === 1 ? "1 match" : `${totalFiles} matches`, totalFiles > VIEWPORT_SIZE && ` (${selectedIndex + 1}/${totalFiles})`, " - Use up/down to navigate, Tab to select")), visibleFiles.map((file, viewportIndex) => {
595
+ const actualIndex = startIndex + viewportIndex;
596
+ const isSelected = actualIndex === selectedIndex;
516
597
  const icon = file.isDirectory ? "[folder]" : "[file] ";
517
598
  const sizeDisplay = file.isDirectory ? "" : file.size !== void 0 ? ` (${formatFileSize(file.size)})` : "";
518
599
  const pathDisplay = file.isDirectory ? `${file.path}/` : file.path;
@@ -521,7 +602,7 @@ function FileAutocomplete({ files, selectedIndex, query }) {
521
602
  }
522
603
 
523
604
  // src/utils/fuzzySearch.ts
524
- import Fuse2 from "fuse.js";
605
+ import Fuse from "fuse.js";
525
606
 
526
607
  // src/config/commands.ts
527
608
  var COMMANDS = [
@@ -645,6 +726,42 @@ var COMMANDS = [
645
726
  name: "compact",
646
727
  description: "Compact conversation into new session",
647
728
  args: "[instructions]"
729
+ },
730
+ // Sandbox commands
731
+ {
732
+ name: "sandbox",
733
+ description: "Show sandbox status and configuration"
734
+ },
735
+ {
736
+ name: "sandbox:enable",
737
+ description: "Enable OS-level sandbox for bash commands"
738
+ },
739
+ {
740
+ name: "sandbox:disable",
741
+ description: "Disable sandbox mode"
742
+ },
743
+ {
744
+ name: "sandbox:mode",
745
+ description: "Set sandbox mode (auto-allow or permissions)",
746
+ args: "<auto-allow|permissions>"
747
+ },
748
+ {
749
+ name: "sandbox:trust-domain",
750
+ description: "Add domain(s) to the network proxy allowlist",
751
+ args: "<domain> [...]"
752
+ },
753
+ {
754
+ name: "sandbox:domains",
755
+ description: "Show network proxy allowed domains"
756
+ },
757
+ {
758
+ name: "sandbox:violations",
759
+ description: "Show recent sandbox violations",
760
+ args: "[count]"
761
+ },
762
+ {
763
+ name: "sandbox:violations:clear",
764
+ description: "Clear all recorded sandbox violations"
648
765
  }
649
766
  ];
650
767
  function getAllCommandNames() {
@@ -698,13 +815,13 @@ function searchCommands(query, commands = COMMANDS) {
698
815
  if (!query || query.trim() === "") {
699
816
  return commands;
700
817
  }
701
- const fuse = new Fuse2(commands, fuseOptions);
818
+ const fuse = new Fuse(commands, fuseOptions);
702
819
  const results = fuse.search(query);
703
820
  return results.map((result) => result.item);
704
821
  }
705
822
 
706
823
  // src/utils/imageDetector.ts
707
- import { readFileSync, existsSync, statSync as statSync2 } from "fs";
824
+ import { readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
708
825
  import { extname as extname2 } from "path";
709
826
  var ImageInputDetector = class {
710
827
  static {
@@ -845,14 +962,14 @@ var ImageInputDetector = class {
845
962
  let filepath = input.trim();
846
963
  filepath = filepath.replace(/^["']|["']$/g, "");
847
964
  filepath = filepath.replace(/\\(.)/g, "$1");
848
- if (!existsSync(filepath)) return null;
965
+ if (!existsSync2(filepath)) return null;
849
966
  const stats = statSync2(filepath);
850
967
  if (!stats.isFile()) return null;
851
968
  const ext = extname2(filepath).toLowerCase();
852
969
  if (!this.SUPPORTED_EXTENSIONS.includes(ext)) return null;
853
970
  if (stats.size > this.MAX_IMAGE_SIZE) return null;
854
971
  try {
855
- const data = readFileSync(filepath);
972
+ const data = readFileSync2(filepath);
856
973
  const format = ext.substring(1) === "jpeg" ? "jpg" : ext.substring(1);
857
974
  const filename = filepath.split("/").pop() || "image";
858
975
  return {
@@ -907,6 +1024,21 @@ var ImageInputDetector = class {
907
1024
  }
908
1025
  };
909
1026
 
1027
+ // src/hooks/useDebounce.ts
1028
+ import { useEffect as useEffect2, useState as useState2 } from "react";
1029
+ function useDebounce(value, delay = 300) {
1030
+ const [debouncedValue, setDebouncedValue] = useState2(value);
1031
+ useEffect2(() => {
1032
+ const timer = setTimeout(() => {
1033
+ setDebouncedValue(value);
1034
+ }, delay);
1035
+ return () => {
1036
+ clearTimeout(timer);
1037
+ };
1038
+ }, [value, delay]);
1039
+ return debouncedValue;
1040
+ }
1041
+
910
1042
  // src/components/InputPrompt.tsx
911
1043
  function looksLikeFilePath(input) {
912
1044
  const trimmed = input.trim();
@@ -947,20 +1079,20 @@ function InputPrompt({
947
1079
  const value = useCliStore((state) => state.inputValue);
948
1080
  const setInputValue = useCliStore((state) => state.setInputValue);
949
1081
  const setValue = setInputValue;
950
- const [selectedIndex, setSelectedIndex] = useState2(0);
951
- const [historyIndex, setHistoryIndex] = useState2(-1);
952
- const [tempInput, setTempInput] = useState2("");
1082
+ const [selectedIndex, setSelectedIndex] = useState3(0);
1083
+ const [historyIndex, setHistoryIndex] = useState3(-1);
1084
+ const [tempInput, setTempInput] = useState3("");
953
1085
  const inputKey = useRef2(0);
954
- useEffect2(() => {
1086
+ useEffect3(() => {
955
1087
  if (prefillInput) {
956
1088
  setValue(prefillInput);
957
1089
  onPrefillConsumed?.();
958
1090
  }
959
1091
  }, [prefillInput, onPrefillConsumed]);
960
- const [fileAutocomplete, setFileAutocomplete] = useState2(null);
961
- const [fileSelectedIndex, setFileSelectedIndex] = useState2(0);
1092
+ const [fileAutocomplete, setFileAutocomplete] = useState3(null);
1093
+ const [fileSelectedIndex, setFileSelectedIndex] = useState3(0);
962
1094
  const isBashMode = value.startsWith("!");
963
- useEffect2(() => {
1095
+ useEffect3(() => {
964
1096
  onBashModeChange?.(isBashMode);
965
1097
  }, [isBashMode, onBashModeChange]);
966
1098
  const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active && !looksLikeFilePath(value);
@@ -969,14 +1101,32 @@ function InputPrompt({
969
1101
  if (!shouldShowCommandAutocomplete) return [];
970
1102
  return searchCommands(commandQuery, commands);
971
1103
  }, [shouldShowCommandAutocomplete, commandQuery, commands]);
972
- const filteredFiles = useMemo(() => {
973
- if (!fileAutocomplete?.active) return [];
974
- return searchFiles(fileAutocomplete.query);
975
- }, [fileAutocomplete?.active, fileAutocomplete?.query]);
976
- useEffect2(() => {
1104
+ const debouncedFileQuery = useDebounce(fileAutocomplete?.query ?? "", 200);
1105
+ const [filteredFiles, setFilteredFiles] = useState3([]);
1106
+ useEffect3(() => {
1107
+ if (!fileAutocomplete?.active) {
1108
+ setFilteredFiles([]);
1109
+ return;
1110
+ }
1111
+ let cancelled = false;
1112
+ searchFiles(debouncedFileQuery).then((results) => {
1113
+ if (!cancelled) {
1114
+ setFilteredFiles(results);
1115
+ }
1116
+ }).catch((error) => {
1117
+ if (!cancelled) {
1118
+ console.error("File search error:", error);
1119
+ setFilteredFiles([]);
1120
+ }
1121
+ });
1122
+ return () => {
1123
+ cancelled = true;
1124
+ };
1125
+ }, [fileAutocomplete?.active, debouncedFileQuery]);
1126
+ useEffect3(() => {
977
1127
  setSelectedIndex(0);
978
1128
  }, [filteredCommands]);
979
- useEffect2(() => {
1129
+ useEffect3(() => {
980
1130
  setFileSelectedIndex(0);
981
1131
  }, [filteredFiles]);
982
1132
  useInput2(
@@ -1214,13 +1364,13 @@ function BackgroundAgentStatus() {
1214
1364
  }
1215
1365
 
1216
1366
  // src/components/CompletedGroupNotification.tsx
1217
- import React9, { useEffect as useEffect3 } from "react";
1367
+ import React9, { useEffect as useEffect4 } from "react";
1218
1368
  import { Box as Box8, Text as Text8 } from "ink";
1219
1369
  var NOTIFICATION_DISPLAY_DURATION_MS = 3e3;
1220
1370
  function CompletedGroupNotification() {
1221
1371
  const notifications = useCliStore((state) => state.completedGroupNotifications);
1222
1372
  const clearNotifications = useCliStore((state) => state.clearCompletedGroupNotifications);
1223
- useEffect3(() => {
1373
+ useEffect4(() => {
1224
1374
  if (notifications.length > 0) {
1225
1375
  const timer = setTimeout(() => {
1226
1376
  clearNotifications();
@@ -1233,14 +1383,20 @@ function CompletedGroupNotification() {
1233
1383
  }
1234
1384
 
1235
1385
  // src/components/PermissionPrompt.tsx
1236
- import React10, { useState as useState3, useCallback } from "react";
1386
+ import React10, { useState as useState4, useCallback } from "react";
1237
1387
  import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
1238
1388
  function renderDiffPreview(preview) {
1239
1389
  const lines = preview.split("\n");
1240
1390
  return lines.map((line, index) => {
1391
+ if (line.startsWith("\u{1F6D1}")) {
1392
+ return /* @__PURE__ */ React10.createElement(Text9, { key: index, bold: true, color: "red" }, line);
1393
+ }
1241
1394
  if (line.startsWith("+")) {
1242
1395
  return /* @__PURE__ */ React10.createElement(Text9, { key: index, color: "green" }, line);
1243
1396
  }
1397
+ if (line.startsWith("- ")) {
1398
+ return /* @__PURE__ */ React10.createElement(Text9, { key: index, color: "yellow" }, line);
1399
+ }
1244
1400
  if (line.startsWith("-")) {
1245
1401
  return /* @__PURE__ */ React10.createElement(Text9, { key: index, color: "red" }, line);
1246
1402
  }
@@ -1260,14 +1416,16 @@ function PermissionPrompt({
1260
1416
  }) {
1261
1417
  const items = canBeTrusted ? [
1262
1418
  { label: "\u2713 Allow once", value: "allow-once" },
1419
+ { label: "\u2713 Allow for this session", value: "allow-session" },
1263
1420
  { label: "\u2713 Always allow (trust this tool)", value: "allow-always" },
1264
1421
  { label: "\u2717 Deny", value: "deny" }
1265
1422
  ] : [
1266
1423
  { label: "\u2713 Allow once", value: "allow-once" },
1424
+ { label: "\u2713 Allow for this session", value: "allow-session" },
1267
1425
  { label: "\u2717 Deny", value: "deny" }
1268
1426
  ];
1269
- const [selectedIndex, setSelectedIndex] = useState3(0);
1270
- const [responded, setResponded] = useState3(false);
1427
+ const [selectedIndex, setSelectedIndex] = useState4(0);
1428
+ const [responded, setResponded] = useState4(false);
1271
1429
  const handleSelect = useCallback(() => {
1272
1430
  if (responded) return;
1273
1431
  setResponded(true);
@@ -1311,11 +1469,15 @@ function PermissionPrompt({
1311
1469
  const rawArgsString = typeof args === "string" ? args : JSON.stringify(args, null, 2);
1312
1470
  const argsString = rawArgsString.length > MAX_ARGS_LENGTH ? rawArgsString.slice(0, MAX_ARGS_LENGTH) + `
1313
1471
  ... (${rawArgsString.length - MAX_ARGS_LENGTH} more chars)` : rawArgsString;
1314
- return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", borderStyle: "bold", borderColor: "yellow", padding: 1, marginY: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "yellow" }, "\u26A0\uFE0F Permission Required")), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Tool: "), /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "cyan" }, toolName)), toolDescription && /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Action: "), /* @__PURE__ */ React10.createElement(Text9, null, toolDescription)), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "Arguments:"), /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, argsString))), preview && /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "Preview:"), /* @__PURE__ */ React10.createElement(Box9, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" }, renderDiffPreview(preview))), !canBeTrusted && /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "red", dimColor: true }, "Note: This tool cannot be trusted due to its dangerous nature.")), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, items.map((item, index) => /* @__PURE__ */ React10.createElement(Box9, { key: item.value }, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, index + 1, "."), /* @__PURE__ */ React10.createElement(Text9, { color: index === selectedIndex ? "cyan" : void 0, bold: index === selectedIndex }, index === selectedIndex ? " \u276F " : " ", item.label)))), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Press 1-", items.length, ", y/n, or \u2191\u2193 + Enter")));
1472
+ const isSandboxBlock = preview?.startsWith("\u{1F6D1}");
1473
+ const borderColor = isSandboxBlock ? "red" : "yellow";
1474
+ const headerColor = isSandboxBlock ? "red" : "yellow";
1475
+ const headerText = isSandboxBlock ? "\u{1F6D1} Sandbox Blocked" : "\u26A0\uFE0F Permission Required";
1476
+ return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", borderStyle: "bold", borderColor, padding: 1, marginY: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: headerColor }, headerText)), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Tool: "), /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "cyan" }, toolName)), toolDescription && /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Action: "), /* @__PURE__ */ React10.createElement(Text9, null, toolDescription)), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "Arguments:"), /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, argsString))), preview && /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "Preview:"), /* @__PURE__ */ React10.createElement(Box9, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" }, renderDiffPreview(preview))), !canBeTrusted && /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "red", dimColor: true }, "Note: This tool cannot be trusted due to its dangerous nature.")), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, items.map((item, index) => /* @__PURE__ */ React10.createElement(Box9, { key: item.value }, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, index + 1, "."), /* @__PURE__ */ React10.createElement(Text9, { color: index === selectedIndex ? "cyan" : void 0, bold: index === selectedIndex }, index === selectedIndex ? " \u276F " : " ", item.label)))), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Press 1-", items.length, ", y/n, or \u2191\u2193 + Enter")));
1315
1477
  }
1316
1478
 
1317
1479
  // src/components/ConfigEditor.tsx
1318
- import React11, { useState as useState4, useMemo as useMemo3 } from "react";
1480
+ import React11, { useState as useState5, useMemo as useMemo3 } from "react";
1319
1481
  import { Box as Box10, Text as Text10, useInput as useInput4 } from "ink";
1320
1482
  var MAX_ITERATIONS_OPTIONS = [
1321
1483
  { label: "10", value: 10 },
@@ -1460,10 +1622,10 @@ function buildConfigItems(availableModels) {
1460
1622
  return items;
1461
1623
  }
1462
1624
  function ConfigEditor({ config, availableModels, onSave, onClose }) {
1463
- const [selectedIndex, setSelectedIndex] = useState4(0);
1464
- const [editedConfig, setEditedConfig] = useState4(config);
1465
- const [saveError, setSaveError] = useState4(null);
1466
- const [isSaving, setIsSaving] = useState4(false);
1625
+ const [selectedIndex, setSelectedIndex] = useState5(0);
1626
+ const [editedConfig, setEditedConfig] = useState5(config);
1627
+ const [saveError, setSaveError] = useState5(null);
1628
+ const [isSaving, setIsSaving] = useState5(false);
1467
1629
  const configItems = useMemo3(() => buildConfigItems(availableModels), [availableModels]);
1468
1630
  const hasChanges = useMemo3(() => {
1469
1631
  return JSON.stringify(config.preferences) !== JSON.stringify(editedConfig.preferences) || config.defaultModel !== editedConfig.defaultModel;
@@ -1744,12 +1906,12 @@ var MessageItem = React14.memo(function MessageItem2({ message }) {
1744
1906
  return /* @__PURE__ */ React14.createElement(Box13, { key: idx, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, "\u{1F527} ", toolName), toolInput && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` Input: ${truncateValue(toolInput, 100)}`), result && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` Result: ${truncateValue(result, 200)}`));
1745
1907
  }
1746
1908
  return null;
1747
- }).filter(Boolean)), !isUser && message.content !== "..." && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginBottom: 1 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React14.createElement(MarkdownRenderer, { content: message.content })), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2 }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)));
1909
+ }).filter(Boolean)), !isUser && message.content !== "..." && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginBottom: 1 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React14.createElement(MarkdownRenderer, { content: message.content })), !isUser && message.content !== "..." && message.metadata && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginBottom: 1 }, (message.metadata.tokenUsage?.total || message.metadata.creditsUsed) && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "(", message.metadata.tokenUsage?.total ? `${message.metadata.tokenUsage.total.toLocaleString()} tokens` : "", message.metadata.creditsUsed && message.metadata.creditsUsed > 0 ? (message.metadata.tokenUsage?.total ? " \u2022 " : "") + `used ${message.metadata.creditsUsed.toLocaleString()} ${message.metadata.creditsUsed === 1 ? "credit" : "credits"}` : "", ")")));
1748
1910
  });
1749
1911
 
1750
1912
  // src/utils/processFileReferences.ts
1751
1913
  import * as fs2 from "fs";
1752
- import * as path2 from "path";
1914
+ import * as path3 from "path";
1753
1915
 
1754
1916
  // src/utils/constants.ts
1755
1917
  var NAME_SUFFIXES = ["jr", "sr", "ii", "iii", "iv", "v", "phd", "md", "esq"];
@@ -1760,7 +1922,7 @@ function isNameSuffix(value) {
1760
1922
  // src/utils/processFileReferences.ts
1761
1923
  var FILE_REFERENCE_REGEX = /(?:^|\s)@([^\s@]+)/g;
1762
1924
  function looksLikeFilePath2(ref) {
1763
- if (ref.includes("/") || ref.includes(path2.sep)) {
1925
+ if (ref.includes("/") || ref.includes(path3.sep)) {
1764
1926
  return true;
1765
1927
  }
1766
1928
  const extensionMatch = /\.(\w+)$/.exec(ref);
@@ -1790,9 +1952,13 @@ function extractFileReferences(message) {
1790
1952
  }
1791
1953
  function readFileContents(filePath) {
1792
1954
  const cwd = process.cwd();
1793
- const absolutePath = path2.resolve(cwd, filePath);
1794
- if (!isPathWithinCwd(filePath)) {
1795
- return { error: `Security: Path "${filePath}" is outside the current working directory` };
1955
+ const isAbsolutePath = path3.isAbsolute(filePath);
1956
+ if (filePath.includes("..")) {
1957
+ return { error: `Security: Path traversal detected in "${filePath}"` };
1958
+ }
1959
+ const absolutePath = isAbsolutePath ? path3.normalize(filePath) : path3.resolve(cwd, filePath);
1960
+ if (!isAbsolutePath && !isPathWithinCwd(filePath)) {
1961
+ return { error: `Security: Relative path "${filePath}" escapes the current working directory` };
1796
1962
  }
1797
1963
  if (!fs2.existsSync(absolutePath)) {
1798
1964
  return { error: `File not found: "${filePath}"` };
@@ -1883,6 +2049,7 @@ function App({
1883
2049
  const sessionName = useCliStore((state) => state.session?.name || "New Session");
1884
2050
  const currentModel = useCliStore((state) => state.session?.model || "claude-sonnet-4-5-20250929");
1885
2051
  const totalTokens = useCliStore((state) => state.session?.metadata.totalTokens || 0);
2052
+ const totalCredits = useCliStore((state) => state.session?.metadata.totalCredits);
1886
2053
  const isThinking = useCliStore((state) => state.isThinking);
1887
2054
  const permissionPrompt = useCliStore((state) => state.permissionPrompt);
1888
2055
  const showConfigEditor = useCliStore((state) => state.showConfigEditor);
@@ -1893,7 +2060,7 @@ function App({
1893
2060
  const setIsThinking = useCliStore((state) => state.setIsThinking);
1894
2061
  const pendingBackgroundTrigger = useCliStore((state) => state.pendingBackgroundTrigger);
1895
2062
  const setPendingBackgroundTrigger = useCliStore((state) => state.setPendingBackgroundTrigger);
1896
- useEffect4(() => {
2063
+ useEffect5(() => {
1897
2064
  if (pendingBackgroundTrigger && !isThinking && onBackgroundCompletion) {
1898
2065
  setPendingBackgroundTrigger(false);
1899
2066
  onBackgroundCompletion();
@@ -1905,7 +2072,7 @@ function App({
1905
2072
  toggleAutoAcceptEdits();
1906
2073
  }
1907
2074
  });
1908
- const [isBashMode, setIsBashMode] = useState5(false);
2075
+ const [isBashMode, setIsBashMode] = useState6(false);
1909
2076
  const handleSubmit = React15.useCallback(
1910
2077
  async (input) => {
1911
2078
  const trimmed = input.trim();
@@ -1968,7 +2135,15 @@ ${errorBlock}`;
1968
2135
  onPrefillConsumed,
1969
2136
  onBashModeChange: setIsBashMode
1970
2137
  }
1971
- )), /* @__PURE__ */ React15.createElement(StatusBar, { sessionName, model: currentModel, tokenUsage: totalTokens })));
2138
+ )), /* @__PURE__ */ React15.createElement(
2139
+ StatusBar,
2140
+ {
2141
+ sessionName,
2142
+ model: currentModel,
2143
+ tokenUsage: totalTokens,
2144
+ creditsUsage: totalCredits
2145
+ }
2146
+ )));
1972
2147
  }
1973
2148
 
1974
2149
  // src/components/MessageList.tsx
@@ -2044,12 +2219,12 @@ function TrustLocationSelector({ inProject, onSelect, onCancel }) {
2044
2219
  }
2045
2220
 
2046
2221
  // src/components/RewindSelector.tsx
2047
- import React18, { useState as useState6 } from "react";
2222
+ import React18, { useState as useState7 } from "react";
2048
2223
  import { Box as Box17, Text as Text17, useInput as useInput7 } from "ink";
2049
2224
  import SelectInput2 from "ink-select-input";
2050
2225
  function RewindSelector({ messages, onSelect, onCancel }) {
2051
- const [step, setStep] = useState6("selection");
2052
- const [selectedMessageIndex, setSelectedMessageIndex] = useState6(null);
2226
+ const [step, setStep] = useState7("selection");
2227
+ const [selectedMessageIndex, setSelectedMessageIndex] = useState7(null);
2053
2228
  useInput7((input, key) => {
2054
2229
  if (key.escape) {
2055
2230
  if (step === "confirmation") {
@@ -2113,12 +2288,12 @@ function RewindSelector({ messages, onSelect, onCancel }) {
2113
2288
  }
2114
2289
 
2115
2290
  // src/components/SessionSelector.tsx
2116
- import React19, { useState as useState7 } from "react";
2291
+ import React19, { useState as useState8 } from "react";
2117
2292
  import { Box as Box18, Text as Text18, useInput as useInput8 } from "ink";
2118
2293
  import SelectInput3 from "ink-select-input";
2119
2294
  function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
2120
- const [step, setStep] = useState7("selection");
2121
- const [selectedSession, setSelectedSession] = useState7(null);
2295
+ const [step, setStep] = useState8("selection");
2296
+ const [selectedSession, setSelectedSession] = useState8(null);
2122
2297
  useInput8((_input, key) => {
2123
2298
  if (key.escape) {
2124
2299
  if (step === "confirmation") {
@@ -2193,7 +2368,7 @@ function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
2193
2368
  }
2194
2369
 
2195
2370
  // src/components/LoginFlow.tsx
2196
- import React20, { useState as useState8, useEffect as useEffect5 } from "react";
2371
+ import React20, { useState as useState9, useEffect as useEffect6 } from "react";
2197
2372
  import { Box as Box19, Text as Text19 } from "ink";
2198
2373
  import Spinner3 from "ink-spinner";
2199
2374
  import jwt from "jsonwebtoken";
@@ -2310,11 +2485,11 @@ var OAuthClient = class {
2310
2485
 
2311
2486
  // src/components/LoginFlow.tsx
2312
2487
  function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
2313
- const [status, setStatus] = useState8("initiating");
2314
- const [deviceFlow, setDeviceFlow] = useState8(null);
2315
- const [statusMessage, setStatusMessage] = useState8("Initiating device authorization...");
2316
- const [error, setError] = useState8(null);
2317
- useEffect5(() => {
2488
+ const [status, setStatus] = useState9("initiating");
2489
+ const [deviceFlow, setDeviceFlow] = useState9(null);
2490
+ const [statusMessage, setStatusMessage] = useState9("Initiating device authorization...");
2491
+ const [error, setError] = useState9(null);
2492
+ useEffect6(() => {
2318
2493
  const runLoginFlow = async () => {
2319
2494
  const oauth = new OAuthClient(apiUrl);
2320
2495
  try {
@@ -2349,7 +2524,7 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
2349
2524
  };
2350
2525
  runLoginFlow();
2351
2526
  }, [apiUrl, configStore, onSuccess, onError]);
2352
- useEffect5(() => {
2527
+ useEffect6(() => {
2353
2528
  if (deviceFlow && status === "waiting") {
2354
2529
  open(deviceFlow.verification_uri_complete).catch((err) => {
2355
2530
  console.error("Failed to auto-open browser:", err);
@@ -2370,12 +2545,12 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
2370
2545
 
2371
2546
  // src/storage/SessionStore.ts
2372
2547
  import { promises as fs3 } from "fs";
2373
- import path3 from "path";
2548
+ import path4 from "path";
2374
2549
  import { homedir } from "os";
2375
2550
  import { v4 as uuidv4 } from "uuid";
2376
2551
  var SessionStore = class {
2377
2552
  constructor(basePath) {
2378
- this.basePath = basePath || path3.join(homedir(), ".bike4mind", "sessions");
2553
+ this.basePath = basePath || path4.join(homedir(), ".bike4mind", "sessions");
2379
2554
  }
2380
2555
  /**
2381
2556
  * Initialize storage directory
@@ -2396,7 +2571,7 @@ var SessionStore = class {
2396
2571
  throw new Error("Cannot save session with no messages");
2397
2572
  }
2398
2573
  await this.init();
2399
- const filePath = path3.join(this.basePath, `${session.id}.json`);
2574
+ const filePath = path4.join(this.basePath, `${session.id}.json`);
2400
2575
  try {
2401
2576
  await fs3.writeFile(filePath, JSON.stringify(session, null, 2), "utf-8");
2402
2577
  } catch (error) {
@@ -2408,7 +2583,7 @@ var SessionStore = class {
2408
2583
  * Load a session from disk by ID
2409
2584
  */
2410
2585
  async load(id) {
2411
- const filePath = path3.join(this.basePath, `${id}.json`);
2586
+ const filePath = path4.join(this.basePath, `${id}.json`);
2412
2587
  try {
2413
2588
  const data = await fs3.readFile(filePath, "utf-8");
2414
2589
  const session = JSON.parse(data);
@@ -2446,7 +2621,7 @@ var SessionStore = class {
2446
2621
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
2447
2622
  const sessionsWithFiles = await Promise.all(
2448
2623
  jsonFiles.map(async (file) => {
2449
- const filePath = path3.join(this.basePath, file);
2624
+ const filePath = path4.join(this.basePath, file);
2450
2625
  const data = await fs3.readFile(filePath, "utf-8");
2451
2626
  const session = JSON.parse(data);
2452
2627
  session.messages = session.messages.map((msg) => {
@@ -2481,7 +2656,7 @@ var SessionStore = class {
2481
2656
  * Delete a session
2482
2657
  */
2483
2658
  async delete(id) {
2484
- const filePath = path3.join(this.basePath, `${id}.json`);
2659
+ const filePath = path4.join(this.basePath, `${id}.json`);
2485
2660
  try {
2486
2661
  await fs3.unlink(filePath);
2487
2662
  return true;
@@ -2520,19 +2695,19 @@ var SessionStore = class {
2520
2695
 
2521
2696
  // src/storage/CommandHistoryStore.ts
2522
2697
  import { promises as fs4 } from "fs";
2523
- import path4 from "path";
2698
+ import path5 from "path";
2524
2699
  import { homedir as homedir2 } from "os";
2525
2700
  var MAX_HISTORY_ENTRIES = 1e3;
2526
2701
  var CommandHistoryStore = class {
2527
2702
  constructor(historyPath) {
2528
2703
  this.history = null;
2529
- this.historyPath = historyPath || path4.join(homedir2(), ".bike4mind", "history.jsonl");
2704
+ this.historyPath = historyPath || path5.join(homedir2(), ".bike4mind", "history.jsonl");
2530
2705
  }
2531
2706
  /**
2532
2707
  * Initialize history directory
2533
2708
  */
2534
2709
  async init() {
2535
- const dir = path4.dirname(this.historyPath);
2710
+ const dir = path5.dirname(this.historyPath);
2536
2711
  try {
2537
2712
  await fs4.mkdir(dir, { recursive: true });
2538
2713
  } catch (error) {
@@ -2635,7 +2810,7 @@ var CommandHistoryStore = class {
2635
2810
 
2636
2811
  // src/storage/CustomCommandStore.ts
2637
2812
  import fs5 from "fs/promises";
2638
- import path5 from "path";
2813
+ import path6 from "path";
2639
2814
  import os from "os";
2640
2815
 
2641
2816
  // src/utils/commandParser.ts
@@ -2798,14 +2973,14 @@ var CustomCommandStore = class {
2798
2973
  const home = os.homedir();
2799
2974
  const root = projectRoot || process.cwd();
2800
2975
  this.globalCommandsDirs = [
2801
- path5.join(home, ".bike4mind", "commands"),
2802
- path5.join(home, ".claude", "commands"),
2803
- path5.join(home, ".claude", "skills")
2976
+ path6.join(home, ".bike4mind", "commands"),
2977
+ path6.join(home, ".claude", "commands"),
2978
+ path6.join(home, ".claude", "skills")
2804
2979
  ];
2805
2980
  this.projectCommandsDirs = [
2806
- path5.join(root, ".bike4mind", "commands"),
2807
- path5.join(root, ".claude", "commands"),
2808
- path5.join(root, ".claude", "skills")
2981
+ path6.join(root, ".bike4mind", "commands"),
2982
+ path6.join(root, ".claude", "commands"),
2983
+ path6.join(root, ".claude", "skills")
2809
2984
  ];
2810
2985
  }
2811
2986
  /**
@@ -2865,7 +3040,7 @@ var CustomCommandStore = class {
2865
3040
  try {
2866
3041
  const entries = await fs5.readdir(directory, { withFileTypes: true });
2867
3042
  for (const entry of entries) {
2868
- const fullPath = path5.join(directory, entry.name);
3043
+ const fullPath = path6.join(directory, entry.name);
2869
3044
  if (entry.isDirectory()) {
2870
3045
  const subFiles = await this.findCommandFiles(fullPath);
2871
3046
  files.push(...subFiles);
@@ -2885,7 +3060,7 @@ var CustomCommandStore = class {
2885
3060
  * @param source - Source identifier ('global' or 'project')
2886
3061
  */
2887
3062
  async loadCommandFile(filePath, source) {
2888
- const filename = path5.basename(filePath);
3063
+ const filename = path6.basename(filePath);
2889
3064
  const isSkillFile = filename.toLowerCase() === "skill.md";
2890
3065
  const commandName = isSkillFile ? this.extractSkillName(filePath) : extractCommandName(filename);
2891
3066
  if (!commandName) {
@@ -2904,7 +3079,7 @@ var CustomCommandStore = class {
2904
3079
  * @returns Skill name or null if invalid
2905
3080
  */
2906
3081
  extractSkillName(filePath) {
2907
- const parentDir = path5.basename(path5.dirname(filePath));
3082
+ const parentDir = path6.basename(path6.dirname(filePath));
2908
3083
  return parentDir && parentDir !== "skills" ? parentDir : null;
2909
3084
  }
2910
3085
  /**
@@ -2966,7 +3141,7 @@ var CustomCommandStore = class {
2966
3141
  */
2967
3142
  async createCommandFile(name, isGlobal = false) {
2968
3143
  const targetDir = isGlobal ? this.globalCommandsDirs[0] : this.projectCommandsDirs[0];
2969
- const filePath = path5.join(targetDir, `${name}.md`);
3144
+ const filePath = path6.join(targetDir, `${name}.md`);
2970
3145
  const fileExists = await fs5.access(filePath).then(
2971
3146
  () => true,
2972
3147
  () => false
@@ -3122,6 +3297,7 @@ var ReActAgent = class extends EventEmitter {
3122
3297
  super();
3123
3298
  this.steps = [];
3124
3299
  this.totalTokens = 0;
3300
+ this.totalCredits = 0;
3125
3301
  this.toolCallCount = 0;
3126
3302
  this.observationQueue = [];
3127
3303
  this.context = {
@@ -3141,6 +3317,7 @@ var ReActAgent = class extends EventEmitter {
3141
3317
  async run(query, options = {}) {
3142
3318
  this.steps = [];
3143
3319
  this.totalTokens = 0;
3320
+ this.totalCredits = 0;
3144
3321
  this.toolCallCount = 0;
3145
3322
  const maxIterations = options.maxIterations ?? this.context.maxIterations ?? 5;
3146
3323
  const temperature = options.temperature ?? this.context.temperature ?? 0.7;
@@ -3177,6 +3354,7 @@ ${options.context}` : this.getSystemPrompt()
3177
3354
  steps: this.steps,
3178
3355
  completionInfo: {
3179
3356
  totalTokens: this.totalTokens,
3357
+ totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
3180
3358
  iterations,
3181
3359
  toolCalls: this.toolCallCount,
3182
3360
  reachedMaxIterations: false
@@ -3212,6 +3390,9 @@ ${options.context}` : this.getSystemPrompt()
3212
3390
  const inputTokens = completionInfo.inputTokens || 0;
3213
3391
  const outputTokens = completionInfo.outputTokens || 0;
3214
3392
  this.totalTokens += inputTokens + outputTokens;
3393
+ if (completionInfo.creditsUsed) {
3394
+ this.totalCredits += completionInfo.creditsUsed;
3395
+ }
3215
3396
  if (currentText.trim() && completionInfo.toolsUsed?.length) {
3216
3397
  const thoughtStep = {
3217
3398
  type: "thought",
@@ -3325,6 +3506,7 @@ ${options.context}` : this.getSystemPrompt()
3325
3506
  steps: this.steps,
3326
3507
  completionInfo: {
3327
3508
  totalTokens: this.totalTokens,
3509
+ totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
3328
3510
  iterations,
3329
3511
  toolCalls: this.toolCallCount,
3330
3512
  reachedMaxIterations
@@ -3342,6 +3524,7 @@ ${options.context}` : this.getSystemPrompt()
3342
3524
  steps: this.steps,
3343
3525
  completionInfo: {
3344
3526
  totalTokens: this.totalTokens,
3527
+ totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
3345
3528
  iterations,
3346
3529
  toolCalls: this.toolCallCount,
3347
3530
  reachedMaxIterations: false
@@ -3763,6 +3946,10 @@ EXAMPLES:
3763
3946
  Remember: Use context from previous messages to understand follow-up questions.${projectContextSection}${skillsSection}`;
3764
3947
  }
3765
3948
 
3949
+ // src/utils/toolsAdapter.ts
3950
+ import { rmSync } from "fs";
3951
+ import path14 from "path";
3952
+
3766
3953
  // ../../b4m-core/packages/services/dist/src/referService/generateCodes.js
3767
3954
  import { randomBytes } from "crypto";
3768
3955
  import range from "lodash/range.js";
@@ -5889,8 +6076,8 @@ async function processAndStoreImages(images, context) {
5889
6076
  const buffer = await downloadImage(image);
5890
6077
  const fileType = await fileTypeFromBuffer2(buffer);
5891
6078
  const filename = `${uuidv45()}.${fileType?.ext}`;
5892
- const path18 = await context.imageGenerateStorage.upload(buffer, filename, {});
5893
- return path18;
6079
+ const path20 = await context.imageGenerateStorage.upload(buffer, filename, {});
6080
+ return path20;
5894
6081
  }));
5895
6082
  }
5896
6083
  async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
@@ -7216,8 +7403,8 @@ async function processAndStoreImage(imageUrl, context) {
7216
7403
  const buffer = await downloadImage2(imageUrl);
7217
7404
  const fileType = await fileTypeFromBuffer3(buffer);
7218
7405
  const filename = `${uuidv46()}.${fileType?.ext}`;
7219
- const path18 = await context.imageGenerateStorage.upload(buffer, filename, {});
7220
- return path18;
7406
+ const path20 = await context.imageGenerateStorage.upload(buffer, filename, {});
7407
+ return path20;
7221
7408
  }
7222
7409
  async function generateFullMask(imageBuffer) {
7223
7410
  const sharp = (await import("sharp")).default;
@@ -9159,18 +9346,18 @@ var planetVisibilityTool = {
9159
9346
 
9160
9347
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
9161
9348
  import { promises as fs7 } from "fs";
9162
- import { existsSync as existsSync3, statSync as statSync4 } from "fs";
9163
- import path6 from "path";
9349
+ import { existsSync as existsSync4, statSync as statSync4 } from "fs";
9350
+ import path7 from "path";
9164
9351
  var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
9165
9352
  async function readFileContent(params) {
9166
9353
  const { path: filePath, encoding = "utf-8", offset = 0, limit } = params;
9167
- const normalizedPath = path6.normalize(filePath);
9168
- const resolvedPath = path6.resolve(process.cwd(), normalizedPath);
9169
- const cwd = path6.resolve(process.cwd());
9354
+ const normalizedPath = path7.normalize(filePath);
9355
+ const resolvedPath = path7.resolve(process.cwd(), normalizedPath);
9356
+ const cwd = path7.resolve(process.cwd());
9170
9357
  if (!resolvedPath.startsWith(cwd)) {
9171
9358
  throw new Error(`Access denied: Cannot read files outside of current working directory`);
9172
9359
  }
9173
- if (!existsSync3(resolvedPath)) {
9360
+ if (!existsSync4(resolvedPath)) {
9174
9361
  throw new Error(`File not found: ${filePath}`);
9175
9362
  }
9176
9363
  const stats = statSync4(resolvedPath);
@@ -9239,7 +9426,7 @@ var fileReadTool = {
9239
9426
  context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
9240
9427
  try {
9241
9428
  const content = await readFileContent(params);
9242
- const stats = statSync4(path6.resolve(process.cwd(), path6.normalize(params.path)));
9429
+ const stats = statSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
9243
9430
  context.logger.info("\u2705 FileRead: Success", {
9244
9431
  path: params.path,
9245
9432
  size: stats.size,
@@ -9284,20 +9471,20 @@ var fileReadTool = {
9284
9471
 
9285
9472
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
9286
9473
  import { promises as fs8 } from "fs";
9287
- import { existsSync as existsSync4 } from "fs";
9288
- import path7 from "path";
9474
+ import { existsSync as existsSync5 } from "fs";
9475
+ import path8 from "path";
9289
9476
  async function createFile(params) {
9290
9477
  const { path: filePath, content, createDirectories = true } = params;
9291
- const normalizedPath = path7.normalize(filePath);
9292
- const resolvedPath = path7.resolve(process.cwd(), normalizedPath);
9293
- const cwd = path7.resolve(process.cwd());
9478
+ const normalizedPath = path8.normalize(filePath);
9479
+ const resolvedPath = path8.resolve(process.cwd(), normalizedPath);
9480
+ const cwd = path8.resolve(process.cwd());
9294
9481
  if (!resolvedPath.startsWith(cwd)) {
9295
9482
  throw new Error(`Access denied: Cannot create files outside of current working directory`);
9296
9483
  }
9297
- const fileExists = existsSync4(resolvedPath);
9484
+ const fileExists = existsSync5(resolvedPath);
9298
9485
  const action = fileExists ? "overwritten" : "created";
9299
9486
  if (createDirectories) {
9300
- const dir = path7.dirname(resolvedPath);
9487
+ const dir = path8.dirname(resolvedPath);
9301
9488
  await fs8.mkdir(dir, { recursive: true });
9302
9489
  }
9303
9490
  await fs8.writeFile(resolvedPath, content, "utf-8");
@@ -9312,7 +9499,7 @@ var createFileTool = {
9312
9499
  implementation: (context) => ({
9313
9500
  toolFn: async (value) => {
9314
9501
  const params = value;
9315
- const fileExists = existsSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
9502
+ const fileExists = existsSync5(path8.resolve(process.cwd(), path8.normalize(params.path)));
9316
9503
  context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
9317
9504
  path: params.path,
9318
9505
  size: params.content.length
@@ -9354,8 +9541,8 @@ var createFileTool = {
9354
9541
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
9355
9542
  import { glob } from "glob";
9356
9543
  import { stat } from "fs/promises";
9357
- import path8 from "path";
9358
- var DEFAULT_IGNORE_PATTERNS2 = [
9544
+ import path9 from "path";
9545
+ var DEFAULT_IGNORE_PATTERNS = [
9359
9546
  "**/node_modules/**",
9360
9547
  "**/.git/**",
9361
9548
  "**/dist/**",
@@ -9370,11 +9557,11 @@ var DEFAULT_IGNORE_PATTERNS2 = [
9370
9557
  async function findFiles(params) {
9371
9558
  const { pattern, dir_path, case_sensitive = true, respect_git_ignore = true } = params;
9372
9559
  const baseCwd = process.cwd();
9373
- const targetDir = dir_path ? path8.resolve(baseCwd, path8.normalize(dir_path)) : baseCwd;
9560
+ const targetDir = dir_path ? path9.resolve(baseCwd, path9.normalize(dir_path)) : baseCwd;
9374
9561
  if (!targetDir.startsWith(baseCwd)) {
9375
9562
  throw new Error(`Access denied: Cannot search outside of current working directory`);
9376
9563
  }
9377
- const ignorePatterns = respect_git_ignore ? DEFAULT_IGNORE_PATTERNS2 : [];
9564
+ const ignorePatterns = respect_git_ignore ? DEFAULT_IGNORE_PATTERNS : [];
9378
9565
  const matches = await glob(pattern, {
9379
9566
  cwd: targetDir,
9380
9567
  dot: false,
@@ -9410,7 +9597,7 @@ async function findFiles(params) {
9410
9597
  const summary = `Found ${filesWithStats.length} file(s)${truncated ? ` (showing first ${MAX_RESULTS})` : ""} matching: ${pattern}`;
9411
9598
  const dirInfo = dir_path ? `
9412
9599
  Directory: ${dir_path}` : "";
9413
- const filesList = results.map((file) => path8.relative(baseCwd, file.path)).join("\n");
9600
+ const filesList = results.map((file) => path9.relative(baseCwd, file.path)).join("\n");
9414
9601
  return `${summary}${dirInfo}
9415
9602
 
9416
9603
  ${filesList}`;
@@ -9464,7 +9651,7 @@ var globFilesTool = {
9464
9651
 
9465
9652
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
9466
9653
  import { stat as stat2 } from "fs/promises";
9467
- import path9 from "path";
9654
+ import path10 from "path";
9468
9655
  import { execFile } from "child_process";
9469
9656
  import { promisify } from "util";
9470
9657
  import { createRequire } from "module";
@@ -9473,18 +9660,18 @@ var require2 = createRequire(import.meta.url);
9473
9660
  function getRipgrepPath() {
9474
9661
  try {
9475
9662
  const ripgrepPath = require2.resolve("@vscode/ripgrep");
9476
- const ripgrepDir = path9.dirname(ripgrepPath);
9477
- const rgBinary = path9.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
9663
+ const ripgrepDir = path10.dirname(ripgrepPath);
9664
+ const rgBinary = path10.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
9478
9665
  return rgBinary;
9479
9666
  } catch (error) {
9480
9667
  throw new Error("ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services");
9481
9668
  }
9482
9669
  }
9483
9670
  function isPathWithinWorkspace(targetPath, baseCwd) {
9484
- const resolvedTarget = path9.resolve(targetPath);
9485
- const resolvedBase = path9.resolve(baseCwd);
9486
- const relativePath = path9.relative(resolvedBase, resolvedTarget);
9487
- return !relativePath.startsWith("..") && !path9.isAbsolute(relativePath);
9671
+ const resolvedTarget = path10.resolve(targetPath);
9672
+ const resolvedBase = path10.resolve(baseCwd);
9673
+ const relativePath = path10.relative(resolvedBase, resolvedTarget);
9674
+ return !relativePath.startsWith("..") && !path10.isAbsolute(relativePath);
9488
9675
  }
9489
9676
  function convertGlobToRipgrepGlobs(globPattern) {
9490
9677
  if (!globPattern || globPattern === "**/*") {
@@ -9500,7 +9687,7 @@ function convertGlobToRipgrepGlobs(globPattern) {
9500
9687
  async function searchFiles2(params) {
9501
9688
  const { pattern, dir_path, include } = params;
9502
9689
  const baseCwd = process.cwd();
9503
- const targetDir = dir_path ? path9.resolve(baseCwd, dir_path) : baseCwd;
9690
+ const targetDir = dir_path ? path10.resolve(baseCwd, dir_path) : baseCwd;
9504
9691
  if (!isPathWithinWorkspace(targetDir, baseCwd)) {
9505
9692
  throw new Error(`Path validation failed: "${dir_path}" resolves outside the allowed workspace directory`);
9506
9693
  }
@@ -9562,7 +9749,7 @@ async function searchFiles2(params) {
9562
9749
  if (item.type === "match") {
9563
9750
  const match = item;
9564
9751
  allMatches.push({
9565
- filePath: path9.relative(targetDir, match.data.path.text) || path9.basename(match.data.path.text),
9752
+ filePath: path10.relative(targetDir, match.data.path.text) || path10.basename(match.data.path.text),
9566
9753
  lineNumber: match.data.line_number,
9567
9754
  line: match.data.lines.text.trimEnd()
9568
9755
  // Remove trailing newline
@@ -9656,17 +9843,17 @@ var grepSearchTool = {
9656
9843
 
9657
9844
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
9658
9845
  import { promises as fs9 } from "fs";
9659
- import { existsSync as existsSync5, statSync as statSync5 } from "fs";
9660
- import path10 from "path";
9846
+ import { existsSync as existsSync6, statSync as statSync5 } from "fs";
9847
+ import path11 from "path";
9661
9848
  async function deleteFile(params) {
9662
9849
  const { path: filePath, recursive = false } = params;
9663
- const normalizedPath = path10.normalize(filePath);
9664
- const resolvedPath = path10.resolve(process.cwd(), normalizedPath);
9665
- const cwd = path10.resolve(process.cwd());
9850
+ const normalizedPath = path11.normalize(filePath);
9851
+ const resolvedPath = path11.resolve(process.cwd(), normalizedPath);
9852
+ const cwd = path11.resolve(process.cwd());
9666
9853
  if (!resolvedPath.startsWith(cwd)) {
9667
9854
  throw new Error(`Access denied: Cannot delete files outside of current working directory`);
9668
9855
  }
9669
- if (!existsSync5(resolvedPath)) {
9856
+ if (!existsSync6(resolvedPath)) {
9670
9857
  throw new Error(`File or directory not found: ${filePath}`);
9671
9858
  }
9672
9859
  const stats = statSync5(resolvedPath);
@@ -9689,8 +9876,8 @@ var deleteFileTool = {
9689
9876
  implementation: (context) => ({
9690
9877
  toolFn: async (value) => {
9691
9878
  const params = value;
9692
- const resolvedPath = path10.resolve(process.cwd(), path10.normalize(params.path));
9693
- const isDirectory = existsSync5(resolvedPath) && statSync5(resolvedPath).isDirectory();
9879
+ const resolvedPath = path11.resolve(process.cwd(), path11.normalize(params.path));
9880
+ const isDirectory = existsSync6(resolvedPath) && statSync5(resolvedPath).isDirectory();
9694
9881
  context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
9695
9882
  path: params.path,
9696
9883
  recursive: params.recursive
@@ -9815,7 +10002,7 @@ var knowledgeBaseSearchTool = {
9815
10002
 
9816
10003
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/bashExecute/index.js
9817
10004
  import { spawn } from "child_process";
9818
- import path11 from "path";
10005
+ import path12 from "path";
9819
10006
  var DEFAULT_TIMEOUT_MS = 6e4;
9820
10007
  var MAX_OUTPUT_SIZE = 100 * 1024;
9821
10008
  var DANGEROUS_PATTERNS = [
@@ -9999,7 +10186,7 @@ async function executeBashCommand(params) {
9999
10186
  };
10000
10187
  }
10001
10188
  const baseCwd = process.cwd();
10002
- const targetCwd = relativeCwd ? path11.resolve(baseCwd, relativeCwd) : baseCwd;
10189
+ const targetCwd = relativeCwd ? path12.resolve(baseCwd, relativeCwd) : baseCwd;
10003
10190
  const effectiveTimeout = Math.min(timeout, 5 * 60 * 1e3);
10004
10191
  return new Promise((resolve3) => {
10005
10192
  let stdout = "";
@@ -10190,8 +10377,8 @@ BLOCKED OPERATIONS:
10190
10377
 
10191
10378
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/editLocalFile/index.js
10192
10379
  import { promises as fs10 } from "fs";
10193
- import { existsSync as existsSync6 } from "fs";
10194
- import path12 from "path";
10380
+ import { existsSync as existsSync7 } from "fs";
10381
+ import path13 from "path";
10195
10382
  import { diffLines as diffLines3 } from "diff";
10196
10383
  function generateDiff(original, modified) {
10197
10384
  const differences = diffLines3(original, modified);
@@ -10215,13 +10402,13 @@ function generateDiff(original, modified) {
10215
10402
  }
10216
10403
  async function editLocalFile(params) {
10217
10404
  const { path: filePath, old_string, new_string } = params;
10218
- const normalizedPath = path12.normalize(filePath);
10219
- const resolvedPath = path12.resolve(process.cwd(), normalizedPath);
10220
- const cwd = path12.resolve(process.cwd());
10405
+ const normalizedPath = path13.normalize(filePath);
10406
+ const resolvedPath = path13.resolve(process.cwd(), normalizedPath);
10407
+ const cwd = path13.resolve(process.cwd());
10221
10408
  if (!resolvedPath.startsWith(cwd)) {
10222
10409
  throw new Error(`Access denied: Cannot edit files outside of current working directory`);
10223
10410
  }
10224
- if (!existsSync6(resolvedPath)) {
10411
+ if (!existsSync7(resolvedPath)) {
10225
10412
  throw new Error(`File not found: ${filePath}`);
10226
10413
  }
10227
10414
  const currentContent = await fs10.readFile(resolvedPath, "utf-8");
@@ -11412,10 +11599,10 @@ var ToolErrorType;
11412
11599
  // src/utils/diffPreview.ts
11413
11600
  import * as Diff from "diff";
11414
11601
  import { readFile } from "fs/promises";
11415
- import { existsSync as existsSync7 } from "fs";
11602
+ import { existsSync as existsSync8 } from "fs";
11416
11603
  async function generateFileDiffPreview(args) {
11417
11604
  try {
11418
- if (!existsSync7(args.path)) {
11605
+ if (!existsSync8(args.path)) {
11419
11606
  const lines2 = args.content.split("\n");
11420
11607
  const preview = lines2.slice(0, 20).join("\n");
11421
11608
  const hasMore = lines2.length > 20;
@@ -11453,7 +11640,7 @@ ${diffLines4.join("\n")}`;
11453
11640
  }
11454
11641
  async function generateFileDeletePreview(args) {
11455
11642
  try {
11456
- if (!existsSync7(args.path)) {
11643
+ if (!existsSync8(args.path)) {
11457
11644
  return `[File does not exist: ${args.path}]`;
11458
11645
  }
11459
11646
  const stats = await import("fs/promises").then((fs14) => fs14.stat(args.path));
@@ -11509,10 +11696,6 @@ var ServerToolExecutor = class {
11509
11696
  };
11510
11697
 
11511
11698
  // src/llm/ToolRouter.ts
11512
- var wsToolExecutor = null;
11513
- function setWebSocketToolExecutor(executor) {
11514
- wsToolExecutor = executor;
11515
- }
11516
11699
  var SERVER_TOOLS = ["weather_info", "web_search", "web_fetch"];
11517
11700
  var LOCAL_TOOLS = [
11518
11701
  "file_read",
@@ -11535,15 +11718,7 @@ function isLocalTool(toolName) {
11535
11718
  }
11536
11719
  async function executeTool(toolName, input, apiClient, localToolFn) {
11537
11720
  if (isServerTool(toolName)) {
11538
- if (wsToolExecutor) {
11539
- logger.debug(`[ToolRouter] Routing ${toolName} to server via WebSocket`);
11540
- const result = await wsToolExecutor.execute(toolName, input);
11541
- if (!result.success) {
11542
- return `Error executing ${toolName}: ${result.error || "Tool execution failed"}`;
11543
- }
11544
- return typeof result.content === "string" ? result.content : JSON.stringify(result.content ?? "");
11545
- }
11546
- logger.debug(`[ToolRouter] Routing ${toolName} to server via HTTP`);
11721
+ logger.debug(`[ToolRouter] Routing ${toolName} to server`);
11547
11722
  const executor = new ServerToolExecutor(apiClient);
11548
11723
  return await executor.executeTool(toolName, input);
11549
11724
  } else if (isLocalTool(toolName)) {
@@ -11759,21 +11934,21 @@ var NoOpStorage = class extends BaseStorage {
11759
11934
  async upload(input, destination, options) {
11760
11935
  return `/tmp/${destination}`;
11761
11936
  }
11762
- async download(path18) {
11937
+ async download(path20) {
11763
11938
  throw new Error("Download not supported in CLI");
11764
11939
  }
11765
- async delete(path18) {
11940
+ async delete(path20) {
11766
11941
  }
11767
- async getSignedUrl(path18) {
11768
- return `/tmp/${path18}`;
11942
+ async getSignedUrl(path20) {
11943
+ return `/tmp/${path20}`;
11769
11944
  }
11770
- getPublicUrl(path18) {
11771
- return `/tmp/${path18}`;
11945
+ getPublicUrl(path20) {
11946
+ return `/tmp/${path20}`;
11772
11947
  }
11773
- async getPreview(path18) {
11774
- return `/tmp/${path18}`;
11948
+ async getPreview(path20) {
11949
+ return `/tmp/${path20}`;
11775
11950
  }
11776
- async getMetadata(path18) {
11951
+ async getMetadata(path20) {
11777
11952
  return { size: 0, contentType: "application/octet-stream" };
11778
11953
  }
11779
11954
  };
@@ -11793,87 +11968,179 @@ var CliLogger = class extends Logger {
11793
11968
  log(...args) {
11794
11969
  }
11795
11970
  };
11796
- function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient) {
11971
+ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator) {
11797
11972
  const originalFn = tool.toolFn;
11798
11973
  const toolName = tool.toolSchema.name;
11799
11974
  return {
11800
11975
  ...tool,
11801
11976
  toolFn: async (args) => {
11802
- if (!permissionManager.needsPermission(toolName)) {
11803
- const result2 = await executeTool(toolName, args, apiClient, originalFn);
11804
- agentContext.observationQueue.push({ toolName, result: result2 });
11805
- return result2;
11806
- }
11807
- const { useCliStore: useCliStore2 } = await import("./store-FU6NDC2W.js");
11808
- if (useCliStore2.getState().autoAcceptEdits) {
11809
- const result2 = await executeTool(toolName, args, apiClient, originalFn);
11810
- agentContext.observationQueue.push({ toolName, result: result2 });
11811
- return result2;
11812
- }
11813
- let preview;
11814
- if (toolName === "edit_local_file" && args?.path && args?.old_string && typeof args?.new_string === "string") {
11815
- try {
11816
- preview = generateEditLocalFilePreview({
11817
- path: args.path,
11818
- old_string: args.old_string,
11819
- new_string: args.new_string
11820
- });
11821
- } catch (error) {
11822
- preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
11823
- }
11824
- } else if (toolName === "create_file" && args?.path && args?.content) {
11825
- try {
11826
- preview = await generateFileDiffPreview({
11827
- path: args.path,
11828
- content: args.content
11977
+ let isSandboxed = false;
11978
+ let sandboxedArgs = args;
11979
+ if (toolName === "bash_execute" && args?.command && sandboxOrchestrator) {
11980
+ const cwd = args.cwd ? path14.resolve(process.cwd(), args.cwd) : process.cwd();
11981
+ const decision = sandboxOrchestrator.shouldSandbox(args.command, cwd);
11982
+ if (decision.type === "blocked") {
11983
+ sandboxOrchestrator.recordBlocked();
11984
+ sandboxOrchestrator.recordViolation({
11985
+ type: "filesystem",
11986
+ command: args.command,
11987
+ blockedBy: "config",
11988
+ timestamp: /* @__PURE__ */ new Date(),
11989
+ detail: decision.reason
11990
+ }).catch(() => {
11829
11991
  });
11830
- } catch (error) {
11831
- preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
11992
+ console.error(
11993
+ `
11994
+ \x1B[41m\x1B[97m BLOCKED \x1B[0m \x1B[31mSandbox denied this command:\x1B[0m ${decision.reason}
11995
+ `
11996
+ );
11997
+ return `Command blocked by sandbox: ${decision.reason}`;
11832
11998
  }
11833
- } else if (toolName === "delete_file" && args?.path) {
11834
- try {
11835
- preview = await generateFileDeletePreview({
11836
- path: args.path
11837
- });
11838
- } catch (error) {
11839
- preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
11999
+ if (decision.type === "sandbox") {
12000
+ sandboxOrchestrator.recordSandboxed();
12001
+ isSandboxed = true;
12002
+ sandboxedArgs = {
12003
+ ...args,
12004
+ command: decision.wrappedCommand.commandString,
12005
+ _sandboxCleanup: decision.wrappedCommand.cleanupPaths
12006
+ };
12007
+ } else if (decision.type === "unsandboxed") {
12008
+ sandboxOrchestrator.recordUnsandboxed();
11840
12009
  }
11841
- } else if (toolName === "bash_execute" && args?.command) {
11842
- const cwd = args.cwd ? ` (in ${args.cwd})` : "";
11843
- const timeout = args.timeout ? ` [timeout: ${args.timeout}ms]` : "";
11844
- preview = `$ ${args.command}${cwd}${timeout}`;
11845
12010
  }
11846
- const response = await showPermissionPrompt(toolName, args, preview);
12011
+ const effectiveArgs = isSandboxed ? sandboxedArgs : args;
12012
+ async function executeAndRecord() {
12013
+ let result = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
12014
+ cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
12015
+ await captureViolations(isSandboxed, result, args?.command, sandboxOrchestrator);
12016
+ result = await retrySandboxFailure(
12017
+ isSandboxed,
12018
+ result,
12019
+ toolName,
12020
+ args,
12021
+ apiClient,
12022
+ originalFn,
12023
+ showPermissionPrompt
12024
+ );
12025
+ agentContext.observationQueue.push({ toolName, result });
12026
+ return result;
12027
+ }
12028
+ if (!permissionManager.needsPermission(toolName, { isSandboxed })) {
12029
+ return executeAndRecord();
12030
+ }
12031
+ const { useCliStore: useCliStore2 } = await import("./store-FU6NDC2W.js");
12032
+ if (useCliStore2.getState().autoAcceptEdits) {
12033
+ return executeAndRecord();
12034
+ }
12035
+ const preview = await generateToolPreview(toolName, args, isSandboxed);
12036
+ const response = await showPermissionPrompt(toolName, effectiveArgs, preview);
11847
12037
  if (response.action === "deny") {
11848
12038
  throw new PermissionDeniedError(toolName, args);
11849
12039
  }
12040
+ if (response.action === "allow-session") {
12041
+ permissionManager.trustToolForSession(toolName);
12042
+ }
11850
12043
  if (response.action === "allow-always") {
11851
- const canTrust = permissionManager.trustTool(toolName);
11852
- if (canTrust) {
11853
- const projectDir = configStore.getProjectConfigDir();
11854
- if (projectDir) {
11855
- try {
11856
- await configStore.initProjectConfig();
11857
- const existingLocal = await configStore.loadRawProjectLocalConfig() || {};
11858
- const updatedLocal = {
11859
- ...existingLocal,
11860
- trustedTools: [...existingLocal.trustedTools || [], toolName]
11861
- };
11862
- await configStore.saveProjectLocalConfig(updatedLocal);
11863
- } catch (error) {
11864
- await configStore.trustTool(toolName);
11865
- }
11866
- } else {
11867
- await configStore.trustTool(toolName);
11868
- }
11869
- }
12044
+ await persistToolTrust(toolName, permissionManager, configStore);
11870
12045
  }
11871
- const result = await executeTool(toolName, args, apiClient, originalFn);
11872
- agentContext.observationQueue.push({ toolName, result });
11873
- return result;
12046
+ return executeAndRecord();
11874
12047
  }
11875
12048
  };
11876
12049
  }
12050
+ function isSandboxFailure(isSandboxed, result) {
12051
+ if (!isSandboxed) return false;
12052
+ return result.includes("sandbox-exec:") || result.includes("bwrap:") || result.includes("Operation not permitted");
12053
+ }
12054
+ async function retrySandboxFailure(isSandboxed, result, toolName, originalArgs, apiClient, originalFn, showPermissionPrompt) {
12055
+ if (!isSandboxFailure(isSandboxed, result)) return result;
12056
+ const errorSnippet = result.slice(0, 200);
12057
+ const retryResponse = await showPermissionPrompt(
12058
+ toolName,
12059
+ originalArgs,
12060
+ `\u{1F6D1} SANDBOX BLOCKED \u2014 This command was denied by the OS sandbox.
12061
+
12062
+ - The sandbox prevented this operation because it violates filesystem restrictions.
12063
+ - You can retry without the sandbox, but the command will run with full system access.
12064
+
12065
+ @@Error Details@@
12066
+ ${errorSnippet}`
12067
+ );
12068
+ if (retryResponse.action !== "deny") {
12069
+ return executeTool(toolName, originalArgs, apiClient, originalFn);
12070
+ }
12071
+ return result;
12072
+ }
12073
+ function cleanupSandboxFiles(paths) {
12074
+ if (!paths || paths.length === 0) return;
12075
+ for (const p of paths) {
12076
+ try {
12077
+ rmSync(p, { recursive: true, force: true });
12078
+ } catch {
12079
+ }
12080
+ }
12081
+ }
12082
+ async function captureViolations(isSandboxed, result, command, orchestrator) {
12083
+ if (!isSandboxed || !orchestrator || !command) return;
12084
+ const { parseSandboxStderr, toSandboxViolations } = await import("./StderrViolationParser-7OYPM2DJ.js");
12085
+ const parsed = parseSandboxStderr(result);
12086
+ if (parsed.length > 0) {
12087
+ const violations = toSandboxViolations(parsed, command);
12088
+ for (const v of violations) {
12089
+ await orchestrator.recordViolation(v).catch(() => {
12090
+ });
12091
+ }
12092
+ }
12093
+ }
12094
+ async function generateToolPreview(toolName, args, isSandboxed) {
12095
+ try {
12096
+ if (toolName === "edit_local_file" && args?.path && args?.old_string && typeof args?.new_string === "string") {
12097
+ return generateEditLocalFilePreview({
12098
+ path: args.path,
12099
+ old_string: args.old_string,
12100
+ new_string: args.new_string
12101
+ });
12102
+ }
12103
+ if (toolName === "create_file" && args?.path && args?.content) {
12104
+ return await generateFileDiffPreview({
12105
+ path: args.path,
12106
+ content: args.content
12107
+ });
12108
+ }
12109
+ if (toolName === "delete_file" && args?.path) {
12110
+ return await generateFileDeletePreview({
12111
+ path: args.path
12112
+ });
12113
+ }
12114
+ if (toolName === "bash_execute" && args?.command) {
12115
+ const cwd = args.cwd ? ` (in ${args.cwd})` : "";
12116
+ const timeout = args.timeout ? ` [timeout: ${args.timeout}ms]` : "";
12117
+ const sandboxLabel = isSandboxed ? " [sandboxed]" : "";
12118
+ return `$ ${args.command}${cwd}${timeout}${sandboxLabel}`;
12119
+ }
12120
+ } catch (error) {
12121
+ return `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
12122
+ }
12123
+ return void 0;
12124
+ }
12125
+ async function persistToolTrust(toolName, permissionManager, configStore) {
12126
+ const canTrust = permissionManager.trustTool(toolName);
12127
+ if (!canTrust) return;
12128
+ const projectDir = configStore.getProjectConfigDir();
12129
+ if (projectDir) {
12130
+ try {
12131
+ await configStore.initProjectConfig();
12132
+ const existingLocal = await configStore.loadRawProjectLocalConfig() || {};
12133
+ await configStore.saveProjectLocalConfig({
12134
+ ...existingLocal,
12135
+ trustedTools: [...existingLocal.trustedTools || [], toolName]
12136
+ });
12137
+ } catch {
12138
+ await configStore.trustTool(toolName);
12139
+ }
12140
+ } else {
12141
+ await configStore.trustTool(toolName);
12142
+ }
12143
+ }
11877
12144
  function wrapToolWithHooks(tool, hooks, hookContext) {
11878
12145
  const hasToolHooks = hooks?.PreToolUse || hooks?.PostToolUse || hooks?.PostToolUseFailure;
11879
12146
  if (!hasToolHooks) {
@@ -11967,7 +12234,7 @@ function normalizeToolName(toolName) {
11967
12234
  }
11968
12235
  return TOOL_NAME_MAPPING[toolName] || toolName;
11969
12236
  }
11970
- function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
12237
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter, sandboxOrchestrator) {
11971
12238
  const logger2 = new CliLogger();
11972
12239
  const storage = new NoOpStorage();
11973
12240
  const user = {
@@ -12027,7 +12294,15 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
12027
12294
  tools_to_generate
12028
12295
  );
12029
12296
  let tools = Object.entries(toolsMap).map(
12030
- ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
12297
+ ([_, tool]) => wrapToolWithPermission(
12298
+ tool,
12299
+ permissionManager,
12300
+ showPermissionPrompt,
12301
+ agentContext,
12302
+ configStore,
12303
+ apiClient,
12304
+ sandboxOrchestrator
12305
+ )
12031
12306
  );
12032
12307
  if (toolFilter) {
12033
12308
  const { allowedTools, deniedTools } = toolFilter;
@@ -12051,11 +12326,23 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
12051
12326
  var PermissionManager = class {
12052
12327
  constructor(trustedTools = [], customCategories, deniedTools) {
12053
12328
  this.trustedTools = /* @__PURE__ */ new Set();
12329
+ this.sessionTrustedTools = /* @__PURE__ */ new Set();
12054
12330
  this.deniedTools = /* @__PURE__ */ new Set();
12331
+ this._sandboxMode = "disabled";
12332
+ this._sandboxActive = false;
12055
12333
  this.trustedTools = new Set(trustedTools);
12056
12334
  this.customCategories = new Map(Object.entries(customCategories || {}));
12057
12335
  this.deniedTools = new Set(deniedTools || []);
12058
12336
  }
12337
+ /** Update sandbox state from the orchestrator */
12338
+ setSandboxState(mode, active) {
12339
+ this._sandboxMode = mode;
12340
+ this._sandboxActive = active;
12341
+ }
12342
+ /** Check if sandbox auto-allow is active */
12343
+ isSandboxAutoAllow() {
12344
+ return this._sandboxMode === "auto-allow" && this._sandboxActive;
12345
+ }
12059
12346
  /**
12060
12347
  * Check if a tool needs permission before execution
12061
12348
  *
@@ -12066,8 +12353,13 @@ var PermissionManager = class {
12066
12353
  *
12067
12354
  * Note: prompt_always tools CANNOT be trusted, so they always need permission
12068
12355
  * Note: denied tools from project config ALWAYS need permission (cannot be overridden locally)
12356
+ *
12357
+ * @param toolName - The tool being checked
12358
+ * @param options.isSandboxed - If true, the command will run inside the OS-level sandbox.
12359
+ * In auto-allow mode, sandboxed bash_execute commands skip the permission prompt
12360
+ * because the sandbox provides the security boundary.
12069
12361
  */
12070
- needsPermission(toolName) {
12362
+ needsPermission(toolName, options) {
12071
12363
  const categoryMap = Object.fromEntries(this.customCategories);
12072
12364
  const category = getToolCategory(toolName, categoryMap);
12073
12365
  if (this.deniedTools.has(toolName)) {
@@ -12076,6 +12368,12 @@ var PermissionManager = class {
12076
12368
  if (category === "auto_approve") {
12077
12369
  return false;
12078
12370
  }
12371
+ if (options?.isSandboxed && toolName === "bash_execute" && this.isSandboxAutoAllow()) {
12372
+ return false;
12373
+ }
12374
+ if (this.sessionTrustedTools.has(toolName)) {
12375
+ return false;
12376
+ }
12079
12377
  if (category === "prompt_always") {
12080
12378
  return true;
12081
12379
  }
@@ -12143,6 +12441,29 @@ var PermissionManager = class {
12143
12441
  getDeniedTools() {
12144
12442
  return Array.from(this.deniedTools).sort();
12145
12443
  }
12444
+ /**
12445
+ * Trust a tool for the current session only (in-memory, no persistence)
12446
+ * Works for all tool categories including prompt_always, but NOT project-denied tools
12447
+ */
12448
+ trustToolForSession(toolName) {
12449
+ if (this.deniedTools.has(toolName)) {
12450
+ return false;
12451
+ }
12452
+ this.sessionTrustedTools.add(toolName);
12453
+ return true;
12454
+ }
12455
+ /**
12456
+ * Check if a tool is trusted for the current session
12457
+ */
12458
+ isSessionTrusted(toolName) {
12459
+ return this.sessionTrustedTools.has(toolName);
12460
+ }
12461
+ /**
12462
+ * Clear all session-scoped trust (called on exit or session reset)
12463
+ */
12464
+ clearSessionTrust() {
12465
+ this.sessionTrustedTools.clear();
12466
+ }
12146
12467
  /**
12147
12468
  * Clear all trusted tools
12148
12469
  */
@@ -12180,7 +12501,7 @@ function getEnvironmentName(configApiConfig) {
12180
12501
 
12181
12502
  // src/utils/contextLoader.ts
12182
12503
  import * as fs11 from "fs";
12183
- import * as path13 from "path";
12504
+ import * as path15 from "path";
12184
12505
  import { homedir as homedir3 } from "os";
12185
12506
  var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
12186
12507
  var PROJECT_CONTEXT_FILES = [
@@ -12201,7 +12522,7 @@ function formatFileSize2(bytes) {
12201
12522
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
12202
12523
  }
12203
12524
  function tryReadContextFile(dir, filename, source) {
12204
- const filePath = path13.join(dir, filename);
12525
+ const filePath = path15.join(dir, filename);
12205
12526
  try {
12206
12527
  const stats = fs11.lstatSync(filePath);
12207
12528
  if (stats.isDirectory()) {
@@ -12269,7 +12590,7 @@ ${project.content}`;
12269
12590
  }
12270
12591
  async function loadContextFiles(projectDir) {
12271
12592
  const errors = [];
12272
- const globalDir = path13.join(homedir3(), ".bike4mind");
12593
+ const globalDir = path15.join(homedir3(), ".bike4mind");
12273
12594
  const projectDirectory = projectDir || process.cwd();
12274
12595
  const [globalResult, projectResult] = await Promise.all([
12275
12596
  Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
@@ -12540,8 +12861,8 @@ function substituteArguments(template, args) {
12540
12861
  // ../../b4m-core/packages/mcp/dist/src/client.js
12541
12862
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
12542
12863
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
12543
- import path14 from "path";
12544
- import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
12864
+ import path16 from "path";
12865
+ import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
12545
12866
  var MCPClient = class {
12546
12867
  // Note: This class handles MCP server communication with repository filtering
12547
12868
  mcp;
@@ -12582,18 +12903,18 @@ var MCPClient = class {
12582
12903
  const root = process.env.INIT_CWD || process.cwd();
12583
12904
  const candidatePaths = [
12584
12905
  // When running from SST Lambda with node_modules structure (copyFiles)
12585
- path14.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
12906
+ path16.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
12586
12907
  // When running from SST Lambda deployed environment (/var/task)
12587
- path14.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12908
+ path16.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12588
12909
  // When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
12589
- path14.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12910
+ path16.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12590
12911
  // When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
12591
- path14.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12912
+ path16.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12592
12913
  // Original paths (backward compatibility)
12593
- path14.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12594
- path14.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
12914
+ path16.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12915
+ path16.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
12595
12916
  ];
12596
- const serverScriptPath = candidatePaths.find((p) => existsSync8(p));
12917
+ const serverScriptPath = candidatePaths.find((p) => existsSync9(p));
12597
12918
  if (!serverScriptPath) {
12598
12919
  const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
12599
12920
  console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
@@ -13153,6 +13474,14 @@ ${totalText}`);
13153
13474
  function stripThinkingBlocks(text) {
13154
13475
  return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
13155
13476
  }
13477
+ function extractUsageInfo(parsed) {
13478
+ return {
13479
+ inputTokens: parsed.usage?.inputTokens,
13480
+ outputTokens: parsed.usage?.outputTokens,
13481
+ creditsUsed: parsed.credits?.used,
13482
+ usdCost: parsed.credits?.usdCost
13483
+ };
13484
+ }
13156
13485
  var ServerLlmBackend = class {
13157
13486
  constructor(options) {
13158
13487
  this.completionsEndpoint = "/api/ai/v1/completions";
@@ -13304,11 +13633,8 @@ var ServerLlmBackend = class {
13304
13633
  if (parsed.type === "content") {
13305
13634
  const textChunk = parsed.text || "";
13306
13635
  accumulatedText += textChunk;
13307
- if (parsed.usage) {
13308
- lastUsageInfo = {
13309
- inputTokens: parsed.usage.inputTokens,
13310
- outputTokens: parsed.usage.outputTokens
13311
- };
13636
+ if (parsed.usage || parsed.credits) {
13637
+ lastUsageInfo = extractUsageInfo(parsed);
13312
13638
  }
13313
13639
  streamLogger.onContent(eventCount, textChunk, accumulatedText);
13314
13640
  } else if (parsed.type === "tool_use") {
@@ -13335,11 +13661,8 @@ var ServerLlmBackend = class {
13335
13661
  thinkingBlocks = parsed.thinking;
13336
13662
  streamLogger.onCriticalEvent(eventCount, "THINKING", `${thinkingBlocks.length} thinking blocks`);
13337
13663
  }
13338
- if (parsed.usage) {
13339
- lastUsageInfo = {
13340
- inputTokens: parsed.usage.inputTokens,
13341
- outputTokens: parsed.usage.outputTokens
13342
- };
13664
+ if (parsed.usage || parsed.credits) {
13665
+ lastUsageInfo = extractUsageInfo(parsed);
13343
13666
  }
13344
13667
  }
13345
13668
  } catch (parseError) {
@@ -13473,173 +13796,6 @@ var ServerLlmBackend = class {
13473
13796
  }
13474
13797
  };
13475
13798
 
13476
- // src/llm/WebSocketLlmBackend.ts
13477
- import { v4 as uuidv411 } from "uuid";
13478
- function stripThinkingBlocks2(text) {
13479
- return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
13480
- }
13481
- var WebSocketLlmBackend = class {
13482
- constructor(options) {
13483
- this.wsManager = options.wsManager;
13484
- this.apiClient = options.apiClient;
13485
- this.currentModel = options.model;
13486
- this.tokenGetter = options.tokenGetter;
13487
- this.wsCompletionUrl = options.wsCompletionUrl;
13488
- }
13489
- /**
13490
- * Send completion request via HTTP POST, receive streaming response via WebSocket.
13491
- * Collects all streamed chunks, then calls callback once at completion
13492
- * with the full accumulated content.
13493
- */
13494
- async complete(model, messages, options, callback) {
13495
- logger.debug(`[WebSocketLlmBackend] Starting complete() with model: ${model}`);
13496
- if (options.abortSignal?.aborted) {
13497
- logger.debug("[WebSocketLlmBackend] Request aborted before start");
13498
- return;
13499
- }
13500
- if (!this.wsManager.isConnected) {
13501
- throw new Error("WebSocket is not connected");
13502
- }
13503
- const requestId = uuidv411();
13504
- return new Promise((resolve3, reject) => {
13505
- const isVerbose = process.env.B4M_VERBOSE === "1";
13506
- const isUltraVerbose = process.env.B4M_DEBUG_STREAM === "1";
13507
- const streamLogger = new StreamLogger(logger, "WebSocketLlmBackend", isVerbose, isUltraVerbose);
13508
- streamLogger.streamStart();
13509
- let eventCount = 0;
13510
- let accumulatedText = "";
13511
- let lastUsageInfo = {};
13512
- let toolsUsed = [];
13513
- let thinkingBlocks = [];
13514
- let settled = false;
13515
- const settle = (action) => {
13516
- if (settled) return;
13517
- settled = true;
13518
- this.wsManager.offRequest(requestId);
13519
- this.wsManager.offDisconnect(onDisconnect);
13520
- action();
13521
- };
13522
- const settleResolve = () => settle(() => resolve3());
13523
- const settleReject = (err) => settle(() => reject(err));
13524
- const onDisconnect = () => {
13525
- logger.debug("[WebSocketLlmBackend] Connection dropped during completion");
13526
- settleReject(new Error("WebSocket connection lost during completion"));
13527
- };
13528
- this.wsManager.onDisconnect(onDisconnect);
13529
- if (options.abortSignal) {
13530
- if (options.abortSignal.aborted) {
13531
- settleResolve();
13532
- return;
13533
- }
13534
- options.abortSignal.addEventListener(
13535
- "abort",
13536
- () => {
13537
- logger.debug("[WebSocketLlmBackend] Abort signal received");
13538
- settleResolve();
13539
- },
13540
- { once: true }
13541
- );
13542
- }
13543
- const updateUsage = (usage) => {
13544
- if (usage) {
13545
- lastUsageInfo = { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens };
13546
- }
13547
- };
13548
- this.wsManager.onRequest(requestId, (message) => {
13549
- if (options.abortSignal?.aborted) return;
13550
- const action = message.action;
13551
- if (action === "cli_completion_chunk") {
13552
- eventCount++;
13553
- const chunk = message.chunk;
13554
- streamLogger.onEvent(eventCount, JSON.stringify(chunk));
13555
- const textChunk = chunk.text || "";
13556
- if (textChunk) accumulatedText += textChunk;
13557
- updateUsage(chunk.usage);
13558
- if (chunk.type === "content") {
13559
- streamLogger.onContent(eventCount, textChunk, accumulatedText);
13560
- } else if (chunk.type === "tool_use") {
13561
- streamLogger.onCriticalEvent(eventCount, "TOOL_USE", `tools: ${chunk.tools?.length}`);
13562
- if (chunk.tools && chunk.tools.length > 0) toolsUsed = chunk.tools;
13563
- if (chunk.thinking && chunk.thinking.length > 0) thinkingBlocks = chunk.thinking;
13564
- }
13565
- } else if (action === "cli_completion_done") {
13566
- streamLogger.streamComplete(accumulatedText);
13567
- const cleanedText = stripThinkingBlocks2(accumulatedText);
13568
- if (!cleanedText && toolsUsed.length === 0) {
13569
- settleResolve();
13570
- return;
13571
- }
13572
- const info = {
13573
- ...lastUsageInfo,
13574
- ...toolsUsed.length > 0 && { toolsUsed },
13575
- ...thinkingBlocks.length > 0 && { thinking: thinkingBlocks }
13576
- };
13577
- callback([cleanedText], info).then(() => settleResolve()).catch((err) => settleReject(err));
13578
- } else if (action === "cli_completion_error") {
13579
- const errorMsg = message.error || "Server error";
13580
- streamLogger.onCriticalEvent(eventCount, "ERROR", errorMsg);
13581
- settleReject(new Error(errorMsg));
13582
- }
13583
- });
13584
- const axiosInstance = this.apiClient.getAxiosInstance();
13585
- axiosInstance.post(
13586
- this.wsCompletionUrl,
13587
- {
13588
- requestId,
13589
- model,
13590
- messages,
13591
- options: {
13592
- temperature: options.temperature,
13593
- maxTokens: options.maxTokens,
13594
- stream: true,
13595
- tools: options.tools || []
13596
- }
13597
- },
13598
- { signal: options.abortSignal }
13599
- ).catch((err) => {
13600
- const msg = err instanceof Error ? err.message : String(err);
13601
- settleReject(new Error(`HTTP request failed: ${msg}`));
13602
- });
13603
- });
13604
- }
13605
- /**
13606
- * Get available models from server (REST call, not streaming).
13607
- * Delegates to ApiClient -- same as ServerLlmBackend.
13608
- */
13609
- async getModelInfo() {
13610
- try {
13611
- logger.debug("[WebSocketLlmBackend] Fetching models from /api/models");
13612
- const response = await this.apiClient.get("/api/models");
13613
- if (!response || typeof response !== "object" || !Array.isArray(response.models)) {
13614
- logger.warn("[WebSocketLlmBackend] Invalid API response format, using fallback models");
13615
- return this.getFallbackModels();
13616
- }
13617
- const filteredModels = response.models.filter(
13618
- (model) => model.type === "text" && model.supportsTools === true
13619
- );
13620
- if (filteredModels.length === 0) {
13621
- logger.warn("[WebSocketLlmBackend] No CLI-compatible models found, using fallback");
13622
- return this.getFallbackModels();
13623
- }
13624
- logger.debug(`[WebSocketLlmBackend] Loaded ${filteredModels.length} models`);
13625
- return filteredModels;
13626
- } catch (error) {
13627
- logger.warn(
13628
- `[WebSocketLlmBackend] Failed to fetch models: ${error instanceof Error ? error.message : String(error)}`
13629
- );
13630
- return this.getFallbackModels();
13631
- }
13632
- }
13633
- getFallbackModels() {
13634
- return [
13635
- { id: "claude-sonnet-4-5-20250929", name: "Claude 4.5 Sonnet" },
13636
- { id: "claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku" },
13637
- { id: "gpt-4o", name: "GPT-4o" },
13638
- { id: "gpt-4o-mini", name: "GPT-4o Mini" }
13639
- ];
13640
- }
13641
- };
13642
-
13643
13799
  // src/llm/NotifyingLlmBackend.ts
13644
13800
  var NotifyingLlmBackend = class {
13645
13801
  constructor(inner, backgroundManager) {
@@ -13674,253 +13830,6 @@ Please acknowledge these background agent results and incorporate them into your
13674
13830
  }
13675
13831
  };
13676
13832
 
13677
- // src/ws/WebSocketConnectionManager.ts
13678
- var WebSocketConnectionManager = class {
13679
- constructor(wsUrl, getToken) {
13680
- this.ws = null;
13681
- this.heartbeatInterval = null;
13682
- this.reconnectAttempts = 0;
13683
- this.maxReconnectDelay = 3e4;
13684
- this.handlers = /* @__PURE__ */ new Map();
13685
- this.disconnectHandlers = /* @__PURE__ */ new Set();
13686
- this.reconnectTimer = null;
13687
- this.connected = false;
13688
- this.connecting = false;
13689
- this.closed = false;
13690
- this.wsUrl = wsUrl;
13691
- this.getToken = getToken;
13692
- }
13693
- /**
13694
- * Connect to the WebSocket server.
13695
- * Resolves when connection is established, rejects on failure.
13696
- */
13697
- async connect() {
13698
- if (this.connected || this.connecting) return;
13699
- this.connecting = true;
13700
- const token = await this.getToken();
13701
- if (!token) {
13702
- this.connecting = false;
13703
- throw new Error("No access token available for WebSocket connection");
13704
- }
13705
- return new Promise((resolve3, reject) => {
13706
- logger.debug(`[WS] Connecting to ${this.wsUrl}...`);
13707
- this.ws = new WebSocket(this.wsUrl, [`access_token.${token}`]);
13708
- this.ws.onopen = () => {
13709
- logger.debug("[WS] Connected");
13710
- this.connected = true;
13711
- this.connecting = false;
13712
- this.reconnectAttempts = 0;
13713
- this.startHeartbeat();
13714
- resolve3();
13715
- };
13716
- this.ws.onmessage = (event) => {
13717
- try {
13718
- const data = typeof event.data === "string" ? event.data : event.data.toString();
13719
- const message = JSON.parse(data);
13720
- const requestId = message.requestId;
13721
- if (requestId && this.handlers.has(requestId)) {
13722
- this.handlers.get(requestId)(message);
13723
- } else {
13724
- logger.debug(`[WS] Unhandled message: ${message.action || "unknown"}`);
13725
- }
13726
- } catch (err) {
13727
- logger.debug(`[WS] Failed to parse message: ${err}`);
13728
- }
13729
- };
13730
- this.ws.onclose = () => {
13731
- logger.debug("[WS] Connection closed");
13732
- this.cleanup();
13733
- this.notifyDisconnect();
13734
- if (!this.closed) {
13735
- this.scheduleReconnect();
13736
- }
13737
- };
13738
- this.ws.onerror = (err) => {
13739
- logger.debug(`[WS] Error: ${err}`);
13740
- if (this.connecting) {
13741
- this.connecting = false;
13742
- this.connected = false;
13743
- reject(new Error("WebSocket connection failed"));
13744
- }
13745
- };
13746
- });
13747
- }
13748
- /** Whether the connection is currently established */
13749
- get isConnected() {
13750
- return this.connected;
13751
- }
13752
- /**
13753
- * Send a JSON message over the WebSocket connection.
13754
- */
13755
- send(data) {
13756
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
13757
- throw new Error("WebSocket is not connected");
13758
- }
13759
- const payload = JSON.stringify(data);
13760
- const sizeKB = (payload.length / 1024).toFixed(1);
13761
- logger.debug(`[WS] Sending ${sizeKB} KB (action: ${data.action})`);
13762
- if (payload.length > 32e3) {
13763
- logger.warn(`[WS] Payload ${sizeKB} KB exceeds API Gateway 32 KB frame limit \u2014 connection will be closed`);
13764
- }
13765
- this.ws.send(payload);
13766
- }
13767
- /**
13768
- * Register a handler for messages matching a specific requestId.
13769
- */
13770
- onRequest(requestId, handler) {
13771
- this.handlers.set(requestId, handler);
13772
- }
13773
- /**
13774
- * Remove a handler for a specific requestId.
13775
- */
13776
- offRequest(requestId) {
13777
- this.handlers.delete(requestId);
13778
- }
13779
- /**
13780
- * Register a handler that fires when the connection drops.
13781
- */
13782
- onDisconnect(handler) {
13783
- this.disconnectHandlers.add(handler);
13784
- }
13785
- /**
13786
- * Remove a disconnect handler.
13787
- */
13788
- offDisconnect(handler) {
13789
- this.disconnectHandlers.delete(handler);
13790
- }
13791
- /**
13792
- * Close the connection and stop all heartbeat/reconnect logic.
13793
- */
13794
- disconnect() {
13795
- this.closed = true;
13796
- this.cleanup();
13797
- if (this.ws) {
13798
- this.ws.close();
13799
- this.ws = null;
13800
- }
13801
- this.handlers.clear();
13802
- this.disconnectHandlers.clear();
13803
- }
13804
- startHeartbeat() {
13805
- this.stopHeartbeat();
13806
- this.heartbeatInterval = setInterval(
13807
- () => {
13808
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
13809
- this.ws.send(JSON.stringify({ action: "heartbeat" }));
13810
- logger.debug("[WS] Heartbeat sent");
13811
- }
13812
- },
13813
- 5 * 60 * 1e3
13814
- );
13815
- }
13816
- stopHeartbeat() {
13817
- if (this.heartbeatInterval) {
13818
- clearInterval(this.heartbeatInterval);
13819
- this.heartbeatInterval = null;
13820
- }
13821
- }
13822
- cleanup() {
13823
- this.connected = false;
13824
- this.connecting = false;
13825
- this.stopHeartbeat();
13826
- if (this.reconnectTimer) {
13827
- clearTimeout(this.reconnectTimer);
13828
- this.reconnectTimer = null;
13829
- }
13830
- }
13831
- notifyDisconnect() {
13832
- for (const handler of this.disconnectHandlers) {
13833
- try {
13834
- handler();
13835
- } catch {
13836
- }
13837
- }
13838
- }
13839
- scheduleReconnect() {
13840
- if (this.closed) return;
13841
- this.reconnectAttempts++;
13842
- const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
13843
- logger.debug(`[WS] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
13844
- this.reconnectTimer = setTimeout(async () => {
13845
- this.reconnectTimer = null;
13846
- if (this.closed) return;
13847
- try {
13848
- await this.connect();
13849
- } catch {
13850
- logger.debug("[WS] Reconnection failed");
13851
- }
13852
- }, delay);
13853
- }
13854
- };
13855
-
13856
- // src/ws/WebSocketToolExecutor.ts
13857
- import { v4 as uuidv412 } from "uuid";
13858
- var WebSocketToolExecutor = class {
13859
- constructor(wsManager, tokenGetter) {
13860
- this.wsManager = wsManager;
13861
- this.tokenGetter = tokenGetter;
13862
- }
13863
- /**
13864
- * Execute a server-side tool via WebSocket.
13865
- * Returns the tool result or throws on error.
13866
- */
13867
- async execute(toolName, input, abortSignal) {
13868
- if (!this.wsManager.isConnected) {
13869
- throw new Error("WebSocket is not connected");
13870
- }
13871
- const token = await this.tokenGetter();
13872
- if (!token) {
13873
- throw new Error("No access token available");
13874
- }
13875
- const requestId = uuidv412();
13876
- return new Promise((resolve3, reject) => {
13877
- let settled = false;
13878
- const settle = (action) => {
13879
- if (settled) return;
13880
- settled = true;
13881
- this.wsManager.offRequest(requestId);
13882
- this.wsManager.offDisconnect(onDisconnect);
13883
- action();
13884
- };
13885
- const settleResolve = (result) => settle(() => resolve3(result));
13886
- const settleReject = (err) => settle(() => reject(err));
13887
- const onDisconnect = () => {
13888
- settleReject(new Error("WebSocket connection lost during tool execution"));
13889
- };
13890
- this.wsManager.onDisconnect(onDisconnect);
13891
- if (abortSignal) {
13892
- if (abortSignal.aborted) {
13893
- settleReject(new Error("Tool execution aborted"));
13894
- return;
13895
- }
13896
- abortSignal.addEventListener("abort", () => settleReject(new Error("Tool execution aborted")), {
13897
- once: true
13898
- });
13899
- }
13900
- this.wsManager.onRequest(requestId, (message) => {
13901
- if (message.action === "cli_tool_response") {
13902
- settleResolve({
13903
- success: message.success,
13904
- content: message.content,
13905
- error: message.error
13906
- });
13907
- }
13908
- });
13909
- try {
13910
- this.wsManager.send({
13911
- action: "cli_tool_request",
13912
- accessToken: token,
13913
- requestId,
13914
- toolName,
13915
- input
13916
- });
13917
- } catch (err) {
13918
- settleReject(err instanceof Error ? err : new Error(String(err)));
13919
- }
13920
- });
13921
- }
13922
- };
13923
-
13924
13833
  // src/auth/ApiClient.ts
13925
13834
  import axios11 from "axios";
13926
13835
  var ApiClient = class {
@@ -14058,7 +13967,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
14058
13967
  // package.json
14059
13968
  var package_default = {
14060
13969
  name: "@bike4mind/cli",
14061
- version: "0.2.29-feat-cli-websocket-streaming.18881+1cfcea5f6",
13970
+ version: "0.2.29-feat-cli-sandbox.18927+ef8f5870a",
14062
13971
  type: "module",
14063
13972
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
14064
13973
  license: "UNLICENSED",
@@ -14129,10 +14038,13 @@ var package_default = {
14129
14038
  diff: "^8.0.2",
14130
14039
  dotenv: "^16.3.1",
14131
14040
  "eventsource-parser": "^3.0.6",
14041
+ fdir: "^6.5.0",
14132
14042
  "file-type": "^18.7.0",
14133
14043
  "fuse.js": "^7.1.0",
14044
+ fzf: "^0.5.2",
14134
14045
  glob: "^13.0.0",
14135
14046
  "gray-matter": "^4.0.3",
14047
+ ignore: "^7.0.5",
14136
14048
  ink: "^6.5.1",
14137
14049
  "ink-select-input": "^6.2.0",
14138
14050
  "ink-spinner": "^5.0.0",
@@ -14148,6 +14060,7 @@ var package_default = {
14148
14060
  open: "^11.0.0",
14149
14061
  openai: "^6.18.0",
14150
14062
  "p-limit": "^6.2.0",
14063
+ picomatch: "^4.0.3",
14151
14064
  qrcode: "^1.5.4",
14152
14065
  react: "^19.2.3",
14153
14066
  sharp: "^0.34.5",
@@ -14168,14 +14081,15 @@ var package_default = {
14168
14081
  },
14169
14082
  devDependencies: {
14170
14083
  "@bike4mind/agents": "0.1.0",
14171
- "@bike4mind/common": "2.50.1-feat-cli-websocket-streaming.18881+1cfcea5f6",
14172
- "@bike4mind/mcp": "1.29.1-feat-cli-websocket-streaming.18881+1cfcea5f6",
14173
- "@bike4mind/services": "2.48.1-feat-cli-websocket-streaming.18881+1cfcea5f6",
14174
- "@bike4mind/utils": "2.5.1-feat-cli-websocket-streaming.18881+1cfcea5f6",
14084
+ "@bike4mind/common": "2.50.1-feat-cli-sandbox.18927+ef8f5870a",
14085
+ "@bike4mind/mcp": "1.29.1-feat-cli-sandbox.18927+ef8f5870a",
14086
+ "@bike4mind/services": "2.48.1-feat-cli-sandbox.18927+ef8f5870a",
14087
+ "@bike4mind/utils": "2.5.1-feat-cli-sandbox.18927+ef8f5870a",
14175
14088
  "@types/better-sqlite3": "^7.6.13",
14176
14089
  "@types/diff": "^5.0.9",
14177
14090
  "@types/jsonwebtoken": "^9.0.4",
14178
14091
  "@types/node": "^22.9.0",
14092
+ "@types/picomatch": "^4.0.2",
14179
14093
  "@types/react": "^19.2.7",
14180
14094
  "@types/uuid": "^9.0.7",
14181
14095
  "@types/yargs": "^17.0.32",
@@ -14188,7 +14102,7 @@ var package_default = {
14188
14102
  optionalDependencies: {
14189
14103
  "@vscode/ripgrep": "^1.17.0"
14190
14104
  },
14191
- gitHead: "1cfcea5f6e4c3d36587681e0d276669b66cdcc4e"
14105
+ gitHead: "ef8f5870abfa559f03d5af462d888986a7b6cd31"
14192
14106
  };
14193
14107
 
14194
14108
  // src/config/constants.ts
@@ -14638,7 +14552,7 @@ var SubagentOrchestrator = class {
14638
14552
 
14639
14553
  // src/agents/AgentStore.ts
14640
14554
  import fs12 from "fs/promises";
14641
- import path15 from "path";
14555
+ import path17 from "path";
14642
14556
  import os2 from "os";
14643
14557
  import matter2 from "gray-matter";
14644
14558
  var FULL_MODEL_ID_PREFIXES = [
@@ -14787,10 +14701,10 @@ var AgentStore = class {
14787
14701
  const root = projectRoot || process.cwd();
14788
14702
  const home = os2.homedir();
14789
14703
  this.builtinAgentsDir = builtinDir;
14790
- this.globalB4MAgentsDir = path15.join(home, ".bike4mind", "agents");
14791
- this.globalClaudeAgentsDir = path15.join(home, ".claude", "agents");
14792
- this.projectB4MAgentsDir = path15.join(root, ".bike4mind", "agents");
14793
- this.projectClaudeAgentsDir = path15.join(root, ".claude", "agents");
14704
+ this.globalB4MAgentsDir = path17.join(home, ".bike4mind", "agents");
14705
+ this.globalClaudeAgentsDir = path17.join(home, ".claude", "agents");
14706
+ this.projectB4MAgentsDir = path17.join(root, ".bike4mind", "agents");
14707
+ this.projectClaudeAgentsDir = path17.join(root, ".claude", "agents");
14794
14708
  }
14795
14709
  /**
14796
14710
  * Load all agents from all directories
@@ -14844,7 +14758,7 @@ var AgentStore = class {
14844
14758
  try {
14845
14759
  const entries = await fs12.readdir(directory, { withFileTypes: true });
14846
14760
  for (const entry of entries) {
14847
- const fullPath = path15.join(directory, entry.name);
14761
+ const fullPath = path17.join(directory, entry.name);
14848
14762
  if (entry.isDirectory()) {
14849
14763
  const subFiles = await this.findAgentFiles(fullPath);
14850
14764
  files.push(...subFiles);
@@ -14864,7 +14778,7 @@ var AgentStore = class {
14864
14778
  const content = await fs12.readFile(filePath, "utf-8");
14865
14779
  const { data: frontmatter, content: body } = matter2(content);
14866
14780
  const parsed = AgentFrontmatterSchema.parse(frontmatter);
14867
- const name = path15.basename(filePath, ".md");
14781
+ const name = path17.basename(filePath, ".md");
14868
14782
  const modelInput = parsed.model || DEFAULT_AGENT_MODEL;
14869
14783
  const resolution = resolveModelAlias(modelInput, name, filePath);
14870
14784
  if (!resolution.resolved && resolution.warning) {
@@ -14944,7 +14858,7 @@ var AgentStore = class {
14944
14858
  */
14945
14859
  async createAgentFile(name, isGlobal = false, useClaude = true) {
14946
14860
  const targetDir = isGlobal ? useClaude ? this.globalClaudeAgentsDir : this.globalB4MAgentsDir : useClaude ? this.projectClaudeAgentsDir : this.projectB4MAgentsDir;
14947
- const filePath = path15.join(targetDir, `${name}.md`);
14861
+ const filePath = path17.join(targetDir, `${name}.md`);
14948
14862
  try {
14949
14863
  await fs12.access(filePath);
14950
14864
  throw new Error(`Agent file already exists: ${filePath}`);
@@ -15641,7 +15555,7 @@ function createTodoStore(onUpdate) {
15641
15555
 
15642
15556
  // src/tools/findDefinitionTool.ts
15643
15557
  import { stat as stat3 } from "fs/promises";
15644
- import path16 from "path";
15558
+ import path18 from "path";
15645
15559
  import { execFile as execFile2 } from "child_process";
15646
15560
  import { promisify as promisify2 } from "util";
15647
15561
  import { createRequire as createRequire2 } from "module";
@@ -15662,8 +15576,8 @@ var ALL_KEYWORDS = Object.values(KIND_KEYWORDS).flat();
15662
15576
  function getRipgrepPath2() {
15663
15577
  try {
15664
15578
  const ripgrepPath = require3.resolve("@vscode/ripgrep");
15665
- const ripgrepDir = path16.dirname(ripgrepPath);
15666
- return path16.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
15579
+ const ripgrepDir = path18.dirname(ripgrepPath);
15580
+ return path18.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
15667
15581
  } catch {
15668
15582
  throw new Error(
15669
15583
  "ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services"
@@ -15671,10 +15585,10 @@ function getRipgrepPath2() {
15671
15585
  }
15672
15586
  }
15673
15587
  function isPathWithinWorkspace2(targetPath, baseCwd) {
15674
- const resolvedTarget = path16.resolve(targetPath);
15675
- const resolvedBase = path16.resolve(baseCwd);
15676
- const relativePath = path16.relative(resolvedBase, resolvedTarget);
15677
- return !relativePath.startsWith("..") && !path16.isAbsolute(relativePath);
15588
+ const resolvedTarget = path18.resolve(targetPath);
15589
+ const resolvedBase = path18.resolve(baseCwd);
15590
+ const relativePath = path18.relative(resolvedBase, resolvedTarget);
15591
+ return !relativePath.startsWith("..") && !path18.isAbsolute(relativePath);
15678
15592
  }
15679
15593
  function escapeRegex(str) {
15680
15594
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -15707,7 +15621,7 @@ async function findDefinitions(params) {
15707
15621
  throw new Error("symbol_name is required");
15708
15622
  }
15709
15623
  const baseCwd = process.cwd();
15710
- const targetDir = search_path ? path16.resolve(baseCwd, search_path) : baseCwd;
15624
+ const targetDir = search_path ? path18.resolve(baseCwd, search_path) : baseCwd;
15711
15625
  if (!isPathWithinWorkspace2(targetDir, baseCwd)) {
15712
15626
  throw new Error(`Path validation failed: "${search_path}" resolves outside the allowed workspace directory`);
15713
15627
  }
@@ -15762,7 +15676,7 @@ async function findDefinitions(params) {
15762
15676
  const lineText = match.data.lines.text.trimEnd();
15763
15677
  if (isLikelyDefinition(lineText)) {
15764
15678
  allMatches.push({
15765
- filePath: path16.relative(targetDir, match.data.path.text) || path16.basename(match.data.path.text),
15679
+ filePath: path18.relative(targetDir, match.data.path.text) || path18.basename(match.data.path.text),
15766
15680
  lineNumber: match.data.line_number,
15767
15681
  line: lineText
15768
15682
  });
@@ -15840,8 +15754,8 @@ function createFindDefinitionTool() {
15840
15754
  }
15841
15755
 
15842
15756
  // src/tools/getFileStructure/index.ts
15843
- import { existsSync as existsSync9, promises as fs13, statSync as statSync6 } from "fs";
15844
- import path17 from "path";
15757
+ import { existsSync as existsSync10, promises as fs13, statSync as statSync6 } from "fs";
15758
+ import path19 from "path";
15845
15759
 
15846
15760
  // src/tools/getFileStructure/formatter.ts
15847
15761
  var SECTIONS = [
@@ -15885,11 +15799,11 @@ function createGetFileStructureTool() {
15885
15799
  const params = value;
15886
15800
  try {
15887
15801
  const cwd = process.cwd();
15888
- const resolvedPath = path17.resolve(cwd, params.path);
15802
+ const resolvedPath = path19.resolve(cwd, params.path);
15889
15803
  if (!resolvedPath.startsWith(cwd)) {
15890
15804
  return "Error: Access denied - cannot read files outside of current working directory";
15891
15805
  }
15892
- if (!existsSync9(resolvedPath)) {
15806
+ if (!existsSync10(resolvedPath)) {
15893
15807
  return `Error: File not found: ${params.path}`;
15894
15808
  }
15895
15809
  const stats = statSync6(resolvedPath);
@@ -15899,7 +15813,7 @@ function createGetFileStructureTool() {
15899
15813
  if (stats.size > MAX_FILE_SIZE3) {
15900
15814
  return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${MAX_FILE_SIZE3 / 1024 / 1024}MB`;
15901
15815
  }
15902
- const ext = path17.extname(resolvedPath).toLowerCase();
15816
+ const ext = path19.extname(resolvedPath).toLowerCase();
15903
15817
  const { getLanguageForExtension, parseFileStructure, getSupportedLanguages } = await import("./treeSitterEngine-4SGFQDY3.js");
15904
15818
  const languageId = getLanguageForExtension(ext);
15905
15819
  if (!languageId) {
@@ -15946,7 +15860,7 @@ var usageCache = null;
15946
15860
  function CliApp() {
15947
15861
  const { exit } = useApp();
15948
15862
  const imageRenderer = new ImageRenderer();
15949
- const [state, setState] = useState9({
15863
+ const [state, setState] = useState10({
15950
15864
  session: null,
15951
15865
  sessionStore: new SessionStore(),
15952
15866
  configStore: new ConfigStore(),
@@ -15968,11 +15882,11 @@ function CliApp() {
15968
15882
  abortController: null,
15969
15883
  contextContent: "",
15970
15884
  backgroundManager: null,
15971
- wsManager: null
15885
+ sandboxOrchestrator: null
15972
15886
  });
15973
- const [isInitialized, setIsInitialized] = useState9(false);
15974
- const [initError, setInitError] = useState9(null);
15975
- const [commandHistory, setCommandHistory] = useState9([]);
15887
+ const [isInitialized, setIsInitialized] = useState10(false);
15888
+ const [initError, setInitError] = useState10(null);
15889
+ const [commandHistory, setCommandHistory] = useState10([]);
15976
15890
  const imageStoreInitPromise = useRef3(null);
15977
15891
  const setStoreSession = useCliStore((state2) => state2.setSession);
15978
15892
  const enqueuePermissionPrompt = useCliStore((state2) => state2.enqueuePermissionPrompt);
@@ -15996,10 +15910,6 @@ function CliApp() {
15996
15910
  })
15997
15911
  );
15998
15912
  }
15999
- if (state.wsManager) {
16000
- state.wsManager.disconnect();
16001
- setWebSocketToolExecutor(null);
16002
- }
16003
15913
  if (state.agent) {
16004
15914
  state.agent.removeAllListeners();
16005
15915
  }
@@ -16018,7 +15928,7 @@ function CliApp() {
16018
15928
  setTimeout(() => {
16019
15929
  process.exit(0);
16020
15930
  }, 100);
16021
- }, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore, state.wsManager]);
15931
+ }, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
16022
15932
  useInput9((input, key) => {
16023
15933
  if (key.escape) {
16024
15934
  if (state.abortController) {
@@ -16088,7 +15998,7 @@ function CliApp() {
16088
15998
  if (!isAuthenticated) {
16089
15999
  console.log("\u2139\uFE0F AI features disabled. Available commands: /login, /help, /config\n");
16090
16000
  const minimalSession = {
16091
- id: uuidv413(),
16001
+ id: uuidv411(),
16092
16002
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
16093
16003
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16094
16004
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -16116,45 +16026,10 @@ function CliApp() {
16116
16026
  console.log(`\u{1F30D} API Environment: ${envName} (${apiBaseURL})`);
16117
16027
  }
16118
16028
  const apiClient = new ApiClient(apiBaseURL, state.configStore);
16119
- const tokenGetter = async () => {
16120
- const tokens = await state.configStore.getAuthTokens();
16121
- return tokens?.accessToken ?? null;
16122
- };
16123
- let wsManager = null;
16124
- let llm;
16125
- try {
16126
- const serverConfig = await apiClient.get(
16127
- "/api/settings/serverConfig"
16128
- );
16129
- const wsUrl = serverConfig?.websocketUrl;
16130
- const wsCompletionUrl = serverConfig?.wsCompletionUrl;
16131
- if (wsUrl && wsCompletionUrl) {
16132
- wsManager = new WebSocketConnectionManager(wsUrl, tokenGetter);
16133
- await wsManager.connect();
16134
- const wsToolExecutor2 = new WebSocketToolExecutor(wsManager, tokenGetter);
16135
- setWebSocketToolExecutor(wsToolExecutor2);
16136
- llm = new WebSocketLlmBackend({
16137
- wsManager,
16138
- apiClient,
16139
- model: config.defaultModel,
16140
- tokenGetter,
16141
- wsCompletionUrl
16142
- });
16143
- logger.debug("\u{1F50C} Using WebSocket transport (bypasses CloudFront timeout)");
16144
- } else {
16145
- throw new Error("No websocketUrl or wsCompletionUrl in server config");
16146
- }
16147
- } catch (wsError) {
16148
- logger.debug(
16149
- `[WS] WebSocket unavailable, using SSE fallback: ${wsError instanceof Error ? wsError.message : String(wsError)}`
16150
- );
16151
- wsManager = null;
16152
- setWebSocketToolExecutor(null);
16153
- llm = new ServerLlmBackend({
16154
- apiClient,
16155
- model: config.defaultModel
16156
- });
16157
- }
16029
+ const llm = new ServerLlmBackend({
16030
+ apiClient,
16031
+ model: config.defaultModel
16032
+ });
16158
16033
  const models = await llm.getModelInfo();
16159
16034
  if (models.length === 0) {
16160
16035
  throw new Error("No models available from server.");
@@ -16167,7 +16042,7 @@ function CliApp() {
16167
16042
  }
16168
16043
  llm.currentModel = modelInfo.id;
16169
16044
  const newSession = {
16170
- id: uuidv413(),
16045
+ id: uuidv411(),
16171
16046
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
16172
16047
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16173
16048
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -16204,6 +16079,52 @@ function CliApp() {
16204
16079
  config.tools.disabled || []
16205
16080
  // denied tools from project config
16206
16081
  );
16082
+ const { createSandboxRuntime } = await import("./SandboxRuntimeAdapter-FQ56MAB2.js");
16083
+ const { SandboxOrchestrator } = await import("./SandboxOrchestrator-MYBNKKWL.js");
16084
+ const { DEFAULT_SANDBOX_CONFIG } = await import("./types-KB5NP6T4.js");
16085
+ const { ProxyManager } = await import("./ProxyManager-4ZZEYTD6.js");
16086
+ const sandboxRuntime = await createSandboxRuntime();
16087
+ const sandboxConfig = config.sandbox ?? DEFAULT_SANDBOX_CONFIG;
16088
+ const proxyManager = new ProxyManager(sandboxConfig.network);
16089
+ const sandboxOrchestrator = new SandboxOrchestrator(sandboxConfig, sandboxRuntime, proxyManager);
16090
+ proxyManager.onEvent((event) => {
16091
+ if (event.type === "blocked") {
16092
+ console.error(
16093
+ `
16094
+ \x1B[41m\x1B[97m BLOCKED \x1B[0m \x1B[31mNetwork proxy denied connection to\x1B[0m \x1B[1m${event.domain}\x1B[0m \x1B[90m(${event.method})\x1B[0m`
16095
+ );
16096
+ console.error(`\x1B[90m Tip: /sandbox:trust-domain ${event.domain}\x1B[0m
16097
+ `);
16098
+ sandboxOrchestrator.recordViolation({
16099
+ type: "network",
16100
+ domain: event.domain,
16101
+ command: `[network] ${event.method} ${event.domain}`,
16102
+ blockedBy: "proxy",
16103
+ timestamp: event.timestamp,
16104
+ detail: `Blocked ${event.method} to ${event.domain}`
16105
+ }).catch(() => {
16106
+ });
16107
+ }
16108
+ });
16109
+ const { ViolationLogStore } = await import("./ViolationLogStore-RIIUVURH.js");
16110
+ const violationStore = new ViolationLogStore();
16111
+ sandboxOrchestrator.setViolationStore(violationStore);
16112
+ permissionManager.setSandboxState(sandboxConfig.mode, sandboxOrchestrator.isActive());
16113
+ if (sandboxConfig.enabled && sandboxConfig.mode !== "disabled") {
16114
+ if (sandboxRuntime) {
16115
+ console.log(`\u{1F512} Sandbox: ${sandboxConfig.mode} (${sandboxRuntime.name})`);
16116
+ } else {
16117
+ console.log("\u26A0\uFE0F Sandbox: enabled but runtime not available on this platform");
16118
+ }
16119
+ if (sandboxConfig.network.enabled) {
16120
+ await proxyManager.start();
16121
+ if (proxyManager.isRunning()) {
16122
+ console.log(
16123
+ `\u{1F310} Network proxy: filtering on port ${proxyManager.getPort()} (${sandboxConfig.network.allowedDomains.length} domains)`
16124
+ );
16125
+ }
16126
+ }
16127
+ }
16207
16128
  let permissionPromptCounter = 0;
16208
16129
  const promptFn = (toolName, args, preview) => {
16209
16130
  return new Promise((resolve3) => {
@@ -16229,7 +16150,10 @@ function CliApp() {
16229
16150
  promptFn,
16230
16151
  agentContext,
16231
16152
  state.configStore,
16232
- apiClient
16153
+ apiClient,
16154
+ void 0,
16155
+ // toolFilter
16156
+ sandboxOrchestrator
16233
16157
  );
16234
16158
  startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
16235
16159
  const mcpManager = new McpManager(config);
@@ -16383,8 +16307,8 @@ function CliApp() {
16383
16307
  // Store raw context for compact instructions
16384
16308
  backgroundManager,
16385
16309
  // Store for grouped notification turn tracking
16386
- wsManager
16387
- // WebSocket connection manager (null if using SSE fallback)
16310
+ sandboxOrchestrator
16311
+ // Store sandbox orchestrator for /sandbox commands
16388
16312
  }));
16389
16313
  setStoreSession(newSession);
16390
16314
  const bannerLines = [
@@ -16430,7 +16354,7 @@ function CliApp() {
16430
16354
  setStoreSession,
16431
16355
  setState
16432
16356
  ]);
16433
- useEffect6(() => {
16357
+ useEffect7(() => {
16434
16358
  init();
16435
16359
  }, [init]);
16436
16360
  const handleCustomCommandMessage = async (fullTemplate, displayMessage) => {
@@ -16479,13 +16403,13 @@ function CliApp() {
16479
16403
  messageContent = multimodalMessage.content;
16480
16404
  }
16481
16405
  const userMessage = {
16482
- id: uuidv413(),
16406
+ id: uuidv411(),
16483
16407
  role: "user",
16484
16408
  content: userMessageContent,
16485
16409
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
16486
16410
  };
16487
16411
  const pendingAssistantMessage = {
16488
- id: uuidv413(),
16412
+ id: uuidv411(),
16489
16413
  role: "assistant",
16490
16414
  content: "...",
16491
16415
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -16541,6 +16465,7 @@ function CliApp() {
16541
16465
  completion: 0,
16542
16466
  total: result.completionInfo.totalTokens
16543
16467
  },
16468
+ creditsUsed: result.completionInfo.totalCredits,
16544
16469
  model: state.session.model,
16545
16470
  permissionDenied
16546
16471
  }
@@ -16552,6 +16477,7 @@ function CliApp() {
16552
16477
  metadata: {
16553
16478
  ...currentSession.metadata,
16554
16479
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
16480
+ totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
16555
16481
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
16556
16482
  }
16557
16483
  };
@@ -16696,13 +16622,13 @@ function CliApp() {
16696
16622
  userMessageContent = message;
16697
16623
  }
16698
16624
  const userMessage = {
16699
- id: uuidv413(),
16625
+ id: uuidv411(),
16700
16626
  role: "user",
16701
16627
  content: userMessageContent,
16702
16628
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
16703
16629
  };
16704
16630
  const pendingAssistantMessage = {
16705
- id: uuidv413(),
16631
+ id: uuidv411(),
16706
16632
  role: "assistant",
16707
16633
  content: "...",
16708
16634
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -16754,6 +16680,7 @@ function CliApp() {
16754
16680
  completion: 0,
16755
16681
  total: result.completionInfo.totalTokens
16756
16682
  },
16683
+ creditsUsed: result.completionInfo.totalCredits,
16757
16684
  steps: result.steps.map(formatStep),
16758
16685
  // Complete history: thoughts, actions, observations
16759
16686
  permissionDenied
@@ -16767,6 +16694,7 @@ function CliApp() {
16767
16694
  metadata: {
16768
16695
  ...currentSession.metadata,
16769
16696
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
16697
+ totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
16770
16698
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
16771
16699
  }
16772
16700
  };
@@ -16780,7 +16708,7 @@ function CliApp() {
16780
16708
  const currentSession = useCliStore.getState().session;
16781
16709
  if (currentSession) {
16782
16710
  const cancelMessage = {
16783
- id: uuidv413(),
16711
+ id: uuidv411(),
16784
16712
  role: "assistant",
16785
16713
  content: "\u26A0\uFE0F Operation cancelled by user",
16786
16714
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -16825,7 +16753,7 @@ function CliApp() {
16825
16753
  setState((prev) => ({ ...prev, abortController }));
16826
16754
  try {
16827
16755
  const pendingAssistantMessage = {
16828
- id: uuidv413(),
16756
+ id: uuidv411(),
16829
16757
  role: "assistant",
16830
16758
  content: "...",
16831
16759
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -16853,7 +16781,7 @@ function CliApp() {
16853
16781
  const currentSession = useCliStore.getState().session;
16854
16782
  if (!currentSession) return;
16855
16783
  const continuationMessage = {
16856
- id: uuidv413(),
16784
+ id: uuidv411(),
16857
16785
  role: "assistant",
16858
16786
  content: "---\n\n**Background Agent Results:**\n\n" + result.finalAnswer,
16859
16787
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -16865,7 +16793,8 @@ function CliApp() {
16865
16793
  prompt: 0,
16866
16794
  completion: 0,
16867
16795
  total: result.completionInfo.totalTokens
16868
- }
16796
+ },
16797
+ creditsUsed: result.completionInfo.totalCredits
16869
16798
  }
16870
16799
  };
16871
16800
  const updatedSession = {
@@ -16875,6 +16804,7 @@ function CliApp() {
16875
16804
  metadata: {
16876
16805
  ...currentSession.metadata,
16877
16806
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
16807
+ totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
16878
16808
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
16879
16809
  }
16880
16810
  };
@@ -16912,13 +16842,13 @@ function CliApp() {
16912
16842
  isError = true;
16913
16843
  }
16914
16844
  const userMessage = {
16915
- id: uuidv413(),
16845
+ id: uuidv411(),
16916
16846
  role: "user",
16917
16847
  content: `$ ${command}`,
16918
16848
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
16919
16849
  };
16920
16850
  const assistantMessage = {
16921
- id: uuidv413(),
16851
+ id: uuidv411(),
16922
16852
  role: "assistant",
16923
16853
  content: isError ? `\u274C Error:
16924
16854
  ${output}` : output.trim() || "(no output)",
@@ -17387,7 +17317,7 @@ Keyboard Shortcuts:
17387
17317
  console.clear();
17388
17318
  const model = state.session?.model || state.config?.defaultModel || "claude-sonnet";
17389
17319
  const newSession = {
17390
- id: uuidv413(),
17320
+ id: uuidv411(),
17391
17321
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
17392
17322
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
17393
17323
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -17868,6 +17798,201 @@ No usage data available for the last ${USAGE_DAYS} days.`);
17868
17798
  }
17869
17799
  break;
17870
17800
  }
17801
+ // --- Sandbox commands ---
17802
+ case "sandbox": {
17803
+ if (!state.sandboxOrchestrator) {
17804
+ console.log("\nSandbox: Not initialized");
17805
+ break;
17806
+ }
17807
+ const status = state.sandboxOrchestrator.getStatus();
17808
+ console.log("\nSandbox Status:");
17809
+ console.log(` Enabled: ${status.enabled ? "Yes" : "No"}`);
17810
+ console.log(` Mode: ${status.mode}`);
17811
+ console.log(` Platform: ${status.platform ?? "N/A"}`);
17812
+ console.log(` Runtime: ${status.runtimeName ?? "N/A"}`);
17813
+ console.log(` Available: ${status.runtimeAvailable ? "Yes" : "No"}`);
17814
+ console.log("");
17815
+ console.log("Filesystem Config:");
17816
+ console.log(` Write only to CWD: ${status.config.filesystem.writeOnlyToWorkingDir}`);
17817
+ console.log(` Allowed reads: ${status.config.filesystem.allowedReadPaths.join(", ") || "(none)"}`);
17818
+ console.log(` Denied paths: ${status.config.filesystem.deniedPaths.join(", ") || "(none)"}`);
17819
+ console.log(` Excluded cmds: ${status.config.excludedCommands.join(", ") || "(none)"}`);
17820
+ console.log("");
17821
+ console.log("Network Filtering:");
17822
+ console.log(` Enabled: ${status.config.network.enabled ? "Yes" : "No"}`);
17823
+ console.log(` Proxy: ${status.proxyRunning ? `running on port ${status.proxyPort}` : "stopped"}`);
17824
+ console.log(` Domains: ${status.config.network.allowedDomains.length} allowed`);
17825
+ console.log("");
17826
+ if (status.stats) {
17827
+ const total = status.stats.sandboxed + status.stats.unsandboxed + status.stats.blocked;
17828
+ console.log("Session Stats:");
17829
+ console.log(` Total commands: ${total}`);
17830
+ console.log(` Sandboxed: ${status.stats.sandboxed}`);
17831
+ console.log(` Unsandboxed: ${status.stats.unsandboxed}`);
17832
+ console.log(` Blocked: ${status.stats.blocked}`);
17833
+ console.log(` Violations: ${status.stats.violations}`);
17834
+ console.log("");
17835
+ }
17836
+ break;
17837
+ }
17838
+ case "sandbox:enable": {
17839
+ if (!state.sandboxOrchestrator) {
17840
+ console.log("Sandbox not initialized");
17841
+ break;
17842
+ }
17843
+ if (!state.sandboxOrchestrator.isAvailable()) {
17844
+ console.log("Sandbox runtime not available on this platform");
17845
+ break;
17846
+ }
17847
+ state.sandboxOrchestrator.setMode("auto-allow");
17848
+ state.permissionManager?.setSandboxState("auto-allow", state.sandboxOrchestrator.isActive());
17849
+ const sandboxCfg = state.sandboxOrchestrator.getConfig();
17850
+ if (sandboxCfg.network.enabled) {
17851
+ await state.sandboxOrchestrator.startProxy();
17852
+ const pm = state.sandboxOrchestrator.getProxyManager();
17853
+ if (pm?.isRunning()) {
17854
+ console.log(`\u{1F310} Network proxy started on port ${pm.getPort()}`);
17855
+ }
17856
+ }
17857
+ const config = await state.configStore.get();
17858
+ await state.configStore.save({
17859
+ ...config,
17860
+ sandbox: { ...state.sandboxOrchestrator.getConfig() }
17861
+ });
17862
+ console.log("Sandbox enabled (auto-allow mode)");
17863
+ break;
17864
+ }
17865
+ case "sandbox:disable": {
17866
+ if (!state.sandboxOrchestrator) {
17867
+ console.log("Sandbox not initialized");
17868
+ break;
17869
+ }
17870
+ await state.sandboxOrchestrator.stopProxy();
17871
+ state.sandboxOrchestrator.setMode("disabled");
17872
+ state.permissionManager?.setSandboxState("disabled", false);
17873
+ const disableConfig = await state.configStore.get();
17874
+ await state.configStore.save({
17875
+ ...disableConfig,
17876
+ sandbox: { ...state.sandboxOrchestrator.getConfig() }
17877
+ });
17878
+ console.log("Sandbox disabled");
17879
+ break;
17880
+ }
17881
+ case "sandbox:mode": {
17882
+ if (!state.sandboxOrchestrator) {
17883
+ console.log("Sandbox not initialized");
17884
+ break;
17885
+ }
17886
+ const modeArg = args[0];
17887
+ if (modeArg !== "auto-allow" && modeArg !== "permissions") {
17888
+ console.log("Usage: /sandbox:mode <auto-allow|permissions>");
17889
+ break;
17890
+ }
17891
+ if (!state.sandboxOrchestrator.isAvailable()) {
17892
+ console.log("Sandbox runtime not available on this platform");
17893
+ break;
17894
+ }
17895
+ state.sandboxOrchestrator.setMode(modeArg);
17896
+ state.permissionManager?.setSandboxState(modeArg, state.sandboxOrchestrator.isActive());
17897
+ const modeConfig = await state.configStore.get();
17898
+ await state.configStore.save({
17899
+ ...modeConfig,
17900
+ sandbox: { ...state.sandboxOrchestrator.getConfig() }
17901
+ });
17902
+ console.log(`Sandbox mode set to: ${modeArg}`);
17903
+ break;
17904
+ }
17905
+ case "sandbox:trust-domain": {
17906
+ if (!state.sandboxOrchestrator) {
17907
+ console.log("Sandbox not initialized");
17908
+ break;
17909
+ }
17910
+ if (args.length === 0) {
17911
+ console.log("Usage: /sandbox:trust-domain <domain> [...]");
17912
+ break;
17913
+ }
17914
+ const proxyMgr = state.sandboxOrchestrator.getProxyManager();
17915
+ if (!proxyMgr) {
17916
+ console.log("Network proxy not initialized");
17917
+ break;
17918
+ }
17919
+ for (const domain of args) {
17920
+ proxyMgr.addAllowedDomain(domain);
17921
+ console.log(` Added: ${domain}`);
17922
+ }
17923
+ const trustDomainConfig = await state.configStore.get();
17924
+ const currentSandboxConfig = state.sandboxOrchestrator.getConfig();
17925
+ await state.configStore.save({
17926
+ ...trustDomainConfig,
17927
+ sandbox: {
17928
+ ...currentSandboxConfig,
17929
+ network: {
17930
+ ...currentSandboxConfig.network,
17931
+ allowedDomains: proxyMgr.getAllowedDomains()
17932
+ }
17933
+ }
17934
+ });
17935
+ console.log(`Trusted ${args.length} domain(s)`);
17936
+ break;
17937
+ }
17938
+ case "sandbox:domains": {
17939
+ if (!state.sandboxOrchestrator) {
17940
+ console.log("Sandbox not initialized");
17941
+ break;
17942
+ }
17943
+ const domainProxyMgr = state.sandboxOrchestrator.getProxyManager();
17944
+ if (!domainProxyMgr) {
17945
+ console.log("Network proxy not initialized");
17946
+ break;
17947
+ }
17948
+ const domains = domainProxyMgr.getAllowedDomains().sort();
17949
+ console.log(`
17950
+ Allowed domains (${domains.length}):`);
17951
+ for (const d of domains) {
17952
+ console.log(` ${d}`);
17953
+ }
17954
+ console.log("");
17955
+ break;
17956
+ }
17957
+ case "sandbox:violations": {
17958
+ const vStore = state.sandboxOrchestrator?.getViolationStore();
17959
+ if (!vStore) {
17960
+ console.log("Violation tracking not initialized");
17961
+ break;
17962
+ }
17963
+ const requestedCount = parseInt(args[0], 10);
17964
+ const violations = await vStore.getRecent(Number.isNaN(requestedCount) ? 20 : requestedCount);
17965
+ if (violations.length === 0) {
17966
+ console.log("\nNo sandbox violations recorded.\n");
17967
+ break;
17968
+ }
17969
+ console.log(`
17970
+ \x1B[1mRecent Sandbox Violations\x1B[0m (${violations.length}):
17971
+ `);
17972
+ for (const v of violations) {
17973
+ const time = new Date(v.timestamp).toLocaleTimeString();
17974
+ const typeColor = v.type === "filesystem" ? "\x1B[33m" : "\x1B[36m";
17975
+ const typeLabel = v.type === "filesystem" ? "FS " : "NET";
17976
+ const target = v.path ?? v.domain ?? "";
17977
+ console.log(` ${typeColor}${typeLabel}\x1B[0m ${time} \x1B[90m${v.blockedBy}\x1B[0m ${target}`);
17978
+ if (v.detail) {
17979
+ console.log(` \x1B[90m${v.detail.slice(0, 120)}\x1B[0m`);
17980
+ }
17981
+ }
17982
+ console.log("");
17983
+ break;
17984
+ }
17985
+ case "sandbox:violations:clear": {
17986
+ const clearStore = state.sandboxOrchestrator?.getViolationStore();
17987
+ if (!clearStore) {
17988
+ console.log("Violation tracking not initialized");
17989
+ break;
17990
+ }
17991
+ await clearStore.clear();
17992
+ state.sandboxOrchestrator?.resetStats();
17993
+ console.log("Violation log cleared.");
17994
+ break;
17995
+ }
17871
17996
  default:
17872
17997
  console.log(`Unknown command: /${command}`);
17873
17998
  console.log("Type /help for available commands");
@@ -17892,9 +18017,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
17892
18017
  return { ...prev, config: updatedConfig };
17893
18018
  });
17894
18019
  if (modelChanged && state.agent) {
17895
- const backend = state.agent.context.llm;
17896
- if (backend) {
17897
- backend.currentModel = updatedConfig.defaultModel;
18020
+ const llm = state.agent.context.llm;
18021
+ if (llm) {
18022
+ llm.currentModel = updatedConfig.defaultModel;
17898
18023
  }
17899
18024
  }
17900
18025
  };
@@ -18018,6 +18143,7 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
18018
18143
  if (isDevMode) {
18019
18144
  logger.debug("\u{1F527} Running in development mode (using TypeScript source)\n");
18020
18145
  }
18146
+ warmFileCache();
18021
18147
  render(/* @__PURE__ */ React21.createElement(CliApp, null), {
18022
18148
  exitOnCtrlC: false
18023
18149
  });