@bike4mind/cli 0.2.7-feature-notebook-splash-redesign.17129 → 0.2.8-bash-mode.17162

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-6S7HV2GF.js";
8
+ import "./chunk-F2K7OCDJ.js";
9
+ import "./chunk-RJYG7BUV.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-SE2T5GED.js";
19
19
  import {
20
20
  Logger
21
21
  } from "./chunk-AMDXHL6S.js";
@@ -73,19 +73,19 @@ import {
73
73
  XAI_IMAGE_MODELS,
74
74
  b4mLLMTools,
75
75
  getMcpProviderMetadata
76
- } from "./chunk-H6SHASMZ.js";
76
+ } from "./chunk-HD4QQGS6.js";
77
77
  import {
78
78
  __require
79
79
  } from "./chunk-PDX44BCA.js";
80
80
 
81
81
  // src/index.tsx
82
- import React13, { useState as useState4, useEffect as useEffect3, useCallback, useRef } from "react";
83
- import { render, Box as Box13, Text as Text12, useApp, useInput as useInput3 } from "ink";
82
+ import React15, { useState as useState7, useEffect as useEffect3, useCallback as useCallback2, useRef } from "react";
83
+ import { render, Box as Box14, Text as Text14, useApp, useInput as useInput5 } from "ink";
84
84
  import { v4 as uuidv49 } from "uuid";
85
85
 
86
86
  // src/components/App.tsx
87
- import React9 from "react";
88
- import { Box as Box9, Text as Text8, Static } from "ink";
87
+ import React10, { useState as useState4, useCallback } from "react";
88
+ import { Box as Box9, Text as Text9 } from "ink";
89
89
 
90
90
  // src/components/StatusBar.tsx
91
91
  import React from "react";
@@ -95,27 +95,127 @@ var StatusBar = React.memo(function StatusBar2({ sessionName, model, tokenUsage
95
95
  });
96
96
 
97
97
  // src/components/InputPrompt.tsx
98
- import React4, { useState, useMemo, useEffect } from "react";
99
- import { Box as Box4, Text as Text4, useInput } from "ink";
100
- import TextInput from "ink-text-input";
98
+ import React5, { useState as useState2, useMemo, useEffect } from "react";
99
+ import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
100
+
101
+ // src/components/CustomTextInput.tsx
102
+ import React2, { useState } from "react";
103
+ import { Text as Text2, useInput } from "ink";
104
+ function CustomTextInput({
105
+ value,
106
+ onChange,
107
+ onSubmit,
108
+ placeholder = "",
109
+ showCursor = true
110
+ }) {
111
+ const [cursorOffset, setCursorOffset] = useState(value.length);
112
+ useInput(
113
+ (input, key) => {
114
+ if (key.return && !key.meta && !key.shift) {
115
+ onSubmit(value);
116
+ return;
117
+ }
118
+ if (key.ctrl) {
119
+ switch (input) {
120
+ case "u":
121
+ {
122
+ const lines = value.split("\n");
123
+ let currentLineStart = 0;
124
+ let currentLineIndex = 0;
125
+ let charCount = 0;
126
+ for (let i = 0; i < lines.length; i++) {
127
+ const lineLength = lines[i].length;
128
+ if (charCount + lineLength >= cursorOffset) {
129
+ currentLineIndex = i;
130
+ currentLineStart = charCount;
131
+ break;
132
+ }
133
+ charCount += lineLength + 1;
134
+ }
135
+ lines.splice(currentLineIndex, 1);
136
+ const newValue = lines.join("\n");
137
+ onChange(newValue);
138
+ setCursorOffset(Math.min(currentLineStart, newValue.length));
139
+ }
140
+ return;
141
+ case "k":
142
+ onChange(value.slice(0, cursorOffset));
143
+ return;
144
+ case "w":
145
+ {
146
+ const beforeCursor2 = value.slice(0, cursorOffset);
147
+ const afterCursor2 = value.slice(cursorOffset);
148
+ const trimmed = beforeCursor2.trimEnd();
149
+ const lastSpace = trimmed.lastIndexOf(" ");
150
+ const newBefore = lastSpace === -1 ? "" : trimmed.slice(0, lastSpace + 1);
151
+ const newValue = newBefore + afterCursor2;
152
+ onChange(newValue);
153
+ setCursorOffset(newBefore.length);
154
+ }
155
+ return;
156
+ case "a":
157
+ setCursorOffset(0);
158
+ return;
159
+ case "e":
160
+ setCursorOffset(value.length);
161
+ return;
162
+ case "l":
163
+ onChange("");
164
+ return;
165
+ }
166
+ }
167
+ if (key.backspace || key.delete) {
168
+ if (cursorOffset > 0) {
169
+ const newValue = value.slice(0, cursorOffset - 1) + value.slice(cursorOffset);
170
+ onChange(newValue);
171
+ setCursorOffset(cursorOffset - 1);
172
+ }
173
+ return;
174
+ }
175
+ if (key.leftArrow) {
176
+ setCursorOffset(Math.max(0, cursorOffset - 1));
177
+ return;
178
+ }
179
+ if (key.rightArrow) {
180
+ setCursorOffset(Math.min(value.length, cursorOffset + 1));
181
+ return;
182
+ }
183
+ if (!key.ctrl && !key.meta && input.length === 1) {
184
+ const sanitizedInput = input === "\r" ? "\n" : input;
185
+ const newValue = value.slice(0, cursorOffset) + sanitizedInput + value.slice(cursorOffset);
186
+ onChange(newValue);
187
+ setCursorOffset(cursorOffset + 1);
188
+ }
189
+ },
190
+ { isActive: true }
191
+ );
192
+ const hasValue = value.length > 0;
193
+ if (!hasValue) {
194
+ return /* @__PURE__ */ React2.createElement(Text2, null, showCursor ? /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, { inverse: true }, " "), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, placeholder)) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, placeholder));
195
+ }
196
+ const beforeCursor = value.slice(0, cursorOffset);
197
+ const cursorChar = value[cursorOffset] || " ";
198
+ const afterCursor = value.slice(cursorOffset + 1);
199
+ return /* @__PURE__ */ React2.createElement(Text2, null, beforeCursor, showCursor && /* @__PURE__ */ React2.createElement(Text2, { inverse: true }, cursorChar), !showCursor && cursorChar, afterCursor);
200
+ }
101
201
 
102
202
  // src/components/CommandAutocomplete.tsx
103
- import React2 from "react";
104
- import { Box as Box2, Text as Text2 } from "ink";
203
+ import React3 from "react";
204
+ import { Box as Box2, Text as Text3 } from "ink";
105
205
  function CommandAutocomplete({ commands, selectedIndex }) {
106
206
  if (commands.length === 0) {
107
- return /* @__PURE__ */ React2.createElement(Box2, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "No matching commands"));
207
+ return /* @__PURE__ */ React3.createElement(Box2, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No matching commands"));
108
208
  }
109
- return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, commands.length === 1 ? "1 command" : `${commands.length} commands`, " \u2022 Use \u2191\u2193 to navigate, Enter to select")), commands.map((cmd, index) => {
209
+ return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, commands.length === 1 ? "1 command" : `${commands.length} commands`, " \u2022 Use \u2191\u2193 to navigate, Enter to select")), commands.map((cmd, index) => {
110
210
  const args = cmd.args ? ` ${cmd.args}` : "";
111
211
  const isSelected = index === selectedIndex;
112
- return /* @__PURE__ */ React2.createElement(Box2, { key: cmd.name, marginLeft: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "\u25B8 " : " ", "/", cmd.name, args, " - ", cmd.description));
212
+ return /* @__PURE__ */ React3.createElement(Box2, { key: cmd.name, marginLeft: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "\u25B8 " : " ", "/", cmd.name, args, " - ", cmd.description));
113
213
  }));
114
214
  }
115
215
 
116
216
  // src/components/FileAutocomplete.tsx
117
- import React3 from "react";
118
- import { Box as Box3, Text as Text3 } from "ink";
217
+ import React4 from "react";
218
+ import { Box as Box3, Text as Text4 } from "ink";
119
219
 
120
220
  // src/utils/fileSearch.ts
121
221
  import * as fs from "fs";
@@ -304,14 +404,14 @@ var MAX_FILE_SIZE = 10 * 1024 * 1024;
304
404
  // src/components/FileAutocomplete.tsx
305
405
  function FileAutocomplete({ files, selectedIndex, query }) {
306
406
  if (files.length === 0) {
307
- return /* @__PURE__ */ React3.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No matching files", query ? ` for "${query}"` : ""));
407
+ return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No matching files", query ? ` for "${query}"` : ""));
308
408
  }
309
- return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, files.length === 1 ? "1 match" : `${files.length} matches`, " - Use up/down to navigate, Tab to select")), files.map((file, index) => {
409
+ 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) => {
310
410
  const isSelected = index === selectedIndex;
311
411
  const icon = file.isDirectory ? "[folder]" : "[file] ";
312
412
  const sizeDisplay = file.isDirectory ? "" : file.size !== void 0 ? ` (${formatFileSize(file.size)})` : "";
313
413
  const pathDisplay = file.isDirectory ? `${file.path}/` : file.path;
314
- return /* @__PURE__ */ React3.createElement(Box3, { key: file.path, marginLeft: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "> " : " "), /* @__PURE__ */ React3.createElement(Text3, { color: file.isDirectory ? "yellow" : isSelected ? "cyan" : void 0, bold: isSelected }, icon), /* @__PURE__ */ React3.createElement(Text3, { color: isSelected ? "cyan" : void 0, bold: isSelected }, " ", pathDisplay), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, sizeDisplay));
414
+ return /* @__PURE__ */ React4.createElement(Box3, { key: file.path, marginLeft: 1 }, /* @__PURE__ */ React4.createElement(Text4, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "> " : " "), /* @__PURE__ */ React4.createElement(Text4, { color: file.isDirectory ? "yellow" : isSelected ? "cyan" : void 0, bold: isSelected }, icon), /* @__PURE__ */ React4.createElement(Text4, { color: isSelected ? "cyan" : void 0, bold: isSelected }, " ", pathDisplay), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, sizeDisplay));
315
415
  }));
316
416
  }
317
417
 
@@ -389,6 +489,11 @@ var COMMANDS = [
389
489
  name: "clear",
390
490
  description: "Start a new session",
391
491
  aliases: ["new"]
492
+ },
493
+ {
494
+ name: "rewind",
495
+ description: "Rewind conversation to a previous point",
496
+ aliases: ["undo"]
392
497
  }
393
498
  ];
394
499
 
@@ -625,14 +730,32 @@ function findAtTrigger(value) {
625
730
  }
626
731
  return null;
627
732
  }
628
- function InputPrompt({ onSubmit, onImageDetected, disabled = false, history = [] }) {
629
- const [value, setValue] = useState("");
630
- const [selectedIndex, setSelectedIndex] = useState(0);
631
- const [historyIndex, setHistoryIndex] = useState(-1);
632
- const [tempInput, setTempInput] = useState("");
633
- const [inputKey, setInputKey] = useState(0);
634
- const [fileAutocomplete, setFileAutocomplete] = useState(null);
635
- const [fileSelectedIndex, setFileSelectedIndex] = useState(0);
733
+ function InputPrompt({
734
+ onSubmit,
735
+ onBashCommand,
736
+ onImageDetected,
737
+ disabled = false,
738
+ history = [],
739
+ prefillInput,
740
+ onPrefillConsumed,
741
+ onBashModeChange
742
+ }) {
743
+ const [value, setValue] = useState2("");
744
+ const [selectedIndex, setSelectedIndex] = useState2(0);
745
+ const [historyIndex, setHistoryIndex] = useState2(-1);
746
+ const [tempInput, setTempInput] = useState2("");
747
+ useEffect(() => {
748
+ if (prefillInput) {
749
+ setValue(prefillInput);
750
+ onPrefillConsumed?.();
751
+ }
752
+ }, [prefillInput, onPrefillConsumed]);
753
+ const [fileAutocomplete, setFileAutocomplete] = useState2(null);
754
+ const [fileSelectedIndex, setFileSelectedIndex] = useState2(0);
755
+ const isBashMode = value.startsWith("!");
756
+ useEffect(() => {
757
+ onBashModeChange?.(isBashMode);
758
+ }, [isBashMode, onBashModeChange]);
636
759
  const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active;
637
760
  const commandQuery = shouldShowCommandAutocomplete ? value.slice(1) : "";
638
761
  const filteredCommands = useMemo(() => {
@@ -649,7 +772,7 @@ function InputPrompt({ onSubmit, onImageDetected, disabled = false, history = []
649
772
  useEffect(() => {
650
773
  setFileSelectedIndex(0);
651
774
  }, [filteredFiles]);
652
- useInput((input, key) => {
775
+ useInput2((_input, key) => {
653
776
  if (fileAutocomplete?.active && filteredFiles.length > 0) {
654
777
  if (key.upArrow) {
655
778
  setFileSelectedIndex((prev) => prev > 0 ? prev - 1 : filteredFiles.length - 1);
@@ -706,7 +829,6 @@ function InputPrompt({ onSubmit, onImageDetected, disabled = false, history = []
706
829
  const pathToInsert = file.isDirectory ? `@${file.path}/` : `@${file.path}`;
707
830
  const newValue = beforeAt + pathToInsert + afterQuery;
708
831
  setValue(newValue);
709
- setInputKey((prev) => prev + 1);
710
832
  if (file.isDirectory) {
711
833
  setFileAutocomplete({
712
834
  active: true,
@@ -733,7 +855,6 @@ function InputPrompt({ onSubmit, onImageDetected, disabled = false, history = []
733
855
  if (selectedCommand.args) {
734
856
  setValue(commandText + " ");
735
857
  setHistoryIndex(-1);
736
- setInputKey((prev) => prev + 1);
737
858
  return;
738
859
  }
739
860
  setValue("");
@@ -741,6 +862,16 @@ function InputPrompt({ onSubmit, onImageDetected, disabled = false, history = []
741
862
  onSubmit(commandText);
742
863
  return;
743
864
  }
865
+ if (isBashMode && onBashCommand) {
866
+ const command = input.slice(1).trim();
867
+ if (command) {
868
+ onBashCommand(command);
869
+ setValue("");
870
+ setHistoryIndex(-1);
871
+ setFileAutocomplete(null);
872
+ }
873
+ return;
874
+ }
744
875
  onSubmit(input);
745
876
  setValue("");
746
877
  setHistoryIndex(-1);
@@ -752,7 +883,6 @@ function InputPrompt({ onSubmit, onImageDetected, disabled = false, history = []
752
883
  if (imageEvent && onImageDetected) {
753
884
  const placeholder = await onImageDetected(imageEvent.data);
754
885
  setValue(`${placeholder} `);
755
- setInputKey((prev) => prev + 1);
756
886
  return;
757
887
  }
758
888
  }
@@ -772,29 +902,33 @@ function InputPrompt({ onSubmit, onImageDetected, disabled = false, history = []
772
902
  setFileAutocomplete(null);
773
903
  }
774
904
  };
775
- return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "cyan" }, "> "), /* @__PURE__ */ React4.createElement(
776
- TextInput,
905
+ const getPlaceholder = () => {
906
+ if (disabled) return "Thinking...";
907
+ if (isBashMode) return "Enter shell command to execute...";
908
+ return "Type your message, /help for commands, @file to reference, or ! for bash";
909
+ };
910
+ 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(
911
+ CustomTextInput,
777
912
  {
778
- key: inputKey,
779
913
  value,
780
914
  onChange: handleChange,
781
915
  onSubmit: handleSubmit,
782
- placeholder: disabled ? "Thinking..." : "Type your message, /help for commands, or @file to reference",
916
+ placeholder: getPlaceholder(),
783
917
  showCursor: !disabled
784
918
  }
785
- )), shouldShowCommandAutocomplete && /* @__PURE__ */ React4.createElement(CommandAutocomplete, { commands: filteredCommands, selectedIndex }), fileAutocomplete?.active && /* @__PURE__ */ React4.createElement(FileAutocomplete, { files: filteredFiles, selectedIndex: fileSelectedIndex, query: fileAutocomplete.query }));
919
+ )), shouldShowCommandAutocomplete && /* @__PURE__ */ React5.createElement(CommandAutocomplete, { commands: filteredCommands, selectedIndex }), fileAutocomplete?.active && /* @__PURE__ */ React5.createElement(FileAutocomplete, { files: filteredFiles, selectedIndex: fileSelectedIndex, query: fileAutocomplete.query }));
786
920
  }
787
921
 
788
922
  // src/components/AgentThinking.tsx
789
- import React6 from "react";
923
+ import React7 from "react";
790
924
  import { Box as Box6 } from "ink";
791
925
 
792
926
  // src/components/ThoughtStream.tsx
793
- import React5 from "react";
794
- import { Box as Box5, Text as Text5 } from "ink";
927
+ import React6 from "react";
928
+ import { Box as Box5, Text as Text6 } from "ink";
795
929
  import Spinner from "ink-spinner";
796
- var ThoughtStream = React5.memo(function ThoughtStream2({ steps }) {
797
- return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", gap: 1 }, steps.map((step, index) => /* @__PURE__ */ React5.createElement(Box5, { key: index, flexDirection: "column" }, step.type === "thought" && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, "\u{1F914} Thought: "), /* @__PURE__ */ React5.createElement(Text5, null, step.content)), step.type === "action" && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, "\u26A1 Action: "), /* @__PURE__ */ React5.createElement(Text5, { bold: true }, step.metadata?.toolName || "unknown")), step.metadata?.toolInput && /* @__PURE__ */ React5.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, JSON.stringify(step.metadata.toolInput, null, 2)))), step.type === "observation" && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "blue" }, "\u{1F4CA} Observation: ")), /* @__PURE__ */ React5.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React5.createElement(Text5, null, step.content))), step.type === "final_answer" && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "green", bold: true }, "\u2705 Final Answer:", " ")), /* @__PURE__ */ React5.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React5.createElement(Text5, null, step.content)), step.metadata?.tokenUsage && /* @__PURE__ */ React5.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(", step.metadata.tokenUsage.total, " tokens)"))))), !steps.some((s) => s.type === "final_answer") && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React5.createElement(Text5, null, " ", steps.length === 0 ? "Thinking..." : "Processing...")));
930
+ var ThoughtStream = React6.memo(function ThoughtStream2({ steps }) {
931
+ return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", gap: 1 }, steps.map((step, index) => /* @__PURE__ */ React6.createElement(Box5, { key: index, flexDirection: "column" }, step.type === "thought" && /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, "\u{1F914} Thought: "), /* @__PURE__ */ React6.createElement(Text6, null, step.content)), step.type === "action" && /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, "\u26A1 Action: "), /* @__PURE__ */ React6.createElement(Text6, { bold: true }, step.metadata?.toolName || "unknown")), step.metadata?.toolInput && /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, JSON.stringify(step.metadata.toolInput, null, 2)))), step.type === "observation" && /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, "\u{1F4CA} Observation: ")), /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React6.createElement(Text6, null, step.content))), step.type === "final_answer" && /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green", bold: true }, "\u2705 Final Answer:", " ")), /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React6.createElement(Text6, null, step.content)), step.metadata?.tokenUsage && /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "(", step.metadata.tokenUsage.total, " tokens)"))))), !steps.some((s) => s.type === "final_answer") && /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, /* @__PURE__ */ React6.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React6.createElement(Text6, null, " ", steps.length === 0 ? "Thinking..." : "Processing...")));
798
932
  });
