@bike4mind/cli 0.2.6-fix-cli-mcp-type-mismatch.17119 → 0.2.7-feat-cli-rewind-command.17128
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ImageStore-RQZ7OF7P.js +225 -0
- package/dist/index.js +712 -63
- package/package.json +9 -7
package/dist/index.js
CHANGED
|
@@ -79,8 +79,8 @@ import {
|
|
|
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 Box14, 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
|
|
@@ -389,6 +389,11 @@ var COMMANDS = [
|
|
|
389
389
|
name: "clear",
|
|
390
390
|
description: "Start a new session",
|
|
391
391
|
aliases: ["new"]
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: "rewind",
|
|
395
|
+
description: "Rewind conversation to a previous point",
|
|
396
|
+
aliases: ["undo"]
|
|
392
397
|
}
|
|
393
398
|
];
|
|
394
399
|
|
|
@@ -413,6 +418,202 @@ function searchCommands(query) {
|
|
|
413
418
|
return results.map((result) => result.item);
|
|
414
419
|
}
|
|
415
420
|
|
|
421
|
+
// src/utils/imageDetector.ts
|
|
422
|
+
import { readFileSync, existsSync, statSync as statSync2 } from "fs";
|
|
423
|
+
import { extname as extname2 } from "path";
|
|
424
|
+
var ImageInputDetector = class {
|
|
425
|
+
static {
|
|
426
|
+
this.MAX_IMAGE_SIZE = 10 * 1024 * 1024;
|
|
427
|
+
}
|
|
428
|
+
static {
|
|
429
|
+
// 10MB
|
|
430
|
+
this.SUPPORTED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Check if input contains image data
|
|
434
|
+
*/
|
|
435
|
+
static containsImageData(input) {
|
|
436
|
+
if (input.includes("\x1B]1337;File=")) {
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
if (input.includes("\x1B_G")) {
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
if (input.includes("\x1BP")) {
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
if (input.match(/data:image\/(png|jpeg|jpg|gif|webp);base64,/)) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
if (this.looksLikeFilePath(input)) {
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Extract image data from terminal input
|
|
455
|
+
*/
|
|
456
|
+
static extractImageData(input) {
|
|
457
|
+
const iterm2 = this.extractITerm2Image(input);
|
|
458
|
+
if (iterm2) return iterm2;
|
|
459
|
+
const kitty = this.extractKittyImage(input);
|
|
460
|
+
if (kitty) return kitty;
|
|
461
|
+
const sixel = this.extractSixelImage(input);
|
|
462
|
+
if (sixel) return sixel;
|
|
463
|
+
const base64 = this.extractBase64Image(input);
|
|
464
|
+
if (base64) return base64;
|
|
465
|
+
const fileDrop = this.extractDroppedFile(input);
|
|
466
|
+
if (fileDrop) return fileDrop;
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Extract iTerm2 inline image
|
|
471
|
+
* Format: ESC]1337;File=[args]:base64data^G
|
|
472
|
+
*/
|
|
473
|
+
static extractITerm2Image(input) {
|
|
474
|
+
const match = input.match(/\x1b\]1337;File=([^:]*):([^\x07]+)\x07/);
|
|
475
|
+
if (!match) return null;
|
|
476
|
+
try {
|
|
477
|
+
const args = match[1];
|
|
478
|
+
const base64Data = match[2];
|
|
479
|
+
const data = Buffer.from(base64Data, "base64");
|
|
480
|
+
let format = "png";
|
|
481
|
+
const nameMatch = args.match(/name=([^;]+)/);
|
|
482
|
+
if (nameMatch) {
|
|
483
|
+
const ext = extname2(nameMatch[1]).substring(1).toLowerCase();
|
|
484
|
+
if (ext && this.SUPPORTED_EXTENSIONS.includes(`.${ext}`)) {
|
|
485
|
+
format = ext === "jpeg" ? "jpg" : ext;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
type: "paste",
|
|
490
|
+
data,
|
|
491
|
+
format,
|
|
492
|
+
protocol: "iterm2"
|
|
493
|
+
};
|
|
494
|
+
} catch (error) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Extract Kitty graphics protocol image
|
|
500
|
+
* Format: ESC_Ga=T,f=100;base64data ESC\
|
|
501
|
+
*/
|
|
502
|
+
static extractKittyImage(input) {
|
|
503
|
+
const match = input.match(/\x1b_G([^;]*);([^\x1b]+)\x1b\\/);
|
|
504
|
+
if (!match) return null;
|
|
505
|
+
try {
|
|
506
|
+
const params = match[1];
|
|
507
|
+
const base64Data = match[2];
|
|
508
|
+
const data = Buffer.from(base64Data, "base64");
|
|
509
|
+
let format = "png";
|
|
510
|
+
const formatMatch = params.match(/f=(\d+)/);
|
|
511
|
+
if (formatMatch) {
|
|
512
|
+
const formatCode = parseInt(formatMatch[1], 10);
|
|
513
|
+
if (formatCode === 100) format = "jpg";
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
type: "paste",
|
|
517
|
+
data,
|
|
518
|
+
format,
|
|
519
|
+
protocol: "kitty"
|
|
520
|
+
};
|
|
521
|
+
} catch (error) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Extract Sixel image
|
|
527
|
+
* Format: ESC P q ... ESC \
|
|
528
|
+
*/
|
|
529
|
+
static extractSixelImage(input) {
|
|
530
|
+
const match = input.match(/\x1bP([^q]*q[^\x1b]+)\x1b\\/);
|
|
531
|
+
if (!match) return null;
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Extract base64 data URL
|
|
536
|
+
* Format: data:image/png;base64,iVBORw0KGgo...
|
|
537
|
+
*/
|
|
538
|
+
static extractBase64Image(input) {
|
|
539
|
+
const match = input.match(/data:image\/(png|jpeg|jpg|gif|webp);base64,([A-Za-z0-9+/=]+)/);
|
|
540
|
+
if (!match) return null;
|
|
541
|
+
try {
|
|
542
|
+
const format = match[1] === "jpeg" ? "jpg" : match[1];
|
|
543
|
+
const base64Data = match[2];
|
|
544
|
+
const data = Buffer.from(base64Data, "base64");
|
|
545
|
+
return {
|
|
546
|
+
type: "paste",
|
|
547
|
+
data,
|
|
548
|
+
format,
|
|
549
|
+
protocol: "base64"
|
|
550
|
+
};
|
|
551
|
+
} catch (error) {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Extract dropped file path and read image
|
|
557
|
+
*/
|
|
558
|
+
static extractDroppedFile(input) {
|
|
559
|
+
if (!this.looksLikeFilePath(input)) return null;
|
|
560
|
+
let filepath = input.trim();
|
|
561
|
+
filepath = filepath.replace(/^["']|["']$/g, "");
|
|
562
|
+
filepath = filepath.replace(/\\\s/g, " ");
|
|
563
|
+
if (!existsSync(filepath)) return null;
|
|
564
|
+
const stats = statSync2(filepath);
|
|
565
|
+
if (!stats.isFile()) return null;
|
|
566
|
+
const ext = extname2(filepath).toLowerCase();
|
|
567
|
+
if (!this.SUPPORTED_EXTENSIONS.includes(ext)) return null;
|
|
568
|
+
if (stats.size > this.MAX_IMAGE_SIZE) return null;
|
|
569
|
+
try {
|
|
570
|
+
const data = readFileSync(filepath);
|
|
571
|
+
const format = ext.substring(1) === "jpeg" ? "jpg" : ext.substring(1);
|
|
572
|
+
const filename = filepath.split("/").pop() || "image";
|
|
573
|
+
return {
|
|
574
|
+
type: "drop",
|
|
575
|
+
data,
|
|
576
|
+
format,
|
|
577
|
+
filename,
|
|
578
|
+
filepath
|
|
579
|
+
};
|
|
580
|
+
} catch (error) {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Check if string looks like a file path
|
|
586
|
+
*/
|
|
587
|
+
static looksLikeFilePath(input) {
|
|
588
|
+
const trimmed = input.trim();
|
|
589
|
+
if (trimmed.startsWith("/") || trimmed.match(/^[a-zA-Z]:\\/)) {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
if (trimmed.startsWith("~")) {
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
if (this.SUPPORTED_EXTENSIONS.some((ext) => trimmed.toLowerCase().endsWith(ext))) {
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Remove image data from input string, leaving only text
|
|
602
|
+
*/
|
|
603
|
+
static stripImageData(input) {
|
|
604
|
+
let cleaned = input;
|
|
605
|
+
cleaned = cleaned.replace(/\x1b\]1337;File=[^:]*:[^\x07]+\x07/g, "");
|
|
606
|
+
cleaned = cleaned.replace(/\x1b_G[^;]*;[^\x1b]+\x1b\\/g, "");
|
|
607
|
+
cleaned = cleaned.replace(/\x1bP[^q]*q[^\x1b]+\x1b\\/g, "");
|
|
608
|
+
cleaned = cleaned.replace(/data:image\/(png|jpeg|jpg|gif|webp);base64,[A-Za-z0-9+/=]+/g, "");
|
|
609
|
+
const filepath = this.extractDroppedFile(cleaned);
|
|
610
|
+
if (filepath) {
|
|
611
|
+
cleaned = cleaned.replace(filepath.filepath, "");
|
|
612
|
+
}
|
|
613
|
+
return cleaned.trim();
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
|
|
416
617
|
// src/components/InputPrompt.tsx
|
|
417
618
|
function findAtTrigger(value) {
|
|
418
619
|
for (let i = value.length - 1; i >= 0; i--) {
|
|
@@ -429,12 +630,26 @@ function findAtTrigger(value) {
|
|
|
429
630
|
}
|
|
430
631
|
return null;
|
|
431
632
|
}
|
|
432
|
-
function InputPrompt({
|
|
633
|
+
function InputPrompt({
|
|
634
|
+
onSubmit,
|
|
635
|
+
onImageDetected,
|
|
636
|
+
disabled = false,
|
|
637
|
+
history = [],
|
|
638
|
+
prefillInput,
|
|
639
|
+
onPrefillConsumed
|
|
640
|
+
}) {
|
|
433
641
|
const [value, setValue] = useState("");
|
|
434
642
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
435
643
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
436
644
|
const [tempInput, setTempInput] = useState("");
|
|
437
645
|
const [inputKey, setInputKey] = useState(0);
|
|
646
|
+
useEffect(() => {
|
|
647
|
+
if (prefillInput) {
|
|
648
|
+
setValue(prefillInput);
|
|
649
|
+
setInputKey((prev) => prev + 1);
|
|
650
|
+
onPrefillConsumed?.();
|
|
651
|
+
}
|
|
652
|
+
}, [prefillInput, onPrefillConsumed]);
|
|
438
653
|
const [fileAutocomplete, setFileAutocomplete] = useState(null);
|
|
439
654
|
const [fileSelectedIndex, setFileSelectedIndex] = useState(0);
|
|
440
655
|
const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active;
|
|
@@ -550,7 +765,16 @@ function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
|
550
765
|
setHistoryIndex(-1);
|
|
551
766
|
setFileAutocomplete(null);
|
|
552
767
|
};
|
|
553
|
-
const handleChange = (newValue) => {
|
|
768
|
+
const handleChange = async (newValue) => {
|
|
769
|
+
if (ImageInputDetector.containsImageData(newValue)) {
|
|
770
|
+
const imageEvent = ImageInputDetector.extractImageData(newValue);
|
|
771
|
+
if (imageEvent && onImageDetected) {
|
|
772
|
+
const placeholder = await onImageDetected(imageEvent.data);
|
|
773
|
+
setValue(`${placeholder} `);
|
|
774
|
+
setInputKey((prev) => prev + 1);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
554
778
|
setValue(newValue);
|
|
555
779
|
if (historyIndex !== -1) {
|
|
556
780
|
setHistoryIndex(-1);
|
|
@@ -1015,10 +1239,13 @@ function App({
|
|
|
1015
1239
|
onMessage,
|
|
1016
1240
|
onCommand,
|
|
1017
1241
|
onPermissionResponse,
|
|
1242
|
+
onImageDetected,
|
|
1018
1243
|
commandHistory = [],
|
|
1019
1244
|
config,
|
|
1020
1245
|
availableModels = [],
|
|
1021
|
-
onSaveConfig
|
|
1246
|
+
onSaveConfig,
|
|
1247
|
+
prefillInput,
|
|
1248
|
+
onPrefillConsumed
|
|
1022
1249
|
}) {
|
|
1023
1250
|
const messages = useCliStore((state) => state.session?.messages || []);
|
|
1024
1251
|
const sessionName = useCliStore((state) => state.session?.name || "New Session");
|
|
@@ -1034,7 +1261,7 @@ function App({
|
|
|
1034
1261
|
async (input) => {
|
|
1035
1262
|
const trimmed = input.trim();
|
|
1036
1263
|
if (!trimmed) return;
|
|
1037
|
-
if (trimmed.startsWith("/")) {
|
|
1264
|
+
if (trimmed.startsWith("/") && !trimmed.match(/^\/[A-Z]/)) {
|
|
1038
1265
|
const [command, ...args] = trimmed.slice(1).split(" ");
|
|
1039
1266
|
await onCommand(command, args);
|
|
1040
1267
|
return;
|
|
@@ -1076,7 +1303,17 @@ ${errorBlock}`;
|
|
|
1076
1303
|
canBeTrusted: permissionPrompt.canBeTrusted,
|
|
1077
1304
|
onResponse: onPermissionResponse
|
|
1078
1305
|
}
|
|
1079
|
-
)) : /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Static, { items: messages }, (message, index) => /* @__PURE__ */ React9.createElement(Box9, { key: index, flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(MessageItem, { message }))), /* @__PURE__ */ React9.createElement(AgentThinking, null), exitRequested && /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow", bold: true }, "Press Ctrl+C again to exit")), /* @__PURE__ */ React9.createElement(Box9, { borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React9.createElement(
|
|
1306
|
+
)) : /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Static, { items: messages }, (message, index) => /* @__PURE__ */ React9.createElement(Box9, { key: index, flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(MessageItem, { message }))), /* @__PURE__ */ React9.createElement(AgentThinking, null), exitRequested && /* @__PURE__ */ React9.createElement(Box9, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow", bold: true }, "Press Ctrl+C again to exit")), /* @__PURE__ */ React9.createElement(Box9, { borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React9.createElement(
|
|
1307
|
+
InputPrompt,
|
|
1308
|
+
{
|
|
1309
|
+
onSubmit: handleSubmit,
|
|
1310
|
+
onImageDetected,
|
|
1311
|
+
disabled: isThinking,
|
|
1312
|
+
history: commandHistory,
|
|
1313
|
+
prefillInput,
|
|
1314
|
+
onPrefillConsumed
|
|
1315
|
+
}
|
|
1316
|
+
))));
|
|
1080
1317
|
}
|
|
1081
1318
|
function MessageItem({ message }) {
|
|
1082
1319
|
return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Box9, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: message.role === "user" ? "cyan" : "green" }, message.role === "user" ? "\u{1F464} You" : "\u{1F916} Assistant"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), /* @__PURE__ */ React9.createElement(Box9, { paddingLeft: 2 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React9.createElement(Text8, null, message.content)), message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React9.createElement(Box9, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true, bold: true }, "\u{1F527} Agent Reasoning Trace (", message.metadata.steps.filter((s) => s.type === "action").length, " ", "tools used, ", message.metadata.steps.length, " total steps)", message.metadata.tokenUsage && ` \u2022 ${message.metadata.tokenUsage.total} tokens`), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
|
|
@@ -1166,9 +1403,78 @@ function TrustLocationSelector({ inProject, onSelect, onCancel }) {
|
|
|
1166
1403
|
), /* @__PURE__ */ React11.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Ctrl+C to cancel")));
|
|
1167
1404
|
}
|
|
1168
1405
|
|
|
1406
|
+
// src/components/RewindSelector.tsx
|
|
1407
|
+
import React12, { useState as useState3 } from "react";
|
|
1408
|
+
import { Box as Box12, Text as Text11, useInput as useInput3 } from "ink";
|
|
1409
|
+
import SelectInput3 from "ink-select-input";
|
|
1410
|
+
function RewindSelector({ messages, onSelect, onCancel }) {
|
|
1411
|
+
const [step, setStep] = useState3("selection");
|
|
1412
|
+
const [selectedMessageIndex, setSelectedMessageIndex] = useState3(null);
|
|
1413
|
+
useInput3((input, key) => {
|
|
1414
|
+
if (key.escape) {
|
|
1415
|
+
if (step === "confirmation") {
|
|
1416
|
+
setStep("selection");
|
|
1417
|
+
setSelectedMessageIndex(null);
|
|
1418
|
+
} else {
|
|
1419
|
+
onCancel();
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
const userMessages = messages.map((msg, idx) => ({ msg, originalIndex: idx })).filter(({ msg }) => msg.role === "user");
|
|
1424
|
+
const items = userMessages.map(({ msg, originalIndex }, index) => {
|
|
1425
|
+
const preview = msg.content.length > 50 ? msg.content.substring(0, 50) + "..." : msg.content;
|
|
1426
|
+
const timestamp = new Date(msg.timestamp).toLocaleString();
|
|
1427
|
+
const position = index + 1;
|
|
1428
|
+
return {
|
|
1429
|
+
label: `[${position}] ${timestamp} - ${preview}`,
|
|
1430
|
+
value: originalIndex
|
|
1431
|
+
};
|
|
1432
|
+
});
|
|
1433
|
+
const handleSelectionSelect = (item) => {
|
|
1434
|
+
setSelectedMessageIndex(item.value);
|
|
1435
|
+
setStep("confirmation");
|
|
1436
|
+
};
|
|
1437
|
+
const handleConfirmationSelect = (item) => {
|
|
1438
|
+
if (item.value === "confirm" && selectedMessageIndex !== null) {
|
|
1439
|
+
onSelect(selectedMessageIndex);
|
|
1440
|
+
} else {
|
|
1441
|
+
setStep("selection");
|
|
1442
|
+
setSelectedMessageIndex(null);
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
const messagesToRemove = selectedMessageIndex !== null && selectedMessageIndex >= 0 && selectedMessageIndex < messages.length ? messages.slice(selectedMessageIndex) : [];
|
|
1446
|
+
const tokensToRemove = messagesToRemove.reduce((sum2, msg) => {
|
|
1447
|
+
return sum2 + (msg.metadata?.tokenUsage?.total || 0);
|
|
1448
|
+
}, 0);
|
|
1449
|
+
if (step === "selection") {
|
|
1450
|
+
return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "Select a point to rewind to:")), /* @__PURE__ */ React12.createElement(
|
|
1451
|
+
SelectInput3,
|
|
1452
|
+
{
|
|
1453
|
+
items,
|
|
1454
|
+
onSelect: handleSelectionSelect,
|
|
1455
|
+
itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
|
|
1456
|
+
}
|
|
1457
|
+
), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Esc to cancel")));
|
|
1458
|
+
}
|
|
1459
|
+
const confirmationItems = [
|
|
1460
|
+
{ label: "Yes, rewind to this point", value: "confirm" },
|
|
1461
|
+
{ label: "No, go back to selection", value: "cancel" }
|
|
1462
|
+
];
|
|
1463
|
+
const selectedMessage = selectedMessageIndex !== null && selectedMessageIndex >= 0 && selectedMessageIndex < messages.length ? messages[selectedMessageIndex] : null;
|
|
1464
|
+
const selectedPreview = selectedMessage ? selectedMessage.content.length > 50 ? selectedMessage.content.substring(0, 50) + "..." : selectedMessage.content : "";
|
|
1465
|
+
return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "Confirm rewind operation:"), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Message: ", selectedPreview), /* @__PURE__ */ React12.createElement(Text11, { color: "yellow" }, "This will remove ", messagesToRemove.length, " message(s) (", tokensToRemove.toLocaleString(), " tokens)"), /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, "The selected message will be placed in your input for editing.")), /* @__PURE__ */ React12.createElement(
|
|
1466
|
+
SelectInput3,
|
|
1467
|
+
{
|
|
1468
|
+
items: confirmationItems,
|
|
1469
|
+
onSelect: handleConfirmationSelect,
|
|
1470
|
+
itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
|
|
1471
|
+
}
|
|
1472
|
+
), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Esc to go back")));
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1169
1475
|
// src/components/LoginFlow.tsx
|
|
1170
|
-
import
|
|
1171
|
-
import { Box as
|
|
1476
|
+
import React13, { useState as useState4, useEffect as useEffect2 } from "react";
|
|
1477
|
+
import { Box as Box13, Text as Text12 } from "ink";
|
|
1172
1478
|
import Spinner2 from "ink-spinner";
|
|
1173
1479
|
import jwt from "jsonwebtoken";
|
|
1174
1480
|
import open from "open";
|
|
@@ -1284,10 +1590,10 @@ var OAuthClient = class {
|
|
|
1284
1590
|
|
|
1285
1591
|
// src/components/LoginFlow.tsx
|
|
1286
1592
|
function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
|
|
1287
|
-
const [status, setStatus] =
|
|
1288
|
-
const [deviceFlow, setDeviceFlow] =
|
|
1289
|
-
const [statusMessage, setStatusMessage] =
|
|
1290
|
-
const [error, setError] =
|
|
1593
|
+
const [status, setStatus] = useState4("initiating");
|
|
1594
|
+
const [deviceFlow, setDeviceFlow] = useState4(null);
|
|
1595
|
+
const [statusMessage, setStatusMessage] = useState4("Initiating device authorization...");
|
|
1596
|
+
const [error, setError] = useState4(null);
|
|
1291
1597
|
useEffect2(() => {
|
|
1292
1598
|
const runLoginFlow = async () => {
|
|
1293
1599
|
const oauth = new OAuthClient(apiUrl);
|
|
@@ -1331,15 +1637,15 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
|
|
|
1331
1637
|
}
|
|
1332
1638
|
}, [deviceFlow, status]);
|
|
1333
1639
|
if (status === "initiating") {
|
|
1334
|
-
return /* @__PURE__ */
|
|
1640
|
+
return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Box13, null, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan" }, /* @__PURE__ */ React13.createElement(Spinner2, { type: "dots" }), " Initiating device authorization...")));
|
|
1335
1641
|
}
|
|
1336
1642
|
if (status === "error") {
|
|
1337
|
-
return /* @__PURE__ */
|
|
1643
|
+
return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, "\u2716 Authentication Failed")), /* @__PURE__ */ React13.createElement(Box13, null, /* @__PURE__ */ React13.createElement(Text12, { color: "red" }, error)));
|
|
1338
1644
|
}
|
|
1339
1645
|
if (status === "success") {
|
|
1340
|
-
return /* @__PURE__ */
|
|
1646
|
+
return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "green", bold: true }, "\u2714 Successfully authenticated!")), /* @__PURE__ */ React13.createElement(Box13, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "You can now use B4M CLI with your account.")));
|
|
1341
1647
|
}
|
|
1342
|
-
return /* @__PURE__ */
|
|
1648
|
+
return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" }, /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan", bold: true }, "\u{1F510} Device Authorization")), /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "Opening browser automatically... If it doesn't open, please visit:")), /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React13.createElement(Text12, { color: "blue", bold: true }, deviceFlow?.verification_uri)), /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "And enter this code when prompted:")), /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React13.createElement(Text12, { color: "yellow", bold: true }, deviceFlow?.user_code)), /* @__PURE__ */ React13.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan" }, /* @__PURE__ */ React13.createElement(Spinner2, { type: "dots" }), " ", statusMessage)), /* @__PURE__ */ React13.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "Expires in ", deviceFlow ? Math.floor(deviceFlow.expires_in / 60) : 0, " minutes")));
|
|
1343
1649
|
}
|
|
1344
1650
|
|
|
1345
1651
|
// src/storage/SessionStore.ts
|
|
@@ -1461,7 +1767,7 @@ var SessionStore = class {
|
|
|
1461
1767
|
};
|
|
1462
1768
|
|
|
1463
1769
|
// src/storage/ConfigStore.ts
|
|
1464
|
-
import { promises as fs4, existsSync as
|
|
1770
|
+
import { promises as fs4, existsSync as existsSync3 } from "fs";
|
|
1465
1771
|
import path4 from "path";
|
|
1466
1772
|
import { homedir as homedir2 } from "os";
|
|
1467
1773
|
import { v4 as uuidv4 } from "uuid";
|
|
@@ -1588,7 +1894,7 @@ function findProjectConfigDir(startDir = process.cwd()) {
|
|
|
1588
1894
|
while (currentDir !== root) {
|
|
1589
1895
|
const gitPath = path4.join(currentDir, ".git");
|
|
1590
1896
|
try {
|
|
1591
|
-
if (
|
|
1897
|
+
if (existsSync3(gitPath)) {
|
|
1592
1898
|
return currentDir;
|
|
1593
1899
|
}
|
|
1594
1900
|
} catch {
|
|
@@ -2196,7 +2502,7 @@ var ReActAgent = class extends EventEmitter {
|
|
|
2196
2502
|
/**
|
|
2197
2503
|
* Run the agent to completion
|
|
2198
2504
|
*
|
|
2199
|
-
* @param query - The user's question or task
|
|
2505
|
+
* @param query - The user's question or task (can be text or multimodal content with images)
|
|
2200
2506
|
* @param options - Optional overrides for this run
|
|
2201
2507
|
* @returns Agent result with final answer and all steps
|
|
2202
2508
|
*/
|
|
@@ -7673,7 +7979,7 @@ var planetVisibilityTool = {
|
|
|
7673
7979
|
|
|
7674
7980
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
|
|
7675
7981
|
import { promises as fs7 } from "fs";
|
|
7676
|
-
import { existsSync as
|
|
7982
|
+
import { existsSync as existsSync4, statSync as statSync4 } from "fs";
|
|
7677
7983
|
import path6 from "path";
|
|
7678
7984
|
var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
|
|
7679
7985
|
async function readFileContent(params) {
|
|
@@ -7684,10 +7990,10 @@ async function readFileContent(params) {
|
|
|
7684
7990
|
if (!resolvedPath.startsWith(cwd)) {
|
|
7685
7991
|
throw new Error(`Access denied: Cannot read files outside of current working directory`);
|
|
7686
7992
|
}
|
|
7687
|
-
if (!
|
|
7993
|
+
if (!existsSync4(resolvedPath)) {
|
|
7688
7994
|
throw new Error(`File not found: ${filePath}`);
|
|
7689
7995
|
}
|
|
7690
|
-
const stats =
|
|
7996
|
+
const stats = statSync4(resolvedPath);
|
|
7691
7997
|
if (stats.isDirectory()) {
|
|
7692
7998
|
throw new Error(`Path is a directory, not a file: ${filePath}`);
|
|
7693
7999
|
}
|
|
@@ -7730,7 +8036,7 @@ var fileReadTool = {
|
|
|
7730
8036
|
context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
|
|
7731
8037
|
try {
|
|
7732
8038
|
const content = await readFileContent(params);
|
|
7733
|
-
const stats =
|
|
8039
|
+
const stats = statSync4(path6.resolve(process.cwd(), path6.normalize(params.path)));
|
|
7734
8040
|
context.logger.info("\u2705 FileRead: Success", {
|
|
7735
8041
|
path: params.path,
|
|
7736
8042
|
size: stats.size,
|
|
@@ -7770,7 +8076,7 @@ var fileReadTool = {
|
|
|
7770
8076
|
|
|
7771
8077
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
|
|
7772
8078
|
import { promises as fs8 } from "fs";
|
|
7773
|
-
import { existsSync as
|
|
8079
|
+
import { existsSync as existsSync5 } from "fs";
|
|
7774
8080
|
import path7 from "path";
|
|
7775
8081
|
async function createFile(params) {
|
|
7776
8082
|
const { path: filePath, content, createDirectories = true } = params;
|
|
@@ -7780,7 +8086,7 @@ async function createFile(params) {
|
|
|
7780
8086
|
if (!resolvedPath.startsWith(cwd)) {
|
|
7781
8087
|
throw new Error(`Access denied: Cannot create files outside of current working directory`);
|
|
7782
8088
|
}
|
|
7783
|
-
const fileExists =
|
|
8089
|
+
const fileExists = existsSync5(resolvedPath);
|
|
7784
8090
|
const action = fileExists ? "overwritten" : "created";
|
|
7785
8091
|
if (createDirectories) {
|
|
7786
8092
|
const dir = path7.dirname(resolvedPath);
|
|
@@ -7798,7 +8104,7 @@ var createFileTool = {
|
|
|
7798
8104
|
implementation: (context) => ({
|
|
7799
8105
|
toolFn: async (value) => {
|
|
7800
8106
|
const params = value;
|
|
7801
|
-
const fileExists =
|
|
8107
|
+
const fileExists = existsSync5(path7.resolve(process.cwd(), path7.normalize(params.path)));
|
|
7802
8108
|
context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
|
|
7803
8109
|
path: params.path,
|
|
7804
8110
|
size: params.content.length
|
|
@@ -7839,7 +8145,7 @@ var createFileTool = {
|
|
|
7839
8145
|
|
|
7840
8146
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
|
|
7841
8147
|
import { readdir } from "fs/promises";
|
|
7842
|
-
import { statSync as
|
|
8148
|
+
import { statSync as statSync5 } from "fs";
|
|
7843
8149
|
import path8 from "path";
|
|
7844
8150
|
function matchGlob(filePath, pattern) {
|
|
7845
8151
|
let regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, options) => `(${options.split(",").join("|")})`).replace(/\[([^\]]+)\]/g, "[$1]");
|
|
@@ -7887,7 +8193,7 @@ async function listFiles(params) {
|
|
|
7887
8193
|
for (const match of matches) {
|
|
7888
8194
|
try {
|
|
7889
8195
|
const fullPath = path8.join(targetCwd, match);
|
|
7890
|
-
const stats =
|
|
8196
|
+
const stats = statSync5(fullPath);
|
|
7891
8197
|
filesInfo.push({
|
|
7892
8198
|
path: match,
|
|
7893
8199
|
size: stats.size,
|
|
@@ -7968,7 +8274,7 @@ var globFilesTool = {
|
|
|
7968
8274
|
|
|
7969
8275
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
|
|
7970
8276
|
import { readdir as readdir2 } from "fs/promises";
|
|
7971
|
-
import { readFileSync as
|
|
8277
|
+
import { readFileSync as readFileSync3, statSync as statSync6 } from "fs";
|
|
7972
8278
|
import path9 from "path";
|
|
7973
8279
|
var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
|
|
7974
8280
|
function matchGlob2(filePath, pattern) {
|
|
@@ -8020,7 +8326,7 @@ async function searchFiles2(params) {
|
|
|
8020
8326
|
for (const match of filesToSearch) {
|
|
8021
8327
|
const fullPath = path9.join(targetCwd, match);
|
|
8022
8328
|
try {
|
|
8023
|
-
const stats =
|
|
8329
|
+
const stats = statSync6(fullPath);
|
|
8024
8330
|
if (stats.isDirectory() || stats.size > MAX_FILE_SIZE3) {
|
|
8025
8331
|
continue;
|
|
8026
8332
|
}
|
|
@@ -8044,7 +8350,7 @@ async function searchFiles2(params) {
|
|
|
8044
8350
|
filesSearched++;
|
|
8045
8351
|
const fullPath = path9.join(targetCwd, file);
|
|
8046
8352
|
try {
|
|
8047
|
-
const content =
|
|
8353
|
+
const content = readFileSync3(fullPath, "utf-8");
|
|
8048
8354
|
const lines = content.split("\n");
|
|
8049
8355
|
for (let i = 0; i < lines.length; i++) {
|
|
8050
8356
|
if (matches.length >= maxResults) {
|
|
@@ -8178,7 +8484,7 @@ var grepSearchTool = {
|
|
|
8178
8484
|
|
|
8179
8485
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
|
|
8180
8486
|
import { promises as fs9 } from "fs";
|
|
8181
|
-
import { existsSync as
|
|
8487
|
+
import { existsSync as existsSync6, statSync as statSync7 } from "fs";
|
|
8182
8488
|
import path10 from "path";
|
|
8183
8489
|
async function deleteFile(params) {
|
|
8184
8490
|
const { path: filePath, recursive = false } = params;
|
|
@@ -8188,10 +8494,10 @@ async function deleteFile(params) {
|
|
|
8188
8494
|
if (!resolvedPath.startsWith(cwd)) {
|
|
8189
8495
|
throw new Error(`Access denied: Cannot delete files outside of current working directory`);
|
|
8190
8496
|
}
|
|
8191
|
-
if (!
|
|
8497
|
+
if (!existsSync6(resolvedPath)) {
|
|
8192
8498
|
throw new Error(`File or directory not found: ${filePath}`);
|
|
8193
8499
|
}
|
|
8194
|
-
const stats =
|
|
8500
|
+
const stats = statSync7(resolvedPath);
|
|
8195
8501
|
const isDirectory = stats.isDirectory();
|
|
8196
8502
|
const size = stats.size;
|
|
8197
8503
|
if (isDirectory && !recursive) {
|
|
@@ -8212,7 +8518,7 @@ var deleteFileTool = {
|
|
|
8212
8518
|
toolFn: async (value) => {
|
|
8213
8519
|
const params = value;
|
|
8214
8520
|
const resolvedPath = path10.resolve(process.cwd(), path10.normalize(params.path));
|
|
8215
|
-
const isDirectory =
|
|
8521
|
+
const isDirectory = existsSync6(resolvedPath) && statSync7(resolvedPath).isDirectory();
|
|
8216
8522
|
context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
|
|
8217
8523
|
path: params.path,
|
|
8218
8524
|
recursive: params.recursive
|
|
@@ -8502,10 +8808,10 @@ import jwt2 from "jsonwebtoken";
|
|
|
8502
8808
|
// src/utils/diffPreview.ts
|
|
8503
8809
|
import * as Diff from "diff";
|
|
8504
8810
|
import { readFile } from "fs/promises";
|
|
8505
|
-
import { existsSync as
|
|
8811
|
+
import { existsSync as existsSync7 } from "fs";
|
|
8506
8812
|
async function generateFileDiffPreview(args) {
|
|
8507
8813
|
try {
|
|
8508
|
-
if (!
|
|
8814
|
+
if (!existsSync7(args.path)) {
|
|
8509
8815
|
const lines2 = args.content.split("\n");
|
|
8510
8816
|
const preview = lines2.slice(0, 20).join("\n");
|
|
8511
8817
|
const hasMore = lines2.length > 20;
|
|
@@ -8535,7 +8841,7 @@ ${preview}${hasMore ? `
|
|
|
8535
8841
|
}
|
|
8536
8842
|
async function generateFileDeletePreview(args) {
|
|
8537
8843
|
try {
|
|
8538
|
-
if (!
|
|
8844
|
+
if (!existsSync7(args.path)) {
|
|
8539
8845
|
return `[File does not exist: ${args.path}]`;
|
|
8540
8846
|
}
|
|
8541
8847
|
const stats = await import("fs/promises").then((fs11) => fs11.stat(args.path));
|
|
@@ -9197,7 +9503,7 @@ var logger = Logger2.getInstance();
|
|
|
9197
9503
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
9198
9504
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
9199
9505
|
import path12 from "path";
|
|
9200
|
-
import { existsSync as
|
|
9506
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
|
|
9201
9507
|
var MCPClient = class {
|
|
9202
9508
|
// Note: This class handles MCP server communication with repository filtering
|
|
9203
9509
|
mcp;
|
|
@@ -9248,7 +9554,7 @@ var MCPClient = class {
|
|
|
9248
9554
|
path12.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
9249
9555
|
path12.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
|
|
9250
9556
|
];
|
|
9251
|
-
const serverScriptPath = candidatePaths.find((p) =>
|
|
9557
|
+
const serverScriptPath = candidatePaths.find((p) => existsSync8(p));
|
|
9252
9558
|
if (!serverScriptPath) {
|
|
9253
9559
|
const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
9254
9560
|
console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
|
|
@@ -9446,6 +9752,190 @@ var McpManager = class {
|
|
|
9446
9752
|
}
|
|
9447
9753
|
};
|
|
9448
9754
|
|
|
9755
|
+
// src/utils/imageRenderer.ts
|
|
9756
|
+
var ImageRenderer = class {
|
|
9757
|
+
constructor() {
|
|
9758
|
+
this.imageCounter = 0;
|
|
9759
|
+
this.placeholderMap = /* @__PURE__ */ new Map();
|
|
9760
|
+
}
|
|
9761
|
+
/**
|
|
9762
|
+
* Generate placeholder for a new image
|
|
9763
|
+
*/
|
|
9764
|
+
createPlaceholder(hash) {
|
|
9765
|
+
const existing = this.placeholderMap.get(hash);
|
|
9766
|
+
if (existing) {
|
|
9767
|
+
return existing.placeholder;
|
|
9768
|
+
}
|
|
9769
|
+
this.imageCounter++;
|
|
9770
|
+
const placeholder = `[Image ${this.imageCounter}]`;
|
|
9771
|
+
this.placeholderMap.set(hash, {
|
|
9772
|
+
hash,
|
|
9773
|
+
number: this.imageCounter,
|
|
9774
|
+
placeholder
|
|
9775
|
+
});
|
|
9776
|
+
return placeholder;
|
|
9777
|
+
}
|
|
9778
|
+
/**
|
|
9779
|
+
* Get all image hashes from a message containing placeholders
|
|
9780
|
+
*/
|
|
9781
|
+
extractImageHashes(message) {
|
|
9782
|
+
const hashes = [];
|
|
9783
|
+
const placeholderRegex = /\[Image \d+\]/g;
|
|
9784
|
+
const matches = message.match(placeholderRegex);
|
|
9785
|
+
if (!matches) return hashes;
|
|
9786
|
+
for (const [hash, data] of this.placeholderMap.entries()) {
|
|
9787
|
+
if (matches.includes(data.placeholder)) {
|
|
9788
|
+
hashes.push(hash);
|
|
9789
|
+
}
|
|
9790
|
+
}
|
|
9791
|
+
return hashes;
|
|
9792
|
+
}
|
|
9793
|
+
/**
|
|
9794
|
+
* Get hash by placeholder text
|
|
9795
|
+
*/
|
|
9796
|
+
getHashFromPlaceholder(placeholder) {
|
|
9797
|
+
for (const [hash, data] of this.placeholderMap.entries()) {
|
|
9798
|
+
if (data.placeholder === placeholder) {
|
|
9799
|
+
return hash;
|
|
9800
|
+
}
|
|
9801
|
+
}
|
|
9802
|
+
return null;
|
|
9803
|
+
}
|
|
9804
|
+
};
|
|
9805
|
+
|
|
9806
|
+
// src/utils/messageBuilder.ts
|
|
9807
|
+
var MessageBuilder = class {
|
|
9808
|
+
constructor(imageStore, imageRenderer) {
|
|
9809
|
+
this.imageStore = imageStore;
|
|
9810
|
+
this.imageRenderer = imageRenderer;
|
|
9811
|
+
}
|
|
9812
|
+
/**
|
|
9813
|
+
* Build a multimodal message from user input
|
|
9814
|
+
* Extracts image placeholders and creates proper message structure with base64-encoded images
|
|
9815
|
+
*/
|
|
9816
|
+
async buildMessage(userInput, role = "user") {
|
|
9817
|
+
const imageHashes = this.imageRenderer.extractImageHashes(userInput);
|
|
9818
|
+
if (imageHashes.length === 0) {
|
|
9819
|
+
return {
|
|
9820
|
+
message: {
|
|
9821
|
+
role,
|
|
9822
|
+
content: userInput
|
|
9823
|
+
},
|
|
9824
|
+
uploadedImages: []
|
|
9825
|
+
};
|
|
9826
|
+
}
|
|
9827
|
+
const content = [];
|
|
9828
|
+
const parts = this.splitMessageByPlaceholders(userInput);
|
|
9829
|
+
for (const part of parts) {
|
|
9830
|
+
if (part.type === "text" && part.content.trim()) {
|
|
9831
|
+
content.push({
|
|
9832
|
+
type: "text",
|
|
9833
|
+
text: part.content
|
|
9834
|
+
});
|
|
9835
|
+
} else if (part.type === "image") {
|
|
9836
|
+
const hash = this.imageRenderer.getHashFromPlaceholder(part.content);
|
|
9837
|
+
if (hash) {
|
|
9838
|
+
const imageData = this.imageStore.readImage(hash);
|
|
9839
|
+
const metadata = this.imageStore.getMetadata(hash);
|
|
9840
|
+
if (imageData && metadata) {
|
|
9841
|
+
const base64Data = imageData.toString("base64");
|
|
9842
|
+
const mediaType = this.getMediaType(metadata.format);
|
|
9843
|
+
content.push({
|
|
9844
|
+
type: "image",
|
|
9845
|
+
source: {
|
|
9846
|
+
type: "base64",
|
|
9847
|
+
media_type: mediaType,
|
|
9848
|
+
data: base64Data
|
|
9849
|
+
}
|
|
9850
|
+
});
|
|
9851
|
+
}
|
|
9852
|
+
}
|
|
9853
|
+
}
|
|
9854
|
+
}
|
|
9855
|
+
return {
|
|
9856
|
+
message: {
|
|
9857
|
+
role,
|
|
9858
|
+
content
|
|
9859
|
+
},
|
|
9860
|
+
uploadedImages: []
|
|
9861
|
+
};
|
|
9862
|
+
}
|
|
9863
|
+
/**
|
|
9864
|
+
* Get MIME type for image format
|
|
9865
|
+
*/
|
|
9866
|
+
getMediaType(format) {
|
|
9867
|
+
switch (format.toLowerCase()) {
|
|
9868
|
+
case "png":
|
|
9869
|
+
return "image/png";
|
|
9870
|
+
case "jpg":
|
|
9871
|
+
case "jpeg":
|
|
9872
|
+
return "image/jpeg";
|
|
9873
|
+
case "gif":
|
|
9874
|
+
return "image/gif";
|
|
9875
|
+
case "webp":
|
|
9876
|
+
return "image/webp";
|
|
9877
|
+
default:
|
|
9878
|
+
return "image/png";
|
|
9879
|
+
}
|
|
9880
|
+
}
|
|
9881
|
+
/**
|
|
9882
|
+
* Split message text by image placeholders
|
|
9883
|
+
* Returns array of text and image parts in order
|
|
9884
|
+
*/
|
|
9885
|
+
splitMessageByPlaceholders(message) {
|
|
9886
|
+
const parts = [];
|
|
9887
|
+
const placeholderRegex = /(\[Image \d+\])/g;
|
|
9888
|
+
let lastIndex = 0;
|
|
9889
|
+
let match;
|
|
9890
|
+
while ((match = placeholderRegex.exec(message)) !== null) {
|
|
9891
|
+
if (match.index > lastIndex) {
|
|
9892
|
+
const text = message.substring(lastIndex, match.index);
|
|
9893
|
+
if (text) {
|
|
9894
|
+
parts.push({ type: "text", content: text });
|
|
9895
|
+
}
|
|
9896
|
+
}
|
|
9897
|
+
parts.push({ type: "image", content: match[0] });
|
|
9898
|
+
lastIndex = match.index + match[0].length;
|
|
9899
|
+
}
|
|
9900
|
+
if (lastIndex < message.length) {
|
|
9901
|
+
const text = message.substring(lastIndex);
|
|
9902
|
+
if (text) {
|
|
9903
|
+
parts.push({ type: "text", content: text });
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9906
|
+
if (parts.length === 0) {
|
|
9907
|
+
parts.push({ type: "text", content: message });
|
|
9908
|
+
}
|
|
9909
|
+
return parts;
|
|
9910
|
+
}
|
|
9911
|
+
/**
|
|
9912
|
+
* Check if a message contains images
|
|
9913
|
+
*/
|
|
9914
|
+
hasImages(message) {
|
|
9915
|
+
return this.imageRenderer.extractImageHashes(message).length > 0;
|
|
9916
|
+
}
|
|
9917
|
+
/**
|
|
9918
|
+
* Convert multimodal message back to simple string (for display/logging)
|
|
9919
|
+
*/
|
|
9920
|
+
messageToString(message) {
|
|
9921
|
+
if (typeof message.content === "string") {
|
|
9922
|
+
return message.content;
|
|
9923
|
+
}
|
|
9924
|
+
return message.content.map((item) => {
|
|
9925
|
+
if (item.type === "text") {
|
|
9926
|
+
return item.text;
|
|
9927
|
+
} else if (item.type === "image") {
|
|
9928
|
+
if (item.source.type === "url") {
|
|
9929
|
+
return `[Image: ${item.source.url}]`;
|
|
9930
|
+
} else {
|
|
9931
|
+
return `[Image: base64 ${item.source.media_type}]`;
|
|
9932
|
+
}
|
|
9933
|
+
}
|
|
9934
|
+
return "";
|
|
9935
|
+
}).join("");
|
|
9936
|
+
}
|
|
9937
|
+
};
|
|
9938
|
+
|
|
9449
9939
|
// src/llm/ServerLlmBackend.ts
|
|
9450
9940
|
import { createParser } from "eventsource-parser";
|
|
9451
9941
|
import { isAxiosError } from "axios";
|
|
@@ -9863,7 +10353,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
9863
10353
|
// package.json
|
|
9864
10354
|
var package_default = {
|
|
9865
10355
|
name: "@bike4mind/cli",
|
|
9866
|
-
version: "0.2.
|
|
10356
|
+
version: "0.2.7-feat-cli-rewind-command.17128+8d4de110f",
|
|
9867
10357
|
type: "module",
|
|
9868
10358
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
9869
10359
|
license: "UNLICENSED",
|
|
@@ -9924,6 +10414,7 @@ var package_default = {
|
|
|
9924
10414
|
"async-mutex": "^0.5.0",
|
|
9925
10415
|
axios: "^1.13.2",
|
|
9926
10416
|
bcryptjs: "^2.4.3",
|
|
10417
|
+
"better-sqlite3": "^12.5.0",
|
|
9927
10418
|
cheerio: "1.0.0-rc.12",
|
|
9928
10419
|
"csv-parse": "^5.5.6",
|
|
9929
10420
|
dayjs: "^1.11.13",
|
|
@@ -9949,7 +10440,7 @@ var package_default = {
|
|
|
9949
10440
|
"p-limit": "^6.2.0",
|
|
9950
10441
|
qrcode: "^1.5.4",
|
|
9951
10442
|
react: "^19.2.3",
|
|
9952
|
-
sharp: "^0.
|
|
10443
|
+
sharp: "^0.34.5",
|
|
9953
10444
|
speakeasy: "^2.0.0",
|
|
9954
10445
|
tiktoken: "^1.0.16",
|
|
9955
10446
|
turndown: "^7.2.0",
|
|
@@ -9965,10 +10456,11 @@ var package_default = {
|
|
|
9965
10456
|
},
|
|
9966
10457
|
devDependencies: {
|
|
9967
10458
|
"@bike4mind/agents": "0.1.0",
|
|
9968
|
-
"@bike4mind/common": "2.38.1-
|
|
9969
|
-
"@bike4mind/mcp": "1.20.1-
|
|
9970
|
-
"@bike4mind/services": "2.33.1-
|
|
9971
|
-
"@bike4mind/utils": "2.1.2-
|
|
10459
|
+
"@bike4mind/common": "2.38.1-feat-cli-rewind-command.17128+8d4de110f",
|
|
10460
|
+
"@bike4mind/mcp": "1.20.1-feat-cli-rewind-command.17128+8d4de110f",
|
|
10461
|
+
"@bike4mind/services": "2.33.1-feat-cli-rewind-command.17128+8d4de110f",
|
|
10462
|
+
"@bike4mind/utils": "2.1.2-feat-cli-rewind-command.17128+8d4de110f",
|
|
10463
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
9972
10464
|
"@types/diff": "^5.0.9",
|
|
9973
10465
|
"@types/jsonwebtoken": "^9.0.4",
|
|
9974
10466
|
"@types/node": "^22.9.0",
|
|
@@ -9980,7 +10472,7 @@ var package_default = {
|
|
|
9980
10472
|
typescript: "^5.9.3",
|
|
9981
10473
|
vitest: "^3.2.4"
|
|
9982
10474
|
},
|
|
9983
|
-
gitHead: "
|
|
10475
|
+
gitHead: "8d4de110f7d8f55bff6bd26cb38b8ca1d4dbd996"
|
|
9984
10476
|
};
|
|
9985
10477
|
|
|
9986
10478
|
// src/config/constants.ts
|
|
@@ -10001,20 +10493,27 @@ var EXIT_TIMEOUT_MS = 2e3;
|
|
|
10001
10493
|
var usageCache = null;
|
|
10002
10494
|
function CliApp() {
|
|
10003
10495
|
const { exit } = useApp();
|
|
10004
|
-
const
|
|
10496
|
+
const imageRenderer = new ImageRenderer();
|
|
10497
|
+
const [state, setState] = useState5({
|
|
10005
10498
|
session: null,
|
|
10006
10499
|
sessionStore: new SessionStore(),
|
|
10007
10500
|
configStore: new ConfigStore(),
|
|
10008
10501
|
commandHistoryStore: new CommandHistoryStore(),
|
|
10502
|
+
imageStore: null,
|
|
10503
|
+
// Lazy-loaded on first use
|
|
10504
|
+
imageRenderer,
|
|
10505
|
+
messageBuilder: null,
|
|
10009
10506
|
agent: null,
|
|
10010
10507
|
mcpManager: null,
|
|
10011
10508
|
permissionManager: null,
|
|
10012
10509
|
permissionPrompt: null,
|
|
10013
|
-
trustLocationSelector: null
|
|
10510
|
+
trustLocationSelector: null,
|
|
10511
|
+
rewindSelector: null
|
|
10014
10512
|
});
|
|
10015
|
-
const [isInitialized, setIsInitialized] =
|
|
10016
|
-
const [initError, setInitError] =
|
|
10017
|
-
const [commandHistory, setCommandHistory] =
|
|
10513
|
+
const [isInitialized, setIsInitialized] = useState5(false);
|
|
10514
|
+
const [initError, setInitError] = useState5(null);
|
|
10515
|
+
const [commandHistory, setCommandHistory] = useState5([]);
|
|
10516
|
+
const imageStoreInitPromise = useRef(null);
|
|
10018
10517
|
const setStoreSession = useCliStore((state2) => state2.setSession);
|
|
10019
10518
|
const setPermissionPrompt = useCliStore((state2) => state2.setPermissionPrompt);
|
|
10020
10519
|
const setShowConfigEditor = useCliStore((state2) => state2.setShowConfigEditor);
|
|
@@ -10038,6 +10537,13 @@ function CliApp() {
|
|
|
10038
10537
|
if (state.agent) {
|
|
10039
10538
|
state.agent.removeAllListeners();
|
|
10040
10539
|
}
|
|
10540
|
+
if (state.imageStore) {
|
|
10541
|
+
try {
|
|
10542
|
+
state.imageStore.close();
|
|
10543
|
+
} catch (err) {
|
|
10544
|
+
logger.debug(`[CLEANUP] Image store close error: ${err}`);
|
|
10545
|
+
}
|
|
10546
|
+
}
|
|
10041
10547
|
await Promise.race([
|
|
10042
10548
|
Promise.all(cleanupTasks),
|
|
10043
10549
|
new Promise((resolve3) => setTimeout(resolve3, 500))
|
|
@@ -10046,8 +10552,8 @@ function CliApp() {
|
|
|
10046
10552
|
setTimeout(() => {
|
|
10047
10553
|
process.exit(0);
|
|
10048
10554
|
}, 100);
|
|
10049
|
-
}, [state.session, state.sessionStore, state.mcpManager, state.agent]);
|
|
10050
|
-
|
|
10555
|
+
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
|
|
10556
|
+
useInput4((input, key) => {
|
|
10051
10557
|
if (key.ctrl && input === "c") {
|
|
10052
10558
|
const now = Date.now();
|
|
10053
10559
|
if (exitTimestamp && now - exitTimestamp < EXIT_TIMEOUT_MS) {
|
|
@@ -10306,9 +10812,16 @@ Remember: Use context from previous messages to understand follow-up questions.`
|
|
|
10306
10812
|
return;
|
|
10307
10813
|
}
|
|
10308
10814
|
try {
|
|
10815
|
+
let messageContent = message;
|
|
10816
|
+
let userMessageContent = message;
|
|
10817
|
+
if (state.messageBuilder && state.messageBuilder.hasImages(message)) {
|
|
10818
|
+
const { message: multimodalMessage } = await state.messageBuilder.buildMessage(message);
|
|
10819
|
+
messageContent = multimodalMessage.content;
|
|
10820
|
+
userMessageContent = message;
|
|
10821
|
+
}
|
|
10309
10822
|
const userMessage = {
|
|
10310
10823
|
role: "user",
|
|
10311
|
-
content:
|
|
10824
|
+
content: userMessageContent,
|
|
10312
10825
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10313
10826
|
};
|
|
10314
10827
|
const recentMessages = state.session.messages.slice(-20);
|
|
@@ -10316,7 +10829,7 @@ Remember: Use context from previous messages to understand follow-up questions.`
|
|
|
10316
10829
|
role: msg.role,
|
|
10317
10830
|
content: msg.content
|
|
10318
10831
|
}));
|
|
10319
|
-
const result = await state.agent.run(
|
|
10832
|
+
const result = await state.agent.run(messageContent, {
|
|
10320
10833
|
previousMessages: previousMessages.length > 0 ? previousMessages : void 0
|
|
10321
10834
|
});
|
|
10322
10835
|
const permissionDenied = result.finalAnswer.startsWith("Permission denied for tool");
|
|
@@ -10365,6 +10878,59 @@ Remember: Use context from previous messages to understand follow-up questions.`
|
|
|
10365
10878
|
console.error("Error processing message:", error);
|
|
10366
10879
|
}
|
|
10367
10880
|
};
|
|
10881
|
+
const handleImageDetected = async (imageData) => {
|
|
10882
|
+
try {
|
|
10883
|
+
let imageStore = state.imageStore;
|
|
10884
|
+
if (!imageStore) {
|
|
10885
|
+
if (!imageStoreInitPromise.current) {
|
|
10886
|
+
imageStoreInitPromise.current = (async () => {
|
|
10887
|
+
const { ImageStore: ImageStoreClass } = await import("./ImageStore-RQZ7OF7P.js");
|
|
10888
|
+
const newImageStore = new ImageStoreClass();
|
|
10889
|
+
setState((prev) => ({
|
|
10890
|
+
...prev,
|
|
10891
|
+
imageStore: newImageStore,
|
|
10892
|
+
messageBuilder: new MessageBuilder(newImageStore, prev.imageRenderer)
|
|
10893
|
+
}));
|
|
10894
|
+
return newImageStore;
|
|
10895
|
+
})();
|
|
10896
|
+
}
|
|
10897
|
+
imageStore = await imageStoreInitPromise.current;
|
|
10898
|
+
}
|
|
10899
|
+
const imageRef = await imageStore.store(imageData);
|
|
10900
|
+
const imagePlaceholder = state.imageRenderer.createPlaceholder(imageRef.hash);
|
|
10901
|
+
console.log(`
|
|
10902
|
+
\u2713 Image detected and stored: ${imagePlaceholder}
|
|
10903
|
+
`);
|
|
10904
|
+
return imagePlaceholder;
|
|
10905
|
+
} catch (error) {
|
|
10906
|
+
console.error("Error storing image:", error);
|
|
10907
|
+
throw error;
|
|
10908
|
+
}
|
|
10909
|
+
};
|
|
10910
|
+
const recalculateSessionMetadata = (messages) => {
|
|
10911
|
+
let totalTokens = 0;
|
|
10912
|
+
let totalCost = 0;
|
|
10913
|
+
let toolCallCount = 0;
|
|
10914
|
+
for (const msg of messages) {
|
|
10915
|
+
if (msg.metadata) {
|
|
10916
|
+
if (msg.metadata.tokenUsage) {
|
|
10917
|
+
totalTokens += msg.metadata.tokenUsage.total || 0;
|
|
10918
|
+
}
|
|
10919
|
+
if (msg.metadata.cost) {
|
|
10920
|
+
totalCost += msg.metadata.cost;
|
|
10921
|
+
}
|
|
10922
|
+
if (msg.metadata.steps) {
|
|
10923
|
+
const observations = msg.metadata.steps.filter((s) => s.type === "observation");
|
|
10924
|
+
toolCallCount += observations.length;
|
|
10925
|
+
}
|
|
10926
|
+
}
|
|
10927
|
+
}
|
|
10928
|
+
return {
|
|
10929
|
+
totalTokens,
|
|
10930
|
+
totalCost,
|
|
10931
|
+
toolCallCount
|
|
10932
|
+
};
|
|
10933
|
+
};
|
|
10368
10934
|
const handleCommand = async (command, args) => {
|
|
10369
10935
|
switch (command) {
|
|
10370
10936
|
case "help":
|
|
@@ -10373,6 +10939,7 @@ Available commands:
|
|
|
10373
10939
|
/help - Show this help message
|
|
10374
10940
|
/exit - Exit the CLI
|
|
10375
10941
|
/clear - Start a new session
|
|
10942
|
+
/rewind - Rewind conversation to a previous point
|
|
10376
10943
|
/login - Authenticate with your B4M account
|
|
10377
10944
|
/logout - Clear authentication and sign out
|
|
10378
10945
|
/whoami - Show current authenticated user
|
|
@@ -10648,6 +11215,65 @@ Project Configuration:
|
|
|
10648
11215
|
`);
|
|
10649
11216
|
break;
|
|
10650
11217
|
}
|
|
11218
|
+
case "rewind":
|
|
11219
|
+
case "undo": {
|
|
11220
|
+
if (!state.session) {
|
|
11221
|
+
console.log("No active session to rewind");
|
|
11222
|
+
return;
|
|
11223
|
+
}
|
|
11224
|
+
if (state.session.messages.length === 0) {
|
|
11225
|
+
console.log("\u26A0\uFE0F Conversation is empty. Nothing to rewind.");
|
|
11226
|
+
return;
|
|
11227
|
+
}
|
|
11228
|
+
const userMessages = state.session.messages.filter((msg) => msg.role === "user");
|
|
11229
|
+
if (userMessages.length === 0) {
|
|
11230
|
+
console.log("\u26A0\uFE0F No user messages found. Nothing to rewind.");
|
|
11231
|
+
return;
|
|
11232
|
+
}
|
|
11233
|
+
if (userMessages.length === 1) {
|
|
11234
|
+
console.log("\u26A0\uFE0F Only one exchange in the conversation. Use /clear to start a new session instead.");
|
|
11235
|
+
return;
|
|
11236
|
+
}
|
|
11237
|
+
const handleRewindSelect = async (messageIndex) => {
|
|
11238
|
+
setState((prev) => ({ ...prev, rewindSelector: null }));
|
|
11239
|
+
if (messageIndex === null) {
|
|
11240
|
+
console.log("\u274C Rewind operation cancelled");
|
|
11241
|
+
return;
|
|
11242
|
+
}
|
|
11243
|
+
if (!state.session) {
|
|
11244
|
+
console.log("\u274C No active session");
|
|
11245
|
+
return;
|
|
11246
|
+
}
|
|
11247
|
+
const selectedMessage = state.session.messages[messageIndex];
|
|
11248
|
+
const prefillContent = selectedMessage?.content || "";
|
|
11249
|
+
const rewindedMessages = state.session.messages.slice(0, messageIndex);
|
|
11250
|
+
const newMetadata = recalculateSessionMetadata(rewindedMessages);
|
|
11251
|
+
const rewindedSession = {
|
|
11252
|
+
...state.session,
|
|
11253
|
+
messages: rewindedMessages,
|
|
11254
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11255
|
+
metadata: newMetadata
|
|
11256
|
+
};
|
|
11257
|
+
setState((prev) => ({
|
|
11258
|
+
...prev,
|
|
11259
|
+
session: rewindedSession,
|
|
11260
|
+
prefillInput: prefillContent
|
|
11261
|
+
}));
|
|
11262
|
+
setStoreSession(rewindedSession);
|
|
11263
|
+
await state.sessionStore.save(rewindedSession);
|
|
11264
|
+
console.log("\u2705 Conversation rewound successfully");
|
|
11265
|
+
console.log(`\u{1F4CA} Current state: ${rewindedMessages.length} messages, ${newMetadata.totalTokens} tokens`);
|
|
11266
|
+
console.log(`\u{1F4DD} Your message has been placed in the input. Edit and send when ready.
|
|
11267
|
+
`);
|
|
11268
|
+
};
|
|
11269
|
+
setState((prev) => ({
|
|
11270
|
+
...prev,
|
|
11271
|
+
rewindSelector: {
|
|
11272
|
+
resolve: handleRewindSelect
|
|
11273
|
+
}
|
|
11274
|
+
}));
|
|
11275
|
+
break;
|
|
11276
|
+
}
|
|
10651
11277
|
case "usage": {
|
|
10652
11278
|
const usageAuthTokens = await state.configStore.getAuthTokens();
|
|
10653
11279
|
if (!usageAuthTokens || new Date(usageAuthTokens.expiresAt) <= /* @__PURE__ */ new Date()) {
|
|
@@ -10801,12 +11427,12 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
10801
11427
|
setState((prev) => ({ ...prev, config: updatedConfig }));
|
|
10802
11428
|
};
|
|
10803
11429
|
if (initError) {
|
|
10804
|
-
return /* @__PURE__ */
|
|
11430
|
+
return /* @__PURE__ */ React14.createElement(Box14, { 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"));
|
|
10805
11431
|
}
|
|
10806
11432
|
if (state.trustLocationSelector) {
|
|
10807
11433
|
const projectDir = state.configStore.getProjectConfigDir();
|
|
10808
11434
|
const inProject = projectDir !== null;
|
|
10809
|
-
return /* @__PURE__ */
|
|
11435
|
+
return /* @__PURE__ */ React14.createElement(
|
|
10810
11436
|
TrustLocationSelector,
|
|
10811
11437
|
{
|
|
10812
11438
|
inProject,
|
|
@@ -10823,9 +11449,27 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
10823
11449
|
}
|
|
10824
11450
|
);
|
|
10825
11451
|
}
|
|
11452
|
+
if (state.rewindSelector && state.session) {
|
|
11453
|
+
return /* @__PURE__ */ React14.createElement(
|
|
11454
|
+
RewindSelector,
|
|
11455
|
+
{
|
|
11456
|
+
messages: state.session.messages,
|
|
11457
|
+
onSelect: (messageIndex) => {
|
|
11458
|
+
if (state.rewindSelector) {
|
|
11459
|
+
state.rewindSelector.resolve(messageIndex);
|
|
11460
|
+
}
|
|
11461
|
+
},
|
|
11462
|
+
onCancel: () => {
|
|
11463
|
+
if (state.rewindSelector) {
|
|
11464
|
+
state.rewindSelector.resolve(null);
|
|
11465
|
+
}
|
|
11466
|
+
}
|
|
11467
|
+
}
|
|
11468
|
+
);
|
|
11469
|
+
}
|
|
10826
11470
|
if (state.showLoginFlow) {
|
|
10827
11471
|
const loginApiUrl = getApiUrl(state.config?.apiConfig);
|
|
10828
|
-
return /* @__PURE__ */
|
|
11472
|
+
return /* @__PURE__ */ React14.createElement(
|
|
10829
11473
|
LoginFlow,
|
|
10830
11474
|
{
|
|
10831
11475
|
apiUrl: loginApiUrl,
|
|
@@ -10848,17 +11492,22 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
10848
11492
|
);
|
|
10849
11493
|
}
|
|
10850
11494
|
if (!isInitialized) {
|
|
10851
|
-
return /* @__PURE__ */
|
|
11495
|
+
return /* @__PURE__ */ React14.createElement(Box14, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React14.createElement(Text13, null, "\u{1F680} Initializing..."));
|
|
10852
11496
|
}
|
|
10853
|
-
return /* @__PURE__ */
|
|
11497
|
+
return /* @__PURE__ */ React14.createElement(
|
|
10854
11498
|
App,
|
|
10855
11499
|
{
|
|
10856
11500
|
onMessage: handleMessage,
|
|
10857
11501
|
onCommand: handleCommand,
|
|
11502
|
+
onImageDetected: handleImageDetected,
|
|
10858
11503
|
commandHistory,
|
|
10859
11504
|
config: state.config,
|
|
10860
11505
|
availableModels: state.availableModels,
|
|
10861
11506
|
onSaveConfig: handleSaveConfig,
|
|
11507
|
+
prefillInput: state.prefillInput,
|
|
11508
|
+
onPrefillConsumed: () => {
|
|
11509
|
+
setState((prev) => ({ ...prev, prefillInput: void 0 }));
|
|
11510
|
+
},
|
|
10862
11511
|
onPermissionResponse: (response) => {
|
|
10863
11512
|
if (state.permissionPrompt) {
|
|
10864
11513
|
state.permissionPrompt.resolve({ action: response });
|
|
@@ -10884,6 +11533,6 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
|
|
|
10884
11533
|
if (isDevMode) {
|
|
10885
11534
|
console.log("\u{1F527} Running in development mode (using TypeScript source)\n");
|
|
10886
11535
|
}
|
|
10887
|
-
render(/* @__PURE__ */
|
|
11536
|
+
render(/* @__PURE__ */ React14.createElement(CliApp, null), {
|
|
10888
11537
|
exitOnCtrlC: false
|
|
10889
11538
|
});
|