@bike4mind/cli 0.2.8-feat-cli-session-resume.17148 → 0.2.10-cli-fix-model-change.17240

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,9 +4,9 @@ import {
4
4
  getEffectiveApiKey,
5
5
  getOpenWeatherKey,
6
6
  getSerperKey
7
- } from "./chunk-3CCBDIFR.js";
8
- import "./chunk-LIRY24M5.js";
9
- import "./chunk-YXHQN7SK.js";
7
+ } from "./chunk-GO75FMLY.js";
8
+ import "./chunk-VZU4Z7WI.js";
9
+ import "./chunk-AQBZVAYO.js";
10
10
  import {
11
11
  BFLImageService,
12
12
  BaseStorage,
@@ -15,7 +15,7 @@ import {
15
15
  OpenAIBackend,
16
16
  OpenAIImageService,
17
17
  XAIImageService
18
- } from "./chunk-DNSBC3LO.js";
18
+ } from "./chunk-GCVIRGIN.js";
19
19
  import {
20
20
  Logger
21
21
  } from "./chunk-AMDXHL6S.js";
@@ -73,19 +73,20 @@ import {
73
73
  XAI_IMAGE_MODELS,
74
74
  b4mLLMTools,
75
75
  getMcpProviderMetadata
76
- } from "./chunk-H6SHASMZ.js";
76
+ } from "./chunk-DJPXSSP4.js";
77
77
  import {
78
78
  __require
79
79
  } from "./chunk-PDX44BCA.js";
80
80
 
81
81
  // src/index.tsx
82
- import React16, { useState as useState7, useEffect as useEffect3, useCallback, useRef } from "react";
82
+ import React16, { useState as useState8, useEffect as useEffect3, useCallback, useRef } from "react";
83
83
  import { render, Box as Box15, Text as Text15, useApp, useInput as useInput6 } from "ink";
84
+ import { execSync } from "child_process";
84
85
  import { v4 as uuidv49 } from "uuid";
85
86
 
86
87
  // src/components/App.tsx
87
- import React10 from "react";
88
- import { Box as Box9, Text as Text9, Static } from "ink";
88
+ import React10, { useState as useState4 } from "react";
89
+ import { Box as Box9, Text as Text9 } from "ink";
89
90
 
90
91
  // src/components/StatusBar.tsx
91
92
  import React from "react";
@@ -732,16 +733,19 @@ function findAtTrigger(value) {
732
733
  }