799
933
 
800
934
  // src/store/index.ts
@@ -810,6 +944,7 @@ var useCliStore = create((set) => ({
810
944
  addAgentStep: (step) => set((state) => ({
811
945
  agentSteps: [...state.agentSteps, step]
812
946
  })),
947
+ setAgentSteps: (steps) => set({ agentSteps: steps }),
813
948
  clearAgentSteps: () => set({ agentSteps: [] }),
814
949
  // Permission prompt
815
950
  permissionPrompt: null,
@@ -823,32 +958,32 @@ var useCliStore = create((set) => ({
823
958
  }));
824
959
 
825
960
  // src/components/AgentThinking.tsx
826
- var AgentThinking = React6.memo(function AgentThinking2() {
961
+ var AgentThinking = React7.memo(function AgentThinking2() {
827
962
  const isThinking = useCliStore((state) => state.isThinking);
828
963
  const agentSteps = useCliStore((state) => state.agentSteps);
829
964
  if (!isThinking) {
830
965
  return null;
831
966
  }
832
- return /* @__PURE__ */ React6.createElement(Box6, { borderStyle: "single", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React6.createElement(ThoughtStream, { steps: agentSteps }));
967
+ return /* @__PURE__ */ React7.createElement(Box6, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React7.createElement(ThoughtStream, { steps: agentSteps }));
833
968
  });
834
969
 
835
970
  // src/components/PermissionPrompt.tsx
836
- import React7 from "react";
837
- import { Box as Box7, Text as Text6 } from "ink";
971
+ import React8 from "react";
972
+ import { Box as Box7, Text as Text7 } from "ink";
838
973
  import SelectInput from "ink-select-input";
839
974
  function renderDiffPreview(preview) {
840
975
  const lines = preview.split("\n");
841
976
  return lines.map((line, index) => {
842
977
  if (line.startsWith("+")) {
843
- return /* @__PURE__ */ React7.createElement(Text6, { key: index, color: "green" }, line);
978
+ return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "green" }, line);
844
979
  }
845
980
  if (line.startsWith("-")) {
846
- return /* @__PURE__ */ React7.createElement(Text6, { key: index, color: "red" }, line);
981
+ return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "red" }, line);
847
982
  }
848
983
  if (line.startsWith("@@")) {
849
- return /* @__PURE__ */ React7.createElement(Text6, { key: index, color: "cyan" }, line);
984
+ return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "cyan" }, line);
850
985
  }
851
- return /* @__PURE__ */ React7.createElement(Text6, { key: index, dimColor: true }, line);
986
+ return /* @__PURE__ */ React8.createElement(Text7, { key: index, dimColor: true }, line);
852
987
  });
853
988
  }
854
989
  function PermissionPrompt({
@@ -868,12 +1003,12 @@ function PermissionPrompt({
868
1003
  { label: "\u2717 Deny", value: "deny" }
869
1004
  ];
870
1005
  const argsString = typeof args === "string" ? args : JSON.stringify(args, null, 2);
871
- return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", borderStyle: "bold", borderColor: "yellow", padding: 1, marginY: 1 }, /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true, color: "yellow" }, "\u26A0\uFE0F Permission Required")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "Tool: "), /* @__PURE__ */ React7.createElement(Text6, { bold: true, color: "cyan" }, toolName)), toolDescription && /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "Action: "), /* @__PURE__ */ React7.createElement(Text6, null, toolDescription)), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Arguments:"), /* @__PURE__ */ React7.createElement(Box7, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, argsString))), preview && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Preview:"), /* @__PURE__ */ React7.createElement(Box7, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" }, renderDiffPreview(preview))), !canBeTrusted && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, { color: "red", dimColor: true }, "Note: This tool cannot be trusted due to its dangerous nature.")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(SelectInput, { items, onSelect: (item) => onResponse(item.value) })));
1006
+ return /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column", borderStyle: "bold", borderColor: "yellow", padding: 1, marginY: 1 }, /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: "yellow" }, "\u26A0\uFE0F Permission Required")), /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, "Tool: "), /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: "cyan" }, toolName)), toolDescription && /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, "Action: "), /* @__PURE__ */ React8.createElement(Text7, null, toolDescription)), /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text7, { bold: true }, "Arguments:"), /* @__PURE__ */ React8.createElement(Box7, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, argsString))), preview && /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text7, { bold: true }, "Preview:"), /* @__PURE__ */ React8.createElement(Box7, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" }, renderDiffPreview(preview))), !canBeTrusted && /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: "red", dimColor: true }, "Note: This tool cannot be trusted due to its dangerous nature.")), /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(SelectInput, { items, onSelect: (item) => onResponse(item.value) })));
872
1007
  }
873
1008
 
874
1009
  // src/components/ConfigEditor.tsx
875
- import React8, { useState as useState2, useMemo as useMemo2 } from "react";
876
- import { Box as Box8, Text as Text7, useInput as useInput2 } from "ink";
1010
+ import React9, { useState as useState3, useMemo as useMemo2 } from "react";
1011
+ import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
877
1012
  var MAX_ITERATIONS_OPTIONS = [
878
1013
  { label: "10", value: 10 },
879
1014
  { label: "20", value: 20 },
@@ -1004,10 +1139,10 @@ function buildConfigItems(availableModels) {
1004
1139
  return items;
1005
1140
  }
1006
1141
  function ConfigEditor({ config, availableModels, onSave, onClose }) {
1007
- const [selectedIndex, setSelectedIndex] = useState2(0);
1008
- const [editedConfig, setEditedConfig] = useState2(config);
1009
- const [saveError, setSaveError] = useState2(null);
1010
- const [isSaving, setIsSaving] = useState2(false);
1142
+ const [selectedIndex, setSelectedIndex] = useState3(0);
1143
+ const [editedConfig, setEditedConfig] = useState3(config);
1144
+ const [saveError, setSaveError] = useState3(null);
1145
+ const [isSaving, setIsSaving] = useState3(false);
1011
1146
  const configItems = useMemo2(() => buildConfigItems(availableModels), [availableModels]);
1012
1147
  const hasChanges = useMemo2(() => {
1013
1148
  return JSON.stringify(config.preferences) !== JSON.stringify(editedConfig.preferences) || config.defaultModel !== editedConfig.defaultModel;
@@ -1027,7 +1162,7 @@ function ConfigEditor({ config, availableModels, onSave, onClose }) {
1027
1162
  }
1028
1163
  onClose();
1029
1164
  };
1030
- useInput2((input, key) => {
1165
+ useInput3((input, key) => {
1031
1166
  const currentItem = configItems[selectedIndex];
1032
1167
  if (key.upArrow) {
1033
1168
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : configItems.length - 1);
@@ -1092,13 +1227,13 @@ function ConfigEditor({ config, availableModels, onSave, onClose }) {
1092
1227
  }
1093
1228
  return String(value);
1094
1229
  };
1095
- return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, marginY: 1 }, /* @__PURE__ */ React8.createElement(Box8, { justifyContent: "space-between", marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: "cyan" }, "Settings"), isSaving ? /* @__PURE__ */ React8.createElement(Text7, { color: "yellow" }, "Saving...") : hasChanges ? /* @__PURE__ */ React8.createElement(Text7, { dimColor: true, color: "yellow" }, "(unsaved changes)") : null), saveError && /* @__PURE__ */ React8.createElement(Box8, { marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: "red" }, "Error: ", saveError)), /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", marginBottom: 1 }, configItems.map((item, index) => {
1230
+ return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, marginY: 1 }, /* @__PURE__ */ React9.createElement(Box8, { justifyContent: "space-between", marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "cyan" }, "Settings"), isSaving ? /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, "Saving...") : hasChanges ? /* @__PURE__ */ React9.createElement(Text8, { dimColor: true, color: "yellow" }, "(unsaved changes)") : null), saveError && /* @__PURE__ */ React9.createElement(Box8, { marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Text8, { color: "red" }, "Error: ", saveError)), /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginBottom: 1 }, configItems.map((item, index) => {
1096
1231
  const isSelected = index === selectedIndex;
1097
1232
  const value = item.getValue(editedConfig);
1098
1233
  const displayValue = formatValue(item, value);
1099
1234
  const hasOptions = item.type === "select" || item.type === "boolean" || item.type === "number";
1100
- return /* @__PURE__ */ React8.createElement(Box8, { key: item.key }, /* @__PURE__ */ React8.createElement(Text7, { color: isSelected ? "cyan" : void 0 }, isSelected ? "> " : " "), /* @__PURE__ */ React8.createElement(Box8, { width: 18 }, /* @__PURE__ */ React8.createElement(Text7, { bold: isSelected, color: isSelected ? "white" : "gray" }, item.label, ":")), /* @__PURE__ */ React8.createElement(Box8, null, hasOptions && isSelected && /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, "< "), /* @__PURE__ */ React8.createElement(Text7, { bold: isSelected, color: isSelected ? "green" : void 0 }, displayValue), hasOptions && isSelected && /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " >")));
1101
- })), /* @__PURE__ */ React8.createElement(Box8, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, "\u2191/\u2193: Navigate | Space/\u2190/\u2192: Change | q/Esc: Save & Exit")));
1235
+ return /* @__PURE__ */ React9.createElement(Box8, { key: item.key }, /* @__PURE__ */ React9.createElement(Text8, { color: isSelected ? "cyan" : void 0 }, isSelected ? "> " : " "), /* @__PURE__ */ React9.createElement(Box8, { width: 18 }, /* @__PURE__ */ React9.createElement(Text8, { bold: isSelected, color: isSelected ? "white" : "gray" }, item.label, ":")), /* @__PURE__ */ React9.createElement(Box8, null, hasOptions && isSelected && /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "< "), /* @__PURE__ */ React9.createElement(Text8, { bold: isSelected, color: isSelected ? "green" : void 0 }, displayValue), hasOptions && isSelected && /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " >")));
1236
+ })), /* @__PURE__ */ React9.createElement(Box8, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "\u2191/\u2193: Navigate | Space/\u2190/\u2192: Change | q/Esc: Save & Exit")));
1102
1237
  }
1103
1238
 
1104
1239
  // src/utils/processFileReferences.ts
@@ -1216,6 +1351,7 @@ function hasFileReferences(message) {
1216
1351
  }
1217
1352
 
1218
1353
  // src/components/App.tsx
