@bike4mind/cli 0.2.5-ja-fix-confluence-table-data-5752.17103 → 0.2.7-basic-cli-shortcuts.17131
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 +50 -4
- package/dist/ImageStore-RQZ7OF7P.js +225 -0
- package/dist/{artifactExtractor-ZU5BBMLI.js → artifactExtractor-DNE5C6DY.js} +1 -1
- package/dist/{chunk-3TADOGT5.js → chunk-3CCBDIFR.js} +2 -2
- package/dist/{chunk-WPD676KY.js → chunk-DNSBC3LO.js} +1 -1
- package/dist/{chunk-QCULHLGT.js → chunk-H6SHASMZ.js} +66 -118
- package/dist/{chunk-ZYMOZ2IB.js → chunk-LIRY24M5.js} +2 -2
- package/dist/{chunk-GVI2BQAN.js → chunk-YXHQN7SK.js} +2 -2
- package/dist/{create-BLOW4E4S.js → create-PNU64QSF.js} +3 -3
- package/dist/index.js +1478 -314
- package/dist/{llmMarkdownGenerator-NMCN2NWQ.js → llmMarkdownGenerator-UQXKK2AS.js} +1 -1
- package/dist/{markdownGenerator-JZOBTDGP.js → markdownGenerator-FLLZFXMT.js} +1 -1
- package/dist/{mementoService-KP72H2HX.js → mementoService-LVFQ5XAW.js} +3 -3
- package/dist/{src-OZRIS46Z.js → src-OH5AAQEW.js} +2 -2
- package/dist/{src-43E2427F.js → src-OYQXRLTM.js} +5 -7
- package/dist/{subtractCredits-D4WRBUDQ.js → subtractCredits-75VT7HP2.js} +3 -3
- package/package.json +9 -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-3CCBDIFR.js";
|
|
8
|
+
import "./chunk-LIRY24M5.js";
|
|
9
|
+
import "./chunk-YXHQN7SK.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-DNSBC3LO.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-H6SHASMZ.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 React14, { useState as useState5, useEffect as useEffect3, useCallback, useRef } from "react";
|
|
83
|
+
import { render, Box as Box13, Text as Text13, useApp, useInput as useInput4 } 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 from "react";
|
|
88
|
+
import { Box as Box9, Text as Text9, Static } 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
|
|
|
@@ -413,6 +513,202 @@ function searchCommands(query) {
|
|
|
413
513
|
return results.map((result) => result.item);
|
|
414
514
|
}
|
|
415
515
|
|
|
516
|
+
// src/utils/imageDetector.ts
|
|
517
|
+
import { readFileSync, existsSync, statSync as statSync2 } from "fs";
|
|
518
|
+
import { extname as extname2 } from "path";
|
|
519
|
+
var ImageInputDetector = class {
|
|
520
|
+
static {
|
|
521
|
+
this.MAX_IMAGE_SIZE = 10 * 1024 * 1024;
|
|
522
|
+
}
|
|
523
|
+
static {
|
|
524
|
+
// 10MB
|
|
525
|
+
this.SUPPORTED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Check if input contains image data
|
|
529
|
+
*/
|
|
530
|
+
static containsImageData(input) {
|
|
531
|
+
if (input.includes("\x1B]1337;File=")) {
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
if (input.includes("\x1B_G")) {
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
if (input.includes("\x1BP")) {
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
if (input.match(/data:image\/(png|jpeg|jpg|gif|webp);base64,/)) {
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
if (this.looksLikeFilePath(input)) {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Extract image data from terminal input
|
|
550
|
+
*/
|
|
551
|
+
static extractImageData(input) {
|
|
552
|
+
const iterm2 = this.extractITerm2Image(input);
|
|
553
|
+
if (iterm2) return iterm2;
|
|
554
|
+
const kitty = this.extractKittyImage(input);
|
|
555
|
+
if (kitty) return kitty;
|
|
556
|
+
const sixel = this.extractSixelImage(input);
|
|
557
|
+
if (sixel) return sixel;
|
|
558
|
+
const base64 = this.extractBase64Image(input);
|
|
559
|
+
if (base64) return base64;
|
|
560
|
+
const fileDrop = this.extractDroppedFile(input);
|
|
561
|
+
if (fileDrop) return fileDrop;
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Extract iTerm2 inline image
|
|
566
|
+
* Format: ESC]1337;File=[args]:base64data^G
|
|
567
|
+
*/
|
|
568
|
+
static extractITerm2Image(input) {
|
|
569
|
+
const match = input.match(/\x1b\]1337;File=([^:]*):([^\x07]+)\x07/);
|
|
570
|
+
if (!match) return null;
|
|
571
|
+
try {
|
|
572
|
+
const args = match[1];
|
|
573
|
+
const base64Data = match[2];
|
|
574
|
+
const data = Buffer.from(base64Data, "base64");
|
|
575
|
+
let format = "png";
|
|
576
|
+
const nameMatch = args.match(/name=([^;]+)/);
|
|
577
|
+
if (nameMatch) {
|
|
578
|
+
const ext = extname2(nameMatch[1]).substring(1).toLowerCase();
|
|
579
|
+
if (ext && this.SUPPORTED_EXTENSIONS.includes(`.${ext}`)) {
|
|
580
|
+
format = ext === "jpeg" ? "jpg" : ext;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
type: "paste",
|
|
585
|
+
data,
|
|
586
|
+
format,
|
|
587
|
+
protocol: "iterm2"
|
|
588
|
+
};
|
|
589
|
+
} catch (error) {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Extract Kitty graphics protocol image
|
|
595
|
+
* Format: ESC_Ga=T,f=100;base64data ESC\
|
|
596
|
+
*/
|
|
597
|
+
static extractKittyImage(input) {
|
|
598
|
+
const match = input.match(/\x1b_G([^;]*);([^\x1b]+)\x1b\\/);
|
|
599
|
+
if (!match) return null;
|
|
600
|
+
try {
|
|
601
|
+
const params = match[1];
|
|
602
|
+
const base64Data = match[2];
|
|
603
|
+
const data = Buffer.from(base64Data, "base64");
|
|
604
|
+
let format = "png";
|
|
605
|
+
const formatMatch = params.match(/f=(\d+)/);
|
|
606
|
+
if (formatMatch) {
|
|
607
|
+
const formatCode = parseInt(formatMatch[1], 10);
|
|
608
|
+
if (formatCode === 100) format = "jpg";
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
type: "paste",
|
|
612
|
+
data,
|
|
613
|
+
format,
|
|
614
|
+
protocol: "kitty"
|
|
615
|
+
};
|
|
616
|
+
} catch (error) {
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Extract Sixel image
|
|
622
|
+
* Format: ESC P q ... ESC \
|
|
623
|
+
*/
|
|
624
|
+
static extractSixelImage(input) {
|
|
625
|
+
const match = input.match(/\x1bP([^q]*q[^\x1b]+)\x1b\\/);
|
|
626
|
+
if (!match) return null;
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Extract base64 data URL
|
|
631
|
+
* Format: data:image/png;base64,iVBORw0KGgo...
|
|
632
|
+
*/
|
|
633
|
+
static extractBase64Image(input) {
|
|
634
|
+
const match = input.match(/data:image\/(png|jpeg|jpg|gif|webp);base64,([A-Za-z0-9+/=]+)/);
|
|
635
|
+
if (!match) return null;
|
|
636
|
+
try {
|
|
637
|
+
const format = match[1] === "jpeg" ? "jpg" : match[1];
|
|
638
|
+
const base64Data = match[2];
|
|
639
|
+
const data = Buffer.from(base64Data, "base64");
|
|
640
|
+
return {
|
|
641
|
+
type: "paste",
|
|
642
|
+
data,
|
|
643
|
+
format,
|
|
644
|
+
protocol: "base64"
|
|
645
|
+
};
|
|
646
|
+
} catch (error) {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Extract dropped file path and read image
|
|
652
|
+
*/
|
|
653
|
+
static extractDroppedFile(input) {
|
|
654
|
+
if (!this.looksLikeFilePath(input)) return null;
|
|
655
|
+
let filepath = input.trim();
|
|
656
|
+
filepath = filepath.replace(/^["']|["']$/g, "");
|
|
657
|
+
filepath = filepath.replace(/\\\s/g, " ");
|
|
658
|
+
if (!existsSync(filepath)) return null;
|
|
659
|
+
const stats = statSync2(filepath);
|
|
660
|
+
if (!stats.isFile()) return null;
|
|
661
|
+
const ext = extname2(filepath).toLowerCase();
|
|
662
|
+
if (!this.SUPPORTED_EXTENSIONS.includes(ext)) return null;
|
|
663
|
+
if (stats.size > this.MAX_IMAGE_SIZE) return null;
|
|
664
|
+
try {
|
|
665
|
+
const data = readFileSync(filepath);
|
|
666
|
+
const format = ext.substring(1) === "jpeg" ? "jpg" : ext.substring(1);
|
|
667
|
+
const filename = filepath.split("/").pop() || "image";
|
|
668
|
+
return {
|
|
669
|
+
type: "drop",
|
|
670
|
+
data,
|
|
671
|
+
format,
|
|
672
|
+
filename,
|
|
673
|
+
filepath
|
|
674
|
+
};
|
|
675
|
+
} catch (error) {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Check if string looks like a file path
|
|
681
|
+
*/
|
|
682
|
+
static looksLikeFilePath(input) {
|
|
683
|
+
const trimmed = input.trim();
|
|
684
|
+
if (trimmed.startsWith("/") || trimmed.match(/^[a-zA-Z]:\\/)) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
if (trimmed.startsWith("~")) {
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
if (this.SUPPORTED_EXTENSIONS.some((ext) => trimmed.toLowerCase().endsWith(ext))) {
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Remove image data from input string, leaving only text
|
|
697
|
+
*/
|
|
698
|
+
static stripImageData(input) {
|
|
699
|
+
let cleaned = input;
|
|
700
|
+
cleaned = cleaned.replace(/\x1b\]1337;File=[^:]*:[^\x07]+\x07/g, "");
|
|
701
|
+
cleaned = cleaned.replace(/\x1b_G[^;]*;[^\x1b]+\x1b\\/g, "");
|
|
702
|
+
cleaned = cleaned.replace(/\x1bP[^q]*q[^\x1b]+\x1b\\/g, "");
|
|
703
|
+
cleaned = cleaned.replace(/data:image\/(png|jpeg|jpg|gif|webp);base64,[A-Za-z0-9+/=]+/g, "");
|
|
704
|
+
const filepath = this.extractDroppedFile(cleaned);
|
|
705
|
+
if (filepath) {
|
|
706
|
+
cleaned = cleaned.replace(filepath.filepath, "");
|
|
707
|
+
}
|
|
708
|
+
return cleaned.trim();
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
|
|
416
712
|
// src/components/InputPrompt.tsx
|
|
417
713
|
function findAtTrigger(value) {
|
|
418
714
|
for (let i = value.length - 1; i >= 0; i--) {
|
|
@@ -429,14 +725,13 @@ function findAtTrigger(value) {
|
|
|
429
725
|
}
|
|
430
726
|
return null;
|
|
431
727
|
}
|
|
432
|
-
function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
433
|
-
const [value, setValue] =
|
|
434
|
-
const [selectedIndex, setSelectedIndex] =
|
|
435
|
-
const [historyIndex, setHistoryIndex] =
|
|
436
|
-
const [tempInput, setTempInput] =
|
|
437
|
-
const [
|
|
438
|
-
const [
|
|
439
|
-
const [fileSelectedIndex, setFileSelectedIndex] = useState(0);
|
|
728
|
+
function InputPrompt({ onSubmit, onImageDetected, disabled = false, history = [] }) {
|
|
729
|
+
const [value, setValue] = useState2("");
|
|
730
|
+
const [selectedIndex, setSelectedIndex] = useState2(0);
|
|
731
|
+
const [historyIndex, setHistoryIndex] = useState2(-1);
|
|
732
|
+
const [tempInput, setTempInput] = useState2("");
|
|
733
|
+
const [fileAutocomplete, setFileAutocomplete] = useState2(null);
|
|
734
|
+
const [fileSelectedIndex, setFileSelectedIndex] = useState2(0);
|
|
440
735
|
const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active;
|
|
441
736
|
const commandQuery = shouldShowCommandAutocomplete ? value.slice(1) : "";
|
|
442
737
|
const filteredCommands = useMemo(() => {
|
|
@@ -453,7 +748,7 @@ function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
|
453
748
|
useEffect(() => {
|
|
454
749
|
setFileSelectedIndex(0);
|
|
455
750
|
}, [filteredFiles]);
|
|
456
|
-
|
|
751
|
+
useInput2((_input, key) => {
|
|
457
752
|
if (fileAutocomplete?.active && filteredFiles.length > 0) {
|
|
458
753
|
if (key.upArrow) {
|
|
459
754
|
setFileSelectedIndex((prev) => prev > 0 ? prev - 1 : filteredFiles.length - 1);
|
|
@@ -510,7 +805,6 @@ function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
|
510
805
|
const pathToInsert = file.isDirectory ? `@${file.path}/` : `@${file.path}`;
|
|
511
806
|
const newValue = beforeAt + pathToInsert + afterQuery;
|
|
512
807
|
setValue(newValue);
|
|
513
|
-
setInputKey((prev) => prev + 1);
|
|
514
808
|
if (file.isDirectory) {
|
|
515
809
|
setFileAutocomplete({
|
|
516
810
|
active: true,
|
|
@@ -537,7 +831,6 @@ function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
|
537
831
|
if (selectedCommand.args) {
|
|
538
832
|
setValue(commandText + " ");
|
|
539
833
|
setHistoryIndex(-1);
|
|
540
|
-
setInputKey((prev) => prev + 1);
|
|
541
834
|
return;
|
|
542
835
|
}
|
|
543
836
|
setValue("");
|
|
@@ -550,7 +843,15 @@ function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
|
550
843
|
setHistoryIndex(-1);
|
|
551
844
|
setFileAutocomplete(null);
|
|
552
845
|
};
|
|
553
|
-
const handleChange = (newValue) => {
|
|
846
|
+
const handleChange = async (newValue) => {
|
|
847
|
+
if (ImageInputDetector.containsImageData(newValue)) {
|
|
848
|
+
const imageEvent = ImageInputDetector.extractImageData(newValue);
|
|
849
|
+
if (imageEvent && onImageDetected) {
|
|
850
|
+
const placeholder = await onImageDetected(imageEvent.data);
|
|
851
|
+
setValue(`${placeholder} `);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
554
855
|
setValue(newValue);
|
|
555
856
|
if (historyIndex !== -1) {
|
|
556
857
|
setHistoryIndex(-1);
|
|
@@ -567,29 +868,28 @@ function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
|
567
868
|
setFileAutocomplete(null);
|
|
568
869
|
}
|
|
569
870
|
};
|
|
570
|
-
return /* @__PURE__ */
|
|
571
|
-
|
|
871
|
+
return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "cyan" }, "> "), /* @__PURE__ */ React5.createElement(
|
|
872
|
+
CustomTextInput,
|
|
572
873
|
{
|
|
573
|
-
key: inputKey,
|
|
574
874
|
value,
|
|
575
875
|
onChange: handleChange,
|
|
576
876
|
onSubmit: handleSubmit,
|
|
577
877
|
placeholder: disabled ? "Thinking..." : "Type your message, /help for commands, or @file to reference",
|
|
578
878
|
showCursor: !disabled
|
|
579
879
|
}
|
|
580
|
-
)), shouldShowCommandAutocomplete && /* @__PURE__ */
|
|
880
|
+
)), shouldShowCommandAutocomplete && /* @__PURE__ */ React5.createElement(CommandAutocomplete, { commands: filteredCommands, selectedIndex }), fileAutocomplete?.active && /* @__PURE__ */ React5.createElement(FileAutocomplete, { files: filteredFiles, selectedIndex: fileSelectedIndex, query: fileAutocomplete.query }));
|
|
581
881
|
}
|
|
582
882
|
|
|
583
883
|
// src/components/AgentThinking.tsx
|
|
584
|
-
import
|
|
884
|
+
import React7 from "react";
|
|
585
885
|
import { Box as Box6 } from "ink";
|
|
586
886
|
|
|
587
887
|
// src/components/ThoughtStream.tsx
|
|
588
|
-
import
|
|
589
|
-
import { Box as Box5, Text as
|
|
888
|
+
import React6 from "react";
|
|
889
|
+
import { Box as Box5, Text as Text6 } from "ink";
|
|
590
890
|
import Spinner from "ink-spinner";
|
|
591
|
-
var ThoughtStream =
|
|
592
|
-
return /* @__PURE__ */
|
|
891
|
+
var ThoughtStream = React6.memo(function ThoughtStream2({ steps }) {
|
|
892
|
+
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...")));
|
|
593
893
|
});
|
|
594
894
|
|
|
595
895
|
// src/store/index.ts
|
|
@@ -618,32 +918,32 @@ var useCliStore = create((set) => ({
|
|
|
618
918
|
}));
|
|
619
919
|
|
|
620
920
|
// src/components/AgentThinking.tsx
|
|
621
|
-
var AgentThinking =
|
|
921
|
+
var AgentThinking = React7.memo(function AgentThinking2() {
|
|
622
922
|
const isThinking = useCliStore((state) => state.isThinking);
|
|
623
923
|
const agentSteps = useCliStore((state) => state.agentSteps);
|
|
624
924
|
if (!isThinking) {
|
|
625
925
|
return null;
|
|
626
926
|
}
|
|
627
|
-
return /* @__PURE__ */
|
|
927
|
+
return /* @__PURE__ */ React7.createElement(Box6, { borderStyle: "single", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React7.createElement(ThoughtStream, { steps: agentSteps }));
|
|
628
928
|
});
|
|
629
929
|
|
|
630
930
|
// src/components/PermissionPrompt.tsx
|
|
631
|
-
import
|
|
632
|
-
import { Box as Box7, Text as
|
|
931
|
+
import React8 from "react";
|
|
932
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
633
933
|
import SelectInput from "ink-select-input";
|
|
634
934
|
function renderDiffPreview(preview) {
|
|
635
935
|
const lines = preview.split("\n");
|
|
636
936
|
return lines.map((line, index) => {
|
|
637
937
|
if (line.startsWith("+")) {
|
|
638
|
-
return /* @__PURE__ */
|
|
938
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "green" }, line);
|
|
639
939
|
}
|
|
640
940
|
if (line.startsWith("-")) {
|
|
641
|
-
return /* @__PURE__ */
|
|
941
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "red" }, line);
|
|
642
942
|
}
|
|
643
943
|
if (line.startsWith("@@")) {
|
|
644
|
-
return /* @__PURE__ */
|
|
944
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "cyan" }, line);
|
|
645
945
|
}
|
|
646
|
-
return /* @__PURE__ */
|
|
946
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, dimColor: true }, line);
|
|
647
947
|
});
|
|
648
948
|
}
|
|
649
949
|
function PermissionPrompt({
|
|
@@ -663,12 +963,12 @@ function PermissionPrompt({
|
|
|
663
963
|
{ label: "\u2717 Deny", value: "deny" }
|
|
664
964
|
];
|
|
665
965
|
const argsString = typeof args === "string" ? args : JSON.stringify(args, null, 2);
|
|
666
|
-
return /* @__PURE__ */
|
|
966
|
+
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) })));
|
|
667
967
|
}
|
|
668
968
|
|
|
669
969
|
// src/components/ConfigEditor.tsx
|
|
670
|
-
import
|
|
671
|
-
import { Box as Box8, Text as
|
|
970
|
+
import React9, { useState as useState3, useMemo as useMemo2 } from "react";
|
|
971
|
+
import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
|
|
672
972
|
var MAX_ITERATIONS_OPTIONS = [
|
|
673
973
|
{ label: "10", value: 10 },
|
|
674
974
|
{ label: "20", value: 20 },
|
|
@@ -685,103 +985,128 @@ var EXPORT_FORMAT_OPTIONS = [
|
|
|
685
985
|
{ label: "markdown", value: "markdown" },
|
|
686
986
|
{ label: "json", value: "json" }
|
|
687
987
|
];
|
|
688
|
-
|
|
689
|
-
{
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
step: 256,
|
|
710
|
-
getValue: (config) => config.preferences.maxTokens,
|
|
711
|
-
setValue: (config, value) => ({
|
|
712
|
-
...config,
|
|
713
|
-
preferences: {
|
|
714
|
-
...config.preferences,
|
|
715
|
-
maxTokens: value
|
|
716
|
-
}
|
|
717
|
-
})
|
|
718
|
-
},
|
|
719
|
-
{
|
|
720
|
-
key: "temperature",
|
|
721
|
-
label: "Temperature",
|
|
722
|
-
type: "number",
|
|
723
|
-
min: 0,
|
|
724
|
-
max: 2,
|
|
725
|
-
step: 0.1,
|
|
726
|
-
getValue: (config) => config.preferences.temperature,
|
|
727
|
-
setValue: (config, value) => ({
|
|
728
|
-
...config,
|
|
729
|
-
preferences: {
|
|
730
|
-
...config.preferences,
|
|
731
|
-
temperature: value
|
|
732
|
-
}
|
|
733
|
-
})
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
key: "autoSave",
|
|
737
|
-
label: "Auto Save",
|
|
738
|
-
type: "boolean",
|
|
739
|
-
getValue: (config) => config.preferences.autoSave,
|
|
740
|
-
setValue: (config, value) => ({
|
|
741
|
-
...config,
|
|
742
|
-
preferences: {
|
|
743
|
-
...config.preferences,
|
|
744
|
-
autoSave: value
|
|
745
|
-
}
|
|
746
|
-
})
|
|
747
|
-
},
|
|
748
|
-
{
|
|
749
|
-
key: "theme",
|
|
750
|
-
label: "Theme",
|
|
751
|
-
type: "select",
|
|
752
|
-
options: THEME_OPTIONS,
|
|
753
|
-
getValue: (config) => config.preferences.theme,
|
|
754
|
-
setValue: (config, value) => ({
|
|
755
|
-
...config,
|
|
756
|
-
preferences: {
|
|
757
|
-
...config.preferences,
|
|
758
|
-
theme: value
|
|
759
|
-
}
|
|
760
|
-
})
|
|
761
|
-
},
|
|
762
|
-
{
|
|
763
|
-
key: "exportFormat",
|
|
764
|
-
label: "Export Format",
|
|
765
|
-
type: "select",
|
|
766
|
-
options: EXPORT_FORMAT_OPTIONS,
|
|
767
|
-
getValue: (config) => config.preferences.exportFormat,
|
|
768
|
-
setValue: (config, value) => ({
|
|
769
|
-
...config,
|
|
770
|
-
preferences: {
|
|
771
|
-
...config.preferences,
|
|
772
|
-
exportFormat: value
|
|
773
|
-
}
|
|
774
|
-
})
|
|
988
|
+
function buildConfigItems(availableModels) {
|
|
989
|
+
const modelOptions = availableModels.map((model) => ({
|
|
990
|
+
label: model.name,
|
|
991
|
+
value: model.id
|
|
992
|
+
}));
|
|
993
|
+
const items = [];
|
|
994
|
+
if (modelOptions.length > 0) {
|
|
995
|
+
items.push({
|
|
996
|
+
key: "model",
|
|
997
|
+
label: "Model",
|
|
998
|
+
type: "select",
|
|
999
|
+
options: modelOptions,
|
|
1000
|
+
getValue: (config) => {
|
|
1001
|
+
const modelExists = modelOptions.some((opt) => opt.value === config.defaultModel);
|
|
1002
|
+
return modelExists ? config.defaultModel : modelOptions[0]?.value ?? config.defaultModel;
|
|
1003
|
+
},
|
|
1004
|
+
setValue: (config, value) => ({
|
|
1005
|
+
...config,
|
|
1006
|
+
defaultModel: value
|
|
1007
|
+
})
|
|
1008
|
+
});
|
|
775
1009
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1010
|
+
items.push(
|
|
1011
|
+
{
|
|
1012
|
+
key: "maxIterations",
|
|
1013
|
+
label: "Max Iterations",
|
|
1014
|
+
type: "select",
|
|
1015
|
+
options: MAX_ITERATIONS_OPTIONS,
|
|
1016
|
+
getValue: (config) => config.preferences.maxIterations,
|
|
1017
|
+
setValue: (config, value) => ({
|
|
1018
|
+
...config,
|
|
1019
|
+
preferences: {
|
|
1020
|
+
...config.preferences,
|
|
1021
|
+
maxIterations: value
|
|
1022
|
+
}
|
|
1023
|
+
})
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
key: "maxTokens",
|
|
1027
|
+
label: "Max Tokens",
|
|
1028
|
+
type: "number",
|
|
1029
|
+
min: 256,
|
|
1030
|
+
max: 128e3,
|
|
1031
|
+
step: 256,
|
|
1032
|
+
getValue: (config) => config.preferences.maxTokens,
|
|
1033
|
+
setValue: (config, value) => ({
|
|
1034
|
+
...config,
|
|
1035
|
+
preferences: {
|
|
1036
|
+
...config.preferences,
|
|
1037
|
+
maxTokens: value
|
|
1038
|
+
}
|
|
1039
|
+
})
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
key: "temperature",
|
|
1043
|
+
label: "Temperature",
|
|
1044
|
+
type: "number",
|
|
1045
|
+
min: 0,
|
|
1046
|
+
max: 2,
|
|
1047
|
+
step: 0.1,
|
|
1048
|
+
getValue: (config) => config.preferences.temperature,
|
|
1049
|
+
setValue: (config, value) => ({
|
|
1050
|
+
...config,
|
|
1051
|
+
preferences: {
|
|
1052
|
+
...config.preferences,
|
|
1053
|
+
temperature: value
|
|
1054
|
+
}
|
|
1055
|
+
})
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
key: "autoSave",
|
|
1059
|
+
label: "Auto Save",
|
|
1060
|
+
type: "boolean",
|
|
1061
|
+
getValue: (config) => config.preferences.autoSave,
|
|
1062
|
+
setValue: (config, value) => ({
|
|
1063
|
+
...config,
|
|
1064
|
+
preferences: {
|
|
1065
|
+
...config.preferences,
|
|
1066
|
+
autoSave: value
|
|
1067
|
+
}
|
|
1068
|
+
})
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
key: "theme",
|
|
1072
|
+
label: "Theme",
|
|
1073
|
+
type: "select",
|
|
1074
|
+
options: THEME_OPTIONS,
|
|
1075
|
+
getValue: (config) => config.preferences.theme,
|
|
1076
|
+
setValue: (config, value) => ({
|
|
1077
|
+
...config,
|
|
1078
|
+
preferences: {
|
|
1079
|
+
...config.preferences,
|
|
1080
|
+
theme: value
|
|
1081
|
+
}
|
|
1082
|
+
})
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
key: "exportFormat",
|
|
1086
|
+
label: "Export Format",
|
|
1087
|
+
type: "select",
|
|
1088
|
+
options: EXPORT_FORMAT_OPTIONS,
|
|
1089
|
+
getValue: (config) => config.preferences.exportFormat,
|
|
1090
|
+
setValue: (config, value) => ({
|
|
1091
|
+
...config,
|
|
1092
|
+
preferences: {
|
|
1093
|
+
...config.preferences,
|
|
1094
|
+
exportFormat: value
|
|
1095
|
+
}
|
|
1096
|
+
})
|
|
1097
|
+
}
|
|
1098
|
+
);
|
|
1099
|
+
return items;
|
|
1100
|
+
}
|
|
1101
|
+
function ConfigEditor({ config, availableModels, onSave, onClose }) {
|
|
1102
|
+
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
1103
|
+
const [editedConfig, setEditedConfig] = useState3(config);
|
|
1104
|
+
const [saveError, setSaveError] = useState3(null);
|
|
1105
|
+
const [isSaving, setIsSaving] = useState3(false);
|
|
1106
|
+
const configItems = useMemo2(() => buildConfigItems(availableModels), [availableModels]);
|
|
782
1107
|
const hasChanges = useMemo2(() => {
|
|
783
|
-
return JSON.stringify(config.preferences) !== JSON.stringify(editedConfig.preferences);
|
|
784
|
-
}, [config.preferences, editedConfig.preferences]);
|
|
1108
|
+
return JSON.stringify(config.preferences) !== JSON.stringify(editedConfig.preferences) || config.defaultModel !== editedConfig.defaultModel;
|
|
1109
|
+
}, [config.preferences, editedConfig.preferences, config.defaultModel, editedConfig.defaultModel]);
|
|
785
1110
|
const handleSaveAndClose = async () => {
|
|
786
1111
|
if (hasChanges) {
|
|
787
1112
|
try {
|
|
@@ -797,14 +1122,14 @@ function ConfigEditor({ config, onSave, onClose }) {
|
|
|
797
1122
|
}
|
|
798
1123
|
onClose();
|
|
799
1124
|
};
|
|
800
|
-
|
|
801
|
-
const currentItem =
|
|
1125
|
+
useInput3((input, key) => {
|
|
1126
|
+
const currentItem = configItems[selectedIndex];
|
|
802
1127
|
if (key.upArrow) {
|
|
803
|
-
setSelectedIndex((prev) => prev > 0 ? prev - 1 :
|
|
1128
|
+
setSelectedIndex((prev) => prev > 0 ? prev - 1 : configItems.length - 1);
|
|
804
1129
|
return;
|
|
805
1130
|
}
|
|
806
1131
|
if (key.downArrow) {
|
|
807
|
-
setSelectedIndex((prev) => prev <
|
|
1132
|
+
setSelectedIndex((prev) => prev < configItems.length - 1 ? prev + 1 : 0);
|
|
808
1133
|
return;
|
|
809
1134
|
}
|
|
810
1135
|
if ((key.escape || input === "q") && !isSaving) {
|
|
@@ -862,13 +1187,13 @@ function ConfigEditor({ config, onSave, onClose }) {
|
|
|
862
1187
|
}
|
|
863
1188
|
return String(value);
|
|
864
1189
|
};
|
|
865
|
-
return /* @__PURE__ */
|
|
1190
|
+
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) => {
|
|
866
1191
|
const isSelected = index === selectedIndex;
|
|
867
1192
|
const value = item.getValue(editedConfig);
|
|
868
1193
|
const displayValue = formatValue(item, value);
|
|
869
1194
|
const hasOptions = item.type === "select" || item.type === "boolean" || item.type === "number";
|
|
870
|
-
return /* @__PURE__ */
|
|
871
|
-
})), /* @__PURE__ */
|
|
1195
|
+
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 }, " >")));
|
|
1196
|
+
})), /* @__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")));
|
|
872
1197
|
}
|
|
873
1198
|
|
|
874
1199
|
// src/utils/processFileReferences.ts
|
|
@@ -990,8 +1315,10 @@ function App({
|
|
|
990
1315
|
onMessage,
|
|
991
1316
|
onCommand,
|
|
992
1317
|
onPermissionResponse,
|
|
1318
|
+
onImageDetected,
|
|
993
1319
|
commandHistory = [],
|
|
994
1320
|
config,
|
|
1321
|
+
availableModels = [],
|
|
995
1322
|
onSaveConfig
|
|
996
1323
|
}) {
|
|
997
1324
|
const messages = useCliStore((state) => state.session?.messages || []);
|
|
@@ -1004,11 +1331,11 @@ function App({
|
|
|
1004
1331
|
const setShowConfigEditor = useCliStore((state) => state.setShowConfigEditor);
|
|
1005
1332
|
const exitRequested = useCliStore((state) => state.exitRequested);
|
|
1006
1333
|
const setIsThinking = useCliStore((state) => state.setIsThinking);
|
|
1007
|
-
const handleSubmit =
|
|
1334
|
+
const handleSubmit = React10.useCallback(
|
|
1008
1335
|
async (input) => {
|
|
1009
1336
|
const trimmed = input.trim();
|
|
1010
1337
|
if (!trimmed) return;
|
|
1011
|
-
if (trimmed.startsWith("/")) {
|
|
1338
|
+
if (trimmed.startsWith("/") && !trimmed.match(/^\/[A-Z]/)) {
|
|
1012
1339
|
const [command, ...args] = trimmed.slice(1).split(" ");
|
|
1013
1340
|
await onCommand(command, args);
|
|
1014
1341
|
return;
|
|
@@ -1033,7 +1360,15 @@ ${errorBlock}`;
|
|
|
1033
1360
|
},
|
|
1034
1361
|
[onMessage, onCommand, setIsThinking]
|
|
1035
1362
|
);
|
|
1036
|
-
return /* @__PURE__ */
|
|
1363
|
+
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(
|
|
1364
|
+
ConfigEditor,
|
|
1365
|
+
{
|
|
1366
|
+
config,
|
|
1367
|
+
availableModels,
|
|
1368
|
+
onSave: onSaveConfig,
|
|
1369
|
+
onClose: () => setShowConfigEditor(false)
|
|
1370
|
+
}
|
|
1371
|
+
)) : permissionPrompt ? /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React10.createElement(
|
|
1037
1372
|
PermissionPrompt,
|
|
1038
1373
|
{
|
|
1039
1374
|
toolName: permissionPrompt.toolName,
|
|
@@ -1042,39 +1377,47 @@ ${errorBlock}`;
|
|
|
1042
1377
|
canBeTrusted: permissionPrompt.canBeTrusted,
|
|
1043
1378
|
onResponse: onPermissionResponse
|
|
1044
1379
|
}
|
|
1045
|
-
)) : /* @__PURE__ */
|
|
1380
|
+
)) : /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Static, { items: messages }, (message, index) => /* @__PURE__ */ React10.createElement(Box9, { key: index, flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React10.createElement(MessageItem, { message }))), /* @__PURE__ */ React10.createElement(AgentThinking, null), exitRequested && /* @__PURE__ */ React10.createElement(Box9, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow", bold: true }, "Press Ctrl+C again to exit")), /* @__PURE__ */ React10.createElement(Box9, { borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React10.createElement(
|
|
1381
|
+
InputPrompt,
|
|
1382
|
+
{
|
|
1383
|
+
onSubmit: handleSubmit,
|
|
1384
|
+
onImageDetected,
|
|
1385
|
+
disabled: isThinking,
|
|
1386
|
+
history: commandHistory
|
|
1387
|
+
}
|
|
1388
|
+
))));
|
|
1046
1389
|
}
|
|
1047
1390
|
function MessageItem({ message }) {
|
|
1048
|
-
return /* @__PURE__ */
|
|
1391
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: message.role === "user" ? "cyan" : "green" }, message.role === "user" ? "\u{1F464} You" : "\u{1F916} Assistant"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React10.createElement(Text9, null, message.content)), message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true, bold: true }, "\u{1F527} Agent Reasoning Trace (", message.metadata.steps.filter((s) => s.type === "action").length, " ", "tools used, ", message.metadata.steps.length, " total steps)", message.metadata.tokenUsage && ` \u2022 ${message.metadata.tokenUsage.total} tokens`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
|
|
1049
1392
|
if (step.type === "thought") {
|
|
1050
|
-
return /* @__PURE__ */
|
|
1393
|
+
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 ? "..." : ""}`));
|
|
1051
1394
|
}
|
|
1052
1395
|
if (step.type === "action") {
|
|
1053
1396
|
const toolName = step.metadata?.toolName || "unknown";
|
|
1054
1397
|
const toolInput = step.metadata?.toolInput;
|
|
1055
1398
|
const observationStep = message.metadata.steps[idx + 1];
|
|
1056
1399
|
const result = observationStep?.type === "observation" ? observationStep.content : null;
|
|
1057
|
-
return /* @__PURE__ */
|
|
1400
|
+
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 ? "..." : ""}`));
|
|
1058
1401
|
}
|
|
1059
1402
|
return null;
|
|
1060
|
-
}).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */
|
|
1403
|
+
}).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)));
|
|
1061
1404
|
}
|
|
1062
1405
|
|
|
1063
1406
|
// src/components/MessageList.tsx
|
|
1064
|
-
import
|
|
1065
|
-
import { Box as Box10, Text as
|
|
1407
|
+
import React11 from "react";
|
|
1408
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1066
1409
|
function stripThinkingTags(content) {
|
|
1067
1410
|
return content.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
1068
1411
|
}
|
|
1069
|
-
var MessageList =
|
|
1412
|
+
var MessageList = React11.memo(
|
|
1070
1413
|
function MessageList2({ messages }) {
|
|
1071
1414
|
if (messages.length === 0) {
|
|
1072
|
-
return /* @__PURE__ */
|
|
1415
|
+
return /* @__PURE__ */ React11.createElement(Box10, { paddingY: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "No messages yet. Type a message to start!"));
|
|
1073
1416
|
}
|
|
1074
|
-
return /* @__PURE__ */
|
|
1417
|
+
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) => {
|
|
1075
1418
|
if (step.type === "thought") {
|
|
1076
|
-
return /* @__PURE__ */
|
|
1077
|
-
|
|
1419
|
+
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(
|
|
1420
|
+
Text10,
|
|
1078
1421
|
{
|
|
1079
1422
|
dimColor: true
|
|
1080
1423
|
},
|
|
@@ -1086,10 +1429,10 @@ var MessageList = React10.memo(
|
|
|
1086
1429
|
const toolInput = step.metadata?.toolInput;
|
|
1087
1430
|
const observationStep = message.metadata.steps[idx + 1];
|
|
1088
1431
|
const result = observationStep?.type === "observation" ? observationStep.content : null;
|
|
1089
|
-
return /* @__PURE__ */
|
|
1432
|
+
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 ? "..." : ""}`));
|
|
1090
1433
|
}
|
|
1091
1434
|
return null;
|
|
1092
|
-
}).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */
|
|
1435
|
+
}).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`)))));
|
|
1093
1436
|
},
|
|
1094
1437
|
(prevProps, nextProps) => {
|
|
1095
1438
|
if (prevProps.messages.length !== nextProps.messages.length) {
|
|
@@ -1099,9 +1442,42 @@ var MessageList = React10.memo(
|
|
|
1099
1442
|
}
|
|
1100
1443
|
);
|
|
1101
1444
|
|
|
1445
|
+
// src/components/TrustLocationSelector.tsx
|
|
1446
|
+
import React12 from "react";
|
|
1447
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1448
|
+
import SelectInput2 from "ink-select-input";
|
|
1449
|
+
function TrustLocationSelector({ inProject, onSelect, onCancel }) {
|
|
1450
|
+
const items = [];
|
|
1451
|
+
if (inProject) {
|
|
1452
|
+
items.push({
|
|
1453
|
+
label: "Project settings (local) - Saved in .bike4mind/local.json (not committed)",
|
|
1454
|
+
value: "local"
|
|
1455
|
+
});
|
|
1456
|
+
items.push({
|
|
1457
|
+
label: "Project settings (team-wide) - Checked in at .bike4mind/config.json",
|
|
1458
|
+
value: "project"
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
items.push({
|
|
1462
|
+
label: "User settings - Saved at ~/.bike4mind/config.json",
|
|
1463
|
+
value: "global"
|
|
1464
|
+
});
|
|
1465
|
+
const handleSelect = (item) => {
|
|
1466
|
+
onSelect(item.value);
|
|
1467
|
+
};
|
|
1468
|
+
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(
|
|
1469
|
+
SelectInput2,
|
|
1470
|
+
{
|
|
1471
|
+
items,
|
|
1472
|
+
onSelect: handleSelect,
|
|
1473
|
+
itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
|
|
1474
|
+
}
|
|
1475
|
+
), /* @__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")));
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1102
1478
|
// src/components/LoginFlow.tsx
|
|
1103
|
-
import
|
|
1104
|
-
import { Box as
|
|
1479
|
+
import React13, { useState as useState4, useEffect as useEffect2 } from "react";
|
|
1480
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1105
1481
|
import Spinner2 from "ink-spinner";
|
|
1106
1482
|
import jwt from "jsonwebtoken";
|
|
1107
1483
|
import open from "open";
|
|
@@ -1217,10 +1593,10 @@ var OAuthClient = class {
|
|
|
1217
1593
|
|
|
1218
1594
|
// src/components/LoginFlow.tsx
|
|
1219
1595
|
function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
|
|
1220
|
-
const [status, setStatus] =
|
|
1221
|
-
const [deviceFlow, setDeviceFlow] =
|
|
1222
|
-
const [statusMessage, setStatusMessage] =
|
|
1223
|
-
const [error, setError] =
|
|
1596
|
+
const [status, setStatus] = useState4("initiating");
|
|
1597
|
+
const [deviceFlow, setDeviceFlow] = useState4(null);
|
|
1598
|
+
const [statusMessage, setStatusMessage] = useState4("Initiating device authorization...");
|
|
1599
|
+
const [error, setError] = useState4(null);
|
|
1224
1600
|
useEffect2(() => {
|
|
1225
1601
|
const runLoginFlow = async () => {
|
|
1226
1602
|
const oauth = new OAuthClient(apiUrl);
|
|
@@ -1264,15 +1640,15 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
|
|
|
1264
1640
|
}
|
|
1265
1641
|
}, [deviceFlow, status]);
|
|
1266
1642
|
if (status === "initiating") {
|
|
1267
|
-
return /* @__PURE__ */
|
|
1643
|
+
return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan" }, /* @__PURE__ */ React13.createElement(Spinner2, { type: "dots" }), " Initiating device authorization...")));
|
|
1268
1644
|
}
|
|
1269
1645
|
if (status === "error") {
|
|
1270
|
-
return /* @__PURE__ */
|
|
1646
|
+
return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, "\u2716 Authentication Failed")), /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Text12, { color: "red" }, error)));
|
|
1271
1647
|
}
|
|
1272
1648
|
if (status === "success") {
|
|
1273
|
-
return /* @__PURE__ */
|
|
1649
|
+
return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "green", bold: true }, "\u2714 Successfully authenticated!")), /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "You can now use B4M CLI with your account.")));
|
|
1274
1650
|
}
|
|
1275
|
-
return /* @__PURE__ */
|
|
1651
|
+
return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" }, /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan", bold: true }, "\u{1F510} Device Authorization")), /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "Opening browser automatically... If it doesn't open, please visit:")), /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React13.createElement(Text12, { color: "blue", bold: true }, deviceFlow?.verification_uri)), /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "And enter this code when prompted:")), /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React13.createElement(Text12, { color: "yellow", bold: true }, deviceFlow?.user_code)), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan" }, /* @__PURE__ */ React13.createElement(Spinner2, { type: "dots" }), " ", statusMessage)), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "Expires in ", deviceFlow ? Math.floor(deviceFlow.expires_in / 60) : 0, " minutes")));
|
|
1276
1652
|
}
|
|
1277
1653
|
|
|
1278
1654
|
// src/storage/SessionStore.ts
|
|
@@ -1394,7 +1770,7 @@ var SessionStore = class {
|
|
|
1394
1770
|
};
|
|
1395
1771
|
|
|
1396
1772
|
// src/storage/ConfigStore.ts
|
|
1397
|
-
import { promises as fs4 } from "fs";
|
|
1773
|
+
import { promises as fs4, existsSync as existsSync3 } from "fs";
|
|
1398
1774
|
import path4 from "path";
|
|
1399
1775
|
import { homedir as homedir2 } from "os";
|
|
1400
1776
|
import { v4 as uuidv4 } from "uuid";
|
|
@@ -1421,8 +1797,8 @@ var CliConfigSchema = z.object({
|
|
|
1421
1797
|
mcpServers: z.array(
|
|
1422
1798
|
z.object({
|
|
1423
1799
|
name: z.string(),
|
|
1424
|
-
command: z.string(),
|
|
1425
|
-
args: z.array(z.string()),
|
|
1800
|
+
command: z.string().optional(),
|
|
1801
|
+
args: z.array(z.string()).optional(),
|
|
1426
1802
|
env: z.record(z.string()),
|
|
1427
1803
|
enabled: z.boolean()
|
|
1428
1804
|
})
|
|
@@ -1442,6 +1818,53 @@ var CliConfigSchema = z.object({
|
|
|
1442
1818
|
}),
|
|
1443
1819
|
trustedTools: z.array(z.string()).optional().default([])
|
|
1444
1820
|
});
|
|
1821
|
+
var ProjectConfigSchema = z.object({
|
|
1822
|
+
tools: z.object({
|
|
1823
|
+
enabled: z.array(z.string()).optional(),
|
|
1824
|
+
denied: z.array(z.string()).optional(),
|
|
1825
|
+
config: z.record(z.any()).optional()
|
|
1826
|
+
}).optional(),
|
|
1827
|
+
defaultModel: z.string().optional(),
|
|
1828
|
+
mcpServers: z.array(
|
|
1829
|
+
z.object({
|
|
1830
|
+
name: z.string(),
|
|
1831
|
+
command: z.string().optional(),
|
|
1832
|
+
args: z.array(z.string()).optional(),
|
|
1833
|
+
env: z.record(z.string()),
|
|
1834
|
+
enabled: z.boolean()
|
|
1835
|
+
})
|
|
1836
|
+
).optional(),
|
|
1837
|
+
preferences: z.object({
|
|
1838
|
+
maxTokens: z.number().optional(),
|
|
1839
|
+
temperature: z.number().optional(),
|
|
1840
|
+
autoSave: z.boolean().optional(),
|
|
1841
|
+
theme: z.enum(["light", "dark"]).optional(),
|
|
1842
|
+
exportFormat: z.enum(["markdown", "json"]).optional()
|
|
1843
|
+
}).optional()
|
|
1844
|
+
});
|
|
1845
|
+
var ProjectLocalConfigSchema = z.object({
|
|
1846
|
+
trustedTools: z.array(z.string()).optional(),
|
|
1847
|
+
toolApiKeys: z.object({
|
|
1848
|
+
openweather: z.string().optional(),
|
|
1849
|
+
serper: z.string().optional()
|
|
1850
|
+
}).optional(),
|
|
1851
|
+
preferences: z.object({
|
|
1852
|
+
maxTokens: z.number().optional(),
|
|
1853
|
+
temperature: z.number().optional(),
|
|
1854
|
+
autoSave: z.boolean().optional(),
|
|
1855
|
+
theme: z.enum(["light", "dark"]).optional(),
|
|
1856
|
+
exportFormat: z.enum(["markdown", "json"]).optional()
|
|
1857
|
+
}).optional(),
|
|
1858
|
+
mcpServers: z.array(
|
|
1859
|
+
z.object({
|
|
1860
|
+
name: z.string(),
|
|
1861
|
+
command: z.string().optional(),
|
|
1862
|
+
args: z.array(z.string()).optional(),
|
|
1863
|
+
env: z.record(z.string()),
|
|
1864
|
+
enabled: z.boolean()
|
|
1865
|
+
})
|
|
1866
|
+
).optional()
|
|
1867
|
+
});
|
|
1445
1868
|
var DEFAULT_CONFIG = {
|
|
1446
1869
|
version: "0.1.0",
|
|
1447
1870
|
userId: uuidv4(),
|
|
@@ -1468,9 +1891,124 @@ var DEFAULT_CONFIG = {
|
|
|
1468
1891
|
trustedTools: []
|
|
1469
1892
|
// No tools trusted by default
|
|
1470
1893
|
};
|
|
1894
|
+
function findProjectConfigDir(startDir = process.cwd()) {
|
|
1895
|
+
let currentDir = startDir;
|
|
1896
|
+
const { root } = path4.parse(currentDir);
|
|
1897
|
+
while (currentDir !== root) {
|
|
1898
|
+
const gitPath = path4.join(currentDir, ".git");
|
|
1899
|
+
try {
|
|
1900
|
+
if (existsSync3(gitPath)) {
|
|
1901
|
+
return currentDir;
|
|
1902
|
+
}
|
|
1903
|
+
} catch {
|
|
1904
|
+
}
|
|
1905
|
+
currentDir = path4.dirname(currentDir);
|
|
1906
|
+
}
|
|
1907
|
+
return process.cwd();
|
|
1908
|
+
}
|
|
1909
|
+
async function loadProjectConfig(projectDir) {
|
|
1910
|
+
const configPath = path4.join(projectDir, ".bike4mind", "config.json");
|
|
1911
|
+
try {
|
|
1912
|
+
const data = await fs4.readFile(configPath, "utf-8");
|
|
1913
|
+
const rawConfig = JSON.parse(data);
|
|
1914
|
+
const validated = ProjectConfigSchema.parse(rawConfig);
|
|
1915
|
+
return validated;
|
|
1916
|
+
} catch (error) {
|
|
1917
|
+
if (error.code === "ENOENT") {
|
|
1918
|
+
return null;
|
|
1919
|
+
}
|
|
1920
|
+
if (error instanceof z.ZodError) {
|
|
1921
|
+
console.error("Project config validation error:", error.errors);
|
|
1922
|
+
return null;
|
|
1923
|
+
}
|
|
1924
|
+
console.error("Failed to load project config:", error);
|
|
1925
|
+
return null;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
async function loadProjectLocalConfig(projectDir) {
|
|
1929
|
+
const configPath = path4.join(projectDir, ".bike4mind", "local.json");
|
|
1930
|
+
try {
|
|
1931
|
+
const data = await fs4.readFile(configPath, "utf-8");
|
|
1932
|
+
const rawConfig = JSON.parse(data);
|
|
1933
|
+
const validated = ProjectLocalConfigSchema.parse(rawConfig);
|
|
1934
|
+
return validated;
|
|
1935
|
+
} catch (error) {
|
|
1936
|
+
if (error.code === "ENOENT") {
|
|
1937
|
+
return null;
|
|
1938
|
+
}
|
|
1939
|
+
if (error instanceof z.ZodError) {
|
|
1940
|
+
console.error("Project local config validation error:", error.errors);
|
|
1941
|
+
return null;
|
|
1942
|
+
}
|
|
1943
|
+
console.error("Failed to load project local config:", error);
|
|
1944
|
+
return null;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
function mergeMcpServers(...serverArrays) {
|
|
1948
|
+
const serverMap = /* @__PURE__ */ new Map();
|
|
1949
|
+
for (const servers of serverArrays) {
|
|
1950
|
+
if (servers) {
|
|
1951
|
+
for (const server of servers) {
|
|
1952
|
+
serverMap.set(server.name, server);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
return Array.from(serverMap.values());
|
|
1957
|
+
}
|
|
1958
|
+
function mergeConfigs(global, project, local) {
|
|
1959
|
+
const merged = { ...global };
|
|
1960
|
+
if (project) {
|
|
1961
|
+
if (project.defaultModel) {
|
|
1962
|
+
merged.defaultModel = project.defaultModel;
|
|
1963
|
+
}
|
|
1964
|
+
if (project.preferences) {
|
|
1965
|
+
merged.preferences = {
|
|
1966
|
+
...merged.preferences,
|
|
1967
|
+
...project.preferences
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
if (project.tools) {
|
|
1971
|
+
merged.tools = {
|
|
1972
|
+
...merged.tools,
|
|
1973
|
+
enabled: [...merged.tools.enabled || [], ...project.tools.enabled || []],
|
|
1974
|
+
disabled: [...merged.tools.disabled, ...project.tools.denied || []],
|
|
1975
|
+
config: {
|
|
1976
|
+
...merged.tools.config,
|
|
1977
|
+
...project.tools.config
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
if (project.mcpServers) {
|
|
1982
|
+
merged.mcpServers = mergeMcpServers(merged.mcpServers, project.mcpServers);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
if (local) {
|
|
1986
|
+
if (local.trustedTools) {
|
|
1987
|
+
const trustedSet = /* @__PURE__ */ new Set([...merged.trustedTools || [], ...local.trustedTools]);
|
|
1988
|
+
merged.trustedTools = Array.from(trustedSet);
|
|
1989
|
+
}
|
|
1990
|
+
if (local.toolApiKeys) {
|
|
1991
|
+
merged.toolApiKeys = {
|
|
1992
|
+
...merged.toolApiKeys,
|
|
1993
|
+
...local.toolApiKeys
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
if (local.preferences) {
|
|
1997
|
+
merged.preferences = {
|
|
1998
|
+
...merged.preferences,
|
|
1999
|
+
...local.preferences
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
if (local.mcpServers) {
|
|
2003
|
+
merged.mcpServers = mergeMcpServers(merged.mcpServers, local.mcpServers);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
return merged;
|
|
2007
|
+
}
|
|
1471
2008
|
var ConfigStore = class {
|
|
1472
2009
|
constructor(configPath) {
|
|
1473
2010
|
this.config = null;
|
|
2011
|
+
this.projectConfigDir = null;
|
|
1474
2012
|
this.configPath = configPath || path4.join(homedir2(), ".bike4mind", "config.json");
|
|
1475
2013
|
}
|
|
1476
2014
|
/**
|
|
@@ -1487,51 +2025,76 @@ var ConfigStore = class {
|
|
|
1487
2025
|
}
|
|
1488
2026
|
/**
|
|
1489
2027
|
* Load configuration from disk with Zod validation
|
|
2028
|
+
* Merges global → project → local configs
|
|
1490
2029
|
*/
|
|
1491
2030
|
async load() {
|
|
1492
2031
|
if (this.config) {
|
|
1493
2032
|
return this.config;
|
|
1494
2033
|
}
|
|
1495
2034
|
try {
|
|
2035
|
+
let globalConfig;
|
|
1496
2036
|
try {
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
2037
|
+
try {
|
|
2038
|
+
const stats = await fs4.stat(this.configPath);
|
|
2039
|
+
const mode = stats.mode & 511;
|
|
2040
|
+
if (mode !== 384) {
|
|
2041
|
+
console.warn(`\u26A0\uFE0F Config file has insecure permissions (${mode.toString(8)}). Setting to 0600...`);
|
|
2042
|
+
await fs4.chmod(this.configPath, 384);
|
|
2043
|
+
}
|
|
2044
|
+
} catch (statError) {
|
|
2045
|
+
}
|
|
2046
|
+
const data = await fs4.readFile(this.configPath, "utf-8");
|
|
2047
|
+
const rawConfig = JSON.parse(data);
|
|
2048
|
+
if (rawConfig.apiConfig && "environment" in rawConfig.apiConfig) {
|
|
2049
|
+
const oldApiConfig = rawConfig.apiConfig;
|
|
2050
|
+
if (oldApiConfig.environment === "custom" && oldApiConfig.customUrl) {
|
|
2051
|
+
rawConfig.apiConfig = { customUrl: oldApiConfig.customUrl };
|
|
2052
|
+
} else {
|
|
2053
|
+
rawConfig.apiConfig = {};
|
|
2054
|
+
}
|
|
1502
2055
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
2056
|
+
const validated = CliConfigSchema.parse(rawConfig);
|
|
2057
|
+
globalConfig = {
|
|
2058
|
+
...DEFAULT_CONFIG,
|
|
2059
|
+
...validated,
|
|
2060
|
+
auth: validated.auth,
|
|
2061
|
+
// Explicitly preserve auth field
|
|
2062
|
+
preferences: {
|
|
2063
|
+
...DEFAULT_CONFIG.preferences,
|
|
2064
|
+
...validated.preferences
|
|
2065
|
+
},
|
|
2066
|
+
tools: {
|
|
2067
|
+
...DEFAULT_CONFIG.tools,
|
|
2068
|
+
...validated.tools
|
|
2069
|
+
},
|
|
2070
|
+
toolApiKeys: {
|
|
2071
|
+
...DEFAULT_CONFIG.toolApiKeys,
|
|
2072
|
+
...validated.toolApiKeys || {}
|
|
2073
|
+
},
|
|
2074
|
+
trustedTools: validated.trustedTools || []
|
|
2075
|
+
};
|
|
2076
|
+
} catch (error) {
|
|
2077
|
+
if (error.code === "ENOENT") {
|
|
2078
|
+
globalConfig = { ...DEFAULT_CONFIG };
|
|
2079
|
+
} else if (error instanceof z.ZodError) {
|
|
2080
|
+
console.error("Global config validation error:", error.errors);
|
|
2081
|
+
console.error("Using default configuration");
|
|
2082
|
+
globalConfig = { ...DEFAULT_CONFIG };
|
|
1511
2083
|
} else {
|
|
1512
|
-
|
|
2084
|
+
throw error;
|
|
1513
2085
|
}
|
|
1514
2086
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
...DEFAULT_CONFIG.tools,
|
|
1527
|
-
...validated.tools
|
|
1528
|
-
},
|
|
1529
|
-
toolApiKeys: {
|
|
1530
|
-
...DEFAULT_CONFIG.toolApiKeys,
|
|
1531
|
-
...validated.toolApiKeys || {}
|
|
1532
|
-
},
|
|
1533
|
-
trustedTools: validated.trustedTools || []
|
|
1534
|
-
};
|
|
2087
|
+
this.projectConfigDir = findProjectConfigDir();
|
|
2088
|
+
let projectConfig = null;
|
|
2089
|
+
let projectLocalConfig = null;
|
|
2090
|
+
if (this.projectConfigDir) {
|
|
2091
|
+
projectConfig = await loadProjectConfig(this.projectConfigDir);
|
|
2092
|
+
projectLocalConfig = await loadProjectLocalConfig(this.projectConfigDir);
|
|
2093
|
+
if (projectConfig) {
|
|
2094
|
+
console.log(`\u{1F4C1} Project config loaded from: ${this.projectConfigDir}/.bike4mind/`);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
this.config = mergeConfigs(globalConfig, projectConfig, projectLocalConfig);
|
|
1535
2098
|
return this.config;
|
|
1536
2099
|
} catch (error) {
|
|
1537
2100
|
if (error.code === "ENOENT") {
|
|
@@ -1720,6 +2283,92 @@ var ConfigStore = class {
|
|
|
1720
2283
|
}
|
|
1721
2284
|
await this.save(config);
|
|
1722
2285
|
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Get project config directory (if any)
|
|
2288
|
+
*/
|
|
2289
|
+
getProjectConfigDir() {
|
|
2290
|
+
return this.projectConfigDir;
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Initialize project config directory
|
|
2294
|
+
* Creates .bike4mind/ directory and ensures local.json is gitignored
|
|
2295
|
+
* Does NOT auto-create config.json (user creates that manually)
|
|
2296
|
+
*/
|
|
2297
|
+
async initProjectConfig() {
|
|
2298
|
+
const projectDir = this.projectConfigDir || findProjectConfigDir();
|
|
2299
|
+
if (!projectDir) {
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
const configDir = path4.join(projectDir, ".bike4mind");
|
|
2303
|
+
await fs4.mkdir(configDir, { recursive: true });
|
|
2304
|
+
await this.ensureGitignore(projectDir);
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Ensure .gitignore includes .bike4mind/local.json
|
|
2308
|
+
*/
|
|
2309
|
+
async ensureGitignore(projectDir) {
|
|
2310
|
+
const gitignorePath = path4.join(projectDir, ".gitignore");
|
|
2311
|
+
const entryToAdd = ".bike4mind/local.json";
|
|
2312
|
+
try {
|
|
2313
|
+
let gitignoreContent = "";
|
|
2314
|
+
try {
|
|
2315
|
+
gitignoreContent = await fs4.readFile(gitignorePath, "utf-8");
|
|
2316
|
+
} catch {
|
|
2317
|
+
}
|
|
2318
|
+
if (gitignoreContent.includes(entryToAdd)) {
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
const newContent = gitignoreContent.trim() + (gitignoreContent ? "\n" : "") + `
|
|
2322
|
+
# Bike4Mind local config (developer-specific)
|
|
2323
|
+
${entryToAdd}
|
|
2324
|
+
`;
|
|
2325
|
+
await fs4.writeFile(gitignorePath, newContent, "utf-8");
|
|
2326
|
+
console.log(`\u2705 Added ${entryToAdd} to .gitignore`);
|
|
2327
|
+
} catch (error) {
|
|
2328
|
+
console.warn(`\u26A0\uFE0F Failed to update .gitignore:`, error);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Save project config to .bike4mind/config.json
|
|
2333
|
+
*/
|
|
2334
|
+
async saveProjectConfig(config, projectDir) {
|
|
2335
|
+
const targetDir = projectDir || this.projectConfigDir || process.cwd();
|
|
2336
|
+
const configPath = path4.join(targetDir, ".bike4mind", "config.json");
|
|
2337
|
+
await fs4.mkdir(path4.dirname(configPath), { recursive: true });
|
|
2338
|
+
const validated = ProjectConfigSchema.parse(config);
|
|
2339
|
+
await fs4.writeFile(configPath, JSON.stringify(validated, null, 2), "utf-8");
|
|
2340
|
+
console.log(`\u2705 Saved project config to: ${configPath}`);
|
|
2341
|
+
}
|
|
2342
|
+
/**
|
|
2343
|
+
* Save project-local config to .bike4mind/local.json
|
|
2344
|
+
*/
|
|
2345
|
+
async saveProjectLocalConfig(config, projectDir) {
|
|
2346
|
+
const targetDir = projectDir || this.projectConfigDir || process.cwd();
|
|
2347
|
+
const configPath = path4.join(targetDir, ".bike4mind", "local.json");
|
|
2348
|
+
await fs4.mkdir(path4.dirname(configPath), { recursive: true });
|
|
2349
|
+
const validated = ProjectLocalConfigSchema.parse(config);
|
|
2350
|
+
await fs4.writeFile(configPath, JSON.stringify(validated, null, 2), "utf-8");
|
|
2351
|
+
await fs4.chmod(configPath, 384);
|
|
2352
|
+
console.log(`\u2705 Saved project-local config to: ${configPath}`);
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Load raw project config (without merging)
|
|
2356
|
+
*/
|
|
2357
|
+
async loadRawProjectConfig() {
|
|
2358
|
+
if (!this.projectConfigDir) {
|
|
2359
|
+
return null;
|
|
2360
|
+
}
|
|
2361
|
+
return loadProjectConfig(this.projectConfigDir);
|
|
2362
|
+
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Load raw project-local config (without merging)
|
|
2365
|
+
*/
|
|
2366
|
+
async loadRawProjectLocalConfig() {
|
|
2367
|
+
if (!this.projectConfigDir) {
|
|
2368
|
+
return null;
|
|
2369
|
+
}
|
|
2370
|
+
return loadProjectLocalConfig(this.projectConfigDir);
|
|
2371
|
+
}
|
|
1723
2372
|
};
|
|
1724
2373
|
|
|
1725
2374
|
// src/storage/CommandHistoryStore.ts
|
|
@@ -1856,7 +2505,7 @@ var ReActAgent = class extends EventEmitter {
|
|
|
1856
2505
|
/**
|
|
1857
2506
|
* Run the agent to completion
|
|
1858
2507
|
*
|
|
1859
|
-
* @param query - The user's question or task
|
|
2508
|
+
* @param query - The user's question or task (can be text or multimodal content with images)
|
|
1860
2509
|
* @param options - Optional overrides for this run
|
|
1861
2510
|
* @returns Agent result with final answer and all steps
|
|
1862
2511
|
*/
|
|
@@ -4471,13 +5120,30 @@ async function performWebSearch(adapters, params) {
|
|
|
4471
5120
|
link: result.link,
|
|
4472
5121
|
snippet: result.snippet
|
|
4473
5122
|
})) || [];
|
|
5123
|
+
const citables = results.map((result, index) => ({
|
|
5124
|
+
id: result.link,
|
|
5125
|
+
// Use URL as unique identifier
|
|
5126
|
+
type: "web_url",
|
|
5127
|
+
title: result.title,
|
|
5128
|
+
url: result.link,
|
|
5129
|
+
description: result.snippet,
|
|
5130
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5131
|
+
status: "complete",
|
|
5132
|
+
metadata: {
|
|
5133
|
+
sourceSystem: "web_search",
|
|
5134
|
+
relevanceScore: 1 - index * 0.1,
|
|
5135
|
+
// Higher relevance for earlier results
|
|
5136
|
+
fullContext: result.snippet
|
|
5137
|
+
}
|
|
5138
|
+
}));
|
|
4474
5139
|
const formattedResults = results.map((result, index) => `${index + 1}. **${result.title}**
|
|
4475
5140
|
${result.snippet}
|
|
4476
5141
|
Source: [${new URL(result.link).hostname}](${result.link})
|
|
4477
5142
|
`).join("\n");
|
|
4478
|
-
|
|
5143
|
+
const formattedOutput = formattedResults ? `Here's what I found from searching the web:
|
|
4479
5144
|
|
|
4480
5145
|
${formattedResults}` : "No results found from web search.";
|
|
5146
|
+
return { formattedResults: formattedOutput, citables };
|
|
4481
5147
|
} catch (error) {
|
|
4482
5148
|
console.error("\u274C WebSearch Tool: Error during search:", error);
|
|
4483
5149
|
throw error;
|
|
@@ -4488,7 +5154,18 @@ var webSearchTool = {
|
|
|
4488
5154
|
implementation: (context) => ({
|
|
4489
5155
|
toolFn: async (value) => {
|
|
4490
5156
|
const params = value;
|
|
4491
|
-
|
|
5157
|
+
const { formattedResults, citables } = await performWebSearch({ db: context.db }, params);
|
|
5158
|
+
if (citables.length > 0) {
|
|
5159
|
+
await context.statusUpdate({
|
|
5160
|
+
// Store citables in promptMeta - statusUpdate will Object.assign this
|
|
5161
|
+
// The receiving code needs to handle merging promptMeta.citables
|
|
5162
|
+
promptMeta: {
|
|
5163
|
+
citables
|
|
5164
|
+
}
|
|
5165
|
+
}, "Web search complete");
|
|
5166
|
+
console.log(`\u{1F4DA} WebSearch Tool: Stored ${citables.length} citables`);
|
|
5167
|
+
}
|
|
5168
|
+
return formattedResults;
|
|
4492
5169
|
},
|
|
4493
5170
|
toolSchema: {
|
|
4494
5171
|
name: "web_search",
|
|
@@ -7305,7 +7982,7 @@ var planetVisibilityTool = {
|
|
|
7305
7982
|
|
|
7306
7983
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
|
|
7307
7984
|
import { promises as fs7 } from "fs";
|
|
7308
|
-
import { existsSync as
|
|
7985
|
+
import { existsSync as existsSync4, statSync as statSync4 } from "fs";
|
|
7309
7986
|
import path6 from "path";
|
|
7310
7987
|
var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
|
|
7311
7988
|
async function readFileContent(params) {
|
|
@@ -7316,10 +7993,10 @@ async function readFileContent(params) {
|
|
|
7316
7993
|
if (!resolvedPath.startsWith(cwd)) {
|
|
7317
7994
|
throw new Error(`Access denied: Cannot read files outside of current working directory`);
|
|
7318
7995
|
}
|
|
7319
|
-
if (!
|
|
7996
|
+
if (!existsSync4(resolvedPath)) {
|
|
7320
7997
|
throw new Error(`File not found: ${filePath}`);
|
|
7321
7998
|
}
|
|
7322
|
-
const stats =
|
|
7999
|
+
const stats = statSync4(resolvedPath);
|
|
7323
8000
|
if (stats.isDirectory()) {
|
|
7324
8001
|
throw new Error(`Path is a directory, not a file: ${filePath}`);
|
|
7325
8002
|
}
|
|
@@ -7362,7 +8039,7 @@ var fileReadTool = {
|
|
|
7362
8039
|
context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
|
|
7363
8040
|
try {
|
|
7364
8041
|
const content = await readFileContent(params);
|
|
7365
|
-
const stats =
|
|
8042
|
+
const stats = statSync4(path6.resolve(process.cwd(), path6.normalize(params.path)));
|
|
7366
8043
|
context.logger.info("\u2705 FileRead: Success", {
|
|
7367
8044
|
path: params.path,
|
|
7368
8045
|
size: stats.size,
|
|
@@ -7402,7 +8079,7 @@ var fileReadTool = {
|
|
|
7402
8079
|
|
|
7403
8080
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
|
|
7404
8081
|
import { promises as fs8 } from "fs";
|
|
7405
|
-
import { existsSync as
|
|
8082
|
+
import { existsSync as existsSync5 } from "fs";
|
|
7406
8083
|
import path7 from "path";
|
|
7407
8084
|
async function createFile(params) {
|
|
7408
8085
|
const { path: filePath, content, createDirectories = true } = params;
|
|
@@ -7412,7 +8089,7 @@ async function createFile(params) {
|
|
|
7412
8089
|
if (!resolvedPath.startsWith(cwd)) {
|
|
7413
8090
|
throw new Error(`Access denied: Cannot create files outside of current working directory`);
|
|
7414
8091
|
}
|
|
7415
|
-
const fileExists =
|
|
8092
|
+
const fileExists = existsSync5(resolvedPath);
|
|
7416
8093
|
const action = fileExists ? "overwritten" : "created";
|
|
7417
8094
|
if (createDirectories) {
|
|
7418
8095
|
const dir = path7.dirname(resolvedPath);
|
|
@@ -7430,7 +8107,7 @@ var createFileTool = {
|
|
|
7430
8107
|
implementation: (context) => ({
|
|
7431
8108
|
toolFn: async (value) => {
|
|
7432
8109
|
const params = value;
|
|
7433
|
-
const fileExists =
|
|
8110
|
+
const fileExists = existsSync5(path7.resolve(process.cwd(), path7.normalize(params.path)));
|
|
7434
8111
|
context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
|
|
7435
8112
|
path: params.path,
|
|
7436
8113
|
size: params.content.length
|
|
@@ -7471,7 +8148,7 @@ var createFileTool = {
|
|
|
7471
8148
|
|
|
7472
8149
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
|
|
7473
8150
|
import { readdir } from "fs/promises";
|
|
7474
|
-
import { statSync as
|
|
8151
|
+
import { statSync as statSync5 } from "fs";
|
|
7475
8152
|
import path8 from "path";
|
|
7476
8153
|
function matchGlob(filePath, pattern) {
|
|
7477
8154
|
let regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, options) => `(${options.split(",").join("|")})`).replace(/\[([^\]]+)\]/g, "[$1]");
|
|
@@ -7519,7 +8196,7 @@ async function listFiles(params) {
|
|
|
7519
8196
|
for (const match of matches) {
|
|
7520
8197
|
try {
|
|
7521
8198
|
const fullPath = path8.join(targetCwd, match);
|
|
7522
|
-
const stats =
|
|
8199
|
+
const stats = statSync5(fullPath);
|
|
7523
8200
|
filesInfo.push({
|
|
7524
8201
|
path: match,
|
|
7525
8202
|
size: stats.size,
|
|
@@ -7600,7 +8277,7 @@ var globFilesTool = {
|
|
|
7600
8277
|
|
|
7601
8278
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
|
|
7602
8279
|
import { readdir as readdir2 } from "fs/promises";
|
|
7603
|
-
import { readFileSync as
|
|
8280
|
+
import { readFileSync as readFileSync3, statSync as statSync6 } from "fs";
|
|
7604
8281
|
import path9 from "path";
|
|
7605
8282
|
var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
|
|
7606
8283
|
function matchGlob2(filePath, pattern) {
|
|
@@ -7652,7 +8329,7 @@ async function searchFiles2(params) {
|
|
|
7652
8329
|
for (const match of filesToSearch) {
|
|
7653
8330
|
const fullPath = path9.join(targetCwd, match);
|
|
7654
8331
|
try {
|
|
7655
|
-
const stats =
|
|
8332
|
+
const stats = statSync6(fullPath);
|
|
7656
8333
|
if (stats.isDirectory() || stats.size > MAX_FILE_SIZE3) {
|
|
7657
8334
|
continue;
|
|
7658
8335
|
}
|
|
@@ -7676,7 +8353,7 @@ async function searchFiles2(params) {
|
|
|
7676
8353
|
filesSearched++;
|
|
7677
8354
|
const fullPath = path9.join(targetCwd, file);
|
|
7678
8355
|
try {
|
|
7679
|
-
const content =
|
|
8356
|
+
const content = readFileSync3(fullPath, "utf-8");
|
|
7680
8357
|
const lines = content.split("\n");
|
|
7681
8358
|
for (let i = 0; i < lines.length; i++) {
|
|
7682
8359
|
if (matches.length >= maxResults) {
|
|
@@ -7810,7 +8487,7 @@ var grepSearchTool = {
|
|
|
7810
8487
|
|
|
7811
8488
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
|
|
7812
8489
|
import { promises as fs9 } from "fs";
|
|
7813
|
-
import { existsSync as
|
|
8490
|
+
import { existsSync as existsSync6, statSync as statSync7 } from "fs";
|
|
7814
8491
|
import path10 from "path";
|
|
7815
8492
|
async function deleteFile(params) {
|
|
7816
8493
|
const { path: filePath, recursive = false } = params;
|
|
@@ -7820,10 +8497,10 @@ async function deleteFile(params) {
|
|
|
7820
8497
|
if (!resolvedPath.startsWith(cwd)) {
|
|
7821
8498
|
throw new Error(`Access denied: Cannot delete files outside of current working directory`);
|
|
7822
8499
|
}
|
|
7823
|
-
if (!
|
|
8500
|
+
if (!existsSync6(resolvedPath)) {
|
|
7824
8501
|
throw new Error(`File or directory not found: ${filePath}`);
|
|
7825
8502
|
}
|
|
7826
|
-
const stats =
|
|
8503
|
+
const stats = statSync7(resolvedPath);
|
|
7827
8504
|
const isDirectory = stats.isDirectory();
|
|
7828
8505
|
const size = stats.size;
|
|
7829
8506
|
if (isDirectory && !recursive) {
|
|
@@ -7844,7 +8521,7 @@ var deleteFileTool = {
|
|
|
7844
8521
|
toolFn: async (value) => {
|
|
7845
8522
|
const params = value;
|
|
7846
8523
|
const resolvedPath = path10.resolve(process.cwd(), path10.normalize(params.path));
|
|
7847
|
-
const isDirectory =
|
|
8524
|
+
const isDirectory = existsSync6(resolvedPath) && statSync7(resolvedPath).isDirectory();
|
|
7848
8525
|
context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
|
|
7849
8526
|
path: params.path,
|
|
7850
8527
|
recursive: params.recursive
|
|
@@ -8134,10 +8811,10 @@ import jwt2 from "jsonwebtoken";
|
|
|
8134
8811
|
// src/utils/diffPreview.ts
|
|
8135
8812
|
import * as Diff from "diff";
|
|
8136
8813
|
import { readFile } from "fs/promises";
|
|
8137
|
-
import { existsSync as
|
|
8814
|
+
import { existsSync as existsSync7 } from "fs";
|
|
8138
8815
|
async function generateFileDiffPreview(args) {
|
|
8139
8816
|
try {
|
|
8140
|
-
if (!
|
|
8817
|
+
if (!existsSync7(args.path)) {
|
|
8141
8818
|
const lines2 = args.content.split("\n");
|
|
8142
8819
|
const preview = lines2.slice(0, 20).join("\n");
|
|
8143
8820
|
const hasMore = lines2.length > 20;
|
|
@@ -8167,7 +8844,7 @@ ${preview}${hasMore ? `
|
|
|
8167
8844
|
}
|
|
8168
8845
|
async function generateFileDeletePreview(args) {
|
|
8169
8846
|
try {
|
|
8170
|
-
if (!
|
|
8847
|
+
if (!existsSync7(args.path)) {
|
|
8171
8848
|
return `[File does not exist: ${args.path}]`;
|
|
8172
8849
|
}
|
|
8173
8850
|
const stats = await import("fs/promises").then((fs11) => fs11.stat(args.path));
|
|
@@ -8220,7 +8897,7 @@ var CliLogger = class extends Logger {
|
|
|
8220
8897
|
log(...args) {
|
|
8221
8898
|
}
|
|
8222
8899
|
};
|
|
8223
|
-
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext) {
|
|
8900
|
+
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore) {
|
|
8224
8901
|
const originalFn = tool.toolFn;
|
|
8225
8902
|
const toolName = tool.toolSchema.name;
|
|
8226
8903
|
return {
|
|
@@ -8264,7 +8941,25 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
|
|
|
8264
8941
|
throw new PermissionDeniedError(toolName, args);
|
|
8265
8942
|
}
|
|
8266
8943
|
if (response.action === "allow-always") {
|
|
8267
|
-
permissionManager.trustTool(toolName);
|
|
8944
|
+
const canTrust = permissionManager.trustTool(toolName);
|
|
8945
|
+
if (canTrust) {
|
|
8946
|
+
const projectDir = configStore.getProjectConfigDir();
|
|
8947
|
+
if (projectDir) {
|
|
8948
|
+
try {
|
|
8949
|
+
await configStore.initProjectConfig();
|
|
8950
|
+
const existingLocal = await configStore.loadRawProjectLocalConfig() || {};
|
|
8951
|
+
const updatedLocal = {
|
|
8952
|
+
...existingLocal,
|
|
8953
|
+
trustedTools: [...existingLocal.trustedTools || [], toolName]
|
|
8954
|
+
};
|
|
8955
|
+
await configStore.saveProjectLocalConfig(updatedLocal);
|
|
8956
|
+
} catch (error) {
|
|
8957
|
+
await configStore.trustTool(toolName);
|
|
8958
|
+
}
|
|
8959
|
+
} else {
|
|
8960
|
+
await configStore.trustTool(toolName);
|
|
8961
|
+
}
|
|
8962
|
+
}
|
|
8268
8963
|
}
|
|
8269
8964
|
const result = await originalFn(args);
|
|
8270
8965
|
agentContext.observationQueue.push({ toolName, result });
|
|
@@ -8272,7 +8967,7 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
|
|
|
8272
8967
|
}
|
|
8273
8968
|
};
|
|
8274
8969
|
}
|
|
8275
|
-
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, toolApiKeys) {
|
|
8970
|
+
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, toolApiKeys) {
|
|
8276
8971
|
const logger2 = new CliLogger();
|
|
8277
8972
|
const storage = new NoOpStorage();
|
|
8278
8973
|
const user = {
|
|
@@ -8356,7 +9051,9 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
8356
9051
|
toolConfig,
|
|
8357
9052
|
model
|
|
8358
9053
|
);
|
|
8359
|
-
const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
|
|
9054
|
+
const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
|
|
9055
|
+
([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore)
|
|
9056
|
+
);
|
|
8360
9057
|
return { tools: tools2, agentContext };
|
|
8361
9058
|
}
|
|
8362
9059
|
|
|
@@ -8421,23 +9118,30 @@ function canTrustTool(toolName, customCategories) {
|
|
|
8421
9118
|
|
|
8422
9119
|
// src/utils/PermissionManager.ts
|
|
8423
9120
|
var PermissionManager = class {
|
|
8424
|
-
constructor(trustedTools = [], customCategories) {
|
|
9121
|
+
constructor(trustedTools = [], customCategories, deniedTools) {
|
|
8425
9122
|
this.trustedTools = /* @__PURE__ */ new Set();
|
|
9123
|
+
this.deniedTools = /* @__PURE__ */ new Set();
|
|
8426
9124
|
this.trustedTools = new Set(trustedTools);
|
|
8427
9125
|
this.customCategories = new Map(Object.entries(customCategories || {}));
|
|
9126
|
+
this.deniedTools = new Set(deniedTools || []);
|
|
8428
9127
|
}
|
|
8429
9128
|
/**
|
|
8430
9129
|
* Check if a tool needs permission before execution
|
|
8431
9130
|
*
|
|
8432
9131
|
* A tool needs permission if:
|
|
8433
|
-
* 1. It's
|
|
8434
|
-
* 2. It's not in the
|
|
9132
|
+
* 1. It's in the denied tools list (from project config), OR
|
|
9133
|
+
* 2. It's not in the auto_approve category, AND
|
|
9134
|
+
* 3. It's not in the trusted tools set
|
|
8435
9135
|
*
|
|
8436
9136
|
* Note: prompt_always tools CANNOT be trusted, so they always need permission
|
|
9137
|
+
* Note: denied tools from project config ALWAYS need permission (cannot be overridden locally)
|
|
8437
9138
|
*/
|
|
8438
9139
|
needsPermission(toolName) {
|
|
8439
9140
|
const categoryMap = Object.fromEntries(this.customCategories);
|
|
8440
9141
|
const category = getToolCategory(toolName, categoryMap);
|
|
9142
|
+
if (this.deniedTools.has(toolName)) {
|
|
9143
|
+
return true;
|
|
9144
|
+
}
|
|
8441
9145
|
if (category === "auto_approve") {
|
|
8442
9146
|
return false;
|
|
8443
9147
|
}
|
|
@@ -8448,10 +9152,13 @@ var PermissionManager = class {
|
|
|
8448
9152
|
}
|
|
8449
9153
|
/**
|
|
8450
9154
|
* Add a tool to the trusted list
|
|
8451
|
-
* Returns false if the tool cannot be trusted (prompt_always category)
|
|
9155
|
+
* Returns false if the tool cannot be trusted (prompt_always category or denied by project)
|
|
8452
9156
|
*/
|
|
8453
9157
|
trustTool(toolName) {
|
|
8454
9158
|
const categoryMap = Object.fromEntries(this.customCategories);
|
|
9159
|
+
if (this.deniedTools.has(toolName)) {
|
|
9160
|
+
return false;
|
|
9161
|
+
}
|
|
8455
9162
|
if (!canTrustTool(toolName, categoryMap)) {
|
|
8456
9163
|
return false;
|
|
8457
9164
|
}
|
|
@@ -8484,12 +9191,27 @@ var PermissionManager = class {
|
|
|
8484
9191
|
return getToolCategory(toolName, categoryMap);
|
|
8485
9192
|
}
|
|
8486
9193
|
/**
|
|
8487
|
-
* Check if a tool can be trusted (not in prompt_always category)
|
|
9194
|
+
* Check if a tool can be trusted (not in prompt_always category or denied by project)
|
|
8488
9195
|
*/
|
|
8489
9196
|
canBeTrusted(toolName) {
|
|
8490
9197
|
const categoryMap = Object.fromEntries(this.customCategories);
|
|
9198
|
+
if (this.deniedTools.has(toolName)) {
|
|
9199
|
+
return false;
|
|
9200
|
+
}
|
|
8491
9201
|
return canTrustTool(toolName, categoryMap);
|
|
8492
9202
|
}
|
|
9203
|
+
/**
|
|
9204
|
+
* Check if a tool is denied by project config
|
|
9205
|
+
*/
|
|
9206
|
+
isDenied(toolName) {
|
|
9207
|
+
return this.deniedTools.has(toolName);
|
|
9208
|
+
}
|
|
9209
|
+
/**
|
|
9210
|
+
* Get list of denied tools
|
|
9211
|
+
*/
|
|
9212
|
+
getDeniedTools() {
|
|
9213
|
+
return Array.from(this.deniedTools).sort();
|
|
9214
|
+
}
|
|
8493
9215
|
/**
|
|
8494
9216
|
* Clear all trusted tools
|
|
8495
9217
|
*/
|
|
@@ -8784,18 +9506,22 @@ var logger = Logger2.getInstance();
|
|
|
8784
9506
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
8785
9507
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8786
9508
|
import path12 from "path";
|
|
8787
|
-
import { existsSync as
|
|
9509
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
|
|
8788
9510
|
var MCPClient = class {
|
|
8789
9511
|
// Note: This class handles MCP server communication with repository filtering
|
|
8790
9512
|
mcp;
|
|
8791
9513
|
transport = null;
|
|
8792
9514
|
envVariables;
|
|
9515
|
+
customCommand;
|
|
9516
|
+
customArgs;
|
|
8793
9517
|
tools = [];
|
|
8794
9518
|
serverName;
|
|
8795
|
-
constructor({ envVariables, name, selectedRepositories }) {
|
|
9519
|
+
constructor({ envVariables, name, selectedRepositories, command, args }) {
|
|
8796
9520
|
this.mcp = new Client2({ name: "mcp-client-cli", version: "1.0.0" });
|
|
8797
9521
|
this.envVariables = [...envVariables];
|
|
8798
9522
|
this.serverName = name;
|
|
9523
|
+
this.customCommand = command;
|
|
9524
|
+
this.customArgs = args;
|
|
8799
9525
|
if (name === "github" && selectedRepositories) {
|
|
8800
9526
|
const reposJson = JSON.stringify(selectedRepositories);
|
|
8801
9527
|
this.envVariables.push({
|
|
@@ -8806,37 +9532,48 @@ var MCPClient = class {
|
|
|
8806
9532
|
}
|
|
8807
9533
|
async connectToServer() {
|
|
8808
9534
|
try {
|
|
8809
|
-
const
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
9535
|
+
const envVarsObject = this.envVariables.reduce((acc, env) => ({
|
|
9536
|
+
...acc,
|
|
9537
|
+
[env.key]: env.value
|
|
9538
|
+
}), {});
|
|
9539
|
+
let command;
|
|
9540
|
+
let args;
|
|
9541
|
+
if (this.customCommand && this.customCommand.trim() !== "") {
|
|
9542
|
+
command = this.customCommand;
|
|
9543
|
+
args = this.customArgs ?? [];
|
|
9544
|
+
console.log(`[MCP] Using external command for ${this.serverName}: ${command} ${args.join(" ")}`);
|
|
9545
|
+
} else {
|
|
9546
|
+
const root = process.env.INIT_CWD || process.cwd();
|
|
9547
|
+
const candidatePaths = [
|
|
9548
|
+
// When running from SST Lambda with node_modules structure (copyFiles)
|
|
9549
|
+
path12.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
|
|
9550
|
+
// When running from SST Lambda deployed environment (/var/task)
|
|
9551
|
+
path12.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
9552
|
+
// When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
|
|
9553
|
+
path12.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
9554
|
+
// When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
|
|
9555
|
+
path12.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
9556
|
+
// Original paths (backward compatibility)
|
|
9557
|
+
path12.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
9558
|
+
path12.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
|
|
9559
|
+
];
|
|
9560
|
+
const serverScriptPath = candidatePaths.find((p) => existsSync8(p));
|
|
9561
|
+
if (!serverScriptPath) {
|
|
9562
|
+
const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
9563
|
+
console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
|
|
9564
|
+
console.error(`[MCP] Available directories in ${root}:`, getDirectories(root));
|
|
9565
|
+
throw new Error(`MCP Server script path not found for ${this.serverName}. Root: ${root}, Available dirs: ${getDirectories(root).join(", ")}`);
|
|
9566
|
+
}
|
|
9567
|
+
command = process.execPath;
|
|
9568
|
+
args = [serverScriptPath];
|
|
9569
|
+
console.log(`[MCP] Using server: ${this.serverName} at ${serverScriptPath}`);
|
|
9570
|
+
}
|
|
8831
9571
|
this.transport = new StdioClientTransport({
|
|
8832
|
-
command
|
|
8833
|
-
args
|
|
9572
|
+
command,
|
|
9573
|
+
args,
|
|
8834
9574
|
env: {
|
|
8835
|
-
...process.env,
|
|
8836
|
-
...
|
|
8837
|
-
...acc,
|
|
8838
|
-
[env.key]: env.value
|
|
8839
|
-
}), {})
|
|
9575
|
+
...Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== void 0)),
|
|
9576
|
+
...envVarsObject
|
|
8840
9577
|
}
|
|
8841
9578
|
});
|
|
8842
9579
|
this.transport.onerror = (error) => {
|
|
@@ -8944,7 +9681,9 @@ var McpManager = class {
|
|
|
8944
9681
|
}));
|
|
8945
9682
|
const client = new MCPClient({
|
|
8946
9683
|
envVariables,
|
|
8947
|
-
name: serverConfig.name
|
|
9684
|
+
name: serverConfig.name,
|
|
9685
|
+
command: serverConfig.command,
|
|
9686
|
+
args: serverConfig.args
|
|
8948
9687
|
});
|
|
8949
9688
|
await client.connectToServer();
|
|
8950
9689
|
const mcpData = {
|
|
@@ -9016,6 +9755,190 @@ var McpManager = class {
|
|
|
9016
9755
|
}
|
|
9017
9756
|
};
|
|
9018
9757
|
|
|
9758
|
+
// src/utils/imageRenderer.ts
|
|
9759
|
+
var ImageRenderer = class {
|
|
9760
|
+
constructor() {
|
|
9761
|
+
this.imageCounter = 0;
|
|
9762
|
+
this.placeholderMap = /* @__PURE__ */ new Map();
|
|
9763
|
+
}
|
|
9764
|
+
/**
|
|
9765
|
+
* Generate placeholder for a new image
|
|
9766
|
+
*/
|
|
9767
|
+
createPlaceholder(hash) {
|
|
9768
|
+
const existing = this.placeholderMap.get(hash);
|
|
9769
|
+
if (existing) {
|
|
9770
|
+
return existing.placeholder;
|
|
9771
|
+
}
|
|
9772
|
+
this.imageCounter++;
|
|
9773
|
+
const placeholder = `[Image ${this.imageCounter}]`;
|
|
9774
|
+
this.placeholderMap.set(hash, {
|
|
9775
|
+
hash,
|
|
9776
|
+
number: this.imageCounter,
|
|
9777
|
+
placeholder
|
|
9778
|
+
});
|
|
9779
|
+
return placeholder;
|
|
9780
|
+
}
|
|
9781
|
+
/**
|
|
9782
|
+
* Get all image hashes from a message containing placeholders
|
|
9783
|
+
*/
|
|
9784
|
+
extractImageHashes(message) {
|
|
9785
|
+
const hashes = [];
|
|
9786
|
+
const placeholderRegex = /\[Image \d+\]/g;
|
|
9787
|
+
const matches = message.match(placeholderRegex);
|
|
9788
|
+
if (!matches) return hashes;
|
|
9789
|
+
for (const [hash, data] of this.placeholderMap.entries()) {
|
|
9790
|
+
if (matches.includes(data.placeholder)) {
|
|
9791
|
+
hashes.push(hash);
|
|
9792
|
+
}
|
|
9793
|
+
}
|
|
9794
|
+
return hashes;
|
|
9795
|
+
}
|
|
9796
|
+
/**
|
|
9797
|
+
* Get hash by placeholder text
|
|
9798
|
+
*/
|
|
9799
|
+
getHashFromPlaceholder(placeholder) {
|
|
9800
|
+
for (const [hash, data] of this.placeholderMap.entries()) {
|
|
9801
|
+
if (data.placeholder === placeholder) {
|
|
9802
|
+
return hash;
|
|
9803
|
+
}
|
|
9804
|
+
}
|
|
9805
|
+
return null;
|
|
9806
|
+
}
|
|
9807
|
+
};
|
|
9808
|
+
|
|
9809
|
+
// src/utils/messageBuilder.ts
|
|
9810
|
+
var MessageBuilder = class {
|
|
9811
|
+
constructor(imageStore, imageRenderer) {
|
|
9812
|
+
this.imageStore = imageStore;
|
|
9813
|
+
this.imageRenderer = imageRenderer;
|
|
9814
|
+
}
|
|
9815
|
+
/**
|
|
9816
|
+
* Build a multimodal message from user input
|
|
9817
|
+
* Extracts image placeholders and creates proper message structure with base64-encoded images
|
|
9818
|
+
*/
|
|
9819
|
+
async buildMessage(userInput, role = "user") {
|
|
9820
|
+
const imageHashes = this.imageRenderer.extractImageHashes(userInput);
|
|
9821
|
+
if (imageHashes.length === 0) {
|
|
9822
|
+
return {
|
|
9823
|
+
message: {
|
|
9824
|
+
role,
|
|
9825
|
+
content: userInput
|
|
9826
|
+
},
|
|
9827
|
+
uploadedImages: []
|
|
9828
|
+
};
|
|
9829
|
+
}
|
|
9830
|
+
const content = [];
|
|
9831
|
+
const parts = this.splitMessageByPlaceholders(userInput);
|
|
9832
|
+
for (const part of parts) {
|
|
9833
|
+
if (part.type === "text" && part.content.trim()) {
|
|
9834
|
+
content.push({
|
|
9835
|
+
type: "text",
|
|
9836
|
+
text: part.content
|
|
9837
|
+
});
|
|
9838
|
+
} else if (part.type === "image") {
|
|
9839
|
+
const hash = this.imageRenderer.getHashFromPlaceholder(part.content);
|
|
9840
|
+
if (hash) {
|
|
9841
|
+
const imageData = this.imageStore.readImage(hash);
|
|
9842
|
+
const metadata = this.imageStore.getMetadata(hash);
|
|
9843
|
+
if (imageData && metadata) {
|
|
9844
|
+
const base64Data = imageData.toString("base64");
|
|
9845
|
+
const mediaType = this.getMediaType(metadata.format);
|
|
9846
|
+
content.push({
|
|
9847
|
+
type: "image",
|
|
9848
|
+
source: {
|
|
9849
|
+
type: "base64",
|
|
9850
|
+
media_type: mediaType,
|
|
9851
|
+
data: base64Data
|
|
9852
|
+
}
|
|
9853
|
+
});
|
|
9854
|
+
}
|
|
9855
|
+
}
|
|
9856
|
+
}
|
|
9857
|
+
}
|
|
9858
|
+
return {
|
|
9859
|
+
message: {
|
|
9860
|
+
role,
|
|
9861
|
+
content
|
|
9862
|
+
},
|
|
9863
|
+
uploadedImages: []
|
|
9864
|
+
};
|
|
9865
|
+
}
|
|
9866
|
+
/**
|
|
9867
|
+
* Get MIME type for image format
|
|
9868
|
+
*/
|
|
9869
|
+
getMediaType(format) {
|
|
9870
|
+
switch (format.toLowerCase()) {
|
|
9871
|
+
case "png":
|
|
9872
|
+
return "image/png";
|
|
9873
|
+
case "jpg":
|
|
9874
|
+
case "jpeg":
|
|
9875
|
+
return "image/jpeg";
|
|
9876
|
+
case "gif":
|
|
9877
|
+
return "image/gif";
|
|
9878
|
+
case "webp":
|
|
9879
|
+
return "image/webp";
|
|
9880
|
+
default:
|
|
9881
|
+
return "image/png";
|
|
9882
|
+
}
|
|
9883
|
+
}
|
|
9884
|
+
/**
|
|
9885
|
+
* Split message text by image placeholders
|
|
9886
|
+
* Returns array of text and image parts in order
|
|
9887
|
+
*/
|
|
9888
|
+
splitMessageByPlaceholders(message) {
|
|
9889
|
+
const parts = [];
|
|
9890
|
+
const placeholderRegex = /(\[Image \d+\])/g;
|
|
9891
|
+
let lastIndex = 0;
|
|
9892
|
+
let match;
|
|
9893
|
+
while ((match = placeholderRegex.exec(message)) !== null) {
|
|
9894
|
+
if (match.index > lastIndex) {
|
|
9895
|
+
const text = message.substring(lastIndex, match.index);
|
|
9896
|
+
if (text) {
|
|
9897
|
+
parts.push({ type: "text", content: text });
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
parts.push({ type: "image", content: match[0] });
|
|
9901
|
+
lastIndex = match.index + match[0].length;
|
|
9902
|
+
}
|
|
9903
|
+
if (lastIndex < message.length) {
|
|
9904
|
+
const text = message.substring(lastIndex);
|
|
9905
|
+
if (text) {
|
|
9906
|
+
parts.push({ type: "text", content: text });
|
|
9907
|
+
}
|
|
9908
|
+
}
|
|
9909
|
+
if (parts.length === 0) {
|
|
9910
|
+
parts.push({ type: "text", content: message });
|
|
9911
|
+
}
|
|
9912
|
+
return parts;
|
|
9913
|
+
}
|
|
9914
|
+
/**
|
|
9915
|
+
* Check if a message contains images
|
|
9916
|
+
*/
|
|
9917
|
+
hasImages(message) {
|
|
9918
|
+
return this.imageRenderer.extractImageHashes(message).length > 0;
|
|
9919
|
+
}
|
|
9920
|
+
/**
|
|
9921
|
+
* Convert multimodal message back to simple string (for display/logging)
|
|
9922
|
+
*/
|
|
9923
|
+
messageToString(message) {
|
|
9924
|
+
if (typeof message.content === "string") {
|
|
9925
|
+
return message.content;
|
|
9926
|
+
}
|
|
9927
|
+
return message.content.map((item) => {
|
|
9928
|
+
if (item.type === "text") {
|
|
9929
|
+
return item.text;
|
|
9930
|
+
} else if (item.type === "image") {
|
|
9931
|
+
if (item.source.type === "url") {
|
|
9932
|
+
return `[Image: ${item.source.url}]`;
|
|
9933
|
+
} else {
|
|
9934
|
+
return `[Image: base64 ${item.source.media_type}]`;
|
|
9935
|
+
}
|
|
9936
|
+
}
|
|
9937
|
+
return "";
|
|
9938
|
+
}).join("");
|
|
9939
|
+
}
|
|
9940
|
+
};
|
|
9941
|
+
|
|
9019
9942
|
// src/llm/ServerLlmBackend.ts
|
|
9020
9943
|
import { createParser } from "eventsource-parser";
|
|
9021
9944
|
import { isAxiosError } from "axios";
|
|
@@ -9215,9 +10138,40 @@ var ServerLlmBackend = class {
|
|
|
9215
10138
|
}
|
|
9216
10139
|
/**
|
|
9217
10140
|
* Get available models from server
|
|
9218
|
-
*
|
|
10141
|
+
* Fetches from /api/models and filters for CLI-compatible models
|
|
10142
|
+
* Falls back to hardcoded list if API fails
|
|
9219
10143
|
*/
|
|
9220
10144
|
async getModelInfo() {
|
|
10145
|
+
try {
|
|
10146
|
+
logger.debug("[ServerLlmBackend] Fetching models from /api/models");
|
|
10147
|
+
const response = await this.apiClient.get("/api/models");
|
|
10148
|
+
if (!response || typeof response !== "object" || !Array.isArray(response.models)) {
|
|
10149
|
+
logger.warn("[ServerLlmBackend] Invalid API response format, using fallback models");
|
|
10150
|
+
logger.info("\u26A0\uFE0F Using fallback model list (API returned invalid format)");
|
|
10151
|
+
return this.getFallbackModels();
|
|
10152
|
+
}
|
|
10153
|
+
const filteredModels = response.models.filter((model) => model.type === "text" && model.supportsTools === true);
|
|
10154
|
+
logger.debug(`[ServerLlmBackend] Fetched ${filteredModels.length} CLI-compatible models`);
|
|
10155
|
+
if (filteredModels.length === 0) {
|
|
10156
|
+
logger.warn("[ServerLlmBackend] No CLI-compatible models found from API, using fallback");
|
|
10157
|
+
logger.info("\u26A0\uFE0F Using fallback model list (no CLI-compatible models available)");
|
|
10158
|
+
return this.getFallbackModels();
|
|
10159
|
+
}
|
|
10160
|
+
logger.info(`\u{1F4CB} Loaded ${filteredModels.length} models from server`);
|
|
10161
|
+
return filteredModels;
|
|
10162
|
+
} catch (error) {
|
|
10163
|
+
logger.warn(
|
|
10164
|
+
`[ServerLlmBackend] Failed to fetch models from API, using fallback: ${error instanceof Error ? error.message : String(error)}`
|
|
10165
|
+
);
|
|
10166
|
+
logger.info("\u26A0\uFE0F Using fallback model list (API unavailable)");
|
|
10167
|
+
return this.getFallbackModels();
|
|
10168
|
+
}
|
|
10169
|
+
}
|
|
10170
|
+
/**
|
|
10171
|
+
* Fallback models when API is unavailable
|
|
10172
|
+
* Returns hardcoded list of commonly supported models
|
|
10173
|
+
*/
|
|
10174
|
+
getFallbackModels() {
|
|
9221
10175
|
return [
|
|
9222
10176
|
{
|
|
9223
10177
|
id: "claude-sonnet-4-5-20250929",
|
|
@@ -9402,7 +10356,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
9402
10356
|
// package.json
|
|
9403
10357
|
var package_default = {
|
|
9404
10358
|
name: "@bike4mind/cli",
|
|
9405
|
-
version: "0.2.
|
|
10359
|
+
version: "0.2.7-basic-cli-shortcuts.17131+b79f34724",
|
|
9406
10360
|
type: "module",
|
|
9407
10361
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
9408
10362
|
license: "UNLICENSED",
|
|
@@ -9463,6 +10417,7 @@ var package_default = {
|
|
|
9463
10417
|
"async-mutex": "^0.5.0",
|
|
9464
10418
|
axios: "^1.13.2",
|
|
9465
10419
|
bcryptjs: "^2.4.3",
|
|
10420
|
+
"better-sqlite3": "^12.5.0",
|
|
9466
10421
|
cheerio: "1.0.0-rc.12",
|
|
9467
10422
|
"csv-parse": "^5.5.6",
|
|
9468
10423
|
dayjs: "^1.11.13",
|
|
@@ -9488,7 +10443,7 @@ var package_default = {
|
|
|
9488
10443
|
"p-limit": "^6.2.0",
|
|
9489
10444
|
qrcode: "^1.5.4",
|
|
9490
10445
|
react: "^19.2.3",
|
|
9491
|
-
sharp: "^0.
|
|
10446
|
+
sharp: "^0.34.5",
|
|
9492
10447
|
speakeasy: "^2.0.0",
|
|
9493
10448
|
tiktoken: "^1.0.16",
|
|
9494
10449
|
turndown: "^7.2.0",
|
|
@@ -9504,10 +10459,11 @@ var package_default = {
|
|
|
9504
10459
|
},
|
|
9505
10460
|
devDependencies: {
|
|
9506
10461
|
"@bike4mind/agents": "0.1.0",
|
|
9507
|
-
"@bike4mind/common": "2.
|
|
9508
|
-
"@bike4mind/mcp": "1.
|
|
9509
|
-
"@bike4mind/services": "2.
|
|
9510
|
-
"@bike4mind/utils": "2.1.
|
|
10462
|
+
"@bike4mind/common": "2.38.1-basic-cli-shortcuts.17131+b79f34724",
|
|
10463
|
+
"@bike4mind/mcp": "1.20.1-basic-cli-shortcuts.17131+b79f34724",
|
|
10464
|
+
"@bike4mind/services": "2.33.1-basic-cli-shortcuts.17131+b79f34724",
|
|
10465
|
+
"@bike4mind/utils": "2.1.2-basic-cli-shortcuts.17131+b79f34724",
|
|
10466
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
9511
10467
|
"@types/diff": "^5.0.9",
|
|
9512
10468
|
"@types/jsonwebtoken": "^9.0.4",
|
|
9513
10469
|
"@types/node": "^22.9.0",
|
|
@@ -9519,7 +10475,7 @@ var package_default = {
|
|
|
9519
10475
|
typescript: "^5.9.3",
|
|
9520
10476
|
vitest: "^3.2.4"
|
|
9521
10477
|
},
|
|
9522
|
-
gitHead: "
|
|
10478
|
+
gitHead: "b79f34724ba9a60aae19010065d78ac75c2f8396"
|
|
9523
10479
|
};
|
|
9524
10480
|
|
|
9525
10481
|
// src/config/constants.ts
|
|
@@ -9540,19 +10496,26 @@ var EXIT_TIMEOUT_MS = 2e3;
|
|
|
9540
10496
|
var usageCache = null;
|
|
9541
10497
|
function CliApp() {
|
|
9542
10498
|
const { exit } = useApp();
|
|
9543
|
-
const
|
|
10499
|
+
const imageRenderer = new ImageRenderer();
|
|
10500
|
+
const [state, setState] = useState5({
|
|
9544
10501
|
session: null,
|
|
9545
10502
|
sessionStore: new SessionStore(),
|
|
9546
10503
|
configStore: new ConfigStore(),
|
|
9547
10504
|
commandHistoryStore: new CommandHistoryStore(),
|
|
10505
|
+
imageStore: null,
|
|
10506
|
+
// Lazy-loaded on first use
|
|
10507
|
+
imageRenderer,
|
|
10508
|
+
messageBuilder: null,
|
|
9548
10509
|
agent: null,
|
|
9549
10510
|
mcpManager: null,
|
|
9550
10511
|
permissionManager: null,
|
|
9551
|
-
permissionPrompt: null
|
|
10512
|
+
permissionPrompt: null,
|
|
10513
|
+
trustLocationSelector: null
|
|
9552
10514
|
});
|
|
9553
|
-
const [isInitialized, setIsInitialized] =
|
|
9554
|
-
const [initError, setInitError] =
|
|
9555
|
-
const [commandHistory, setCommandHistory] =
|
|
10515
|
+
const [isInitialized, setIsInitialized] = useState5(false);
|
|
10516
|
+
const [initError, setInitError] = useState5(null);
|
|
10517
|
+
const [commandHistory, setCommandHistory] = useState5([]);
|
|
10518
|
+
const imageStoreInitPromise = useRef(null);
|
|
9556
10519
|
const setStoreSession = useCliStore((state2) => state2.setSession);
|
|
9557
10520
|
const setPermissionPrompt = useCliStore((state2) => state2.setPermissionPrompt);
|
|
9558
10521
|
const setShowConfigEditor = useCliStore((state2) => state2.setShowConfigEditor);
|
|
@@ -9576,6 +10539,13 @@ function CliApp() {
|
|
|
9576
10539
|
if (state.agent) {
|
|
9577
10540
|
state.agent.removeAllListeners();
|
|
9578
10541
|
}
|
|
10542
|
+
if (state.imageStore) {
|
|
10543
|
+
try {
|
|
10544
|
+
state.imageStore.close();
|
|
10545
|
+
} catch (err) {
|
|
10546
|
+
logger.debug(`[CLEANUP] Image store close error: ${err}`);
|
|
10547
|
+
}
|
|
10548
|
+
}
|
|
9579
10549
|
await Promise.race([
|
|
9580
10550
|
Promise.all(cleanupTasks),
|
|
9581
10551
|
new Promise((resolve3) => setTimeout(resolve3, 500))
|
|
@@ -9584,8 +10554,8 @@ function CliApp() {
|
|
|
9584
10554
|
setTimeout(() => {
|
|
9585
10555
|
process.exit(0);
|
|
9586
10556
|
}, 100);
|
|
9587
|
-
}, [state.session, state.sessionStore, state.mcpManager, state.agent]);
|
|
9588
|
-
|
|
10557
|
+
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
|
|
10558
|
+
useInput4((input, key) => {
|
|
9589
10559
|
if (key.ctrl && input === "c") {
|
|
9590
10560
|
const now = Date.now();
|
|
9591
10561
|
if (exitTimestamp && now - exitTimestamp < EXIT_TIMEOUT_MS) {
|
|
@@ -9710,7 +10680,13 @@ function CliApp() {
|
|
|
9710
10680
|
debug: () => {
|
|
9711
10681
|
}
|
|
9712
10682
|
};
|
|
9713
|
-
const permissionManager = new PermissionManager(
|
|
10683
|
+
const permissionManager = new PermissionManager(
|
|
10684
|
+
config.trustedTools || [],
|
|
10685
|
+
void 0,
|
|
10686
|
+
// customCategories (not used yet)
|
|
10687
|
+
config.tools.disabled || []
|
|
10688
|
+
// denied tools from project config
|
|
10689
|
+
);
|
|
9714
10690
|
const promptFn = (toolName, args, preview) => {
|
|
9715
10691
|
return new Promise((resolve3) => {
|
|
9716
10692
|
const canBeTrusted = permissionManager.canBeTrusted(toolName);
|
|
@@ -9738,6 +10714,7 @@ function CliApp() {
|
|
|
9738
10714
|
permissionManager,
|
|
9739
10715
|
promptFn,
|
|
9740
10716
|
agentContext,
|
|
10717
|
+
state.configStore,
|
|
9741
10718
|
config.toolApiKeys
|
|
9742
10719
|
);
|
|
9743
10720
|
console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools.length} B4M tool(s)`);
|
|
@@ -9802,8 +10779,10 @@ Remember: Use context from previous messages to understand follow-up questions.`
|
|
|
9802
10779
|
agent,
|
|
9803
10780
|
mcpManager,
|
|
9804
10781
|
permissionManager,
|
|
9805
|
-
config
|
|
10782
|
+
config,
|
|
9806
10783
|
// Store config for synchronous access
|
|
10784
|
+
availableModels: models
|
|
10785
|
+
// Store models for ConfigEditor
|
|
9807
10786
|
}));
|
|
9808
10787
|
setStoreSession(newSession);
|
|
9809
10788
|
setIsInitialized(true);
|
|
@@ -9835,9 +10814,16 @@ Remember: Use context from previous messages to understand follow-up questions.`
|
|
|
9835
10814
|
return;
|
|
9836
10815
|
}
|
|
9837
10816
|
try {
|
|
10817
|
+
let messageContent = message;
|
|
10818
|
+
let userMessageContent = message;
|
|
10819
|
+
if (state.messageBuilder && state.messageBuilder.hasImages(message)) {
|
|
10820
|
+
const { message: multimodalMessage } = await state.messageBuilder.buildMessage(message);
|
|
10821
|
+
messageContent = multimodalMessage.content;
|
|
10822
|
+
userMessageContent = message;
|
|
10823
|
+
}
|
|
9838
10824
|
const userMessage = {
|
|
9839
10825
|
role: "user",
|
|
9840
|
-
content:
|
|
10826
|
+
content: userMessageContent,
|
|
9841
10827
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9842
10828
|
};
|
|
9843
10829
|
const recentMessages = state.session.messages.slice(-20);
|
|
@@ -9845,7 +10831,7 @@ Remember: Use context from previous messages to understand follow-up questions.`
|
|
|
9845
10831
|
role: msg.role,
|
|
9846
10832
|
content: msg.content
|
|
9847
10833
|
}));
|
|
9848
|
-
const result = await state.agent.run(
|
|
10834
|
+
const result = await state.agent.run(messageContent, {
|
|
9849
10835
|
previousMessages: previousMessages.length > 0 ? previousMessages : void 0
|
|
9850
10836
|
});
|
|
9851
10837
|
const permissionDenied = result.finalAnswer.startsWith("Permission denied for tool");
|
|
@@ -9894,6 +10880,35 @@ Remember: Use context from previous messages to understand follow-up questions.`
|
|
|
9894
10880
|
console.error("Error processing message:", error);
|
|
9895
10881
|
}
|
|
9896
10882
|
};
|
|
10883
|
+
const handleImageDetected = async (imageData) => {
|
|
10884
|
+
try {
|
|
10885
|
+
let imageStore = state.imageStore;
|
|
10886
|
+
if (!imageStore) {
|
|
10887
|
+
if (!imageStoreInitPromise.current) {
|
|
10888
|
+
imageStoreInitPromise.current = (async () => {
|
|
10889
|
+
const { ImageStore: ImageStoreClass } = await import("./ImageStore-RQZ7OF7P.js");
|
|
10890
|
+
const newImageStore = new ImageStoreClass();
|
|
10891
|
+
setState((prev) => ({
|
|
10892
|
+
...prev,
|
|
10893
|
+
imageStore: newImageStore,
|
|
10894
|
+
messageBuilder: new MessageBuilder(newImageStore, prev.imageRenderer)
|
|
10895
|
+
}));
|
|
10896
|
+
return newImageStore;
|
|
10897
|
+
})();
|
|
10898
|
+
}
|
|
10899
|
+
imageStore = await imageStoreInitPromise.current;
|
|
10900
|
+
}
|
|
10901
|
+
const imageRef = await imageStore.store(imageData);
|
|
10902
|
+
const imagePlaceholder = state.imageRenderer.createPlaceholder(imageRef.hash);
|
|
10903
|
+
console.log(`
|
|
10904
|
+
\u2713 Image detected and stored: ${imagePlaceholder}
|
|
10905
|
+
`);
|
|
10906
|
+
return imagePlaceholder;
|
|
10907
|
+
} catch (error) {
|
|
10908
|
+
console.error("Error storing image:", error);
|
|
10909
|
+
throw error;
|
|
10910
|
+
}
|
|
10911
|
+
};
|
|
9897
10912
|
const handleCommand = async (command, args) => {
|
|
9898
10913
|
switch (command) {
|
|
9899
10914
|
case "help":
|
|
@@ -9919,6 +10934,9 @@ Tool Permissions:
|
|
|
9919
10934
|
/trust <tool-name> - Trust a tool (won't ask permission again)
|
|
9920
10935
|
/untrust <tool-name> - Remove tool from trusted list
|
|
9921
10936
|
/trusted - List all trusted tools
|
|
10937
|
+
|
|
10938
|
+
Project Configuration:
|
|
10939
|
+
/project-config - Show merged project configuration
|
|
9922
10940
|
`);
|
|
9923
10941
|
break;
|
|
9924
10942
|
case "exit":
|
|
@@ -9997,7 +11015,7 @@ Tool Permissions:
|
|
|
9997
11015
|
console.log("");
|
|
9998
11016
|
break;
|
|
9999
11017
|
}
|
|
10000
|
-
case "trust":
|
|
11018
|
+
case "trust": {
|
|
10001
11019
|
if (!state.permissionManager) {
|
|
10002
11020
|
console.log("Permission manager not initialized");
|
|
10003
11021
|
return;
|
|
@@ -10005,16 +11023,97 @@ Tool Permissions:
|
|
|
10005
11023
|
const toolToTrust = args[0];
|
|
10006
11024
|
if (!toolToTrust) {
|
|
10007
11025
|
console.log("Usage: /trust <tool-name>");
|
|
11026
|
+
console.log("");
|
|
11027
|
+
console.log("Example:");
|
|
11028
|
+
console.log(" /trust file_read - Opens interactive location selector");
|
|
11029
|
+
console.log("");
|
|
10008
11030
|
return;
|
|
10009
11031
|
}
|
|
10010
|
-
const
|
|
10011
|
-
if (
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
11032
|
+
const canTrust = state.permissionManager.trustTool(toolToTrust);
|
|
11033
|
+
if (!canTrust) {
|
|
11034
|
+
if (state.permissionManager.isDenied(toolToTrust)) {
|
|
11035
|
+
console.log(`\u274C Tool '${toolToTrust}' is denied by project configuration and cannot be trusted`);
|
|
11036
|
+
} else {
|
|
11037
|
+
console.log(`\u274C Tool '${toolToTrust}' cannot be trusted (it's in the 'prompt_always' category)`);
|
|
11038
|
+
}
|
|
11039
|
+
return;
|
|
10016
11040
|
}
|
|
11041
|
+
state.permissionManager.untrustTool(toolToTrust);
|
|
11042
|
+
const handleLocationSelect = async (location) => {
|
|
11043
|
+
setState((prev) => ({ ...prev, trustLocationSelector: null }));
|
|
11044
|
+
if (!location) {
|
|
11045
|
+
console.log("\u274C Trust operation cancelled");
|
|
11046
|
+
return;
|
|
11047
|
+
}
|
|
11048
|
+
const projectDir = state.configStore.getProjectConfigDir();
|
|
11049
|
+
switch (location) {
|
|
11050
|
+
case "local": {
|
|
11051
|
+
if (!projectDir) {
|
|
11052
|
+
console.log('\u274C No project found. Use "global" to save to ~/.bike4mind/config.json');
|
|
11053
|
+
return;
|
|
11054
|
+
}
|
|
11055
|
+
try {
|
|
11056
|
+
await state.configStore.initProjectConfig();
|
|
11057
|
+
const existingLocal = await state.configStore.loadRawProjectLocalConfig() || {};
|
|
11058
|
+
const updatedLocal = {
|
|
11059
|
+
...existingLocal,
|
|
11060
|
+
trustedTools: [.../* @__PURE__ */ new Set([...existingLocal.trustedTools || [], toolToTrust])]
|
|
11061
|
+
};
|
|
11062
|
+
await state.configStore.saveProjectLocalConfig(updatedLocal);
|
|
11063
|
+
state.permissionManager?.trustTool(toolToTrust);
|
|
11064
|
+
console.log(`\u2705 Tool '${toolToTrust}' trusted for this project only`);
|
|
11065
|
+
console.log(` Saved in: ${projectDir}/.bike4mind/local.json`);
|
|
11066
|
+
} catch (error) {
|
|
11067
|
+
console.error(`\u274C Failed to save project-local config:`, error);
|
|
11068
|
+
}
|
|
11069
|
+
break;
|
|
11070
|
+
}
|
|
11071
|
+
case "project": {
|
|
11072
|
+
if (!projectDir) {
|
|
11073
|
+
console.log('\u274C No project found. Use "global" to save to ~/.bike4mind/config.json');
|
|
11074
|
+
return;
|
|
11075
|
+
}
|
|
11076
|
+
try {
|
|
11077
|
+
const existingProject = await state.configStore.loadRawProjectConfig() || {};
|
|
11078
|
+
const updatedProject = {
|
|
11079
|
+
...existingProject,
|
|
11080
|
+
tools: {
|
|
11081
|
+
...existingProject.tools,
|
|
11082
|
+
enabled: [.../* @__PURE__ */ new Set([...existingProject.tools?.enabled || [], toolToTrust])]
|
|
11083
|
+
}
|
|
11084
|
+
};
|
|
11085
|
+
await state.configStore.saveProjectConfig(updatedProject);
|
|
11086
|
+
state.permissionManager?.trustTool(toolToTrust);
|
|
11087
|
+
console.log(`\u2705 Tool '${toolToTrust}' trusted for entire team`);
|
|
11088
|
+
console.log(` Saved in: ${projectDir}/.bike4mind/config.json`);
|
|
11089
|
+
console.log(" \u26A0\uFE0F Remember to commit this file to share with your team!");
|
|
11090
|
+
} catch (error) {
|
|
11091
|
+
console.error(`\u274C Failed to save project config:`, error);
|
|
11092
|
+
}
|
|
11093
|
+
break;
|
|
11094
|
+
}
|
|
11095
|
+
case "global": {
|
|
11096
|
+
try {
|
|
11097
|
+
await state.configStore.trustTool(toolToTrust);
|
|
11098
|
+
state.permissionManager?.trustTool(toolToTrust);
|
|
11099
|
+
console.log(`\u2705 Tool '${toolToTrust}' trusted globally (all projects)`);
|
|
11100
|
+
console.log(" Saved in: ~/.bike4mind/config.json");
|
|
11101
|
+
} catch (error) {
|
|
11102
|
+
console.error(`\u274C Failed to save global config:`, error);
|
|
11103
|
+
}
|
|
11104
|
+
break;
|
|
11105
|
+
}
|
|
11106
|
+
}
|
|
11107
|
+
};
|
|
11108
|
+
setState((prev) => ({
|
|
11109
|
+
...prev,
|
|
11110
|
+
trustLocationSelector: {
|
|
11111
|
+
toolName: toolToTrust,
|
|
11112
|
+
resolve: handleLocationSelect
|
|
11113
|
+
}
|
|
11114
|
+
}));
|
|
10017
11115
|
break;
|
|
11116
|
+
}
|
|
10018
11117
|
case "untrust":
|
|
10019
11118
|
if (!state.permissionManager) {
|
|
10020
11119
|
console.log("Permission manager not initialized");
|
|
@@ -10190,6 +11289,49 @@ Usage by Model (Last ${USAGE_DAYS} days):`);
|
|
|
10190
11289
|
console.log(`
|
|
10191
11290
|
No usage data available for the last ${USAGE_DAYS} days.`);
|
|
10192
11291
|
}
|
|
11292
|
+
break;
|
|
11293
|
+
}
|
|
11294
|
+
case "project-config": {
|
|
11295
|
+
const projectDir = state.configStore.getProjectConfigDir();
|
|
11296
|
+
const config = await state.configStore.get();
|
|
11297
|
+
console.log("\n\u{1F4C1} Project Configuration:\n");
|
|
11298
|
+
if (projectDir) {
|
|
11299
|
+
console.log(`Project Directory: ${projectDir}/.bike4mind/`);
|
|
11300
|
+
console.log("");
|
|
11301
|
+
const projectConfig = await state.configStore.loadRawProjectConfig();
|
|
11302
|
+
const localConfig = await state.configStore.loadRawProjectLocalConfig();
|
|
11303
|
+
if (projectConfig) {
|
|
11304
|
+
console.log("Team Config (.bike4mind/config.json):");
|
|
11305
|
+
if (projectConfig.defaultModel) {
|
|
11306
|
+
console.log(` Default Model: ${projectConfig.defaultModel}`);
|
|
11307
|
+
}
|
|
11308
|
+
if (projectConfig.tools?.denied && projectConfig.tools.denied.length > 0) {
|
|
11309
|
+
console.log(` Denied Tools: ${projectConfig.tools.denied.join(", ")}`);
|
|
11310
|
+
}
|
|
11311
|
+
if (projectConfig.mcpServers && projectConfig.mcpServers.length > 0) {
|
|
11312
|
+
console.log(` MCP Servers: ${projectConfig.mcpServers.map((s) => s.name).join(", ")}`);
|
|
11313
|
+
}
|
|
11314
|
+
console.log("");
|
|
11315
|
+
}
|
|
11316
|
+
if (localConfig) {
|
|
11317
|
+
console.log("Local Config (.bike4mind/local.json):");
|
|
11318
|
+
if (localConfig.trustedTools && localConfig.trustedTools.length > 0) {
|
|
11319
|
+
console.log(` Trusted Tools: ${localConfig.trustedTools.join(", ")}`);
|
|
11320
|
+
}
|
|
11321
|
+
if (localConfig.mcpServers && localConfig.mcpServers.length > 0) {
|
|
11322
|
+
console.log(` MCP Servers: ${localConfig.mcpServers.map((s) => s.name).join(", ")}`);
|
|
11323
|
+
}
|
|
11324
|
+
console.log("");
|
|
11325
|
+
}
|
|
11326
|
+
console.log("Merged Configuration (what you're using):");
|
|
11327
|
+
console.log(` Default Model: ${config.defaultModel}`);
|
|
11328
|
+
console.log(` Trusted Tools: ${config.trustedTools?.length || 0} tools`);
|
|
11329
|
+
console.log(` Denied Tools: ${config.tools.disabled.length} tools`);
|
|
11330
|
+
console.log(` MCP Servers: ${config.mcpServers.length} servers`);
|
|
11331
|
+
} else {
|
|
11332
|
+
console.log("No project configuration found.");
|
|
11333
|
+
console.log("Tip: Create .bike4mind/config.json in your project root for team-wide settings.");
|
|
11334
|
+
}
|
|
10193
11335
|
console.log("");
|
|
10194
11336
|
break;
|
|
10195
11337
|
}
|
|
@@ -10203,11 +11345,31 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
10203
11345
|
setState((prev) => ({ ...prev, config: updatedConfig }));
|
|
10204
11346
|
};
|
|
10205
11347
|
if (initError) {
|
|
10206
|
-
return /* @__PURE__ */
|
|
11348
|
+
return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React14.createElement(Text13, { color: "red", bold: true }, "\u274C Initialization Error"), /* @__PURE__ */ React14.createElement(Text13, null, initError), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "\n", "Tip: Run /config to set up your API keys"));
|
|
11349
|
+
}
|
|
11350
|
+
if (state.trustLocationSelector) {
|
|
11351
|
+
const projectDir = state.configStore.getProjectConfigDir();
|
|
11352
|
+
const inProject = projectDir !== null;
|
|
11353
|
+
return /* @__PURE__ */ React14.createElement(
|
|
11354
|
+
TrustLocationSelector,
|
|
11355
|
+
{
|
|
11356
|
+
inProject,
|
|
11357
|
+
onSelect: (location) => {
|
|
11358
|
+
if (state.trustLocationSelector) {
|
|
11359
|
+
state.trustLocationSelector.resolve(location);
|
|
11360
|
+
}
|
|
11361
|
+
},
|
|
11362
|
+
onCancel: () => {
|
|
11363
|
+
if (state.trustLocationSelector) {
|
|
11364
|
+
state.trustLocationSelector.resolve(null);
|
|
11365
|
+
}
|
|
11366
|
+
}
|
|
11367
|
+
}
|
|
11368
|
+
);
|
|
10207
11369
|
}
|
|
10208
11370
|
if (state.showLoginFlow) {
|
|
10209
11371
|
const loginApiUrl = getApiUrl(state.config?.apiConfig);
|
|
10210
|
-
return /* @__PURE__ */
|
|
11372
|
+
return /* @__PURE__ */ React14.createElement(
|
|
10211
11373
|
LoginFlow,
|
|
10212
11374
|
{
|
|
10213
11375
|
apiUrl: loginApiUrl,
|
|
@@ -10230,15 +11392,17 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
10230
11392
|
);
|
|
10231
11393
|
}
|
|
10232
11394
|
if (!isInitialized) {
|
|
10233
|
-
return /* @__PURE__ */
|
|
11395
|
+
return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React14.createElement(Text13, null, "\u{1F680} Initializing..."));
|
|
10234
11396
|
}
|
|
10235
|
-
return /* @__PURE__ */
|
|
11397
|
+
return /* @__PURE__ */ React14.createElement(
|
|
10236
11398
|
App,
|
|
10237
11399
|
{
|
|
10238
11400
|
onMessage: handleMessage,
|
|
10239
11401
|
onCommand: handleCommand,
|
|
11402
|
+
onImageDetected: handleImageDetected,
|
|
10240
11403
|
commandHistory,
|
|
10241
11404
|
config: state.config,
|
|
11405
|
+
availableModels: state.availableModels,
|
|
10242
11406
|
onSaveConfig: handleSaveConfig,
|
|
10243
11407
|
onPermissionResponse: (response) => {
|
|
10244
11408
|
if (state.permissionPrompt) {
|
|
@@ -10265,6 +11429,6 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
|
|
|
10265
11429
|
if (isDevMode) {
|
|
10266
11430
|
console.log("\u{1F527} Running in development mode (using TypeScript source)\n");
|
|
10267
11431
|
}
|
|
10268
|
-
render(/* @__PURE__ */
|
|
11432
|
+
render(/* @__PURE__ */ React14.createElement(CliApp, null), {
|
|
10269
11433
|
exitOnCtrlC: false
|
|
10270
11434
|
});
|