733
734
  function InputPrompt({
734
735
  onSubmit,
736
+ onBashCommand,
735
737
  onImageDetected,
736
738
  disabled = false,
737
739
  history = [],
738
740
  prefillInput,
739
- onPrefillConsumed
741
+ onPrefillConsumed,
742
+ onBashModeChange
740
743
  }) {
741
744
  const [value, setValue] = useState2("");
742
745
  const [selectedIndex, setSelectedIndex] = useState2(0);
743
746
  const [historyIndex, setHistoryIndex] = useState2(-1);
744
747
  const [tempInput, setTempInput] = useState2("");
748
+ const [inputKey, setInputKey] = useState2(0);
745
749
  useEffect(() => {
746
750
  if (prefillInput) {
747
751
  setValue(prefillInput);
@@ -750,6 +754,10 @@ function InputPrompt({
750
754
  }, [prefillInput, onPrefillConsumed]);
751
755
  const [fileAutocomplete, setFileAutocomplete] = useState2(null);
752
756
  const [fileSelectedIndex, setFileSelectedIndex] = useState2(0);
757
+ const isBashMode = value.startsWith("!");
758
+ useEffect(() => {
759
+ onBashModeChange?.(isBashMode);
760
+ }, [isBashMode, onBashModeChange]);
753
761
  const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active;
754
762
  const commandQuery = shouldShowCommandAutocomplete ? value.slice(1) : "";
755
763
  const filteredCommands = useMemo(() => {
@@ -856,6 +864,16 @@ function InputPrompt({
856
864
  onSubmit(commandText);
857
865
  return;
858
866
  }
867
+ if (isBashMode && onBashCommand) {
868
+ const command = input.slice(1).trim();
869
+ if (command) {
870
+ onBashCommand(command);
871
+ setValue("");
872
+ setHistoryIndex(-1);
873
+ setFileAutocomplete(null);
874
+ }
875
+ return;
876
+ }
859
877
  onSubmit(input);
860
878
  setValue("");
861
879
  setHistoryIndex(-1);
@@ -886,13 +904,19 @@ function InputPrompt({
886
904
  setFileAutocomplete(null);
887
905
  }
888
906
  };
889
- return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "cyan" }, "> "), /* @__PURE__ */ React5.createElement(
907
+ const getPlaceholder = () => {
908
+ if (disabled) return "Thinking...";
909
+ if (isBashMode) return "Enter shell command to execute...";
910
+ return "Type your message, /help for commands, @file to reference, or ! for bash";
911
+ };
912
+ return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: isBashMode ? "yellow" : "cyan" }, isBashMode ? "$ " : "> "), /* @__PURE__ */ React5.createElement(
890
913
  CustomTextInput,
891
914
  {
915
+ key: inputKey,
892
916
  value,
893
917
  onChange: handleChange,
894
918
  onSubmit: handleSubmit,
895
- placeholder: disabled ? "Thinking..." : "Type your message, /help for commands, or @file to reference",
919
+ placeholder: getPlaceholder(),
896
920
  showCursor: !disabled
897
921
  }
898
922
  )), shouldShowCommandAutocomplete && /* @__PURE__ */ React5.createElement(CommandAutocomplete, { commands: filteredCommands, selectedIndex }), fileAutocomplete?.active && /* @__PURE__ */ React5.createElement(FileAutocomplete, { files: filteredFiles, selectedIndex: fileSelectedIndex, query: fileAutocomplete.query }));
@@ -916,6 +940,16 @@ var useCliStore = create((set) => ({
916
940
  // Session state
917
941
  session: null,
918
942
  setSession: (session) => set({ session }),
943
+ addMessage: (message) => set((state) => {
944
+ if (!state.session) return state;
945
+ return {
946
+ session: {
947
+ ...state.session,
948
+ messages: [...state.session.messages, message],
949
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
950
+ }
951
+ };
952
+ }),
919
953
  // UI state
920
954
  isThinking: false,
921
955
  setIsThinking: (thinking) => set({ isThinking: thinking }),
@@ -923,6 +957,7 @@ var useCliStore = create((set) => ({
923
957
  addAgentStep: (step) => set((state) => ({
924
958
  agentSteps: [...state.agentSteps, step]
925
959
  })),
960
+ setAgentSteps: (steps) => set({ agentSteps: steps }),
926
961
  clearAgentSteps: () => set({ agentSteps: [] }),
927
962
  // Permission prompt
928
963
  permissionPrompt: null,
@@ -942,7 +977,7 @@ var AgentThinking = React7.memo(function AgentThinking2() {
942
977
  if (!isThinking) {
943
978
  return null;
944
979
  }
945
- return /* @__PURE__ */ React7.createElement(Box6, { borderStyle: "single", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React7.createElement(ThoughtStream, { steps: agentSteps }));
980
+ return /* @__PURE__ */ React7.createElement(Box6, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React7.createElement(ThoughtStream, { steps: agentSteps }));
946
981
  });
947
982
 
948
983
  // src/components/PermissionPrompt.tsx
@@ -1332,6 +1367,7 @@ function hasFileReferences(message) {
1332
1367
  function App({
1333
1368
  onMessage,
1334
1369
  onCommand,
1370
+ onBashCommand,
1335
1371
  onPermissionResponse,
1336
1372
  onImageDetected,
1337
1373
  commandHistory = [],
@@ -1351,6 +1387,7 @@ function App({
1351
1387
  const setShowConfigEditor = useCliStore((state) => state.setShowConfigEditor);
1352
1388
  const exitRequested = useCliStore((state) => state.exitRequested);
1353
1389
  const setIsThinking = useCliStore((state) => state.setIsThinking);
1390
+ const [isBashMode, setIsBashMode] = useState4(false);
1354
1391
  const handleSubmit = React10.useCallback(
1355
1392
  async (input) => {
1356
1393
  const trimmed = input.trim();
@@ -1397,20 +1434,23 @@ ${errorBlock}`;
1397
1434
  canBeTrusted: permissionPrompt.canBeTrusted,
1398
1435
  onResponse: onPermissionResponse
1399
1436
  }
1400
- )) : /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Static, { items: messages }, (message, index) => /* @__PURE__ */ React10.createElement(Box9, { key: index, flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React10.createElement(MessageItem, { message }))), /* @__PURE__ */ React10.createElement(AgentThinking, null), exitRequested && /* @__PURE__ */ React10.createElement(Box9, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow", bold: true }, "Press Ctrl+C again to exit")), /* @__PURE__ */ React10.createElement(Box9, { borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React10.createElement(
1437
+ )) : /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column" }, messages.map((message, index) => /* @__PURE__ */ React10.createElement(Box9, { key: `${message.timestamp}-${message.role}-${index}`, flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React10.createElement(MessageItem, { message })))), /* @__PURE__ */ React10.createElement(AgentThinking, null), exitRequested && /* @__PURE__ */ React10.createElement(Box9, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow", bold: true }, "Press Ctrl+C again to exit")), /* @__PURE__ */ React10.createElement(Box9, { borderStyle: "single", borderColor: isBashMode ? "yellow" : "cyan", paddingX: 1 }, /* @__PURE__ */ React10.createElement(
1401
1438
  InputPrompt,
1402
1439
  {
1403
1440
  onSubmit: handleSubmit,
1441
+ onBashCommand,
1404
1442
  onImageDetected,
1405
1443
  disabled: isThinking,
1406
1444
  history: commandHistory,
1407
1445
  prefillInput,
1408
- onPrefillConsumed
1446
+ onPrefillConsumed,
1447
+ onBashModeChange: setIsBashMode
1409
1448
  }
1410
1449
  ))));
1411
1450
  }
1412
1451
  function MessageItem({ message }) {
1413
- return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: message.role === "user" ? "cyan" : "green" }, message.role === "user" ? "\u{1F464} You" : "\u{1F916} Assistant"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React10.createElement(Text9, null, message.content)), message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true, bold: true }, "\u{1F527} Agent Reasoning Trace (", message.metadata.steps.filter((s) => s.type === "action").length, " ", "tools used, ", message.metadata.steps.length, " total steps)", message.metadata.tokenUsage && ` \u2022 ${message.metadata.tokenUsage.total} tokens`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
1452
+ const isUser = message.role === "user";
1453
+ return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: isUser ? "cyan" : "green" }, isUser ? "\u{1F464} You" : "\u{1F916} Assistant"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), isUser && message.content && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { backgroundColor: "whiteBright", color: "black" }, " ", message.content, " ")), !isUser && message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true, bold: true }, "\u{1F527} Agent Reasoning Trace (", message.metadata.steps.filter((s) => s.type === "action").length, " ", "tools used, ", message.metadata.steps.length, " total steps)", message.metadata.tokenUsage && ` \u2022 ${message.metadata.tokenUsage.total} tokens`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
1414
1454
  if (step.type === "thought") {
1415
1455
  return /* @__PURE__ */ React10.createElement(Box9, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` ${step.content.slice(0, 200)}${step.content.length > 200 ? "..." : ""}`));
1416
1456
  }
@@ -1422,7 +1462,7 @@ function MessageItem({ message }) {
1422
1462
  return /* @__PURE__ */ React10.createElement(Box9, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u{1F527} Action: ", toolName), toolInput && /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` Input: ${typeof toolInput === "string" ? toolInput : JSON.stringify(toolInput).slice(0, 100)}`), result && /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` Result: ${typeof result === "string" ? result.slice(0, 200) : JSON.stringify(result).slice(0, 200)}${(typeof result === "string" ? result.length : JSON.stringify(result).length) > 200 ? "..." : ""}`));
1423
1463
  }
1424
1464
  return null;
1425
- }).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)));
1465
+ }).filter(Boolean)), !isUser && message.content !== "..." && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, marginTop: message.metadata?.steps?.length ? 1 : 0 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React10.createElement(Text9, null, message.content)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)));
1426
1466
  }
1427
1467
 
1428
1468
  // src/components/MessageList.tsx
@@ -1498,12 +1538,12 @@ function TrustLocationSelector({ inProject, onSelect, onCancel }) {
1498
1538
  }
1499
1539
 
1500
1540
  // src/components/RewindSelector.tsx
1501
- import React13, { useState as useState4 } from "react";
1541
+ import React13, { useState as useState5 } from "react";
1502
1542
  import { Box as Box12, Text as Text12, useInput as useInput4 } from "ink";
1503
1543
  import SelectInput3 from "ink-select-input";
1504
1544
  function RewindSelector({ messages, onSelect, onCancel }) {
1505
- const [step, setStep] = useState4("selection");
1506
- const [selectedMessageIndex, setSelectedMessageIndex] = useState4(null);
1545
+ const [step, setStep] = useState5("selection");
1546
+ const [selectedMessageIndex, setSelectedMessageIndex] = useState5(null);
1507
1547
  useInput4((input, key) => {
1508
1548
  if (key.escape) {
1509
1549
  if (step === "confirmation") {
@@ -1567,12 +1607,12 @@ function RewindSelector({ messages, onSelect, onCancel }) {
1567
1607
  }
1568
1608
 
1569
1609
  // src/components/SessionSelector.tsx
1570
- import React14, { useState as useState5 } from "react";
1610
+ import React14, { useState as useState6 } from "react";
1571
1611
  import { Box as Box13, Text as Text13, useInput as useInput5 } from "ink";
1572
1612
  import SelectInput4 from "ink-select-input";
1573
1613
  function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
1574
- const [step, setStep] = useState5("selection");
1575
- const [selectedSession, setSelectedSession] = useState5(null);
1614
+ const [step, setStep] = useState6("selection");
1615
+ const [selectedSession, setSelectedSession] = useState6(null);
1576
1616
  useInput5((input, key) => {
1577
1617
  if (key.escape) {
1578
1618
  if (step === "confirmation") {
@@ -1630,7 +1670,7 @@ function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
1630
1670
  }
1631
1671
 
1632
1672
  // src/components/LoginFlow.tsx
1633
- import React15, { useState as useState6, useEffect as useEffect2 } from "react";
1673
+ import React15, { useState as useState7, useEffect as useEffect2 } from "react";
1634
1674
  import { Box as Box14, Text as Text14 } from "ink";
1635
1675
  import Spinner2 from "ink-spinner";
1636
1676
  import jwt from "jsonwebtoken";
@@ -1747,10 +1787,10 @@ var OAuthClient = class {
1747
1787
 
1748
1788
  // src/components/LoginFlow.tsx
1749
1789
  function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
1750
- const [status, setStatus] = useState6("initiating");
1751
- const [deviceFlow, setDeviceFlow] = useState6(null);
1752
- const [statusMessage, setStatusMessage] = useState6("Initiating device authorization...");
1753
- const [error, setError] = useState6(null);
1790
+ const [status, setStatus] = useState7("initiating");
1791
+ const [deviceFlow, setDeviceFlow] = useState7(null);
1792
+ const [statusMessage, setStatusMessage] = useState7("Initiating device authorization...");
1793
+ const [error, setError] = useState7(null);
1754
1794
  useEffect2(() => {
1755
1795
  const runLoginFlow = async () => {
1756
1796
  const oauth = new OAuthClient(apiUrl);
@@ -2688,10 +2728,9 @@ ${options.context}` : this.getSystemPrompt()
2688
2728
  content: query
2689
2729
  }
2690
2730
  ];
2691
- console.log("[DEBUG ReActAgent] Messages array being sent to LLM:");
2692
2731
  messages.forEach((msg, i) => {
2693
2732
  const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
2694
- console.log(` [${i}] ${msg.role}: ${content.substring(0, 150)}...`);
2733
+ this.context.logger.debug(` [${i}] ${msg.role}: ${content.substring(0, 150)}...`);
2695
2734
  });
2696
2735
  let finalAnswer = "";
2697
2736
  let reachedMaxIterations = false;
@@ -5084,8 +5123,8 @@ async function processAndStoreImages(images, context) {
5084
5123
  const buffer = await downloadImage(image);
5085
5124
  const fileType = await fileTypeFromBuffer2(buffer);
5086
5125
  const filename = `${uuidv45()}.${fileType?.ext}`;
5087
- const path13 = await context.imageGenerateStorage.upload(buffer, filename, {});
5088
- return path13;
5126
+ const path15 = await context.imageGenerateStorage.upload(buffer, filename, {});
5127
+ return path15;
5089
5128
  }));
5090
5129
  }
5091
5130
  async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
@@ -6297,8 +6336,8 @@ async function processAndStoreImage(imageUrl, context) {
6297
6336
  const buffer = await downloadImage2(imageUrl);
6298
6337
  const fileType = await fileTypeFromBuffer3(buffer);
6299
6338
  const filename = `${uuidv46()}.${fileType?.ext}`;
6300
- const path13 = await context.imageGenerateStorage.upload(buffer, filename, {});
6301
- return path13;
6339
+ const path15 = await context.imageGenerateStorage.upload(buffer, filename, {});
6340
+ return path15;
6302
6341
  }
6303
6342
  async function updateQuestAndReturnMarkdown2(storedImagePath, context) {
6304
6343
  await context.onFinish?.("edit_image", storedImagePath);
@@ -8710,6 +8749,381 @@ var deleteFileTool = {
8710
8749
  })
8711
8750
  };
8712
8751
 
8752
+ // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/bashExecute/index.js
8753
+ import { spawn } from "child_process";
8754
+ import path11 from "path";
8755
+ var DEFAULT_TIMEOUT_MS = 6e4;
8756
+ var MAX_OUTPUT_SIZE = 100 * 1024;
8757
+ var DANGEROUS_PATTERNS = [
8758
+ // Destructive file operations
8759
+ {
8760
+ pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+|.*\s+-[a-zA-Z]*r).*\//i,
8761
+ reason: "Recursive delete with path",
8762
+ block: true
8763
+ },
8764
+ {
8765
+ pattern: /\brm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|.*\s+-[a-zA-Z]*f).*--no-preserve-root/i,
8766
+ reason: "Force delete without preserve root",
8767
+ block: true
8768
+ },
8769
+ {
8770
+ pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+\//i,
8771
+ reason: "Recursive force delete on root paths",
8772
+ block: true
8773
+ },
8774
+ {
8775
+ pattern: /\brm\s+-[a-zA-Z]*f[a-zA-Z]*r[a-zA-Z]*\s+\//i,
8776
+ reason: "Force recursive delete on root paths",
8777
+ block: true
8778
+ },
8779
+ // System-level dangerous commands
8780
+ { pattern: /\bsudo\b/i, reason: "Elevated privileges (sudo)", block: true },
8781
+ { pattern: /\bsu\s+(-|root)/i, reason: "Switch to root user", block: true },
8782
+ { pattern: /\bchmod\s+777\b/i, reason: "Overly permissive chmod", block: false },
8783
+ { pattern: /\bchown\s+-R\s+root/i, reason: "Recursive chown to root", block: true },
8784
+ // Disk/partition operations
8785
+ { pattern: /\bmkfs\b/i, reason: "Filesystem creation", block: true },
8786
+ { pattern: /\bfdisk\b/i, reason: "Disk partitioning", block: true },
8787
+ { pattern: /\bdd\s+.*of=\/dev\//i, reason: "Direct disk write", block: true },
8788
+ // Network attacks
8789
+ { pattern: /\b(nc|netcat)\s+.*-e\s+\/bin\/(ba)?sh/i, reason: "Reverse shell attempt", block: true },
8790
+ { pattern: /\bcurl\s+.*\|\s*(ba)?sh/i, reason: "Piping remote script to shell", block: true },
8791
+ { pattern: /\bwget\s+.*\|\s*(ba)?sh/i, reason: "Piping remote script to shell", block: true },
8792
+ // Fork bombs and resource exhaustion
8793
+ { pattern: /:\(\)\s*\{\s*:\|:&\s*\}\s*;/i, reason: "Fork bomb", block: true },
8794
+ { pattern: /\bwhile\s+true.*do.*done.*&/i, reason: "Infinite loop in background", block: false },
8795
+ // Credential/sensitive data access
8796
+ { pattern: /\/etc\/shadow/i, reason: "Access to shadow file", block: true },
8797
+ { pattern: /\/etc\/passwd.*>/i, reason: "Modifying passwd file", block: true },
8798
+ { pattern: /\baws\s+.*--profile\s+/i, reason: "AWS profile access", block: false },
8799
+ // History/log manipulation
8800
+ { pattern: /\bhistory\s+-c\b/i, reason: "Clearing shell history", block: false },
8801
+ { pattern: />\s*\/var\/log\//i, reason: "Overwriting system logs", block: true },
8802
+ // Dangerous redirects
8803
+ { pattern: />\s*\/dev\/sda/i, reason: "Writing to block device", block: true },
8804
+ { pattern: />\s*\/dev\/null.*2>&1.*</i, reason: "Potentially hiding output", block: false },
8805
+ // Environment manipulation
8806
+ { pattern: /\bexport\s+PATH\s*=\s*[^$]/i, reason: "Overwriting PATH", block: false },
8807
+ { pattern: /\bexport\s+LD_PRELOAD/i, reason: "LD_PRELOAD manipulation", block: true },
8808
+ // Process/system control
8809
+ { pattern: /\bkill\s+-9\s+(-1|1)\b/i, reason: "Killing all processes", block: true },
8810
+ { pattern: /\bkillall\s+-9\b/i, reason: "Force killing processes", block: false },
8811
+ { pattern: /\bshutdown\b/i, reason: "System shutdown", block: true },
8812
+ { pattern: /\breboot\b/i, reason: "System reboot", block: true },
8813
+ { pattern: /\binit\s+[06]\b/i, reason: "System runlevel change", block: true }
8814
+ ];
8815
+ var SAFE_COMMAND_PREFIXES = [
8816
+ "ls",
8817
+ "cat",
8818
+ "head",
8819
+ "tail",
8820
+ "grep",
8821
+ "find",
8822
+ "echo",
8823
+ "pwd",
8824
+ "whoami",
8825
+ "date",
8826
+ "cal",
8827
+ "wc",
8828
+ "sort",
8829
+ "uniq",
8830
+ "cut",
8831
+ "tr",
8832
+ "sed",
8833
+ "awk",
8834
+ "git",
8835
+ "npm",
8836
+ "pnpm",
8837
+ "yarn",
8838
+ "node",
8839
+ "npx",
8840
+ "tsx",
8841
+ "ts-node",
8842
+ "python",
8843
+ "python3",
8844
+ "pip",
8845
+ "pip3",
8846
+ "docker",
8847
+ "docker-compose",
8848
+ "curl",
8849
+ "wget",
8850
+ // These are safe for fetching, blocked when piped to shell
8851
+ "mkdir",
8852
+ "touch",
8853
+ "cp",
8854
+ "mv",
8855
+ // File operations (still require permission)
8856
+ "which",
8857
+ "whereis",
8858
+ "type",
8859
+ "file",
8860
+ "stat",
8861
+ "env",
8862
+ "printenv",
8863
+ "set",
8864
+ "man",
8865
+ "help",
8866
+ "info",
8867
+ "diff",
8868
+ "comm",
8869
+ "cmp",
8870
+ "tar",
8871
+ "zip",
8872
+ "unzip",
8873
+ "gzip",
8874
+ "gunzip",
8875
+ "ssh",
8876
+ "scp",
8877
+ "rsync",
8878
+ // Network tools
8879
+ "make",
8880
+ "cmake",
8881
+ "cargo",
8882
+ "go",
8883
+ "rustc",
8884
+ "gcc",
8885
+ "g++",
8886
+ // Build tools
8887
+ "jest",
8888
+ "vitest",
8889
+ "mocha",
8890
+ "pytest",
8891
+ // Test runners
8892
+ "eslint",
8893
+ "prettier",
8894
+ "tsc"
8895
+ // Linters/formatters
8896
+ ];
8897
+ function checkDangerousPatterns(command) {
8898
+ for (const { pattern, reason, block } of DANGEROUS_PATTERNS) {
8899
+ if (pattern.test(command)) {
8900
+ return { blocked: block, reason };
8901
+ }
8902
+ }
8903
+ return { blocked: false };
8904
+ }
8905
+ function getBaseCommand(command) {
8906
+ const trimmed = command.trim();
8907
+ const match = trimmed.match(/^(\S+)/);
8908
+ return match ? match[1] : "";
8909
+ }
8910
+ function isSafeCommandPrefix(command) {
8911
+ const baseCommand = getBaseCommand(command);
8912
+ return SAFE_COMMAND_PREFIXES.some((safe) => baseCommand === safe || baseCommand.endsWith(`/${safe}`));
8913
+ }
8914
+ async function executeBashCommand(params) {
8915
+ const { command, cwd: relativeCwd, timeout = DEFAULT_TIMEOUT_MS } = params;
8916
+ if (!command || command.trim().length === 0) {
8917
+ return {
8918
+ stdout: "",
8919
+ stderr: "Error: Command cannot be empty",
8920
+ exitCode: 1,
8921
+ timedOut: false,
8922
+ blocked: true,
8923
+ blockedReason: "Empty command"
8924
+ };
8925
+ }
8926
+ const dangerCheck = checkDangerousPatterns(command);
8927
+ if (dangerCheck.blocked) {
8928
+ return {
8929
+ stdout: "",
8930
+ stderr: `Command blocked for safety: ${dangerCheck.reason}`,
8931
+ exitCode: 1,
8932
+ timedOut: false,
8933
+ blocked: true,
8934
+ blockedReason: dangerCheck.reason
8935
+ };
8936
+ }
8937
+ const baseCwd = process.cwd();
8938
+ const targetCwd = relativeCwd ? path11.resolve(baseCwd, relativeCwd) : baseCwd;
8939
+ const effectiveTimeout = Math.min(timeout, 5 * 60 * 1e3);
8940
+ return new Promise((resolve3) => {
8941
+ let stdout = "";
8942
+ let stderr = "";
8943
+ let timedOut = false;
8944
+ const proc = spawn("bash", ["-c", command], {
8945
+ cwd: targetCwd,
8946
+ env: {
8947
+ ...process.env,
8948
+ // Prevent color codes that might be hard to read
8949
+ NO_COLOR: "1",
8950
+ FORCE_COLOR: "0"
8951
+ },
8952
+ stdio: ["ignore", "pipe", "pipe"]
8953
+ });
8954
+ const timeoutId = setTimeout(() => {
8955
+ timedOut = true;
8956
+ proc.kill("SIGTERM");
8957
+ setTimeout(() => {
8958
+ if (!proc.killed) {
8959
+ proc.kill("SIGKILL");
8960
+ }
8961
+ }, 5e3);
8962
+ }, effectiveTimeout);
8963
+ proc.stdout.on("data", (data) => {
8964
+ if (stdout.length < MAX_OUTPUT_SIZE) {
8965
+ stdout += data.toString();
8966
+ if (stdout.length > MAX_OUTPUT_SIZE) {
8967
+ stdout = stdout.slice(0, MAX_OUTPUT_SIZE) + "\n... [output truncated]";
8968
+ }
8969
+ }
8970
+ });
8971
+ proc.stderr.on("data", (data) => {
8972
+ if (stderr.length < MAX_OUTPUT_SIZE) {
8973
+ stderr += data.toString();
8974
+ if (stderr.length > MAX_OUTPUT_SIZE) {
8975
+ stderr = stderr.slice(0, MAX_OUTPUT_SIZE) + "\n... [output truncated]";
8976
+ }
8977
+ }
8978
+ });
8979
+ proc.on("close", (exitCode) => {
8980
+ clearTimeout(timeoutId);
8981
+ resolve3({
8982
+ stdout: stdout.trim(),
8983
+ stderr: stderr.trim(),
8984
+ exitCode,
8985
+ timedOut,
8986
+ blocked: false
8987
+ });
8988
+ });
8989
+ proc.on("error", (error) => {
8990
+ clearTimeout(timeoutId);
8991
+ resolve3({
8992
+ stdout: "",
8993
+ stderr: `Failed to execute command: ${error.message}`,
8994
+ exitCode: 1,
8995
+ timedOut: false,
8996
+ blocked: false
8997
+ });
8998
+ });
8999
+ });
9000
+ }
9001
+ function formatResult(result, command) {
9002
+ const parts = [];
9003
+ parts.push(`$ ${command}`);
9004
+ parts.push("");
9005
+ if (result.blocked) {
9006
+ parts.push(`BLOCKED: ${result.blockedReason}`);
9007
+ parts.push("");
9008
+ parts.push("This command was blocked for safety reasons.");
9009
+ parts.push("If you believe this is a false positive, please run the command manually.");
9010
+ return parts.join("\n");
9011
+ }
9012
+ if (result.timedOut) {
9013
+ parts.push("WARNING: Command timed out and was terminated.");
9014
+ parts.push("");
9015
+ }
9016
+ if (result.stdout) {
9017
+ parts.push(result.stdout);
9018
+ }
9019
+ if (result.stderr) {
9020
+ if (result.stdout) {
9021
+ parts.push("");
9022
+ }
9023
+ parts.push("STDERR:");
9024
+ parts.push(result.stderr);
9025
+ }
9026
+ if (result.exitCode !== 0 && result.exitCode !== null) {
9027
+ parts.push("");
9028
+ parts.push(`Exit code: ${result.exitCode}`);
9029
+ }
9030
+ if (!result.stdout && !result.stderr && !result.timedOut) {
9031
+ parts.push("(command completed with no output)");
9032
+ }
9033
+ return parts.join("\n");
9034
+ }
9035
+ var bashExecuteTool = {
9036
+ name: "bash_execute",
9037
+ implementation: (context) => ({
9038
+ toolFn: async (value) => {
9039
+ const params = value;
9040
+ const isSafe = isSafeCommandPrefix(params.command);
9041
+ context.logger.info("Bash: Executing command", {
9042
+ command: params.command,
9043
+ cwd: params.cwd || ".",
9044
+ timeout: params.timeout || DEFAULT_TIMEOUT_MS,
9045
+ isSafeCommand: isSafe
9046
+ });
9047
+ if (context.onStart) {
9048
+ await context.onStart("bash_execute", {
9049
+ command: params.command,
9050
+ cwd: params.cwd
9051
+ });
9052
+ }
9053
+ try {
9054
+ const result = await executeBashCommand(params);
9055
+ const formattedResult = formatResult(result, params.command);
9056
+ context.logger.info("Bash: Command completed", {
9057
+ exitCode: result.exitCode,
9058
+ timedOut: result.timedOut,
9059
+ blocked: result.blocked
9060
+ });
9061
+ if (context.onFinish) {
9062
+ await context.onFinish("bash_execute", {
9063
+ command: params.command,
9064
+ exitCode: result.exitCode,
9065
+ blocked: result.blocked
9066
+ });
9067
+ }
9068
+ return formattedResult;
9069
+ } catch (error) {
9070
+ context.logger.error("Bash: Command failed", error);
9071
+ if (context.onFinish) {
9072
+ await context.onFinish("bash_execute", {
9073
+ command: params.command,
9074
+ error: error instanceof Error ? error.message : "Unknown error"
9075
+ });
9076
+ }
9077
+ throw error;
9078
+ }
9079
+ },
9080
+ toolSchema: {
9081
+ name: "bash_execute",
9082
+ description: `Execute a bash command in the terminal. Use this for running shell commands, scripts, build tools, git operations, and other CLI tasks.
9083
+
9084
+ SAFETY NOTES:
9085
+ - Commands are executed in the current working directory (or specified cwd)
9086
+ - Dangerous commands (sudo, rm -rf /, etc.) are automatically blocked
9087
+ - Commands have a default timeout of 60 seconds (max 5 minutes)
9088
+ - Output is limited to prevent overwhelming responses
9089
+ - This tool ALWAYS requires user permission before execution
9090
+
9091
+ COMMON USE CASES:
9092
+ - Running build commands: npm run build, make, cargo build
9093
+ - Git operations: git status, git log, git diff
9094
+ - Viewing system info: ls, pwd, cat, head, tail
9095
+ - Running tests: npm test, pytest, cargo test
9096
+ - Package management: npm install, pip install
9097
+ - File operations: mkdir, cp, mv (with permission)
9098
+
9099
+ BLOCKED OPERATIONS:
9100
+ - sudo and privilege escalation
9101
+ - Recursive deletes on system paths
9102
+ - Direct disk operations
9103
+ - Fork bombs and resource exhaustion
9104
+ - Piping remote scripts to shell`,
9105
+ parameters: {
9106
+ type: "object",
9107
+ properties: {
9108
+ command: {
9109
+ type: "string",
9110
+ description: "The bash command to execute. Can include pipes, redirects, and chained commands."
9111
+ },
9112
+ cwd: {
9113
+ type: "string",
9114
+ description: "Working directory for the command (optional). Can be relative or absolute path. Defaults to current directory."
9115
+ },
9116
+ timeout: {
9117
+ type: "number",
9118
+ description: "Timeout in milliseconds (optional, default: 60000, max: 300000). Command will be terminated if it exceeds this time."
9119
+ }
9120
+ },
9121
+ required: ["command"]
9122
+ }
9123
+ }
9124
+ })
9125
+ };
9126
+
8713
9127
  // ../../b4m-core/packages/services/dist/src/llm/tools/index.js
8714
9128
  var tools = {
8715
9129
  dice_roll: diceRollTool,
@@ -8737,7 +9151,9 @@ var tools = {
8737
9151
  create_file: createFileTool,
8738
9152
  glob_files: globFilesTool,
8739
9153
  grep_search: grepSearchTool,
8740
- delete_file: deleteFileTool
9154
+ delete_file: deleteFileTool,
9155
+ // Shell execution
9156
+ bash_execute: bashExecuteTool
8741
9157
  };
8742
9158
  var generateTools = (userId, user, logger2, { db }, storage, imageGenerateStorage, statusUpdate, onStart, onFinish, llm, config, model) => {
8743
9159
  const context = {
@@ -9012,7 +9428,7 @@ async function generateFileDeletePreview(args) {
9012
9428
  if (!existsSync7(args.path)) {
9013
9429
  return `[File does not exist: ${args.path}]`;
9014
9430
  }
9015
- const stats = await import("fs/promises").then((fs11) => fs11.stat(args.path));
9431
+ const stats = await import("fs/promises").then((fs12) => fs12.stat(args.path));
9016
9432
  return `[File will be deleted]
9017
9433
 
9018
9434
  Path: ${args.path}
@@ -9025,7 +9441,7 @@ Last modified: ${stats.mtime.toLocaleString()}`;
9025
9441
 
9026
9442
  // src/utils/Logger.ts
9027
9443
  import fs10 from "fs/promises";
9028
- import path11 from "path";
9444
+ import path12 from "path";
9029
9445
  import os from "os";
9030
9446
  var Logger2 = class _Logger {
9031
9447
  constructor() {
@@ -9048,9 +9464,9 @@ var Logger2 = class _Logger {
9048
9464
  */
9049
9465
  async initialize(sessionId) {
9050
9466
  this.sessionId = sessionId;
9051
- const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
9467
+ const debugDir = path12.join(os.homedir(), ".bike4mind", "debug");
9052
9468
  await fs10.mkdir(debugDir, { recursive: true });
9053
- this.logFilePath = path11.join(debugDir, `${sessionId}.txt`);
9469
+ this.logFilePath = path12.join(debugDir, `${sessionId}.txt`);
9054
9470
  await this.writeToFile("INFO", "=== CLI SESSION START ===");
9055
9471
  }
9056
9472
  /**
@@ -9260,12 +9676,12 @@ var Logger2 = class _Logger {
9260
9676
  async cleanupOldLogs() {
9261
9677
  if (!this.fileLoggingEnabled) return;
9262
9678
  try {
9263
- const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
9679
+ const debugDir = path12.join(os.homedir(), ".bike4mind", "debug");
9264
9680
  const files = await fs10.readdir(debugDir);
9265
9681
  const now = Date.now();
9266
9682
  const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1e3;
9267
9683
  for (const file of files) {
9268
- const filePath = path11.join(debugDir, file);
9684
+ const filePath = path12.join(debugDir, file);
9269
9685
  const stats = await fs10.stat(filePath);
9270
9686
  if (stats.mtime.getTime() < thirtyDaysAgo) {
9271
9687
  await fs10.unlink(filePath);
@@ -9330,7 +9746,8 @@ var LOCAL_TOOLS = [
9330
9746
  "edit_file",
9331
9747
  "dice_roll",
9332
9748
  "math_evaluate",
9333
- "current_datetime"
9749
+ "current_datetime",
9750
+ "bash_execute"
9334
9751
  ];
9335
9752
  function isServerTool(toolName) {
9336
9753
  return SERVER_TOOLS.includes(toolName);
@@ -9360,21 +9777,21 @@ var NoOpStorage = class extends BaseStorage {
9360
9777
  async upload(input, destination, options) {
9361
9778
  return `/tmp/${destination}`;
9362
9779
  }
9363
- async download(path13) {
9780
+ async download(path15) {
9364
9781
  throw new Error("Download not supported in CLI");
9365
9782
  }
9366
- async delete(path13) {
9783
+ async delete(path15) {
9367
9784
  }
9368
- async getSignedUrl(path13) {
9369
- return `/tmp/${path13}`;
9785
+ async getSignedUrl(path15) {
9786
+ return `/tmp/${path15}`;
9370
9787
  }
9371
- getPublicUrl(path13) {
9372
- return `/tmp/${path13}`;
9788
+ getPublicUrl(path15) {
9789
+ return `/tmp/${path15}`;
9373
9790
  }
9374
- async getPreview(path13) {
9375
- return `/tmp/${path13}`;
9791
+ async getPreview(path15) {
9792
+ return `/tmp/${path15}`;
9376
9793
  }
9377
- async getMetadata(path13) {
9794
+ async getMetadata(path15) {
9378
9795
  return { size: 0, contentType: "application/octet-stream" };
9379
9796
  }
9380
9797
  };
@@ -9432,6 +9849,10 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
9432
9849
  } catch (error) {
9433
9850
  preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
9434
9851
  }
9852
+ } else if (toolName === "bash_execute" && args?.command) {
9853
+ const cwd = args.cwd ? ` (in ${args.cwd})` : "";
9854
+ const timeout = args.timeout ? ` [timeout: ${args.timeout}ms]` : "";
9855
+ preview = `$ ${args.command}${cwd}${timeout}`;
9435
9856
  }
9436
9857
  const response = await showPermissionPrompt(toolName, args, preview);
9437
9858
  if (response.action === "deny") {
@@ -9502,6 +9923,8 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
9502
9923
  glob_files: {},
9503
9924
  grep_search: {},
9504
9925
  delete_file: {},
9926
+ // Shell execution (always requires permission)
9927
+ bash_execute: {},
9505
9928
  // Server-side tools (executed via /api/ai/v1/tools Lambda)
9506
9929
  // Routed by ToolRouter to use B4M company API keys
9507
9930
  weather_info: {},
@@ -9567,6 +9990,7 @@ var DEFAULT_TOOL_CATEGORIES = {
9567
9990
  create_file: "prompt_always",
9568
9991
  delete_file: "prompt_always",
9569
9992
  shell_execute: "prompt_always",
9993
+ bash_execute: "prompt_always",
9570
9994
  git_commit: "prompt_always",
9571
9995
  git_push: "prompt_always",
9572
9996
  // ===== PROMPT DEFAULT (Repository read tools) =====
@@ -9728,10 +10152,122 @@ function getEnvironmentName(configApiConfig) {
9728
10152
  return "Bike4Mind";
9729
10153
  }
9730
10154
 
10155
+ // src/utils/contextLoader.ts
10156
+ import * as fs11 from "fs";
10157
+ import * as path13 from "path";
10158
+ import { homedir as homedir4 } from "os";
10159
+ var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
10160
+ var PROJECT_CONTEXT_FILES = [
10161
+ "CLAUDE.local.md",
10162
+ // gitignored, highest priority
10163
+ "CLAUDE.md",
10164
+ "AGENTS.md",
10165
+ // cross-tool standard
10166
+ "AI.local.md",
10167
+ // gitignored
10168
+ "AI.md",
10169
+ "INSTRUCTIONS.md"
10170
+ ];
10171
+ var GLOBAL_CONTEXT_FILES = ["AI.local.md", "AI.md"];
10172
+ function formatFileSize2(bytes) {
10173
+ if (bytes < 1024) return `${bytes}B`;
10174
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
10175
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
10176
+ }
10177
+ function tryReadContextFile(dir, filename, source) {
10178
+ const filePath = path13.join(dir, filename);
10179
+ try {
10180
+ const stats = fs11.lstatSync(filePath);
10181
+ if (stats.isDirectory()) {
10182
+ return null;
10183
+ }
10184
+ if (stats.isSymbolicLink()) {
10185
+ return {
10186
+ error: `${source === "global" ? "Global" : "Project"} ${filename} is a symlink (not allowed for security)`
10187
+ };
10188
+ }
10189
+ if (stats.size > CONTEXT_FILE_SIZE_LIMIT) {
10190
+ return {
10191
+ error: `${source === "global" ? "Global" : "Project"} ${filename} exceeds 100KB limit (${formatFileSize2(stats.size)})`
10192
+ };
10193
+ }
10194
+ const content = fs11.readFileSync(filePath, "utf-8");
10195
+ return {
10196
+ filename,
10197
+ content,
10198
+ source,
10199
+ path: filePath
10200
+ };
10201
+ } catch (err) {
10202
+ if (err.code === "ENOENT") {
10203
+ return null;
10204
+ }
10205
+ if (err.code === "EACCES") {
10206
+ return {
10207
+ error: `Cannot read ${source} ${filename}: permission denied`
10208
+ };
10209
+ }
10210
+ return {
10211
+ error: `Cannot read ${source} ${filename}: ${err instanceof Error ? err.message : "Unknown error"}`
10212
+ };
10213
+ }
10214
+ }
10215
+ function findContextFile(dir, candidates, source) {
10216
+ for (const filename of candidates) {
10217
+ const result = tryReadContextFile(dir, filename, source);
10218
+ if (result === null) {
10219
+ continue;
10220
+ }
10221
+ if ("error" in result) {
10222
+ return { result: null, error: result.error };
10223
+ }
10224
+ return { result, error: null };
10225
+ }
10226
+ return { result: null, error: null };
10227
+ }
10228
+ function mergeContextContent(global, project) {
10229
+ if (global && project) {
10230
+ return `${global.content}
10231
+
10232
+ ---
10233
+
10234
+ ${project.content}`;
10235
+ }
10236
+ if (global) {
10237
+ return global.content;
10238
+ }
10239
+ if (project) {
10240
+ return project.content;
10241
+ }
10242
+ return "";
10243
+ }
10244
+ async function loadContextFiles(projectDir) {
10245
+ const errors = [];
10246
+ const globalDir = path13.join(homedir4(), ".bike4mind");
10247
+ const projectDirectory = projectDir || process.cwd();
10248
+ const [globalResult, projectResult] = await Promise.all([
10249
+ Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
10250
+ Promise.resolve(findContextFile(projectDirectory, PROJECT_CONTEXT_FILES, "project"))
10251
+ ]);
10252
+ if (globalResult.error) {
10253
+ errors.push(globalResult.error);
10254
+ }
10255
+ if (projectResult.error) {
10256
+ errors.push(projectResult.error);
10257
+ }
10258
+ const mergedContent = mergeContextContent(globalResult.result, projectResult.result);
10259
+ return {
10260
+ globalContext: globalResult.result,
10261
+ projectContext: projectResult.result,
10262
+ mergedContent,
10263
+ errors
10264
+ };
10265
+ }
10266
+
9731
10267
  // ../../b4m-core/packages/mcp/dist/src/client.js
9732
10268
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
9733
10269
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
9734
- import path12 from "path";
10270
+ import path14 from "path";
9735
10271
  import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
9736
10272
  var MCPClient = class {
9737
10273
  // Note: This class handles MCP server communication with repository filtering
@@ -9772,16 +10308,16 @@ var MCPClient = class {
9772
10308
  const root = process.env.INIT_CWD || process.cwd();
9773
10309
  const candidatePaths = [
9774
10310
  // When running from SST Lambda with node_modules structure (copyFiles)
9775
- path12.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
10311
+ path14.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
9776
10312
  // When running from SST Lambda deployed environment (/var/task)
9777
- path12.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10313
+ path14.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
9778
10314
  // When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
9779
- path12.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10315
+ path14.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
9780
10316
  // When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
9781
- path12.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10317
+ path14.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
9782
10318
  // Original paths (backward compatibility)
9783
- path12.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
9784
- path12.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
10319
+ path14.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10320
+ path14.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
9785
10321
  ];
9786
10322
  const serverScriptPath = candidatePaths.find((p) => existsSync8(p));
9787
10323
  if (!serverScriptPath) {
@@ -10585,7 +11121,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
10585
11121
  // package.json
10586
11122
  var package_default = {
10587
11123
  name: "@bike4mind/cli",
10588
- version: "0.2.8-feat-cli-session-resume.17148+30840381b",
11124
+ version: "0.2.10-cli-fix-model-change.17240+efd37dd24",
10589
11125
  type: "module",
10590
11126
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
10591
11127
  license: "UNLICENSED",
@@ -10640,7 +11176,7 @@ var package_default = {
10640
11176
  "@google/generative-ai": "^0.21.0",
10641
11177
  "@joplin/turndown-plugin-gfm": "^1.0.62",
10642
11178
  "@mendable/firecrawl-js": "^1.29.3",
10643
- "@modelcontextprotocol/sdk": "^1.7.0",
11179
+ "@modelcontextprotocol/sdk": "1.20.1",
10644
11180
  "@octokit/rest": "^22.0.0",
10645
11181
  "@opensearch-project/opensearch": "2.11.0",
10646
11182
  "async-mutex": "^0.5.0",
@@ -10688,11 +11224,11 @@ var package_default = {
10688
11224
  },
10689
11225
  devDependencies: {
10690
11226
  "@bike4mind/agents": "0.1.0",
10691
- "@bike4mind/common": "2.38.2-feat-cli-session-resume.17148+30840381b",
10692
- "@bike4mind/mcp": "1.20.2-feat-cli-session-resume.17148+30840381b",
10693
- "@bike4mind/services": "2.33.2-feat-cli-session-resume.17148+30840381b",
10694
- "@bike4mind/utils": "2.1.3-feat-cli-session-resume.17148+30840381b",
10695
- "@types/better-sqlite3": "^7.6.12",
11227
+ "@bike4mind/common": "2.39.1-cli-fix-model-change.17240+efd37dd24",
11228
+ "@bike4mind/mcp": "1.20.4-cli-fix-model-change.17240+efd37dd24",
11229
+ "@bike4mind/services": "2.34.2-cli-fix-model-change.17240+efd37dd24",
11230
+ "@bike4mind/utils": "2.1.4-cli-fix-model-change.17240+efd37dd24",
11231
+ "@types/better-sqlite3": "^7.6.13",
10696
11232
  "@types/diff": "^5.0.9",
10697
11233
  "@types/jsonwebtoken": "^9.0.4",
10698
11234
  "@types/node": "^22.9.0",
@@ -10704,7 +11240,7 @@ var package_default = {
10704
11240
  typescript: "^5.9.3",
10705
11241
  vitest: "^3.2.4"
10706
11242
  },
10707
- gitHead: "30840381b429950872f0bb0c7eb7ea4408179cff"
11243
+ gitHead: "efd37dd24812e8e34852cf32fa190f2b1c7663d9"
10708
11244
  };
10709
11245
 
10710
11246
  // src/config/constants.ts
@@ -10726,7 +11262,7 @@ var usageCache = null;
10726
11262
  function CliApp() {
10727
11263
  const { exit } = useApp();
10728
11264
  const imageRenderer = new ImageRenderer();
10729
- const [state, setState] = useState7({
11265
+ const [state, setState] = useState8({
10730
11266
  session: null,
10731
11267
  sessionStore: new SessionStore(),
10732
11268
  configStore: new ConfigStore(),
@@ -10743,9 +11279,9 @@ function CliApp() {
10743
11279
  rewindSelector: null,
10744
11280
  sessionSelector: null
10745
11281
  });
10746
- const [isInitialized, setIsInitialized] = useState7(false);
10747
- const [initError, setInitError] = useState7(null);
10748
- const [commandHistory, setCommandHistory] = useState7([]);
11282
+ const [isInitialized, setIsInitialized] = useState8(false);
11283
+ const [initError, setInitError] = useState8(null);
11284
+ const [commandHistory, setCommandHistory] = useState8([]);
10749
11285
  const imageStoreInitPromise = useRef(null);
10750
11286
  const setStoreSession = useCliStore((state2) => state2.setSession);
10751
11287
  const setPermissionPrompt = useCliStore((state2) => state2.setPermissionPrompt);
@@ -10956,6 +11492,24 @@ function CliApp() {
10956
11492
  }
10957
11493
  const allTools = [...b4mTools, ...mcpTools];
10958
11494
  console.log(`\u2705 Total: ${allTools.length} tool(s) available`);
11495
+ const projectDir = state.configStore.getProjectConfigDir();
11496
+ const contextResult = await loadContextFiles(projectDir);
11497
+ if (contextResult.globalContext) {
11498
+ console.log(`\u{1F4C4} Global context: ${contextResult.globalContext.filename}`);
11499
+ }
11500
+ if (contextResult.projectContext) {
11501
+ console.log(`\u{1F4C4} Project context: ${contextResult.projectContext.filename}`);
11502
+ }
11503
+ for (const error of contextResult.errors) {
11504
+ console.log(`\u26A0\uFE0F Context file error: ${error}`);
11505
+ }
11506
+ const contextSection = contextResult.mergedContent ? `
11507
+
11508
+ ## Project Context
11509
+
11510
+ Follow these project-specific instructions:
11511
+
11512
+ ${contextResult.mergedContent}` : "";
10959
11513
  const cliSystemPrompt = `You are an autonomous AI assistant with access to tools. Your job is to help users by taking action and solving problems proactively.
10960
11514
 
10961
11515
  CORE BEHAVIOR:
@@ -10985,7 +11539,7 @@ EXAMPLES:
10985
11539
  - "enhance README" \u2192 file_read \u2192 generate \u2192 create_file
10986
11540
  - "what packages installed?" \u2192 glob_files "**/package.json" \u2192 file_read
10987
11541
 
10988
- Remember: Use context from previous messages to understand follow-up questions.`;
11542
+ Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
10989
11543
  const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
10990
11544
  const agent = new ReActAgent({
10991
11545
  userId: config.userId,
@@ -11040,6 +11594,29 @@ Remember: Use context from previous messages to understand follow-up questions.`
11040
11594
  console.log("\u{1F4A1} Run /login to authenticate with your B4M account.\n");
11041
11595
  return;
11042
11596
  }
11597
+ const currentSteps = [];
11598
+ const stepHandler = (step) => {
11599
+ currentSteps.push(step);
11600
+ const currentSession = useCliStore.getState().session;
11601
+ if (currentSession) {
11602
+ const messages = [...currentSession.messages];
11603
+ const lastMessage = messages[messages.length - 1];
11604
+ if (lastMessage && lastMessage.role === "assistant") {
11605
+ messages[messages.length - 1] = {
11606
+ ...lastMessage,
11607
+ metadata: {
11608
+ ...lastMessage.metadata,
11609
+ steps: [...currentSteps]
11610
+ }
11611
+ };
11612
+ setStoreSession({
11613
+ ...currentSession,
11614
+ messages
11615
+ });
11616
+ }
11617
+ }
11618
+ };
11619
+ state.agent.on("action", stepHandler);
11043
11620
  try {
11044
11621
  let messageContent = message;
11045
11622
  let userMessageContent = message;
@@ -11053,6 +11630,21 @@ Remember: Use context from previous messages to understand follow-up questions.`
11053
11630
  content: userMessageContent,
11054
11631
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
11055
11632
  };
11633
+ const pendingAssistantMessage = {
11634
+ role: "assistant",
11635
+ content: "...",
11636
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11637
+ metadata: {
11638
+ steps: []
11639
+ }
11640
+ };
11641
+ const sessionWithMessages = {
11642
+ ...state.session,
11643
+ messages: [...state.session.messages, userMessage, pendingAssistantMessage],
11644
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
11645
+ };
11646
+ setState((prev) => ({ ...prev, session: sessionWithMessages }));
11647
+ setStoreSession(sessionWithMessages);
11056
11648
  const recentMessages = state.session.messages.slice(-20);
11057
11649
  const previousMessages = recentMessages.filter((msg) => msg.role === "user" || msg.role === "assistant").map((msg) => ({
11058
11650
  role: msg.role,
@@ -11066,26 +11658,28 @@ Remember: Use context from previous messages to understand follow-up questions.`
11066
11658
  console.log("\n\u26A0\uFE0F Action denied by user\n");
11067
11659
  }
11068
11660
  const successfulToolCalls = result.steps.filter((s) => s.type === "observation").length;
11069
- const assistantMessage = {
11661
+ const currentSession = useCliStore.getState().session;
11662
+ if (!currentSession) return;
11663
+ const finalMessages = [...currentSession.messages];
11664
+ const finalAssistantMessage = {
11070
11665
  role: "assistant",
11071
11666
  content: result.finalAnswer,
11072
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11667
+ timestamp: pendingAssistantMessage.timestamp,
11073
11668
  metadata: {
11074
11669
  tokenUsage: {
11075
11670
  prompt: 0,
11076
- // TODO: Extract from result
11077
11671
  completion: 0,
11078
11672
  total: result.completionInfo.totalTokens
11079
11673
  },
11080
11674
  steps: result.steps,
11081
11675
  // Complete history: thoughts, actions, observations
11082
11676
  permissionDenied
11083
- // Mark if this ended due to permission denial
11084
11677
  }
11085
11678
  };
11679
+ finalMessages[finalMessages.length - 1] = finalAssistantMessage;
11086
11680
  const updatedSession = {
11087
- ...state.session,
11088
- messages: [...state.session.messages, userMessage, assistantMessage],
11681
+ ...currentSession,
11682
+ messages: finalMessages,
11089
11683
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11090
11684
  metadata: {
11091
11685
  ...state.session.metadata,
@@ -11105,8 +11699,50 @@ Remember: Use context from previous messages to understand follow-up questions.`
11105
11699
  }
11106
11700
  }
11107
11701
  console.error("Error processing message:", error);
11702
+ } finally {
11703
+ state.agent.off("action", stepHandler);
11108
11704
  }
11109
11705
  };
11706
+ const handleBashCommand = useCallback(
11707
+ (command) => {
11708
+ if (!state.session) return;
11709
+ let output;
11710
+ let isError = false;
11711
+ try {
11712
+ output = execSync(command, {
11713
+ encoding: "utf-8",
11714
+ stdio: ["pipe", "pipe", "pipe"],
11715
+ cwd: process.cwd(),
11716
+ maxBuffer: 10 * 1024 * 1024
11717
+ // 10MB buffer
11718
+ });
11719
+ } catch (error) {
11720
+ const execError = error;
11721
+ output = execError.stderr || execError.message || "Command failed";
11722
+ isError = true;
11723
+ }
11724
+ const userMessage = {
11725
+ role: "user",
11726
+ content: `$ ${command}`,
11727
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11728
+ };
11729
+ const assistantMessage = {
11730
+ role: "assistant",
11731
+ content: isError ? `\u274C Error:
11732
+ ${output}` : output.trim() || "(no output)",
11733
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11734
+ };
11735
+ const updatedSession = {
11736
+ ...state.session,
11737
+ messages: [...state.session.messages, userMessage, assistantMessage],
11738
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
11739
+ };
11740
+ setState((prev) => ({ ...prev, session: updatedSession }));
11741
+ setStoreSession(updatedSession);
11742
+ state.sessionStore.save(updatedSession);
11743
+ },
11744
+ [state.session, state.sessionStore]
11745
+ );
11110
11746
  const handleImageDetected = async (imageData) => {
11111
11747
  try {
11112
11748
  let imageStore = state.imageStore;
@@ -11682,7 +12318,28 @@ No usage data available for the last ${USAGE_DAYS} days.`);
11682
12318
  };
11683
12319
  const handleSaveConfig = async (updatedConfig) => {
11684
12320
  await state.configStore.save(updatedConfig);
11685
- setState((prev) => ({ ...prev, config: updatedConfig }));
12321
+ const modelChanged = state.config?.defaultModel !== updatedConfig.defaultModel;
12322
+ setState((prev) => {
12323
+ if (modelChanged && prev.session) {
12324
+ const updatedSession = {
12325
+ ...prev.session,
12326
+ model: updatedConfig.defaultModel,
12327
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
12328
+ };
12329
+ setStoreSession(updatedSession);
12330
+ if (prev.agent) {
12331
+ prev.agent.context.model = updatedConfig.defaultModel;
12332
+ }
12333
+ return { ...prev, config: updatedConfig, session: updatedSession };
12334
+ }
12335
+ return { ...prev, config: updatedConfig };
12336
+ });
12337
+ if (modelChanged && state.agent) {
12338
+ const llm = state.agent.context.llm;
12339
+ if (llm) {
12340
+ llm.currentModel = updatedConfig.defaultModel;
12341
+ }
12342
+ }
11686
12343
  };
11687
12344
  if (initError) {
11688
12345
  return /* @__PURE__ */ React16.createElement(Box15, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React16.createElement(Text15, { color: "red", bold: true }, "\u274C Initialization Error"), /* @__PURE__ */ React16.createElement(Text15, null, initError), /* @__PURE__ */ React16.createElement(Text15, { dimColor: true }, "\n", "Tip: Run /config to set up your API keys"));
@@ -11776,6 +12433,7 @@ No usage data available for the last ${USAGE_DAYS} days.`);
11776
12433
  {
11777
12434
  onMessage: handleMessage,
11778
12435
  onCommand: handleCommand,
12436
+ onBashCommand: handleBashCommand,
11779
12437
  onImageDetected: handleImageDetected,
11780
12438
  commandHistory,
11781
12439
  config: state.config,