1354
+ import { execSync } from "child_process";
1219
1355
  function App({
1220
1356
  onMessage,
1221
1357
  onCommand,
@@ -1224,7 +1360,9 @@ function App({
1224
1360
  commandHistory = [],
1225
1361
  config,
1226
1362
  availableModels = [],
1227
- onSaveConfig
1363
+ onSaveConfig,
1364
+ prefillInput,
1365
+ onPrefillConsumed
1228
1366
  }) {
1229
1367
  const messages = useCliStore((state) => state.session?.messages || []);
1230
1368
  const sessionName = useCliStore((state) => state.session?.name || "New Session");
@@ -1236,7 +1374,30 @@ function App({
1236
1374
  const setShowConfigEditor = useCliStore((state) => state.setShowConfigEditor);
1237
1375
  const exitRequested = useCliStore((state) => state.exitRequested);
1238
1376
  const setIsThinking = useCliStore((state) => state.setIsThinking);
1239
- const handleSubmit = React9.useCallback(
1377
+ const [isBashMode, setIsBashMode] = useState4(false);
1378
+ const handleBashCommand = useCallback((command) => {
1379
+ try {
1380
+ const output = execSync(command, {
1381
+ encoding: "utf-8",
1382
+ stdio: ["pipe", "pipe", "pipe"],
1383
+ cwd: process.cwd(),
1384
+ maxBuffer: 10 * 1024 * 1024
1385
+ // 10MB buffer
1386
+ });
1387
+ process.stdout.write(`
1388
+ $ ${command}
1389
+ ${output}
1390
+ `);
1391
+ } catch (error) {
1392
+ const execError = error;
1393
+ const errorOutput = execError.stderr || execError.message || "Command failed";
1394
+ process.stdout.write(`
1395
+ $ ${command}
1396
+ ${errorOutput}
1397
+ `);
1398
+ }
1399
+ }, []);
1400
+ const handleSubmit = React10.useCallback(
1240
1401
  async (input) => {
1241
1402
  const trimmed = input.trim();
1242
1403
  if (!trimmed) return;
@@ -1265,7 +1426,7 @@ ${errorBlock}`;
1265
1426
  },
1266
1427
  [onMessage, onCommand, setIsThinking]
1267
1428
  );
1268
- return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(StatusBar, { sessionName, model: currentModel, tokenUsage: totalTokens }), showConfigEditor && config && onSaveConfig ? /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(
1429
+ return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(StatusBar, { sessionName, model: currentModel, tokenUsage: totalTokens }), showConfigEditor && config && onSaveConfig ? /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React10.createElement(
1269
1430
  ConfigEditor,
1270
1431
  {
1271
1432
  config,
@@ -1273,7 +1434,7 @@ ${errorBlock}`;
1273
1434
  onSave: onSaveConfig,
1274
1435
  onClose: () => setShowConfigEditor(false)
1275
1436
  }
1276
- )) : permissionPrompt ? /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(
1437
+ )) : permissionPrompt ? /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React10.createElement(
1277
1438
  PermissionPrompt,
1278
1439
  {
1279
1440
  toolName: permissionPrompt.toolName,
@@ -1282,47 +1443,52 @@ ${errorBlock}`;
1282
1443
  canBeTrusted: permissionPrompt.canBeTrusted,
1283
1444
  onResponse: onPermissionResponse
1284
1445
  }
1285
- )) : /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Static, { items: messages }, (message, index) => /* @__PURE__ */ React9.createElement(Box9, { key: index, flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(MessageItem, { message }))), /* @__PURE__ */ React9.createElement(AgentThinking, null), exitRequested && /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow", bold: true }, "Press Ctrl+C again to exit")), /* @__PURE__ */ React9.createElement(Box9, { borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React9.createElement(
1446
+ )) : /* @__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(
1286
1447
  InputPrompt,
1287
1448
  {
1288
1449
  onSubmit: handleSubmit,
1450
+ onBashCommand: handleBashCommand,
1289
1451
  onImageDetected,
1290
1452
  disabled: isThinking,
1291
- history: commandHistory
1453
+ history: commandHistory,
1454
+ prefillInput,
1455
+ onPrefillConsumed,
1456
+ onBashModeChange: setIsBashMode
1292
1457
  }
1293
1458
  ))));
1294
1459
  }
1295
1460
  function MessageItem({ message }) {
1296
- return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Box9, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: message.role === "user" ? "cyan" : "green" }, message.role === "user" ? "\u{1F464} You" : "\u{1F916} Assistant"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), /* @__PURE__ */ React9.createElement(Box9, { paddingLeft: 2 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React9.createElement(Text8, null, message.content)), message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React9.createElement(Box9, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text8, { 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__ */ React9.createElement(Text8, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
1461
+ const isUser = message.role === "user";
1462
+ 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) => {
1297
1463
  if (step.type === "thought") {
1298
- return /* @__PURE__ */ React9.createElement(Box9, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text8, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` ${step.content.slice(0, 200)}${step.content.length > 200 ? "..." : ""}`));
1464
+ 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 ? "..." : ""}`));
1299
1465
  }
1300
1466
  if (step.type === "action") {
1301
1467
  const toolName = step.metadata?.toolName || "unknown";
1302
1468
  const toolInput = step.metadata?.toolInput;
1303
1469
  const observationStep = message.metadata.steps[idx + 1];
1304
1470
  const result = observationStep?.type === "observation" ? observationStep.content : null;
1305
- return /* @__PURE__ */ React9.createElement(Box9, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, "\u{1F527} Action: ", toolName), toolInput && /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` Input: ${typeof toolInput === "string" ? toolInput : JSON.stringify(toolInput).slice(0, 100)}`), result && /* @__PURE__ */ React9.createElement(Text8, { 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 ? "..." : ""}`));
1471
+ 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 ? "..." : ""}`));
1306
1472
  }
1307
1473
  return null;
1308
- }).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React9.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)));
1474
+ }).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`)));
1309
1475
  }
1310
1476
 
1311
1477
  // src/components/MessageList.tsx
1312
- import React10 from "react";
1313
- import { Box as Box10, Text as Text9 } from "ink";
1478
+ import React11 from "react";
1479
+ import { Box as Box10, Text as Text10 } from "ink";
1314
1480
  function stripThinkingTags(content) {
1315
1481
  return content.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
1316
1482
  }
1317
- var MessageList = React10.memo(
1483
+ var MessageList = React11.memo(
1318
1484
  function MessageList2({ messages }) {
1319
1485
  if (messages.length === 0) {
1320
- return /* @__PURE__ */ React10.createElement(Box10, { paddingY: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "No messages yet. Type a message to start!"));
1486
+ return /* @__PURE__ */ React11.createElement(Box10, { paddingY: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "No messages yet. Type a message to start!"));
1321
1487
  }
1322
- return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", gap: 1, paddingY: 1 }, messages.map((message, index) => /* @__PURE__ */ React10.createElement(Box10, { key: index, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Box10, 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(Box10, { paddingLeft: 2 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u26A0\uFE0F ", stripThinkingTags(message.content)) : /* @__PURE__ */ React10.createElement(Text9, null, stripThinkingTags(message.content))), message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React10.createElement(Box10, { 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) => {
1488
+ return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", gap: 1, paddingY: 1 }, messages.map((message, index) => /* @__PURE__ */ React11.createElement(Box10, { key: index, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { bold: true, color: message.role === "user" ? "cyan" : "green" }, message.role === "user" ? "\u{1F464} You" : "\u{1F916} Assistant"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), /* @__PURE__ */ React11.createElement(Box10, { paddingLeft: 2 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, "\u26A0\uFE0F ", stripThinkingTags(message.content)) : /* @__PURE__ */ React11.createElement(Text10, null, stripThinkingTags(message.content))), message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React11.createElement(Box10, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React11.createElement(Text10, { 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__ */ React11.createElement(Text10, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
1323
1489
  if (step.type === "thought") {
1324
- return /* @__PURE__ */ React10.createElement(Box10, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React10.createElement(
1325
- Text9,
1490
+ return /* @__PURE__ */ React11.createElement(Box10, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React11.createElement(Text10, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React11.createElement(
1491
+ Text10,
1326
1492
  {
1327
1493
  dimColor: true
1328
1494
  },
@@ -1334,10 +1500,10 @@ var MessageList = React10.memo(
1334
1500
  const toolInput = step.metadata?.toolInput;
1335
1501
  const observationStep = message.metadata.steps[idx + 1];
1336
1502
  const result = observationStep?.type === "observation" ? observationStep.content : null;
1337
- return /* @__PURE__ */ React10.createElement(Box10, { 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 ? "..." : ""}`));
1503
+ return /* @__PURE__ */ React11.createElement(Box10, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, "\u{1F527} Action: ", toolName), toolInput && /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, ` Input: ${typeof toolInput === "string" ? toolInput : JSON.stringify(toolInput).slice(0, 100)}`), result && /* @__PURE__ */ React11.createElement(Text10, { 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 ? "..." : ""}`));
1338
1504
  }
1339
1505
  return null;
1340
- }).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React10.createElement(Box10, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)))));
1506
+ }).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React11.createElement(Box10, { paddingLeft: 2 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)))));
1341
1507
  },
1342
1508
  (prevProps, nextProps) => {
1343
1509
  if (prevProps.messages.length !== nextProps.messages.length) {
@@ -1348,8 +1514,8 @@ var MessageList = React10.memo(
1348
1514
  );
1349
1515
 
1350
1516
  // src/components/TrustLocationSelector.tsx
1351
- import React11 from "react";
1352
- import { Box as Box11, Text as Text10 } from "ink";
1517
+ import React12 from "react";
1518
+ import { Box as Box11, Text as Text11 } from "ink";
1353
1519
  import SelectInput2 from "ink-select-input";
1354
1520
  function TrustLocationSelector({ inProject, onSelect, onCancel }) {
1355
1521
  const items = [];
@@ -1370,19 +1536,88 @@ function TrustLocationSelector({ inProject, onSelect, onCancel }) {
1370
1536
  const handleSelect = (item) => {
1371
1537
  onSelect(item.value);
1372
1538
  };
1373
- return /* @__PURE__ */ React11.createElement(Box11, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React11.createElement(Box11, { marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "Where should this rule be saved?")), /* @__PURE__ */ React11.createElement(
1539
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React12.createElement(Box11, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "Where should this rule be saved?")), /* @__PURE__ */ React12.createElement(
1374
1540
  SelectInput2,
1375
1541
  {
1376
1542
  items,
1377
1543
  onSelect: handleSelect,
1378
- itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React11.createElement(Box11, null, /* @__PURE__ */ React11.createElement(Text10, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
1544
+ itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
1545
+ }
1546
+ ), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Ctrl+C to cancel")));
1547
+ }
1548
+
1549
+ // src/components/RewindSelector.tsx
1550
+ import React13, { useState as useState5 } from "react";
1551
+ import { Box as Box12, Text as Text12, useInput as useInput4 } from "ink";
1552
+ import SelectInput3 from "ink-select-input";
1553
+ function RewindSelector({ messages, onSelect, onCancel }) {
1554
+ const [step, setStep] = useState5("selection");
1555
+ const [selectedMessageIndex, setSelectedMessageIndex] = useState5(null);
1556
+ useInput4((input, key) => {
1557
+ if (key.escape) {
1558
+ if (step === "confirmation") {
1559
+ setStep("selection");
1560
+ setSelectedMessageIndex(null);
1561
+ } else {
1562
+ onCancel();
1563
+ }
1564
+ }
1565
+ });
1566
+ const userMessages = messages.map((msg, idx) => ({ msg, originalIndex: idx })).filter(({ msg }) => msg.role === "user");
1567
+ const items = userMessages.map(({ msg, originalIndex }, index) => {
1568
+ const preview = msg.content.length > 50 ? msg.content.substring(0, 50) + "..." : msg.content;
1569
+ const timestamp = new Date(msg.timestamp).toLocaleString();
1570
+ const position = index + 1;
1571
+ return {
1572
+ label: `[${position}] ${timestamp} - ${preview}`,
1573
+ value: originalIndex
1574
+ };
1575
+ });
1576
+ const handleSelectionSelect = (item) => {
1577
+ setSelectedMessageIndex(item.value);
1578
+ setStep("confirmation");
1579
+ };
1580
+ const handleConfirmationSelect = (item) => {
1581
+ if (item.value === "confirm" && selectedMessageIndex !== null) {
1582
+ onSelect(selectedMessageIndex);
1583
+ } else {
1584
+ setStep("selection");
1585
+ setSelectedMessageIndex(null);
1586
+ }
1587
+ };
1588
+ const messagesToRemove = selectedMessageIndex !== null && selectedMessageIndex >= 0 && selectedMessageIndex < messages.length ? messages.slice(selectedMessageIndex) : [];
1589
+ const tokensToRemove = messagesToRemove.reduce((sum2, msg) => {
1590
+ return sum2 + (msg.metadata?.tokenUsage?.total || 0);
1591
+ }, 0);
1592
+ if (step === "selection") {
1593
+ return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { bold: true }, "Select a point to rewind to:")), /* @__PURE__ */ React13.createElement(
1594
+ SelectInput3,
1595
+ {
1596
+ items,
1597
+ onSelect: handleSelectionSelect,
1598
+ itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Text12, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
1599
+ }
1600
+ ), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Esc to cancel")));
1601
+ }
1602
+ const confirmationItems = [
1603
+ { label: "Yes, rewind to this point", value: "confirm" },
1604
+ { label: "No, go back to selection", value: "cancel" }
1605
+ ];
1606
+ const selectedMessage = selectedMessageIndex !== null && selectedMessageIndex >= 0 && selectedMessageIndex < messages.length ? messages[selectedMessageIndex] : null;
1607
+ const selectedPreview = selectedMessage ? selectedMessage.content.length > 50 ? selectedMessage.content.substring(0, 50) + "..." : selectedMessage.content : "";
1608
+ return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React13.createElement(Text12, { bold: true }, "Confirm rewind operation:"), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "Message: ", selectedPreview), /* @__PURE__ */ React13.createElement(Text12, { color: "yellow" }, "This will remove ", messagesToRemove.length, " message(s) (", tokensToRemove.toLocaleString(), " tokens)"), /* @__PURE__ */ React13.createElement(Text12, { color: "cyan" }, "The selected message will be placed in your input for editing.")), /* @__PURE__ */ React13.createElement(
1609
+ SelectInput3,
1610
+ {
1611
+ items: confirmationItems,
1612
+ onSelect: handleConfirmationSelect,
1613
+ itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Text12, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
1379
1614
  }
1380
- ), /* @__PURE__ */ React11.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Ctrl+C to cancel")));
1615
+ ), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Esc to go back")));
1381
1616
  }
1382
1617
 
1383
1618
  // src/components/LoginFlow.tsx
1384
- import React12, { useState as useState3, useEffect as useEffect2 } from "react";
1385
- import { Box as Box12, Text as Text11 } from "ink";
1619
+ import React14, { useState as useState6, useEffect as useEffect2 } from "react";
1620
+ import { Box as Box13, Text as Text13 } from "ink";
1386
1621
  import Spinner2 from "ink-spinner";
1387
1622
  import jwt from "jsonwebtoken";
1388
1623
  import open from "open";
@@ -1498,10 +1733,10 @@ var OAuthClient = class {
1498
1733
 
1499
1734
  // src/components/LoginFlow.tsx
1500
1735
  function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
1501
- const [status, setStatus] = useState3("initiating");
1502
- const [deviceFlow, setDeviceFlow] = useState3(null);
1503
- const [statusMessage, setStatusMessage] = useState3("Initiating device authorization...");
1504
- const [error, setError] = useState3(null);
1736
+ const [status, setStatus] = useState6("initiating");
1737
+ const [deviceFlow, setDeviceFlow] = useState6(null);
1738
+ const [statusMessage, setStatusMessage] = useState6("Initiating device authorization...");
1739
+ const [error, setError] = useState6(null);
1505
1740
  useEffect2(() => {
1506
1741
  const runLoginFlow = async () => {
1507
1742
  const oauth = new OAuthClient(apiUrl);
@@ -1545,15 +1780,15 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
1545
1780
  }
1546
1781
  }, [deviceFlow, status]);
1547
1782
  if (status === "initiating") {
1548
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, /* @__PURE__ */ React12.createElement(Spinner2, { type: "dots" }), " Initiating device authorization...")));
1783
+ return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Text13, { color: "cyan" }, /* @__PURE__ */ React14.createElement(Spinner2, { type: "dots" }), " Initiating device authorization...")));
1549
1784
  }
1550
1785
  if (status === "error") {
1551
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "red", bold: true }, "\u2716 Authentication Failed")), /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: "red" }, error)));
1786
+ return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React14.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React14.createElement(Text13, { color: "red", bold: true }, "\u2716 Authentication Failed")), /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Text13, { color: "red" }, error)));
1552
1787
  }
1553
1788
  if (status === "success") {
1554
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "green", bold: true }, "\u2714 Successfully authenticated!")), /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "You can now use B4M CLI with your account.")));
1789
+ return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React14.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React14.createElement(Text13, { color: "green", bold: true }, "\u2714 Successfully authenticated!")), /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "You can now use B4M CLI with your account.")));
1555
1790
  }
1556
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan", bold: true }, "\u{1F510} Device Authorization")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "Opening browser automatically... If it doesn't open, please visit:")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "blue", bold: true }, deviceFlow?.verification_uri)), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "And enter this code when prompted:")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "yellow", bold: true }, deviceFlow?.user_code)), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, /* @__PURE__ */ React12.createElement(Spinner2, { type: "dots" }), " ", statusMessage)), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Expires in ", deviceFlow ? Math.floor(deviceFlow.expires_in / 60) : 0, " minutes")));
1791
+ return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" }, /* @__PURE__ */ React14.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React14.createElement(Text13, { color: "cyan", bold: true }, "\u{1F510} Device Authorization")), /* @__PURE__ */ React14.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React14.createElement(Text13, null, "Opening browser automatically... If it doesn't open, please visit:")), /* @__PURE__ */ React14.createElement(Box13, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React14.createElement(Text13, { color: "blue", bold: true }, deviceFlow?.verification_uri)), /* @__PURE__ */ React14.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React14.createElement(Text13, null, "And enter this code when prompted:")), /* @__PURE__ */ React14.createElement(Box13, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React14.createElement(Text13, { color: "yellow", bold: true }, deviceFlow?.user_code)), /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { color: "cyan" }, /* @__PURE__ */ React14.createElement(Spinner2, { type: "dots" }), " ", statusMessage)), /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "Expires in ", deviceFlow ? Math.floor(deviceFlow.expires_in / 60) : 0, " minutes")));
1557
1792
  }
1558
1793
 
1559
1794
  // src/storage/SessionStore.ts
@@ -2439,10 +2674,9 @@ ${options.context}` : this.getSystemPrompt()
2439
2674
  content: query
2440
2675
  }
