@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/README.md +1 -0
- package/dist/{artifactExtractor-DNE5C6DY.js → artifactExtractor-LF5DAVZL.js} +1 -1
- package/dist/{chunk-3CCBDIFR.js → chunk-6S7HV2GF.js} +2 -2
- package/dist/{chunk-LIRY24M5.js → chunk-F2K7OCDJ.js} +2 -2
- package/dist/{chunk-H6SHASMZ.js → chunk-HD4QQGS6.js} +3 -1
- package/dist/{chunk-YXHQN7SK.js → chunk-RJYG7BUV.js} +2 -2
- package/dist/{chunk-DNSBC3LO.js → chunk-SE2T5GED.js} +1 -1
- package/dist/{create-PNU64QSF.js → create-S7N2FBWR.js} +3 -3
- package/dist/index.js +1360 -521
- package/dist/{llmMarkdownGenerator-UQXKK2AS.js → llmMarkdownGenerator-VY7FSNOI.js} +1 -1
- package/dist/{markdownGenerator-FLLZFXMT.js → markdownGenerator-KES2TDKF.js} +1 -1
- package/dist/{mementoService-LVFQ5XAW.js → mementoService-S4OOMZIT.js} +3 -3
- package/dist/{src-OH5AAQEW.js → src-7PSZ4VA4.js} +2 -2
- package/dist/{src-OYQXRLTM.js → src-VFRCY2I4.js} +1 -1
- package/dist/{subtractCredits-75VT7HP2.js → subtractCredits-DD7PBL22.js} +3 -3
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
getEffectiveApiKey,
|
|
5
5
|
getOpenWeatherKey,
|
|
6
6
|
getSerperKey
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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
|
|
83
|
-
import { render, Box as
|
|
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
|
|
88
|
-
import { Box as Box9, Text as
|
|
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
|
|
99
|
-
import { Box as Box4, Text as
|
|
100
|
-
|
|
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
|
|
104
|
-
import { Box as Box2, Text as
|
|
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__ */
|
|
207
|
+
return /* @__PURE__ */ React3.createElement(Box2, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No matching commands"));
|
|
108
208
|
}
|
|
109
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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
|
|
118
|
-
import { Box as Box3, Text as
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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({
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
-
|
|
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
|
-
|
|
776
|
-
|
|
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:
|
|
916
|
+
placeholder: getPlaceholder(),
|
|
783
917
|
showCursor: !disabled
|
|
784
918
|
}
|
|
785
|
-
)), shouldShowCommandAutocomplete && /* @__PURE__ */
|
|
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
|
|
923
|
+
import React7 from "react";
|
|
790
924
|
import { Box as Box6 } from "ink";
|
|
791
925
|
|
|
792
926
|
// src/components/ThoughtStream.tsx
|
|
793
|
-
import
|
|
794
|
-
import { Box as Box5, Text as
|
|
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 =
|
|
797
|
-
return /* @__PURE__ */
|
|
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 =
|
|
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__ */
|
|
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
|
|
837
|
-
import { Box as Box7, Text as
|
|
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__ */
|
|
978
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "green" }, line);
|
|
844
979
|
}
|
|
845
980
|
if (line.startsWith("-")) {
|
|
846
|
-
return /* @__PURE__ */
|
|
981
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "red" }, line);
|
|
847
982
|
}
|
|
848
983
|
if (line.startsWith("@@")) {
|
|
849
|
-
return /* @__PURE__ */
|
|
984
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "cyan" }, line);
|
|
850
985
|
}
|
|
851
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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
|
|
876
|
-
import { Box as Box8, Text as
|
|
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] =
|
|
1008
|
-
const [editedConfig, setEditedConfig] =
|
|
1009
|
-
const [saveError, setSaveError] =
|
|
1010
|
-
const [isSaving, setIsSaving] =
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
1101
|
-
})), /* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
1313
|
-
import { Box as Box10, Text as
|
|
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 =
|
|
1483
|
+
var MessageList = React11.memo(
|
|
1318
1484
|
function MessageList2({ messages }) {
|
|
1319
1485
|
if (messages.length === 0) {
|
|
1320
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1325
|
-
|
|
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__ */
|
|
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__ */
|
|
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
|
|
1352
|
-
import { Box as Box11, Text as
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
1385
|
-
import { Box as
|
|
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] =
|
|
1502
|
-
const [deviceFlow, setDeviceFlow] =
|
|
1503
|
-
const [statusMessage, setStatusMessage] =
|
|
1504
|
-
const [error, setError] =
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
-
|
|
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
|
|
4839
|
-
return
|
|
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
|
|
6052
|
-
return
|
|
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/
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
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
|
-
|
|
8777
|
-
|
|
9399
|
+
static {
|
|
9400
|
+
this.instance = null;
|
|
8778
9401
|
}
|
|
8779
|
-
|
|
8780
|
-
|
|
9402
|
+
static getInstance() {
|
|
9403
|
+
if (!_Logger.instance) {
|
|
9404
|
+
_Logger.instance = new _Logger();
|
|
9405
|
+
}
|
|
9406
|
+
return _Logger.instance;
|
|
8781
9407
|
}
|
|
8782
|
-
|
|
8783
|
-
|
|
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
|
-
|
|
8786
|
-
|
|
9418
|
+
/**
|
|
9419
|
+
* Set whether verbose console logging is enabled
|
|
9420
|
+
*/
|
|
9421
|
+
setVerbose(enabled) {
|
|
9422
|
+
this.consoleVerbose = enabled;
|
|
8787
9423
|
}
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
9424
|
+
/**
|
|
9425
|
+
* Set whether file logging is enabled
|
|
9426
|
+
*/
|
|
9427
|
+
setFileLoggingEnabled(enabled) {
|
|
9428
|
+
this.fileLoggingEnabled = enabled;
|
|
8792
9429
|
}
|
|
8793
|
-
|
|
8794
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
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
|
|
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,
|
|
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
|
-
//
|
|
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
|
-
//
|
|
8928
|
-
|
|
8929
|
-
//
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
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
|
-
*
|
|
10020
|
+
* Remove a tool from the trusted list
|
|
9253
10021
|
*/
|
|
9254
|
-
|
|
9255
|
-
|
|
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
|
-
*
|
|
10026
|
+
* Get list of all trusted tools
|
|
9293
10027
|
*/
|
|
9294
|
-
|
|
9295
|
-
|
|
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
|
-
*
|
|
10032
|
+
* Check if a tool is currently trusted
|
|
9319
10033
|
*/
|
|
9320
|
-
|
|
9321
|
-
|
|
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
|
-
*
|
|
10038
|
+
* Get the category for a tool
|
|
9350
10039
|
*/
|
|
9351
|
-
|
|
9352
|
-
const
|
|
9353
|
-
|
|
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
|
-
*
|
|
10045
|
+
* Check if a tool can be trusted (not in prompt_always category or denied by project)
|
|
9369
10046
|
*/
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
10047
|
+
canBeTrusted(toolName) {
|
|
10048
|
+
const categoryMap = Object.fromEntries(this.customCategories);
|
|
10049
|
+
if (this.deniedTools.has(toolName)) {
|
|
10050
|
+
return false;
|
|
9373
10051
|
}
|
|
9374
|
-
return
|
|
10052
|
+
return canTrustTool(toolName, categoryMap);
|
|
9375
10053
|
}
|
|
9376
10054
|
/**
|
|
9377
|
-
*
|
|
10055
|
+
* Check if a tool is denied by project config
|
|
9378
10056
|
*/
|
|
9379
|
-
|
|
9380
|
-
|
|
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
|
-
*
|
|
10061
|
+
* Get list of denied tools
|
|
9388
10062
|
*/
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10151
|
+
path13.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
9461
10152
|
// Original paths (backward compatibility)
|
|
9462
|
-
|
|
9463
|
-
|
|
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.
|
|
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.
|
|
10368
|
-
"@bike4mind/mcp": "1.20.
|
|
10369
|
-
"@bike4mind/services": "2.33.
|
|
10370
|
-
"@bike4mind/utils": "2.1.
|
|
10371
|
-
"@types/better-sqlite3": "^7.6.
|
|
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: "
|
|
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] =
|
|
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] =
|
|
10421
|
-
const [initError, setInitError] =
|
|
10422
|
-
const [commandHistory, setCommandHistory] =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
...
|
|
10766
|
-
messages:
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
12135
|
+
return /* @__PURE__ */ React15.createElement(Box14, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React15.createElement(Text14, null, "\u{1F680} Initializing..."));
|
|
11301
12136
|
}
|
|
11302
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
12176
|
+
render(/* @__PURE__ */ React15.createElement(CliApp, null), {
|
|
11338
12177
|
exitOnCtrlC: false
|
|
11339
12178
|
});
|