2441
2676
  ];
2442
- console.log("[DEBUG ReActAgent] Messages array being sent to LLM:");
2443
2677
  messages.forEach((msg, i) => {
2444
2678
  const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
2445
- console.log(` [${i}] ${msg.role}: ${content.substring(0, 150)}...`);
2679
+ this.context.logger.debug(` [${i}] ${msg.role}: ${content.substring(0, 150)}...`);
2446
2680
  });
2447
2681
  let finalAnswer = "";
2448
2682
  let reachedMaxIterations = false;
@@ -4835,8 +5069,8 @@ async function processAndStoreImages(images, context) {
4835
5069
  const buffer = await downloadImage(image);
4836
5070
  const fileType = await fileTypeFromBuffer2(buffer);
4837
5071
  const filename = `${uuidv45()}.${fileType?.ext}`;
4838
- const path13 = await context.imageGenerateStorage.upload(buffer, filename, {});
4839
- return path13;
5072
+ const path14 = await context.imageGenerateStorage.upload(buffer, filename, {});
5073
+ return path14;
4840
5074
  }));
4841
5075
  }
4842
5076
  async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
@@ -6048,8 +6282,8 @@ async function processAndStoreImage(imageUrl, context) {
6048
6282
  const buffer = await downloadImage2(imageUrl);
6049
6283
  const fileType = await fileTypeFromBuffer3(buffer);
6050
6284
  const filename = `${uuidv46()}.${fileType?.ext}`;
6051
- const path13 = await context.imageGenerateStorage.upload(buffer, filename, {});
6052
- return path13;
6285
+ const path14 = await context.imageGenerateStorage.upload(buffer, filename, {});
6286
+ return path14;
6053
6287
  }
6054
6288
  async function updateQuestAndReturnMarkdown2(storedImagePath, context) {
6055
6289
  await context.onFinish?.("edit_image", storedImagePath);
@@ -8461,6 +8695,381 @@ var deleteFileTool = {
8461
8695
  })
8462
8696
  };
8463
8697
 
8698
+ // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/bashExecute/index.js
8699
+ import { spawn } from "child_process";
8700
+ import path11 from "path";
8701
+ var DEFAULT_TIMEOUT_MS = 6e4;
8702
+ var MAX_OUTPUT_SIZE = 100 * 1024;
8703
+ var DANGEROUS_PATTERNS = [
8704
+ // Destructive file operations
8705
+ {
8706
+ pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+|.*\s+-[a-zA-Z]*r).*\//i,
8707
+ reason: "Recursive delete with path",
8708
+ block: true
8709
+ },
8710
+ {
8711
+ pattern: /\brm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|.*\s+-[a-zA-Z]*f).*--no-preserve-root/i,
8712
+ reason: "Force delete without preserve root",
8713
+ block: true
8714
+ },
8715
+ {
8716
+ pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+\//i,
8717
+ reason: "Recursive force delete on root paths",
8718
+ block: true
8719
+ },
8720
+ {
8721
+ pattern: /\brm\s+-[a-zA-Z]*f[a-zA-Z]*r[a-zA-Z]*\s+\//i,
8722
+ reason: "Force recursive delete on root paths",
8723
+ block: true
8724
+ },
8725
+ // System-level dangerous commands
8726
+ { pattern: /\bsudo\b/i, reason: "Elevated privileges (sudo)", block: true },
8727
+ { pattern: /\bsu\s+(-|root)/i, reason: "Switch to root user", block: true },
8728
+ { pattern: /\bchmod\s+777\b/i, reason: "Overly permissive chmod", block: false },
8729
+ { pattern: /\bchown\s+-R\s+root/i, reason: "Recursive chown to root", block: true },
8730
+ // Disk/partition operations
8731
+ { pattern: /\bmkfs\b/i, reason: "Filesystem creation", block: true },
8732
+ { pattern: /\bfdisk\b/i, reason: "Disk partitioning", block: true },
8733
+ { pattern: /\bdd\s+.*of=\/dev\//i, reason: "Direct disk write", block: true },
8734
+ // Network attacks
8735
+ { pattern: /\b(nc|netcat)\s+.*-e\s+\/bin\/(ba)?sh/i, reason: "Reverse shell attempt", block: true },
8736
+ { pattern: /\bcurl\s+.*\|\s*(ba)?sh/i, reason: "Piping remote script to shell", block: true },
8737
+ { pattern: /\bwget\s+.*\|\s*(ba)?sh/i, reason: "Piping remote script to shell", block: true },
8738
+ // Fork bombs and resource exhaustion
8739
+ { pattern: /:\(\)\s*\{\s*:\|:&\s*\}\s*;/i, reason: "Fork bomb", block: true },
8740
+ { pattern: /\bwhile\s+true.*do.*done.*&/i, reason: "Infinite loop in background", block: false },
8741
+ // Credential/sensitive data access
8742
+ { pattern: /\/etc\/shadow/i, reason: "Access to shadow file", block: true },
8743
+ { pattern: /\/etc\/passwd.*>/i, reason: "Modifying passwd file", block: true },
8744
+ { pattern: /\baws\s+.*--profile\s+/i, reason: "AWS profile access", block: false },
8745
+ // History/log manipulation
8746
+ { pattern: /\bhistory\s+-c\b/i, reason: "Clearing shell history", block: false },
8747
+ { pattern: />\s*\/var\/log\//i, reason: "Overwriting system logs", block: true },
8748
+ // Dangerous redirects
8749
+ { pattern: />\s*\/dev\/sda/i, reason: "Writing to block device", block: true },
8750
+ { pattern: />\s*\/dev\/null.*2>&1.*</i, reason: "Potentially hiding output", block: false },
8751
+ // Environment manipulation
8752
+ { pattern: /\bexport\s+PATH\s*=\s*[^$]/i, reason: "Overwriting PATH", block: false },
8753
+ { pattern: /\bexport\s+LD_PRELOAD/i, reason: "LD_PRELOAD manipulation", block: true },
8754
+ // Process/system control
8755
+ { pattern: /\bkill\s+-9\s+(-1|1)\b/i, reason: "Killing all processes", block: true },
8756
+ { pattern: /\bkillall\s+-9\b/i, reason: "Force killing processes", block: false },
8757
+ { pattern: /\bshutdown\b/i, reason: "System shutdown", block: true },
8758
+ { pattern: /\breboot\b/i, reason: "System reboot", block: true },
8759
+ { pattern: /\binit\s+[06]\b/i, reason: "System runlevel change", block: true }
8760
+ ];
8761
+ var SAFE_COMMAND_PREFIXES = [
8762
+ "ls",
8763
+ "cat",
8764
+ "head",
8765
+ "tail",
8766
+ "grep",
8767
+ "find",
8768
+ "echo",
8769
+ "pwd",
8770
+ "whoami",
8771
+ "date",
8772
+ "cal",
8773
+ "wc",
8774
+ "sort",
8775
+ "uniq",
8776
+ "cut",
8777
+ "tr",
8778
+ "sed",
8779
+ "awk",
8780
+ "git",
8781
+ "npm",
8782
+ "pnpm",
8783
+ "yarn",
8784
+ "node",
8785
+ "npx",
8786
+ "tsx",
8787
+ "ts-node",
8788
+ "python",
8789
+ "python3",
8790
+ "pip",
8791
+ "pip3",
8792
+ "docker",
8793
+ "docker-compose",
8794
+ "curl",
8795
+ "wget",
8796
+ // These are safe for fetching, blocked when piped to shell
8797
+ "mkdir",
8798
+ "touch",
8799
+ "cp",
8800
+ "mv",
8801
+ // File operations (still require permission)
8802
+ "which",
8803
+ "whereis",
8804
+ "type",
8805
+ "file",
8806
+ "stat",
8807
+ "env",
8808
+ "printenv",
8809
+ "set",
8810
+ "man",
8811
+ "help",
8812
+ "info",
8813
+ "diff",
8814
+ "comm",
8815
+ "cmp",
8816
+ "tar",
8817
+ "zip",
8818
+ "unzip",
8819
+ "gzip",
8820
+ "gunzip",
8821
+ "ssh",
8822
+ "scp",
8823
+ "rsync",
8824
+ // Network tools
8825
+ "make",
8826
+ "cmake",
8827
+ "cargo",
8828
+ "go",
8829
+ "rustc",
8830
+ "gcc",
8831
+ "g++",
8832
+ // Build tools
8833
+ "jest",
8834
+ "vitest",
8835
+ "mocha",
8836
+ "pytest",
8837
+ // Test runners
8838
+ "eslint",
8839
+ "prettier",
8840
+ "tsc"
8841
+ // Linters/formatters
8842
+ ];
8843
+ function checkDangerousPatterns(command) {
8844
+ for (const { pattern, reason, block } of DANGEROUS_PATTERNS) {
8845
+ if (pattern.test(command)) {
8846
+ return { blocked: block, reason };
8847
+ }
8848
+ }
8849
+ return { blocked: false };
8850
+ }
8851
+ function getBaseCommand(command) {
8852
+ const trimmed = command.trim();
8853
+ const match = trimmed.match(/^(\S+)/);
8854
+ return match ? match[1] : "";
8855
+ }
8856
+ function isSafeCommandPrefix(command) {
8857
+ const baseCommand = getBaseCommand(command);
8858
+ return SAFE_COMMAND_PREFIXES.some((safe) => baseCommand === safe || baseCommand.endsWith(`/${safe}`));
8859
+ }
8860
+ async function executeBashCommand(params) {
8861
+ const { command, cwd: relativeCwd, timeout = DEFAULT_TIMEOUT_MS } = params;
8862
+ if (!command || command.trim().length === 0) {
8863
+ return {
8864
+ stdout: "",
8865
+ stderr: "Error: Command cannot be empty",
8866
+ exitCode: 1,
8867
+ timedOut: false,
8868
+ blocked: true,
8869
+ blockedReason: "Empty command"
8870
+ };
8871
+ }
8872
+ const dangerCheck = checkDangerousPatterns(command);
8873
+ if (dangerCheck.blocked) {
8874
+ return {
8875
+ stdout: "",
8876
+ stderr: `Command blocked for safety: ${dangerCheck.reason}`,
8877
+ exitCode: 1,
8878
+ timedOut: false,
8879
+ blocked: true,
8880
+ blockedReason: dangerCheck.reason
8881
+ };
8882
+ }
8883
+ const baseCwd = process.cwd();
8884
+ const targetCwd = relativeCwd ? path11.resolve(baseCwd, relativeCwd) : baseCwd;
8885
+ const effectiveTimeout = Math.min(timeout, 5 * 60 * 1e3);
8886
+ return new Promise((resolve3) => {
8887
+ let stdout = "";
8888
+ let stderr = "";
8889
+ let timedOut = false;
8890
+ const proc = spawn("bash", ["-c", command], {
8891
+ cwd: targetCwd,
8892
+ env: {
8893
+ ...process.env,
8894
+ // Prevent color codes that might be hard to read
8895
+ NO_COLOR: "1",
8896
+ FORCE_COLOR: "0"
8897
+ },
8898
+ stdio: ["ignore", "pipe", "pipe"]
8899
+ });
8900
+ const timeoutId = setTimeout(() => {
8901
+ timedOut = true;
8902
+ proc.kill("SIGTERM");
8903
+ setTimeout(() => {
8904
+ if (!proc.killed) {
8905
+ proc.kill("SIGKILL");
8906
+ }
8907
+ }, 5e3);
8908
+ }, effectiveTimeout);
8909
+ proc.stdout.on("data", (data) => {
8910
+ if (stdout.length < MAX_OUTPUT_SIZE) {
8911
+ stdout += data.toString();
8912
+ if (stdout.length > MAX_OUTPUT_SIZE) {
8913
+ stdout = stdout.slice(0, MAX_OUTPUT_SIZE) + "\n... [output truncated]";
8914
+ }
8915
+ }
8916
+ });
8917
+ proc.stderr.on("data", (data) => {
8918
+ if (stderr.length < MAX_OUTPUT_SIZE) {
8919
+ stderr += data.toString();
8920
+ if (stderr.length > MAX_OUTPUT_SIZE) {
8921
+ stderr = stderr.slice(0, MAX_OUTPUT_SIZE) + "\n... [output truncated]";
8922
+ }
8923
+ }
8924
+ });
8925
+ proc.on("close", (exitCode) => {
8926
+ clearTimeout(timeoutId);
8927
+ resolve3({
8928
+ stdout: stdout.trim(),
8929
+ stderr: stderr.trim(),
8930
+ exitCode,
8931
+ timedOut,
8932
+ blocked: false
8933
+ });
8934
+ });
8935
+ proc.on("error", (error) => {
8936
+ clearTimeout(timeoutId);
8937
+ resolve3({
8938
+ stdout: "",
8939
+ stderr: `Failed to execute command: ${error.message}`,
8940
+ exitCode: 1,
8941
+ timedOut: false,
8942
+ blocked: false
8943
+ });
8944
+ });
8945
+ });
8946
+ }
8947
+ function formatResult(result, command) {
8948
+ const parts = [];
8949
+ parts.push(`$ ${command}`);
8950
+ parts.push("");
8951
+ if (result.blocked) {
8952
+ parts.push(`BLOCKED: ${result.blockedReason}`);
8953
+ parts.push("");
8954
+ parts.push("This command was blocked for safety reasons.");
8955
+ parts.push("If you believe this is a false positive, please run the command manually.");
8956
+ return parts.join("\n");
8957
+ }
8958
+ if (result.timedOut) {
8959
+ parts.push("WARNING: Command timed out and was terminated.");
8960
+ parts.push("");
8961
+ }
8962
+ if (result.stdout) {
8963
+ parts.push(result.stdout);
8964
+ }
8965
+ if (result.stderr) {
8966
+ if (result.stdout) {
8967
+ parts.push("");
8968
+ }
8969
+ parts.push("STDERR:");
8970
+ parts.push(result.stderr);
8971
+ }
8972
+ if (result.exitCode !== 0 && result.exitCode !== null) {
8973
+ parts.push("");
8974
+ parts.push(`Exit code: ${result.exitCode}`);
8975
+ }
8976
+ if (!result.stdout && !result.stderr && !result.timedOut) {
8977
+ parts.push("(command completed with no output)");
8978
+ }
8979
+ return parts.join("\n");
8980
+ }
8981
+ var bashExecuteTool = {
8982
+ name: "bash_execute",
8983
+ implementation: (context) => ({
8984
+ toolFn: async (value) => {
8985
+ const params = value;
8986
+ const isSafe = isSafeCommandPrefix(params.command);
8987
+ context.logger.info("Bash: Executing command", {
8988
+ command: params.command,
8989
+ cwd: params.cwd || ".",
8990
+ timeout: params.timeout || DEFAULT_TIMEOUT_MS,
8991
+ isSafeCommand: isSafe
8992
+ });
8993
+ if (context.onStart) {
8994
+ await context.onStart("bash_execute", {
8995
+ command: params.command,
8996
+ cwd: params.cwd
8997
+ });
8998
+ }
8999
+ try {
9000
+ const result = await executeBashCommand(params);
9001
+ const formattedResult = formatResult(result, params.command);
9002
+ context.logger.info("Bash: Command completed", {
9003
+ exitCode: result.exitCode,
9004
+ timedOut: result.timedOut,
9005
+ blocked: result.blocked
9006
+ });
9007
+ if (context.onFinish) {
9008
+ await context.onFinish("bash_execute", {
9009
+ command: params.command,
9010
+ exitCode: result.exitCode,
9011
+ blocked: result.blocked
9012
+ });
9013
+ }
9014
+ return formattedResult;
9015
+ } catch (error) {
9016
+ context.logger.error("Bash: Command failed", error);
9017
+ if (context.onFinish) {
9018
+ await context.onFinish("bash_execute", {
9019
+ command: params.command,
9020
+ error: error instanceof Error ? error.message : "Unknown error"
9021
+ });
9022
+ }
9023
+ throw error;
9024
+ }
9025
+ },
9026
+ toolSchema: {
9027
+ name: "bash_execute",
9028
+ description: `Execute a bash command in the terminal. Use this for running shell commands, scripts, build tools, git operations, and other CLI tasks.
9029
+
9030
+ SAFETY NOTES:
9031
+ - Commands are executed in the current working directory (or specified cwd)
9032
+ - Dangerous commands (sudo, rm -rf /, etc.) are automatically blocked
9033
+ - Commands have a default timeout of 60 seconds (max 5 minutes)
9034
+ - Output is limited to prevent overwhelming responses
9035
+ - This tool ALWAYS requires user permission before execution
9036
+
9037
+ COMMON USE CASES:
9038
+ - Running build commands: npm run build, make, cargo build
9039
+ - Git operations: git status, git log, git diff
9040
+ - Viewing system info: ls, pwd, cat, head, tail
9041
+ - Running tests: npm test, pytest, cargo test
9042
+ - Package management: npm install, pip install
9043
+ - File operations: mkdir, cp, mv (with permission)
9044
+
9045
+ BLOCKED OPERATIONS:
9046
+ - sudo and privilege escalation
9047
+ - Recursive deletes on system paths
9048
+ - Direct disk operations
9049
+ - Fork bombs and resource exhaustion
9050
+ - Piping remote scripts to shell`,
9051
+ parameters: {
9052
+ type: "object",
9053
+ properties: {
9054
+ command: {
9055
+ type: "string",
9056
+ description: "The bash command to execute. Can include pipes, redirects, and chained commands."
9057
+ },
9058
+ cwd: {
9059
+ type: "string",
9060
+ description: "Working directory for the command (optional). Can be relative or absolute path. Defaults to current directory."
9061
+ },
9062
+ timeout: {
9063
+ type: "number",
9064
+ description: "Timeout in milliseconds (optional, default: 60000, max: 300000). Command will be terminated if it exceeds this time."
9065
+ }
9066
+ },
9067
+ required: ["command"]
9068
+ }
9069
+ }
9070
+ })
9071
+ };
9072
+
8464
9073
  // ../../b4m-core/packages/services/dist/src/llm/tools/index.js
8465
9074
  var tools = {
8466
9075
  dice_roll: diceRollTool,
@@ -8488,7 +9097,9 @@ var tools = {
8488
9097
  create_file: createFileTool,
8489
9098
  glob_files: globFilesTool,
8490
9099
  grep_search: grepSearchTool,
8491
- delete_file: deleteFileTool
9100
+ delete_file: deleteFileTool,
9101
+ // Shell execution
9102
+ bash_execute: bashExecuteTool
8492
9103
  };
8493
9104
  var generateTools = (userId, user, logger2, { db }, storage, imageGenerateStorage, statusUpdate, onStart, onFinish, llm, config, model) => {
8494
9105
  const context = {
@@ -8713,6 +9324,17 @@ var MementoEvalResponseSchema = z142.object({
8713
9324
  // ../../b4m-core/packages/services/dist/src/auth/AccessTokenGeneratorService.js
8714
9325
  import jwt2 from "jsonwebtoken";
8715
9326
 
9327
+ // ../../b4m-core/packages/services/dist/src/cliTools/types.js
9328
+ var ToolErrorType;
9329
+ (function(ToolErrorType2) {
9330
+ ToolErrorType2["INVALID_INPUT"] = "INVALID_INPUT";
9331
+ ToolErrorType2["API_KEY_MISSING"] = "API_KEY_MISSING";
9332
+ ToolErrorType2["EXTERNAL_API_ERROR"] = "EXTERNAL_API_ERROR";
9333
+ ToolErrorType2["RATE_LIMIT"] = "RATE_LIMIT";
9334
+ ToolErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
9335
+ ToolErrorType2["UNKNOWN"] = "UNKNOWN";
9336
+ })(ToolErrorType || (ToolErrorType = {}));
9337
+
8716
9338
  // src/utils/diffPreview.ts
8717
9339
  import * as Diff from "diff";
8718
9340
  import { readFile } from "fs/promises";
@@ -8763,54 +9385,387 @@ Last modified: ${stats.mtime.toLocaleString()}`;
8763
9385
  }
8764
9386
  }
8765
9387
 
8766
- // src/utils/toolsAdapter.ts
8767
- var NoOpStorage = class extends BaseStorage {
8768
- async upload(input, destination, options) {
8769
- return `/tmp/${destination}`;
8770
- }
8771
- async download(path13) {
8772
- throw new Error("Download not supported in CLI");
8773
- }
8774
- async delete(path13) {
9388
+ // src/utils/Logger.ts
9389
+ import fs10 from "fs/promises";
9390
+ import path12 from "path";
9391
+ import os from "os";
9392
+ var Logger2 = class _Logger {
9393
+ constructor() {
9394
+ this.logFilePath = null;
9395
+ this.sessionId = null;
9396
+ this.fileLoggingEnabled = true;
9397
+ this.consoleVerbose = false;
8775
9398
  }
8776
- async getSignedUrl(path13) {
8777
- return `/tmp/${path13}`;
9399
+ static {
9400
+ this.instance = null;
8778
9401
  }
8779
- getPublicUrl(path13) {
8780
- return `/tmp/${path13}`;
9402
+ static getInstance() {
9403
+ if (!_Logger.instance) {
9404
+ _Logger.instance = new _Logger();
9405
+ }
9406
+ return _Logger.instance;
8781
9407
  }
8782
- async getPreview(path13) {
8783
- return `/tmp/${path13}`;
9408
+ /**
9409
+ * Initialize the logger with a session ID
9410
+ */
9411
+ async initialize(sessionId) {
9412
+ this.sessionId = sessionId;
9413
+ const debugDir = path12.join(os.homedir(), ".bike4mind", "debug");
9414
+ await fs10.mkdir(debugDir, { recursive: true });
9415
+ this.logFilePath = path12.join(debugDir, `${sessionId}.txt`);
9416
+ await this.writeToFile("INFO", "=== CLI SESSION START ===");
8784
9417
  }
8785
- async getMetadata(path13) {
8786
- return { size: 0, contentType: "application/octet-stream" };
9418
+ /**
9419
+ * Set whether verbose console logging is enabled
9420
+ */
9421
+ setVerbose(enabled) {
9422
+ this.consoleVerbose = enabled;
8787
9423
  }
8788
- };
8789
- var CliLogger = class extends Logger {
8790
- constructor() {
8791
- super({ logInJson: false });
9424
+ /**
9425
+ * Set whether file logging is enabled
9426
+ */
9427
+ setFileLoggingEnabled(enabled) {
9428
+ this.fileLoggingEnabled = enabled;
8792
9429
  }
8793
- // Override all logging methods to be silent
8794
- info(...args) {
9430
+ /**
9431
+ * DEBUG level - verbose-only console, always file
9432
+ */
9433
+ debug(message) {
9434
+ this.writeToFile("DEBUG", message).catch(() => {
9435
+ });
9436
+ if (this.consoleVerbose) {
9437
+ console.log(message);
9438
+ }
8795
9439
  }
8796
- warn(...args) {
9440
+ /**
9441
+ * INFO level - always shown to user
9442
+ */
9443
+ info(message) {
9444
+ this.writeToFile("INFO", message).catch(() => {
9445
+ });
9446
+ console.log(message);
8797
9447
  }
8798
- error(...args) {
9448
+ /**
9449
+ * WARN level - always shown to user
9450
+ */
9451
+ warn(message) {
9452
+ this.writeToFile("WARN", message).catch(() => {
9453
+ });
9454
+ console.warn(message);
8799
9455
  }
8800
- debug(...args) {
9456
+ /**
9457
+ * ERROR level - always shown to user
9458
+ */
9459
+ error(message, err) {
9460
+ this.writeToFile("ERROR", message).catch(() => {
9461
+ });
9462
+ if (err) {
9463
+ this.logErrorDetailsToFile(err).catch(() => {
9464
+ });
9465
+ }
9466
+ console.error(message);
8801
9467
  }
8802
- log(...args) {
9468
+ /**
9469
+ * Write log entry to file
9470
+ */
9471
+ async writeToFile(level, message) {
9472
+ if (!this.fileLoggingEnabled || !this.logFilePath) {
9473
+ return;
9474
+ }
9475
+ try {
9476
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
9477
+ const logEntry = `[${timestamp}] [${level}] ${message}
9478
+ `;
9479
+ await fs10.appendFile(this.logFilePath, logEntry, "utf-8");
9480
+ } catch (error) {
9481
+ console.error("File logging failed:", error);
9482
+ }
8803
9483
  }
8804
- };
8805
- function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore) {
8806
- const originalFn = tool.toolFn;
8807
- const toolName = tool.toolSchema.name;
8808
- return {
8809
- ...tool,
8810
- toolFn: async (args) => {
8811
- if (!permissionManager.needsPermission(toolName)) {
8812
- const result2 = await originalFn(args);
8813
- agentContext.observationQueue.push({ toolName, result: result2 });
9484
+ /**
9485
+ * Log error details to file
9486
+ */
9487
+ async logErrorDetailsToFile(err) {
9488
+ if (!this.fileLoggingEnabled || !this.logFilePath) {
9489
+ return;
9490
+ }
9491
+ try {
9492
+ if (err && typeof err === "object" && "response" in err && err.response) {
9493
+ const response = err.response;
9494
+ const config = err && typeof err === "object" && "config" in err ? err.config : void 0;
9495
+ await this.writeToFile("ERROR", ` Status: ${response.status} ${response.statusText || ""}`);
9496
+ await this.writeToFile("ERROR", ` URL: ${config?.url || "unknown"}`);
9497
+ await this.writeToFile("ERROR", ` Headers: ${this.safeStringify(response.headers)}`);
9498
+ if (response.data) {
9499
+ const errorText = this.extractErrorMessage(response.data);
9500
+ if (errorText.trim().startsWith("<!DOCTYPE") || errorText.trim().startsWith("<html")) {
9501
+ await this.writeToFile("ERROR", ` Response Type: HTML Error Page`);
9502
+ const parsedError = this.parseHtmlError(errorText);
9503
+ if (parsedError) {
9504
+ await this.writeToFile("ERROR", ` Error Message: ${parsedError}`);
9505
+ }
9506
+ await this.writeToFile("ERROR", ` Raw HTML: ${this.truncate(errorText, 1e3)}`);
9507
+ } else {
9508
+ const preview = this.truncate(errorText, 500);
9509
+ await this.writeToFile("ERROR", ` Response: ${preview}`);
9510
+ }
9511
+ } else {
9512
+ await this.writeToFile("ERROR", ` Response: (no data)`);
9513
+ }
9514
+ }
9515
+ if (err && typeof err === "object" && "stack" in err && typeof err.stack === "string") {
9516
+ const stackLines = err.stack.split("\n").slice(0, 5).join("\n ");
9517
+ await this.writeToFile("ERROR", ` Stack: ${stackLines}`);
9518
+ } else if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
9519
+ await this.writeToFile("ERROR", ` Message: ${err.message}`);
9520
+ }
9521
+ } catch (error) {
9522
+ }
9523
+ }
9524
+ /**
9525
+ * Safely stringify object, handling circular references
9526
+ */
9527
+ safeStringify(obj) {
9528
+ try {
9529
+ return JSON.stringify(obj);
9530
+ } catch (error) {
9531
+ if (error instanceof Error && error.message.includes("circular")) {
9532
+ try {
9533
+ const seen = /* @__PURE__ */ new WeakSet();
9534
+ return JSON.stringify(obj, (key, value) => {
9535
+ if (typeof value === "object" && value !== null) {
9536
+ if (seen.has(value)) {
9537
+ return "[Circular]";
9538
+ }
9539
+ seen.add(value);
9540
+ }
9541
+ return value;
9542
+ });
9543
+ } catch {
9544
+ return "[Unable to stringify]";
9545
+ }
9546
+ }
9547
+ return "[Stringify error]";
9548
+ }
9549
+ }
9550
+ /**
9551
+ * Extract readable text from error response data
9552
+ */
9553
+ extractErrorMessage(data) {
9554
+ if (Buffer.isBuffer(data)) {
9555
+ return data.toString("utf-8");
9556
+ }
9557
+ if (typeof data === "string") {
9558
+ return data;
9559
+ }
9560
+ if (data && typeof data === "object" && "_readableState" in data && data._readableState && typeof data._readableState === "object" && "buffer" in data._readableState && Array.isArray(data._readableState.buffer) && data._readableState.buffer.length > 0) {
9561
+ const chunks = [];
9562
+ for (const chunk of data._readableState.buffer) {
9563
+ if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "Buffer" && "data" in chunk && Array.isArray(chunk.data)) {
9564
+ chunks.push(Buffer.from(chunk.data));
9565
+ } else if (Buffer.isBuffer(chunk)) {
9566
+ chunks.push(chunk);
9567
+ } else if (chunk && typeof chunk === "object" && "data" in chunk) {
9568
+ if (Buffer.isBuffer(chunk.data)) {
9569
+ chunks.push(chunk.data);
9570
+ } else if (Array.isArray(chunk.data)) {
9571
+ chunks.push(Buffer.from(chunk.data));
9572
+ }
9573
+ }
9574
+ }
9575
+ if (chunks.length > 0) {
9576
+ return Buffer.concat(chunks).toString("utf-8");
9577
+ }
9578
+ }
9579
+ return this.safeStringify(data);
9580
+ }
9581
+ /**
9582
+ * Parse HTML error page to extract error message
9583
+ */
9584
+ parseHtmlError(html) {
9585
+ const titleMatch = html.match(/<title>(.*?)<\/title>/i);
9586
+ const h1Match = html.match(/<h1>(.*?)<\/h1>/i);
9587
+ const bodyMatch = html.match(/<body[^>]*>(.*?)<\/body>/is);
9588
+ if (titleMatch && titleMatch[1] !== "Error") {
9589
+ return titleMatch[1].trim();
9590
+ }
9591
+ if (h1Match) {
9592
+ return h1Match[1].trim();
9593
+ }
9594
+ if (bodyMatch) {
9595
+ const text = bodyMatch[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9596
+ return text.substring(0, 200);
9597
+ }
9598
+ return null;
9599
+ }
9600
+ /**
9601
+ * Truncate string to max length with ellipsis
9602
+ */
9603
+ truncate(str, maxLength) {
9604
+ if (str.length <= maxLength) {
9605
+ return str;
9606
+ }
9607
+ return str.substring(0, maxLength) + "... [truncated]";
9608
+ }
9609
+ /**
9610
+ * Format bytes to human-readable size
9611
+ */
9612
+ formatBytes(bytes) {
9613
+ if (bytes === 0) return "0 B";
9614
+ const k = 1024;
9615
+ const sizes = ["B", "KB", "MB", "GB"];
9616
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
9617
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
9618
+ }
9619
+ /**
9620
+ * Clean up old debug logs (older than 30 days)
9621
+ */
9622
+ async cleanupOldLogs() {
9623
+ if (!this.fileLoggingEnabled) return;
9624
+ try {
9625
+ const debugDir = path12.join(os.homedir(), ".bike4mind", "debug");
9626
+ const files = await fs10.readdir(debugDir);
9627
+ const now = Date.now();
9628
+ const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1e3;
9629
+ for (const file of files) {
9630
+ const filePath = path12.join(debugDir, file);
9631
+ const stats = await fs10.stat(filePath);
9632
+ if (stats.mtime.getTime() < thirtyDaysAgo) {
9633
+ await fs10.unlink(filePath);
9634
+ }
9635
+ }
9636
+ } catch (error) {
9637
+ console.error("Failed to cleanup old logs:", error);
9638
+ }
9639
+ }
9640
+ };
9641
+ var logger = Logger2.getInstance();
9642
+
9643
+ // src/llm/ServerToolExecutor.ts
9644
+ var ServerToolExecutor = class {
9645
+ constructor(apiClient) {
9646
+ this.toolsEndpoint = "/api/ai/v1/tools";
9647
+ this.apiClient = apiClient;
9648
+ }
9649
+ /**
9650
+ * Execute a tool server-side
9651
+ *
9652
+ * @param toolName - Name of the tool to execute (e.g., 'weather_info', 'web_search')
9653
+ * @param input - Tool input parameters
9654
+ * @returns Tool execution result (content or error)
9655
+ */
9656
+ async executeTool(toolName, input) {
9657
+ logger.debug(`[ServerToolExecutor] Executing tool: ${toolName}`);
9658
+ logger.debug(`[ServerToolExecutor] Endpoint: ${this.toolsEndpoint}`);
9659
+ logger.debug(`[ServerToolExecutor] Input: ${JSON.stringify(input)}`);
9660
+ try {
9661
+ const result = await this.apiClient.post(this.toolsEndpoint, {
9662
+ toolName,
9663
+ input
9664
+ });
9665
+ logger.debug(`[ServerToolExecutor] Response: ${JSON.stringify(result)}`);
9666
+ if (!result.success) {
9667
+ const errorMessage = result.error || "Tool execution failed";
9668
+ logger.error(`[ServerToolExecutor] Tool ${toolName} failed: ${errorMessage}`);
9669
+ return `Error executing ${toolName}: ${errorMessage}`;
9670
+ }
9671
+ logger.debug(`[ServerToolExecutor] Tool ${toolName} succeeded in ${result.executionTimeMs}ms`);
9672
+ return result.content || "";
9673
+ } catch (error) {
9674
+ const err = error;
9675
+ const errorMessage = err?.response?.data?.error || err?.message || "Unknown error";
9676
+ const status = err?.response?.status || "unknown";
9677
+ logger.error(`[ServerToolExecutor] Request failed for ${toolName}: ${errorMessage} (status: ${status})`);
9678
+ logger.error(`[ServerToolExecutor] Full error: ${JSON.stringify(err, null, 2)}`);
9679
+ return `Error executing ${toolName}: ${errorMessage}`;
9680
+ }
9681
+ }
9682
+ };
9683
+
9684
+ // src/llm/ToolRouter.ts
9685
+ var SERVER_TOOLS = ["weather_info", "web_search"];
9686
+ var LOCAL_TOOLS = [
9687
+ "file_read",
9688
+ "create_file",
9689
+ "glob_files",
9690
+ "grep_search",
9691
+ "delete_file",
9692
+ "edit_file",
9693
+ "dice_roll",
9694
+ "math_evaluate",
9695
+ "current_datetime",
9696
+ "bash_execute"
9697
+ ];
9698
+ function isServerTool(toolName) {
9699
+ return SERVER_TOOLS.includes(toolName);
9700
+ }
9701
+ function isLocalTool(toolName) {
9702
+ return LOCAL_TOOLS.includes(toolName);
9703
+ }
9704
+ async function executeTool(toolName, input, apiClient, localToolFn) {
9705
+ if (isServerTool(toolName)) {
9706
+ logger.debug(`[ToolRouter] Routing ${toolName} to server`);
9707
+ const executor = new ServerToolExecutor(apiClient);
9708
+ return await executor.executeTool(toolName, input);
9709
+ } else if (isLocalTool(toolName)) {
9710
+ logger.debug(`[ToolRouter] Executing ${toolName} locally`);
9711
+ if (!localToolFn) {
9712
+ throw new Error(`Local tool ${toolName} has no implementation`);
9713
+ }
9714
+ const result = await localToolFn(input);
9715
+ return typeof result === "string" ? result : JSON.stringify(result);
9716
+ } else {
9717
+ throw new Error(`Unknown tool: ${toolName}`);
9718
+ }
9719
+ }
9720
+
9721
+ // src/utils/toolsAdapter.ts
9722
+ var NoOpStorage = class extends BaseStorage {
9723
+ async upload(input, destination, options) {
9724
+ return `/tmp/${destination}`;
9725
+ }
9726
+ async download(path14) {
9727
+ throw new Error("Download not supported in CLI");
9728
+ }
9729
+ async delete(path14) {
9730
+ }
9731
+ async getSignedUrl(path14) {
9732
+ return `/tmp/${path14}`;
9733
+ }
9734
+ getPublicUrl(path14) {
9735
+ return `/tmp/${path14}`;
9736
+ }
9737
+ async getPreview(path14) {
9738
+ return `/tmp/${path14}`;
9739
+ }
9740
+ async getMetadata(path14) {
9741
+ return { size: 0, contentType: "application/octet-stream" };
9742
+ }
9743
+ };
9744
+ var CliLogger = class extends Logger {
9745
+ constructor() {
9746
+ super({ logInJson: false });
9747
+ }
9748
+ // Override all logging methods to be silent
9749
+ info(...args) {
9750
+ }
9751
+ warn(...args) {
9752
+ }
9753
+ error(...args) {
9754
+ }
9755
+ debug(...args) {
9756
+ }
9757
+ log(...args) {
9758
+ }
9759
+ };
9760
+ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient) {
9761
+ const originalFn = tool.toolFn;
9762
+ const toolName = tool.toolSchema.name;
9763
+ return {
9764
+ ...tool,
9765
+ toolFn: async (args) => {
9766
+ if (!permissionManager.needsPermission(toolName)) {
9767
+ const result2 = await executeTool(toolName, args, apiClient, originalFn);
9768
+ agentContext.observationQueue.push({ toolName, result: result2 });
8814
9769
  return result2;
8815
9770
  }
8816
9771
  let preview;
@@ -8840,6 +9795,10 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
8840
9795
  } catch (error) {
8841
9796
  preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
8842
9797
  }
9798
+ } else if (toolName === "bash_execute" && args?.command) {
9799
+ const cwd = args.cwd ? ` (in ${args.cwd})` : "";
9800
+ const timeout = args.timeout ? ` [timeout: ${args.timeout}ms]` : "";
9801
+ preview = `$ ${args.command}${cwd}${timeout}`;
8843
9802
  }
8844
9803
  const response = await showPermissionPrompt(toolName, args, preview);
8845
9804
  if (response.action === "deny") {
@@ -8866,13 +9825,13 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
8866
9825
  }
8867
9826
  }
8868
9827
  }
8869
- const result = await originalFn(args);
9828
+ const result = await executeTool(toolName, args, apiClient, originalFn);
8870
9829
  agentContext.observationQueue.push({ toolName, result });
8871
9830
  return result;
8872
9831
  }
8873
9832
  };
8874
9833
  }
8875
- function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, toolApiKeys) {
9834
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient) {
8876
9835
  const logger2 = new CliLogger();
8877
9836
  const storage = new NoOpStorage();
8878
9837
  const user = {
@@ -8889,18 +9848,6 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
8889
9848
  };
8890
9849
  const mockAdminSettings = {
8891
9850
  findBySettingName: async (settingName) => {
8892
- if (settingName === "OpenWeatherKey") {
8893
- const key = toolApiKeys?.openweather || process.env.OPENWEATHER_API_KEY;
8894
- if (key) {
8895
- return { settingValue: key };
8896
- }
8897
- }
8898
- if (settingName === "SerperKey") {
8899
- const key = toolApiKeys?.serper || process.env.SERPER_API_KEY;
8900
- if (key) {
8901
- return { settingValue: key };
8902
- }
8903
- }
8904
9851
  return null;
8905
9852
  }
8906
9853
  };
@@ -8910,27 +9857,24 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
8910
9857
  adminSettings: mockAdminSettings
8911
9858
  }
8912
9859
  };
8913
- const hasSerperKey = !!(toolApiKeys?.serper || process.env.SERPER_API_KEY);
8914
- const hasOpenWeatherKey = !!(toolApiKeys?.openweather || process.env.OPENWEATHER_API_KEY);
8915
9860
  const toolConfig = {
8916
- // Enable these tools for CLI (no external API keys needed)
9861
+ // Local-only tools (no external API keys needed)
8917
9862
  dice_roll: {},
8918
9863
  math_evaluate: {},
8919
9864
  current_datetime: {},
8920
9865
  prompt_enhancement: {},
8921
- // File operation tools (CLI-specific)
9866
+ // File operation tools (CLI-specific, local execution)
8922
9867
  file_read: {},
8923
9868
  create_file: {},
8924
9869
  glob_files: {},
8925
9870
  grep_search: {},
8926
9871
  delete_file: {},
8927
- // Tools requiring API keys (only include if API key is available)
8928
- ...hasSerperKey && { web_search: {} },
8929
- // Requires SERPER_API_KEY
8930
- ...hasOpenWeatherKey && { weather_info: {} },
8931
- // Requires OPENWEATHER_API_KEY
8932
- ...hasSerperKey && { deep_research: {} }
8933
- // Requires SERPER_API_KEY
9872
+ // Shell execution (always requires permission)
9873
+ bash_execute: {},
9874
+ // Server-side tools (executed via /api/ai/v1/tools Lambda)
9875
+ // Routed by ToolRouter to use B4M company API keys
9876
+ weather_info: {},
9877
+ web_search: {}
8934
9878
  // Disable web-only tools
8935
9879
  // blog_publish: undefined,
8936
9880
  // blog_edit: undefined,
@@ -8940,6 +9884,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
8940
9884
  // mermaid_chart: undefined,
8941
9885
  // recharts: undefined,
8942
9886
  // edit_file: undefined,
9887
+ // deep_research: undefined,
8943
9888
  };
8944
9889
  const toolsMap = generateTools(
8945
9890
  userId,
@@ -8957,7 +9902,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
8957
9902
  model
8958
9903
  );
8959
9904
  const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
8960
- ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore)
9905
+ ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
8961
9906
  );
8962
9907
  return { tools: tools2, agentContext };
8963
9908
  }
@@ -8991,6 +9936,7 @@ var DEFAULT_TOOL_CATEGORIES = {
8991
9936
  create_file: "prompt_always",
8992
9937
  delete_file: "prompt_always",
8993
9938
  shell_execute: "prompt_always",
9939
+ bash_execute: "prompt_always",
8994
9940
  git_commit: "prompt_always",
8995
9941
  git_push: "prompt_always",
8996
9942
  // ===== PROMPT DEFAULT (Repository read tools) =====
@@ -9067,350 +10013,95 @@ var PermissionManager = class {
9067
10013
  if (!canTrustTool(toolName, categoryMap)) {
9068
10014
  return false;
9069
10015
  }
9070
- this.trustedTools.add(toolName);
9071
- return true;
9072
- }
9073
- /**
9074
- * Remove a tool from the trusted list
9075
- */
9076
- untrustTool(toolName) {
9077
- this.trustedTools.delete(toolName);
9078
- }
9079
- /**
9080
- * Get list of all trusted tools
9081
- */
9082
- getTrustedTools() {
9083
- return Array.from(this.trustedTools).sort();
9084
- }
9085
- /**
9086
- * Check if a tool is currently trusted
9087
- */
9088
- isTrusted(toolName) {
9089
- return this.trustedTools.has(toolName);
9090
- }
9091
- /**
9092
- * Get the category for a tool
9093
- */
9094
- getCategory(toolName) {
9095
- const categoryMap = Object.fromEntries(this.customCategories);
9096
- return getToolCategory(toolName, categoryMap);
9097
- }
9098
- /**
9099
- * Check if a tool can be trusted (not in prompt_always category or denied by project)
9100
- */
9101
- canBeTrusted(toolName) {
9102
- const categoryMap = Object.fromEntries(this.customCategories);
9103
- if (this.deniedTools.has(toolName)) {
9104
- return false;
9105
- }
9106
- return canTrustTool(toolName, categoryMap);
9107
- }
9108
- /**
9109
- * Check if a tool is denied by project config
9110
- */
9111
- isDenied(toolName) {
9112
- return this.deniedTools.has(toolName);
9113
- }
9114
- /**
9115
- * Get list of denied tools
9116
- */
9117
- getDeniedTools() {
9118
- return Array.from(this.deniedTools).sort();
9119
- }
9120
- /**
9121
- * Clear all trusted tools
9122
- */
9123
- clearTrustedTools() {
9124
- this.trustedTools.clear();
9125
- }
9126
- /**
9127
- * Get statistics about permissions
9128
- */
9129
- getStats() {
9130
- return {
9131
- trustedCount: this.trustedTools.size,
9132
- autoApproveCount: 0,
9133
- // TODO: Would need full tool list
9134
- promptAlwaysCount: 0,
9135
- promptDefaultCount: 0
9136
- };
9137
- }
9138
- };
9139
-
9140
- // src/utils/apiUrl.ts
9141
- var BIKE4MIND_URL = "https://app.bike4mind.com";
9142
- function getApiUrl(configApiConfig) {
9143
- if (configApiConfig?.customUrl) {
9144
- return configApiConfig.customUrl;
9145
- }
9146
- return BIKE4MIND_URL;
9147
- }
9148
- function getEnvironmentName(configApiConfig) {
9149
- if (configApiConfig?.customUrl) {
9150
- return "Self-Hosted";
9151
- }
9152
- return "Bike4Mind";
9153
- }
9154
-
9155
- // src/utils/Logger.ts
9156
- import fs10 from "fs/promises";
9157
- import path11 from "path";
9158
- import os from "os";
9159
- var Logger2 = class _Logger {
9160
- constructor() {
9161
- this.logFilePath = null;
9162
- this.sessionId = null;
9163
- this.fileLoggingEnabled = true;
9164
- this.consoleVerbose = false;
9165
- }
9166
- static {
9167
- this.instance = null;
9168
- }
9169
- static getInstance() {
9170
- if (!_Logger.instance) {
9171
- _Logger.instance = new _Logger();
9172
- }
9173
- return _Logger.instance;
9174
- }
9175
- /**
9176
- * Initialize the logger with a session ID
9177
- */
9178
- async initialize(sessionId) {
9179
- this.sessionId = sessionId;
9180
- const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
9181
- await fs10.mkdir(debugDir, { recursive: true });
9182
- this.logFilePath = path11.join(debugDir, `${sessionId}.txt`);
9183
- await this.writeToFile("INFO", "=== CLI SESSION START ===");
9184
- }
9185
- /**
9186
- * Set whether verbose console logging is enabled
9187
- */
9188
- setVerbose(enabled) {
9189
- this.consoleVerbose = enabled;
9190
- }
9191
- /**
9192
- * Set whether file logging is enabled
9193
- */
9194
- setFileLoggingEnabled(enabled) {
9195
- this.fileLoggingEnabled = enabled;
9196
- }
9197
- /**
9198
- * DEBUG level - verbose-only console, always file
9199
- */
9200
- debug(message) {
9201
- this.writeToFile("DEBUG", message).catch(() => {
9202
- });
9203
- if (this.consoleVerbose) {
9204
- console.log(message);
9205
- }
9206
- }
9207
- /**
9208
- * INFO level - always shown to user
9209
- */
9210
- info(message) {
9211
- this.writeToFile("INFO", message).catch(() => {
9212
- });
9213
- console.log(message);
9214
- }
9215
- /**
9216
- * WARN level - always shown to user
9217
- */
9218
- warn(message) {
9219
- this.writeToFile("WARN", message).catch(() => {
9220
- });
9221
- console.warn(message);
9222
- }
9223
- /**
9224
- * ERROR level - always shown to user
9225
- */
9226
- error(message, err) {
9227
- this.writeToFile("ERROR", message).catch(() => {
9228
- });
9229
- if (err) {
9230
- this.logErrorDetailsToFile(err).catch(() => {
9231
- });
9232
- }
9233
- console.error(message);
9234
- }
9235
- /**
9236
- * Write log entry to file
9237
- */
9238
- async writeToFile(level, message) {
9239
- if (!this.fileLoggingEnabled || !this.logFilePath) {
9240
- return;
9241
- }
9242
- try {
9243
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
9244
- const logEntry = `[${timestamp}] [${level}] ${message}
9245
- `;
9246
- await fs10.appendFile(this.logFilePath, logEntry, "utf-8");
9247
- } catch (error) {
9248
- console.error("File logging failed:", error);
9249
- }
10016
+ this.trustedTools.add(toolName);
10017
+ return true;
9250
10018
  }
9251
10019
  /**
9252
- * Log error details to file
10020
+ * Remove a tool from the trusted list
9253
10021
  */
9254
- async logErrorDetailsToFile(err) {
9255
- if (!this.fileLoggingEnabled || !this.logFilePath) {
9256
- return;
9257
- }
9258
- try {
9259
- if (err && typeof err === "object" && "response" in err && err.response) {
9260
- const response = err.response;
9261
- const config = err && typeof err === "object" && "config" in err ? err.config : void 0;
9262
- await this.writeToFile("ERROR", ` Status: ${response.status} ${response.statusText || ""}`);
9263
- await this.writeToFile("ERROR", ` URL: ${config?.url || "unknown"}`);
9264
- await this.writeToFile("ERROR", ` Headers: ${this.safeStringify(response.headers)}`);
9265
- if (response.data) {
9266
- const errorText = this.extractErrorMessage(response.data);
9267
- if (errorText.trim().startsWith("<!DOCTYPE") || errorText.trim().startsWith("<html")) {
9268
- await this.writeToFile("ERROR", ` Response Type: HTML Error Page`);
9269
- const parsedError = this.parseHtmlError(errorText);
9270
- if (parsedError) {
9271
- await this.writeToFile("ERROR", ` Error Message: ${parsedError}`);
9272
- }
9273
- await this.writeToFile("ERROR", ` Raw HTML: ${this.truncate(errorText, 1e3)}`);
9274
- } else {
9275
- const preview = this.truncate(errorText, 500);
9276
- await this.writeToFile("ERROR", ` Response: ${preview}`);
9277
- }
9278
- } else {
9279
- await this.writeToFile("ERROR", ` Response: (no data)`);
9280
- }
9281
- }
9282
- if (err && typeof err === "object" && "stack" in err && typeof err.stack === "string") {
9283
- const stackLines = err.stack.split("\n").slice(0, 5).join("\n ");
9284
- await this.writeToFile("ERROR", ` Stack: ${stackLines}`);
9285
- } else if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
9286
- await this.writeToFile("ERROR", ` Message: ${err.message}`);
9287
- }
9288
- } catch (error) {
9289
- }
10022
+ untrustTool(toolName) {
10023
+ this.trustedTools.delete(toolName);
9290
10024
  }
9291
10025
  /**
9292
- * Safely stringify object, handling circular references
10026
+ * Get list of all trusted tools
9293
10027
  */
9294
- safeStringify(obj) {
9295
- try {
9296
- return JSON.stringify(obj);
9297
- } catch (error) {
9298
- if (error instanceof Error && error.message.includes("circular")) {
9299
- try {
9300
- const seen = /* @__PURE__ */ new WeakSet();
9301
- return JSON.stringify(obj, (key, value) => {
9302
- if (typeof value === "object" && value !== null) {
9303
- if (seen.has(value)) {
9304
- return "[Circular]";
9305
- }
9306
- seen.add(value);
9307
- }
9308
- return value;
9309
- });
9310
- } catch {
9311
- return "[Unable to stringify]";
9312
- }
9313
- }
9314
- return "[Stringify error]";
9315
- }
10028
+ getTrustedTools() {
10029
+ return Array.from(this.trustedTools).sort();
9316
10030
  }
9317
10031
  /**
9318
- * Extract readable text from error response data
10032
+ * Check if a tool is currently trusted
9319
10033
  */
9320
- extractErrorMessage(data) {
9321
- if (Buffer.isBuffer(data)) {
9322
- return data.toString("utf-8");
9323
- }
9324
- if (typeof data === "string") {
9325
- return data;
9326
- }
9327
- if (data && typeof data === "object" && "_readableState" in data && data._readableState && typeof data._readableState === "object" && "buffer" in data._readableState && Array.isArray(data._readableState.buffer) && data._readableState.buffer.length > 0) {
9328
- const chunks = [];
9329
- for (const chunk of data._readableState.buffer) {
9330
- if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "Buffer" && "data" in chunk && Array.isArray(chunk.data)) {
9331
- chunks.push(Buffer.from(chunk.data));
9332
- } else if (Buffer.isBuffer(chunk)) {
9333
- chunks.push(chunk);
9334
- } else if (chunk && typeof chunk === "object" && "data" in chunk) {
9335
- if (Buffer.isBuffer(chunk.data)) {
9336
- chunks.push(chunk.data);
9337
- } else if (Array.isArray(chunk.data)) {
9338
- chunks.push(Buffer.from(chunk.data));
9339
- }
9340
- }
9341
- }
9342
- if (chunks.length > 0) {
9343
- return Buffer.concat(chunks).toString("utf-8");
9344
- }
9345
- }
9346
- return this.safeStringify(data);
10034
+ isTrusted(toolName) {
10035
+ return this.trustedTools.has(toolName);
9347
10036
  }
9348
10037
  /**
9349
- * Parse HTML error page to extract error message
10038
+ * Get the category for a tool
9350
10039
  */
9351
- parseHtmlError(html) {
9352
- const titleMatch = html.match(/<title>(.*?)<\/title>/i);
9353
- const h1Match = html.match(/<h1>(.*?)<\/h1>/i);
9354
- const bodyMatch = html.match(/<body[^>]*>(.*?)<\/body>/is);
9355
- if (titleMatch && titleMatch[1] !== "Error") {
9356
- return titleMatch[1].trim();
9357
- }
9358
- if (h1Match) {
9359
- return h1Match[1].trim();
9360
- }
9361
- if (bodyMatch) {
9362
- const text = bodyMatch[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9363
- return text.substring(0, 200);
9364
- }
9365
- return null;
10040
+ getCategory(toolName) {
10041
+ const categoryMap = Object.fromEntries(this.customCategories);
10042
+ return getToolCategory(toolName, categoryMap);
9366
10043
  }
9367
10044
  /**
9368
- * Truncate string to max length with ellipsis
10045
+ * Check if a tool can be trusted (not in prompt_always category or denied by project)
9369
10046
  */
9370
- truncate(str, maxLength) {
9371
- if (str.length <= maxLength) {
9372
- return str;
10047
+ canBeTrusted(toolName) {
10048
+ const categoryMap = Object.fromEntries(this.customCategories);
10049
+ if (this.deniedTools.has(toolName)) {
10050
+ return false;
9373
10051
  }
9374
- return str.substring(0, maxLength) + "... [truncated]";
10052
+ return canTrustTool(toolName, categoryMap);
9375
10053
  }
9376
10054
  /**
9377
- * Format bytes to human-readable size
10055
+ * Check if a tool is denied by project config
9378
10056
  */
9379
- formatBytes(bytes) {
9380
- if (bytes === 0) return "0 B";
9381
- const k = 1024;
9382
- const sizes = ["B", "KB", "MB", "GB"];
9383
- const i = Math.floor(Math.log(bytes) / Math.log(k));
9384
- return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
10057
+ isDenied(toolName) {
10058
+ return this.deniedTools.has(toolName);
9385
10059
  }
9386
10060
  /**
9387
- * Clean up old debug logs (older than 30 days)
10061
+ * Get list of denied tools
9388
10062
  */
9389
- async cleanupOldLogs() {
9390
- if (!this.fileLoggingEnabled) return;
9391
- try {
9392
- const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
9393
- const files = await fs10.readdir(debugDir);
9394
- const now = Date.now();
9395
- const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1e3;
9396
- for (const file of files) {
9397
- const filePath = path11.join(debugDir, file);
9398
- const stats = await fs10.stat(filePath);
9399
- if (stats.mtime.getTime() < thirtyDaysAgo) {
9400
- await fs10.unlink(filePath);
9401
- }
9402
- }
9403
- } catch (error) {
9404
- console.error("Failed to cleanup old logs:", error);
9405
- }
10063
+ getDeniedTools() {
10064
+ return Array.from(this.deniedTools).sort();
10065
+ }
10066
+ /**
10067
+ * Clear all trusted tools
10068
+ */
10069
+ clearTrustedTools() {
10070
+ this.trustedTools.clear();
10071
+ }
10072
+ /**
10073
+ * Get statistics about permissions
10074
+ */
10075
+ getStats() {
10076
+ return {
10077
+ trustedCount: this.trustedTools.size,
10078
+ autoApproveCount: 0,
10079
+ // TODO: Would need full tool list
10080
+ promptAlwaysCount: 0,
10081
+ promptDefaultCount: 0
10082
+ };
9406
10083
  }
9407
10084
  };
9408
- var logger = Logger2.getInstance();
10085
+
10086
+ // src/utils/apiUrl.ts
10087
+ var BIKE4MIND_URL = "https://app.bike4mind.com";
10088
+ function getApiUrl(configApiConfig) {
10089
+ if (configApiConfig?.customUrl) {
10090
+ return configApiConfig.customUrl;
10091
+ }
10092
+ return BIKE4MIND_URL;
10093
+ }
10094
+ function getEnvironmentName(configApiConfig) {
10095
+ if (configApiConfig?.customUrl) {
10096
+ return "Self-Hosted";
10097
+ }
10098
+ return "Bike4Mind";
10099
+ }
9409
10100
 
9410
10101
  // ../../b4m-core/packages/mcp/dist/src/client.js
9411
10102
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
9412
10103
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
9413
- import path12 from "path";
10104
+ import path13 from "path";
9414
10105
  import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
9415
10106
  var MCPClient = class {
9416
10107
  // Note: This class handles MCP server communication with repository filtering
@@ -9451,16 +10142,16 @@ var MCPClient = class {
9451
10142
  const root = process.env.INIT_CWD || process.cwd();
9452
10143
  const candidatePaths = [
9453
10144
  // When running from SST Lambda with node_modules structure (copyFiles)
9454
- path12.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
10145
+ path13.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
9455
10146
  // When running from SST Lambda deployed environment (/var/task)
9456
- path12.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10147
+ path13.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
9457
10148
  // When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
9458
- path12.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10149
+ path13.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
9459
10150
  // When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
9460
- path12.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10151
+ path13.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
9461
10152
  // Original paths (backward compatibility)
9462
- path12.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
9463
- path12.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
10153
+ path13.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
10154
+ path13.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
9464
10155
  ];
9465
10156
  const serverScriptPath = candidatePaths.find((p) => existsSync8(p));
9466
10157
  if (!serverScriptPath) {
@@ -10210,7 +10901,10 @@ var ApiClient = class {
10210
10901
  * Make a POST request
10211
10902
  */
10212
10903
  async post(url, data, config) {
10904
+ logger.debug(`[ApiClient] POST ${this.client.defaults.baseURL}${url}`);
10905
+ logger.debug(`[ApiClient] Request body: ${JSON.stringify(data)}`);
10213
10906
  const response = await this.client.post(url, data, config);
10907
+ logger.debug(`[ApiClient] Response status: ${response.status}`);
10214
10908
  return response.data;
10215
10909
  }
10216
10910
  /**
@@ -10261,7 +10955,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
10261
10955
  // package.json
10262
10956
  var package_default = {
10263
10957
  name: "@bike4mind/cli",
10264
- version: "0.2.7-feature-notebook-splash-redesign.17129+59a337a9d",
10958
+ version: "0.2.8-bash-mode.17162+cba7df97f",
10265
10959
  type: "module",
10266
10960
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
10267
10961
  license: "UNLICENSED",
@@ -10364,11 +11058,11 @@ var package_default = {
10364
11058
  },
10365
11059
  devDependencies: {
10366
11060
  "@bike4mind/agents": "0.1.0",
10367
- "@bike4mind/common": "2.38.1-feature-notebook-splash-redesign.17129+59a337a9d",
10368
- "@bike4mind/mcp": "1.20.1-feature-notebook-splash-redesign.17129+59a337a9d",
10369
- "@bike4mind/services": "2.33.1-feature-notebook-splash-redesign.17129+59a337a9d",
10370
- "@bike4mind/utils": "2.1.2-feature-notebook-splash-redesign.17129+59a337a9d",
10371
- "@types/better-sqlite3": "^7.6.13",
11061
+ "@bike4mind/common": "2.38.2-bash-mode.17162+cba7df97f",
11062
+ "@bike4mind/mcp": "1.20.2-bash-mode.17162+cba7df97f",
11063
+ "@bike4mind/services": "2.33.2-bash-mode.17162+cba7df97f",
11064
+ "@bike4mind/utils": "2.1.3-bash-mode.17162+cba7df97f",
11065
+ "@types/better-sqlite3": "^7.6.12",
10372
11066
  "@types/diff": "^5.0.9",
10373
11067
  "@types/jsonwebtoken": "^9.0.4",
10374
11068
  "@types/node": "^22.9.0",
@@ -10380,7 +11074,7 @@ var package_default = {
10380
11074
  typescript: "^5.9.3",
10381
11075
  vitest: "^3.2.4"
10382
11076
  },
10383
- gitHead: "59a337a9d7a8fddb596b5903547f09463ae4a05e"
11077
+ gitHead: "cba7df97f6d3fd25869e8ab6b2561b3ee03b1276"
10384
11078
  };
10385
11079
 
10386
11080
  // src/config/constants.ts
@@ -10402,7 +11096,7 @@ var usageCache = null;
10402
11096
  function CliApp() {
10403
11097
  const { exit } = useApp();
10404
11098
  const imageRenderer = new ImageRenderer();
10405
- const [state, setState] = useState4({
11099
+ const [state, setState] = useState7({
10406
11100
  session: null,
10407
11101
  sessionStore: new SessionStore(),
10408
11102
  configStore: new ConfigStore(),
@@ -10415,17 +11109,18 @@ function CliApp() {
10415
11109
  mcpManager: null,
10416
11110
  permissionManager: null,
10417
11111
  permissionPrompt: null,
10418
- trustLocationSelector: null
11112
+ trustLocationSelector: null,
11113
+ rewindSelector: null
10419
11114
  });
10420
- const [isInitialized, setIsInitialized] = useState4(false);
10421
- const [initError, setInitError] = useState4(null);
10422
- const [commandHistory, setCommandHistory] = useState4([]);
11115
+ const [isInitialized, setIsInitialized] = useState7(false);
11116
+ const [initError, setInitError] = useState7(null);
11117
+ const [commandHistory, setCommandHistory] = useState7([]);
10423
11118
  const imageStoreInitPromise = useRef(null);
10424
11119
  const setStoreSession = useCliStore((state2) => state2.setSession);
10425
11120
  const setPermissionPrompt = useCliStore((state2) => state2.setPermissionPrompt);
10426
11121
  const setShowConfigEditor = useCliStore((state2) => state2.setShowConfigEditor);
10427
11122
  const setExitRequested = useCliStore((state2) => state2.setExitRequested);
10428
- const performCleanup = useCallback(async () => {
11123
+ const performCleanup = useCallback2(async () => {
10429
11124
  const cleanupTasks = [];
10430
11125
  if (state.session) {
10431
11126
  cleanupTasks.push(
@@ -10460,7 +11155,7 @@ function CliApp() {
10460
11155
  process.exit(0);
10461
11156
  }, 100);
10462
11157
  }, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
10463
- useInput3((input, key) => {
11158
+ useInput5((input, key) => {
10464
11159
  if (key.ctrl && input === "c") {
10465
11160
  const now = Date.now();
10466
11161
  if (exitTimestamp && now - exitTimestamp < EXIT_TIMEOUT_MS) {
@@ -10479,7 +11174,7 @@ function CliApp() {
10479
11174
  }
10480
11175
  }
10481
11176
  });
10482
- const init = useCallback(async () => {
11177
+ const init = useCallback2(async () => {
10483
11178
  try {
10484
11179
  const config = await state.configStore.load();
10485
11180
  const history = await state.commandHistoryStore.load();
@@ -10604,10 +11299,6 @@ function CliApp() {
10604
11299
  setPermissionPrompt(prompt);
10605
11300
  });
10606
11301
  };
10607
- console.log("\u{1F511} Tool API Keys:", {
10608
- openweather: config.toolApiKeys?.openweather ? "\u2713 configured" : "\u2717 missing",
10609
- serper: config.toolApiKeys?.serper ? "\u2713 configured" : "\u2717 missing"
10610
- });
10611
11302
  const agentContext = {
10612
11303
  currentAgent: null,
10613
11304
  observationQueue: []
@@ -10620,7 +11311,7 @@ function CliApp() {
10620
11311
  promptFn,
10621
11312
  agentContext,
10622
11313
  state.configStore,
10623
- config.toolApiKeys
11314
+ apiClient
10624
11315
  );
10625
11316
  console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools.length} B4M tool(s)`);
10626
11317
  const mcpManager = new McpManager(config);
@@ -10718,6 +11409,29 @@ Remember: Use context from previous messages to understand follow-up questions.`
10718
11409
  console.log("\u{1F4A1} Run /login to authenticate with your B4M account.\n");
10719
11410
  return;
10720
11411
  }
11412
+ const currentSteps = [];
11413
+ const stepHandler = (step) => {
11414
+ currentSteps.push(step);
11415
+ const currentSession = useCliStore.getState().session;
11416
+ if (currentSession) {
11417
+ const messages = [...currentSession.messages];
11418
+ const lastMessage = messages[messages.length - 1];
11419
+ if (lastMessage && lastMessage.role === "assistant") {
11420
+ messages[messages.length - 1] = {
11421
+ ...lastMessage,
11422
+ metadata: {
11423
+ ...lastMessage.metadata,
11424
+ steps: [...currentSteps]
11425
+ }
11426
+ };
11427
+ setStoreSession({
11428
+ ...currentSession,
11429
+ messages
11430
+ });
11431
+ }
11432
+ }
11433
+ };
11434
+ state.agent.on("action", stepHandler);
10721
11435
  try {
10722
11436
  let messageContent = message;
10723
11437
  let userMessageContent = message;
@@ -10731,6 +11445,21 @@ Remember: Use context from previous messages to understand follow-up questions.`
10731
11445
  content: userMessageContent,
10732
11446
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
10733
11447
  };
11448
+ const pendingAssistantMessage = {
11449
+ role: "assistant",
11450
+ content: "...",
11451
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11452
+ metadata: {
11453
+ steps: []
11454
+ }
11455
+ };
11456
+ const sessionWithMessages = {
11457
+ ...state.session,
11458
+ messages: [...state.session.messages, userMessage, pendingAssistantMessage],
11459
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
11460
+ };
11461
+ setState((prev) => ({ ...prev, session: sessionWithMessages }));
11462
+ setStoreSession(sessionWithMessages);
10734
11463
  const recentMessages = state.session.messages.slice(-20);
10735
11464
  const previousMessages = recentMessages.filter((msg) => msg.role === "user" || msg.role === "assistant").map((msg) => ({
10736
11465
  role: msg.role,
@@ -10744,26 +11473,28 @@ Remember: Use context from previous messages to understand follow-up questions.`
10744
11473
  console.log("\n\u26A0\uFE0F Action denied by user\n");
10745
11474
  }
10746
11475
  const successfulToolCalls = result.steps.filter((s) => s.type === "observation").length;
10747
- const assistantMessage = {
11476
+ const currentSession = useCliStore.getState().session;
11477
+ if (!currentSession) return;
11478
+ const finalMessages = [...currentSession.messages];
11479
+ const finalAssistantMessage = {
10748
11480
  role: "assistant",
10749
11481
  content: result.finalAnswer,
10750
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11482
+ timestamp: pendingAssistantMessage.timestamp,
10751
11483
  metadata: {
10752
11484
  tokenUsage: {
10753
11485
  prompt: 0,
10754
- // TODO: Extract from result
10755
11486
  completion: 0,
10756
11487
  total: result.completionInfo.totalTokens
10757
11488
  },
10758
11489
  steps: result.steps,
10759
11490
  // Complete history: thoughts, actions, observations
10760
11491
  permissionDenied
10761
- // Mark if this ended due to permission denial
10762
11492
  }
10763
11493
  };
11494
+ finalMessages[finalMessages.length - 1] = finalAssistantMessage;
10764
11495
  const updatedSession = {
10765
- ...state.session,
10766
- messages: [...state.session.messages, userMessage, assistantMessage],
11496
+ ...currentSession,
11497
+ messages: finalMessages,
10767
11498
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10768
11499
  metadata: {
10769
11500
  ...state.session.metadata,
@@ -10783,6 +11514,8 @@ Remember: Use context from previous messages to understand follow-up questions.`
10783
11514
  }
10784
11515
  }
10785
11516
  console.error("Error processing message:", error);
11517
+ } finally {
11518
+ state.agent.off("action", stepHandler);
10786
11519
  }
10787
11520
  };
10788
11521
  const handleImageDetected = async (imageData) => {
@@ -10814,6 +11547,30 @@ Remember: Use context from previous messages to understand follow-up questions.`
10814
11547
  throw error;
10815
11548
  }
10816
11549
  };
11550
+ const recalculateSessionMetadata = (messages) => {
11551
+ let totalTokens = 0;
11552
+ let totalCost = 0;
11553
+ let toolCallCount = 0;
11554
+ for (const msg of messages) {
11555
+ if (msg.metadata) {
11556
+ if (msg.metadata.tokenUsage) {
11557
+ totalTokens += msg.metadata.tokenUsage.total || 0;
11558
+ }
11559
+ if (msg.metadata.cost) {
11560
+ totalCost += msg.metadata.cost;
11561
+ }
11562
+ if (msg.metadata.steps) {
11563
+ const observations = msg.metadata.steps.filter((s) => s.type === "observation");
11564
+ toolCallCount += observations.length;
11565
+ }
11566
+ }
11567
+ }
11568
+ return {
11569
+ totalTokens,
11570
+ totalCost,
11571
+ toolCallCount
11572
+ };
11573
+ };
10817
11574
  const handleCommand = async (command, args) => {
10818
11575
  switch (command) {
10819
11576
  case "help":
@@ -10822,6 +11579,7 @@ Available commands:
10822
11579
  /help - Show this help message
10823
11580
  /exit - Exit the CLI
10824
11581
  /clear - Start a new session
11582
+ /rewind - Rewind conversation to a previous point
10825
11583
  /login - Authenticate with your B4M account
10826
11584
  /logout - Clear authentication and sign out
10827
11585
  /whoami - Show current authenticated user
@@ -11097,6 +11855,65 @@ Project Configuration:
11097
11855
  `);
11098
11856
  break;
11099
11857
  }
11858
+ case "rewind":
11859
+ case "undo": {
11860
+ if (!state.session) {
11861
+ console.log("No active session to rewind");
11862
+ return;
11863
+ }
11864
+ if (state.session.messages.length === 0) {
11865
+ console.log("\u26A0\uFE0F Conversation is empty. Nothing to rewind.");
11866
+ return;
11867
+ }
11868
+ const userMessages = state.session.messages.filter((msg) => msg.role === "user");
11869
+ if (userMessages.length === 0) {
11870
+ console.log("\u26A0\uFE0F No user messages found. Nothing to rewind.");
11871
+ return;
11872
+ }
11873
+ if (userMessages.length === 1) {
11874
+ console.log("\u26A0\uFE0F Only one exchange in the conversation. Use /clear to start a new session instead.");
11875
+ return;
11876
+ }
11877
+ const handleRewindSelect = async (messageIndex) => {
11878
+ setState((prev) => ({ ...prev, rewindSelector: null }));
11879
+ if (messageIndex === null) {
11880
+ console.log("\u274C Rewind operation cancelled");
11881
+ return;
11882
+ }
11883
+ if (!state.session) {
11884
+ console.log("\u274C No active session");
11885
+ return;
11886
+ }
11887
+ const selectedMessage = state.session.messages[messageIndex];
11888
+ const prefillContent = selectedMessage?.content || "";
11889
+ const rewindedMessages = state.session.messages.slice(0, messageIndex);
11890
+ const newMetadata = recalculateSessionMetadata(rewindedMessages);
11891
+ const rewindedSession = {
11892
+ ...state.session,
11893
+ messages: rewindedMessages,
11894
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11895
+ metadata: newMetadata
11896
+ };
11897
+ setState((prev) => ({
11898
+ ...prev,
11899
+ session: rewindedSession,
11900
+ prefillInput: prefillContent
11901
+ }));
11902
+ setStoreSession(rewindedSession);
11903
+ await state.sessionStore.save(rewindedSession);
11904
+ console.log("\u2705 Conversation rewound successfully");
11905
+ console.log(`\u{1F4CA} Current state: ${rewindedMessages.length} messages, ${newMetadata.totalTokens} tokens`);
11906
+ console.log(`\u{1F4DD} Your message has been placed in the input. Edit and send when ready.
11907
+ `);
11908
+ };
11909
+ setState((prev) => ({
11910
+ ...prev,
11911
+ rewindSelector: {
11912
+ resolve: handleRewindSelect
11913
+ }
11914
+ }));
11915
+ break;
11916
+ }
11100
11917
  case "usage": {
11101
11918
  const usageAuthTokens = await state.configStore.getAuthTokens();
11102
11919
  if (!usageAuthTokens || new Date(usageAuthTokens.expiresAt) <= /* @__PURE__ */ new Date()) {
@@ -11250,12 +12067,12 @@ No usage data available for the last ${USAGE_DAYS} days.`);
11250
12067
  setState((prev) => ({ ...prev, config: updatedConfig }));
11251
12068
  };
11252
12069
  if (initError) {
11253
- return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, "\u274C Initialization Error"), /* @__PURE__ */ React13.createElement(Text12, null, initError), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "\n", "Tip: Run /config to set up your API keys"));
12070
+ return /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React15.createElement(Text14, { color: "red", bold: true }, "\u274C Initialization Error"), /* @__PURE__ */ React15.createElement(Text14, null, initError), /* @__PURE__ */ React15.createElement(Text14, { dimColor: true }, "\n", "Tip: Run /config to set up your API keys"));
11254
12071
  }
11255
12072
  if (state.trustLocationSelector) {
11256
12073
  const projectDir = state.configStore.getProjectConfigDir();
11257
12074
  const inProject = projectDir !== null;
11258
- return /* @__PURE__ */ React13.createElement(
12075
+ return /* @__PURE__ */ React15.createElement(
11259
12076
  TrustLocationSelector,
11260
12077
  {
11261
12078
  inProject,
@@ -11272,9 +12089,27 @@ No usage data available for the last ${USAGE_DAYS} days.`);
11272
12089
  }
11273
12090
  );
11274
12091
  }
12092
+ if (state.rewindSelector && state.session) {
12093
+ return /* @__PURE__ */ React15.createElement(
12094
+ RewindSelector,
12095
+ {
12096
+ messages: state.session.messages,
12097
+ onSelect: (messageIndex) => {
12098
+ if (state.rewindSelector) {
12099
+ state.rewindSelector.resolve(messageIndex);
12100
+ }
12101
+ },
12102
+ onCancel: () => {
12103
+ if (state.rewindSelector) {
12104
+ state.rewindSelector.resolve(null);
12105
+ }
12106
+ }
12107
+ }
12108
+ );
12109
+ }
11275
12110
  if (state.showLoginFlow) {
11276
12111
  const loginApiUrl = getApiUrl(state.config?.apiConfig);
11277
- return /* @__PURE__ */ React13.createElement(
12112
+ return /* @__PURE__ */ React15.createElement(
11278
12113
  LoginFlow,
11279
12114
  {
11280
12115
  apiUrl: loginApiUrl,
@@ -11297,9 +12132,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
11297
12132
  );
11298
12133
  }
11299
12134
  if (!isInitialized) {
11300
- return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "\u{1F680} Initializing..."));
12135
+ return /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React15.createElement(Text14, null, "\u{1F680} Initializing..."));
11301
12136
  }
11302
- return /* @__PURE__ */ React13.createElement(
12137
+ return /* @__PURE__ */ React15.createElement(
11303
12138
  App,
11304
12139
  {
11305
12140
  onMessage: handleMessage,
@@ -11309,6 +12144,10 @@ No usage data available for the last ${USAGE_DAYS} days.`);
11309
12144
  config: state.config,
11310
12145
  availableModels: state.availableModels,
11311
12146
  onSaveConfig: handleSaveConfig,
12147
+ prefillInput: state.prefillInput,
12148
+ onPrefillConsumed: () => {
12149
+ setState((prev) => ({ ...prev, prefillInput: void 0 }));
12150
+ },
11312
12151
  onPermissionResponse: (response) => {
11313
12152
  if (state.permissionPrompt) {
11314
12153
  state.permissionPrompt.resolve({ action: response });
@@ -11334,6 +12173,6 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
11334
12173
  if (isDevMode) {
11335
12174
  console.log("\u{1F527} Running in development mode (using TypeScript source)\n");
11336
12175
  }
11337
- render(/* @__PURE__ */ React13.createElement(CliApp, null), {
12176
+ render(/* @__PURE__ */ React15.createElement(CliApp, null), {
11338
12177
  exitOnCtrlC: false
11339
12178
  });