@bike4mind/cli 0.2.6-fix-cli-mcp-type-mismatch.17119 → 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/dist/index.js CHANGED
@@ -79,13 +79,13 @@ import {
79
79
  } from "./chunk-PDX44BCA.js";
80
80
 
81
81
  // src/index.tsx
82
- import React13, { useState as useState4, useEffect as useEffect3, useCallback } from "react";
83
- import { render, Box as Box13, Text as Text12, useApp, useInput as useInput3 } from "ink";
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 React9 from "react";
88
- import { Box as Box9, Text as Text8, Static } from "ink";
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 React4, { useState, useMemo, useEffect } from "react";
99
- import { Box as Box4, Text as Text4, useInput } from "ink";
100
- import TextInput from "ink-text-input";
98
+ import React5, { useState as useState2, useMemo, useEffect } from "react";
99
+ import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
100
+
101
+ // src/components/CustomTextInput.tsx
102
+ import React2, { useState } from "react";
103
+ import { Text as Text2, useInput } from "ink";
104
+ function CustomTextInput({
105
+ value,
106
+ onChange,
107
+ onSubmit,
108
+ placeholder = "",
109
+ showCursor = true
110
+ }) {
111
+ const [cursorOffset, setCursorOffset] = useState(value.length);
112
+ useInput(
113
+ (input, key) => {
114
+ if (key.return && !key.meta && !key.shift) {
115
+ onSubmit(value);
116
+ return;
117
+ }
118
+ if (key.ctrl) {
119
+ switch (input) {
120
+ case "u":
121
+ {
122
+ const lines = value.split("\n");
123
+ let currentLineStart = 0;
124
+ let currentLineIndex = 0;
125
+ let charCount = 0;
126
+ for (let i = 0; i < lines.length; i++) {
127
+ const lineLength = lines[i].length;
128
+ if (charCount + lineLength >= cursorOffset) {
129
+ currentLineIndex = i;
130
+ currentLineStart = charCount;
131
+ break;
132
+ }
133
+ charCount += lineLength + 1;
134
+ }
135
+ lines.splice(currentLineIndex, 1);
136
+ const newValue = lines.join("\n");
137
+ onChange(newValue);
138
+ setCursorOffset(Math.min(currentLineStart, newValue.length));
139
+ }
140
+ return;
141
+ case "k":
142
+ onChange(value.slice(0, cursorOffset));
143
+ return;
144
+ case "w":
145
+ {
146
+ const beforeCursor2 = value.slice(0, cursorOffset);
147
+ const afterCursor2 = value.slice(cursorOffset);
148
+ const trimmed = beforeCursor2.trimEnd();
149
+ const lastSpace = trimmed.lastIndexOf(" ");
150
+ const newBefore = lastSpace === -1 ? "" : trimmed.slice(0, lastSpace + 1);
151
+ const newValue = newBefore + afterCursor2;
152
+ onChange(newValue);
153
+ setCursorOffset(newBefore.length);
154
+ }
155
+ return;
156
+ case "a":
157
+ setCursorOffset(0);
158
+ return;
159
+ case "e":
160
+ setCursorOffset(value.length);
161
+ return;
162
+ case "l":
163
+ onChange("");
164
+ return;
165
+ }
166
+ }
167
+ if (key.backspace || key.delete) {
168
+ if (cursorOffset > 0) {
169
+ const newValue = value.slice(0, cursorOffset - 1) + value.slice(cursorOffset);
170
+ onChange(newValue);
171
+ setCursorOffset(cursorOffset - 1);
172
+ }
173
+ return;
174
+ }
175
+ if (key.leftArrow) {
176
+ setCursorOffset(Math.max(0, cursorOffset - 1));
177
+ return;
178
+ }
179
+ if (key.rightArrow) {
180
+ setCursorOffset(Math.min(value.length, cursorOffset + 1));
181
+ return;
182
+ }
183
+ if (!key.ctrl && !key.meta && input.length === 1) {
184
+ const sanitizedInput = input === "\r" ? "\n" : input;
185
+ const newValue = value.slice(0, cursorOffset) + sanitizedInput + value.slice(cursorOffset);
186
+ onChange(newValue);
187
+ setCursorOffset(cursorOffset + 1);
188
+ }
189
+ },
190
+ { isActive: true }
191
+ );
192
+ const hasValue = value.length > 0;
193
+ if (!hasValue) {
194
+ return /* @__PURE__ */ React2.createElement(Text2, null, showCursor ? /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, { inverse: true }, " "), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, placeholder)) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, placeholder));
195
+ }
196
+ const beforeCursor = value.slice(0, cursorOffset);
197
+ const cursorChar = value[cursorOffset] || " ";
198
+ const afterCursor = value.slice(cursorOffset + 1);
199
+ return /* @__PURE__ */ React2.createElement(Text2, null, beforeCursor, showCursor && /* @__PURE__ */ React2.createElement(Text2, { inverse: true }, cursorChar), !showCursor && cursorChar, afterCursor);
200
+ }
101
201
 
102
202
  // src/components/CommandAutocomplete.tsx
103
- import React2 from "react";
104
- import { Box as Box2, Text as Text2 } from "ink";
203
+ import React3 from "react";
204
+ import { Box as Box2, Text as Text3 } from "ink";
105
205
  function CommandAutocomplete({ commands, selectedIndex }) {
106
206
  if (commands.length === 0) {
107
- return /* @__PURE__ */ React2.createElement(Box2, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "No matching commands"));
207
+ return /* @__PURE__ */ React3.createElement(Box2, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No matching commands"));
108
208
  }
109
- return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, commands.length === 1 ? "1 command" : `${commands.length} commands`, " \u2022 Use \u2191\u2193 to navigate, Enter to select")), commands.map((cmd, index) => {
209
+ return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, commands.length === 1 ? "1 command" : `${commands.length} commands`, " \u2022 Use \u2191\u2193 to navigate, Enter to select")), commands.map((cmd, index) => {
110
210
  const args = cmd.args ? ` ${cmd.args}` : "";
111
211
  const isSelected = index === selectedIndex;
112
- return /* @__PURE__ */ React2.createElement(Box2, { key: cmd.name, marginLeft: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "\u25B8 " : " ", "/", cmd.name, args, " - ", cmd.description));
212
+ return /* @__PURE__ */ React3.createElement(Box2, { key: cmd.name, marginLeft: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "\u25B8 " : " ", "/", cmd.name, args, " - ", cmd.description));
113
213
  }));
114
214
  }
115
215
 
116
216
  // src/components/FileAutocomplete.tsx
117
- import React3 from "react";
118
- import { Box as Box3, Text as Text3 } from "ink";
217
+ import React4 from "react";
218
+ import { Box as Box3, Text as Text4 } from "ink";
119
219
 
120
220
  // src/utils/fileSearch.ts
121
221
  import * as fs from "fs";
@@ -304,14 +404,14 @@ var MAX_FILE_SIZE = 10 * 1024 * 1024;
304
404
  // src/components/FileAutocomplete.tsx
305
405
  function FileAutocomplete({ files, selectedIndex, query }) {
306
406
  if (files.length === 0) {
307
- return /* @__PURE__ */ React3.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No matching files", query ? ` for "${query}"` : ""));
407
+ return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No matching files", query ? ` for "${query}"` : ""));
308
408
  }
309
- return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, files.length === 1 ? "1 match" : `${files.length} matches`, " - Use up/down to navigate, Tab to select")), files.map((file, index) => {
409
+ return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, files.length === 1 ? "1 match" : `${files.length} matches`, " - Use up/down to navigate, Tab to select")), files.map((file, index) => {
310
410
  const isSelected = index === selectedIndex;
311
411
  const icon = file.isDirectory ? "[folder]" : "[file] ";
312
412
  const sizeDisplay = file.isDirectory ? "" : file.size !== void 0 ? ` (${formatFileSize(file.size)})` : "";
313
413
  const pathDisplay = file.isDirectory ? `${file.path}/` : file.path;
314
- return /* @__PURE__ */ React3.createElement(Box3, { key: file.path, marginLeft: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "> " : " "), /* @__PURE__ */ React3.createElement(Text3, { color: file.isDirectory ? "yellow" : isSelected ? "cyan" : void 0, bold: isSelected }, icon), /* @__PURE__ */ React3.createElement(Text3, { color: isSelected ? "cyan" : void 0, bold: isSelected }, " ", pathDisplay), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, sizeDisplay));
414
+ return /* @__PURE__ */ React4.createElement(Box3, { key: file.path, marginLeft: 1 }, /* @__PURE__ */ React4.createElement(Text4, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "> " : " "), /* @__PURE__ */ React4.createElement(Text4, { color: file.isDirectory ? "yellow" : isSelected ? "cyan" : void 0, bold: isSelected }, icon), /* @__PURE__ */ React4.createElement(Text4, { color: isSelected ? "cyan" : void 0, bold: isSelected }, " ", pathDisplay), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, sizeDisplay));
315
415
  }));
316
416
  }
317
417
 
@@ -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] = useState("");
434
- const [selectedIndex, setSelectedIndex] = useState(0);
435
- const [historyIndex, setHistoryIndex] = useState(-1);
436
- const [tempInput, setTempInput] = useState("");
437
- const [inputKey, setInputKey] = useState(0);
438
- const [fileAutocomplete, setFileAutocomplete] = useState(null);
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
- useInput((input, key) => {
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__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "cyan" }, "> "), /* @__PURE__ */ React4.createElement(
571
- TextInput,
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__ */ React4.createElement(CommandAutocomplete, { commands: filteredCommands, selectedIndex }), fileAutocomplete?.active && /* @__PURE__ */ React4.createElement(FileAutocomplete, { files: filteredFiles, selectedIndex: fileSelectedIndex, query: fileAutocomplete.query }));
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 React6 from "react";
884
+ import React7 from "react";
585
885
  import { Box as Box6 } from "ink";
586
886
 
587
887
  // src/components/ThoughtStream.tsx
588
- import React5 from "react";
589
- import { Box as Box5, Text as Text5 } from "ink";
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 = React5.memo(function ThoughtStream2({ steps }) {
592
- return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", gap: 1 }, steps.map((step, index) => /* @__PURE__ */ React5.createElement(Box5, { key: index, flexDirection: "column" }, step.type === "thought" && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, "\u{1F914} Thought: "), /* @__PURE__ */ React5.createElement(Text5, null, step.content)), step.type === "action" && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, "\u26A1 Action: "), /* @__PURE__ */ React5.createElement(Text5, { bold: true }, step.metadata?.toolName || "unknown")), step.metadata?.toolInput && /* @__PURE__ */ React5.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, JSON.stringify(step.metadata.toolInput, null, 2)))), step.type === "observation" && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "blue" }, "\u{1F4CA} Observation: ")), /* @__PURE__ */ React5.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React5.createElement(Text5, null, step.content))), step.type === "final_answer" && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "green", bold: true }, "\u2705 Final Answer:", " ")), /* @__PURE__ */ React5.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React5.createElement(Text5, null, step.content)), step.metadata?.tokenUsage && /* @__PURE__ */ React5.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(", step.metadata.tokenUsage.total, " tokens)"))))), !steps.some((s) => s.type === "final_answer") && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React5.createElement(Text5, null, " ", steps.length === 0 ? "Thinking..." : "Processing...")));
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 = React6.memo(function AgentThinking2() {
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__ */ React6.createElement(Box6, { borderStyle: "single", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React6.createElement(ThoughtStream, { steps: agentSteps }));
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 React7 from "react";
632
- import { Box as Box7, Text as Text6 } from "ink";
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__ */ React7.createElement(Text6, { key: index, color: "green" }, line);
938
+ return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "green" }, line);
639
939
  }
640
940
  if (line.startsWith("-")) {
641
- return /* @__PURE__ */ React7.createElement(Text6, { key: index, color: "red" }, line);
941
+ return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "red" }, line);
642
942
  }
643
943
  if (line.startsWith("@@")) {
644
- return /* @__PURE__ */ React7.createElement(Text6, { key: index, color: "cyan" }, line);
944
+ return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "cyan" }, line);
645
945
  }
646
- return /* @__PURE__ */ React7.createElement(Text6, { key: index, dimColor: true }, line);
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__ */ React7.createElement(Box7, { flexDirection: "column", borderStyle: "bold", borderColor: "yellow", padding: 1, marginY: 1 }, /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true, color: "yellow" }, "\u26A0\uFE0F Permission Required")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "Tool: "), /* @__PURE__ */ React7.createElement(Text6, { bold: true, color: "cyan" }, toolName)), toolDescription && /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "Action: "), /* @__PURE__ */ React7.createElement(Text6, null, toolDescription)), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Arguments:"), /* @__PURE__ */ React7.createElement(Box7, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, argsString))), preview && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text6, { bold: true }, "Preview:"), /* @__PURE__ */ React7.createElement(Box7, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" }, renderDiffPreview(preview))), !canBeTrusted && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, { color: "red", dimColor: true }, "Note: This tool cannot be trusted due to its dangerous nature.")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(SelectInput, { items, onSelect: (item) => onResponse(item.value) })));
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 React8, { useState as useState2, useMemo as useMemo2 } from "react";
671
- import { Box as Box8, Text as Text7, useInput as useInput2 } from "ink";
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 },
@@ -799,10 +1099,10 @@ function buildConfigItems(availableModels) {
799
1099
  return items;
800
1100
  }
801
1101
  function ConfigEditor({ config, availableModels, onSave, onClose }) {
802
- const [selectedIndex, setSelectedIndex] = useState2(0);
803
- const [editedConfig, setEditedConfig] = useState2(config);
804
- const [saveError, setSaveError] = useState2(null);
805
- const [isSaving, setIsSaving] = useState2(false);
1102
+ const [selectedIndex, setSelectedIndex] = useState3(0);
1103
+ const [editedConfig, setEditedConfig] = useState3(config);
1104
+ const [saveError, setSaveError] = useState3(null);
1105
+ const [isSaving, setIsSaving] = useState3(false);
806
1106
  const configItems = useMemo2(() => buildConfigItems(availableModels), [availableModels]);
807
1107
  const hasChanges = useMemo2(() => {
808
1108
  return JSON.stringify(config.preferences) !== JSON.stringify(editedConfig.preferences) || config.defaultModel !== editedConfig.defaultModel;
@@ -822,7 +1122,7 @@ function ConfigEditor({ config, availableModels, onSave, onClose }) {
822
1122
  }
823
1123
  onClose();
824
1124
  };
825
- useInput2((input, key) => {
1125
+ useInput3((input, key) => {
826
1126
  const currentItem = configItems[selectedIndex];
827
1127
  if (key.upArrow) {
828
1128
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : configItems.length - 1);
@@ -887,13 +1187,13 @@ function ConfigEditor({ config, availableModels, onSave, onClose }) {
887
1187
  }
888
1188
  return String(value);
889
1189
  };
890
- return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, marginY: 1 }, /* @__PURE__ */ React8.createElement(Box8, { justifyContent: "space-between", marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: "cyan" }, "Settings"), isSaving ? /* @__PURE__ */ React8.createElement(Text7, { color: "yellow" }, "Saving...") : hasChanges ? /* @__PURE__ */ React8.createElement(Text7, { dimColor: true, color: "yellow" }, "(unsaved changes)") : null), saveError && /* @__PURE__ */ React8.createElement(Box8, { marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: "red" }, "Error: ", saveError)), /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", marginBottom: 1 }, configItems.map((item, index) => {
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) => {
891
1191
  const isSelected = index === selectedIndex;
892
1192
  const value = item.getValue(editedConfig);
893
1193
  const displayValue = formatValue(item, value);
894
1194
  const hasOptions = item.type === "select" || item.type === "boolean" || item.type === "number";
895
- return /* @__PURE__ */ React8.createElement(Box8, { key: item.key }, /* @__PURE__ */ React8.createElement(Text7, { color: isSelected ? "cyan" : void 0 }, isSelected ? "> " : " "), /* @__PURE__ */ React8.createElement(Box8, { width: 18 }, /* @__PURE__ */ React8.createElement(Text7, { bold: isSelected, color: isSelected ? "white" : "gray" }, item.label, ":")), /* @__PURE__ */ React8.createElement(Box8, null, hasOptions && isSelected && /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, "< "), /* @__PURE__ */ React8.createElement(Text7, { bold: isSelected, color: isSelected ? "green" : void 0 }, displayValue), hasOptions && isSelected && /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " >")));
896
- })), /* @__PURE__ */ React8.createElement(Box8, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, "\u2191/\u2193: Navigate | Space/\u2190/\u2192: Change | q/Esc: Save & Exit")));
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")));
897
1197
  }
898
1198
 
899
1199
  // src/utils/processFileReferences.ts
@@ -1015,6 +1315,7 @@ function App({
1015
1315
  onMessage,
1016
1316
  onCommand,
1017
1317
  onPermissionResponse,
1318
+ onImageDetected,
1018
1319
  commandHistory = [],
1019
1320
  config,
1020
1321
  availableModels = [],
@@ -1030,11 +1331,11 @@ function App({
1030
1331
  const setShowConfigEditor = useCliStore((state) => state.setShowConfigEditor);
1031
1332
  const exitRequested = useCliStore((state) => state.exitRequested);
1032
1333
  const setIsThinking = useCliStore((state) => state.setIsThinking);
1033
- const handleSubmit = React9.useCallback(
1334
+ const handleSubmit = React10.useCallback(
1034
1335
  async (input) => {
1035
1336
  const trimmed = input.trim();
1036
1337
  if (!trimmed) return;
1037
- if (trimmed.startsWith("/")) {
1338
+ if (trimmed.startsWith("/") && !trimmed.match(/^\/[A-Z]/)) {
1038
1339
  const [command, ...args] = trimmed.slice(1).split(" ");
1039
1340
  await onCommand(command, args);
1040
1341
  return;
@@ -1059,7 +1360,7 @@ ${errorBlock}`;
1059
1360
  },
1060
1361
  [onMessage, onCommand, setIsThinking]
1061
1362
  );
1062
- return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(StatusBar, { sessionName, model: currentModel, tokenUsage: totalTokens }), showConfigEditor && config && onSaveConfig ? /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(
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(
1063
1364
  ConfigEditor,
1064
1365
  {
1065
1366
  config,
@@ -1067,7 +1368,7 @@ ${errorBlock}`;
1067
1368
  onSave: onSaveConfig,
1068
1369
  onClose: () => setShowConfigEditor(false)
1069
1370
  }
1070
- )) : permissionPrompt ? /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(
1371
+ )) : permissionPrompt ? /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React10.createElement(
1071
1372
  PermissionPrompt,
1072
1373
  {
1073
1374
  toolName: permissionPrompt.toolName,
@@ -1076,39 +1377,47 @@ ${errorBlock}`;
1076
1377
  canBeTrusted: permissionPrompt.canBeTrusted,
1077
1378
  onResponse: onPermissionResponse
1078
1379
  }
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(InputPrompt, { onSubmit: handleSubmit, disabled: isThinking, history: commandHistory }))));
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
+ ))));
1080
1389
  }
1081
1390
  function MessageItem({ message }) {
1082
- 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) => {
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) => {
1083
1392
  if (step.type === "thought") {
1084
- return /* @__PURE__ */ React9.createElement(Box9, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text8, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` ${step.content.slice(0, 200)}${step.content.length > 200 ? "..." : ""}`));
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 ? "..." : ""}`));
1085
1394
  }
1086
1395
  if (step.type === "action") {
1087
1396
  const toolName = step.metadata?.toolName || "unknown";
1088
1397
  const toolInput = step.metadata?.toolInput;
1089
1398
  const observationStep = message.metadata.steps[idx + 1];
1090
1399
  const result = observationStep?.type === "observation" ? observationStep.content : null;
1091
- return /* @__PURE__ */ React9.createElement(Box9, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, "\u{1F527} Action: ", toolName), toolInput && /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` Input: ${typeof toolInput === "string" ? toolInput : JSON.stringify(toolInput).slice(0, 100)}`), result && /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` Result: ${typeof result === "string" ? result.slice(0, 200) : JSON.stringify(result).slice(0, 200)}${(typeof result === "string" ? result.length : JSON.stringify(result).length) > 200 ? "..." : ""}`));
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 ? "..." : ""}`));
1092
1401
  }
1093
1402
  return null;
1094
- }).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React9.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)));
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`)));
1095
1404
  }
1096
1405
 
1097
1406
  // src/components/MessageList.tsx
1098
- import React10 from "react";
1099
- import { Box as Box10, Text as Text9 } from "ink";
1407
+ import React11 from "react";
1408
+ import { Box as Box10, Text as Text10 } from "ink";
1100
1409
  function stripThinkingTags(content) {
1101
1410
  return content.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
1102
1411
  }
1103
- var MessageList = React10.memo(
1412
+ var MessageList = React11.memo(
1104
1413
  function MessageList2({ messages }) {
1105
1414
  if (messages.length === 0) {
1106
- return /* @__PURE__ */ React10.createElement(Box10, { paddingY: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "No messages yet. Type a message to start!"));
1415
+ return /* @__PURE__ */ React11.createElement(Box10, { paddingY: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "No messages yet. Type a message to start!"));
1107
1416
  }
1108
- return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", gap: 1, paddingY: 1 }, messages.map((message, index) => /* @__PURE__ */ React10.createElement(Box10, { key: index, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Box10, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: message.role === "user" ? "cyan" : "green" }, message.role === "user" ? "\u{1F464} You" : "\u{1F916} Assistant"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), /* @__PURE__ */ React10.createElement(Box10, { paddingLeft: 2 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u26A0\uFE0F ", stripThinkingTags(message.content)) : /* @__PURE__ */ React10.createElement(Text9, null, stripThinkingTags(message.content))), message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React10.createElement(Box10, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true, bold: true }, "\u{1F527} Agent Reasoning Trace (", message.metadata.steps.filter((s) => s.type === "action").length, " tools used,", " ", message.metadata.steps.length, " total steps)", message.metadata.tokenUsage && ` \u2022 ${message.metadata.tokenUsage.total} tokens`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
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) => {
1109
1418
  if (step.type === "thought") {
1110
- return /* @__PURE__ */ React10.createElement(Box10, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React10.createElement(
1111
- Text9,
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,
1112
1421
  {
1113
1422
  dimColor: true
1114
1423
  },
@@ -1120,10 +1429,10 @@ var MessageList = React10.memo(
1120
1429
  const toolInput = step.metadata?.toolInput;
1121
1430
  const observationStep = message.metadata.steps[idx + 1];
1122
1431
  const result = observationStep?.type === "observation" ? observationStep.content : null;
1123
- return /* @__PURE__ */ React10.createElement(Box10, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u{1F527} Action: ", toolName), toolInput && /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` Input: ${typeof toolInput === "string" ? toolInput : JSON.stringify(toolInput).slice(0, 100)}`), result && /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` Result: ${typeof result === "string" ? result.slice(0, 200) : JSON.stringify(result).slice(0, 200)}${(typeof result === "string" ? result.length : JSON.stringify(result).length) > 200 ? "..." : ""}`));
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 ? "..." : ""}`));
1124
1433
  }
1125
1434
  return null;
1126
- }).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React10.createElement(Box10, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)))));
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`)))));
1127
1436
  },
1128
1437
  (prevProps, nextProps) => {
1129
1438
  if (prevProps.messages.length !== nextProps.messages.length) {
@@ -1134,8 +1443,8 @@ var MessageList = React10.memo(
1134
1443
  );
1135
1444
 
1136
1445
  // src/components/TrustLocationSelector.tsx
1137
- import React11 from "react";
1138
- import { Box as Box11, Text as Text10 } from "ink";
1446
+ import React12 from "react";
1447
+ import { Box as Box11, Text as Text11 } from "ink";
1139
1448
  import SelectInput2 from "ink-select-input";
1140
1449
  function TrustLocationSelector({ inProject, onSelect, onCancel }) {
1141
1450
  const items = [];
@@ -1156,19 +1465,19 @@ function TrustLocationSelector({ inProject, onSelect, onCancel }) {
1156
1465
  const handleSelect = (item) => {
1157
1466
  onSelect(item.value);
1158
1467
  };
1159
- return /* @__PURE__ */ React11.createElement(Box11, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React11.createElement(Box11, { marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "Where should this rule be saved?")), /* @__PURE__ */ React11.createElement(
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(
1160
1469
  SelectInput2,
1161
1470
  {
1162
1471
  items,
1163
1472
  onSelect: handleSelect,
1164
- itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React11.createElement(Box11, null, /* @__PURE__ */ React11.createElement(Text10, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
1473
+ itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
1165
1474
  }
1166
- ), /* @__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")));
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")));
1167
1476
  }
1168
1477
 
1169
1478
  // src/components/LoginFlow.tsx
1170
- import React12, { useState as useState3, useEffect as useEffect2 } from "react";
1171
- import { Box as Box12, Text as Text11 } from "ink";
1479
+ import React13, { useState as useState4, useEffect as useEffect2 } from "react";
1480
+ import { Box as Box12, Text as Text12 } from "ink";
1172
1481
  import Spinner2 from "ink-spinner";
1173
1482
  import jwt from "jsonwebtoken";
1174
1483
  import open from "open";
@@ -1284,10 +1593,10 @@ var OAuthClient = class {
1284
1593
 
1285
1594
  // src/components/LoginFlow.tsx
1286
1595
  function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
1287
- const [status, setStatus] = useState3("initiating");
1288
- const [deviceFlow, setDeviceFlow] = useState3(null);
1289
- const [statusMessage, setStatusMessage] = useState3("Initiating device authorization...");
1290
- const [error, setError] = useState3(null);
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);
1291
1600
  useEffect2(() => {
1292
1601
  const runLoginFlow = async () => {
1293
1602
  const oauth = new OAuthClient(apiUrl);
@@ -1331,15 +1640,15 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
1331
1640
  }
1332
1641
  }, [deviceFlow, status]);
1333
1642
  if (status === "initiating") {
1334
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, /* @__PURE__ */ React12.createElement(Spinner2, { type: "dots" }), " Initiating device authorization...")));
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...")));
1335
1644
  }
1336
1645
  if (status === "error") {
1337
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "red", bold: true }, "\u2716 Authentication Failed")), /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: "red" }, error)));
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)));
1338
1647
  }
1339
1648
  if (status === "success") {
1340
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "green", bold: true }, "\u2714 Successfully authenticated!")), /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "You can now use B4M CLI with your account.")));
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.")));
1341
1650
  }
1342
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan", bold: true }, "\u{1F510} Device Authorization")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "Opening browser automatically... If it doesn't open, please visit:")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "blue", bold: true }, deviceFlow?.verification_uri)), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "And enter this code when prompted:")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "yellow", bold: true }, deviceFlow?.user_code)), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, /* @__PURE__ */ React12.createElement(Spinner2, { type: "dots" }), " ", statusMessage)), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Expires in ", deviceFlow ? Math.floor(deviceFlow.expires_in / 60) : 0, " minutes")));
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")));
1343
1652
  }
1344
1653
 
1345
1654
  // src/storage/SessionStore.ts
@@ -1461,7 +1770,7 @@ var SessionStore = class {
1461
1770
  };
1462
1771
 
1463
1772
  // src/storage/ConfigStore.ts
1464
- import { promises as fs4, existsSync as existsSync2 } from "fs";
1773
+ import { promises as fs4, existsSync as existsSync3 } from "fs";
1465
1774
  import path4 from "path";
1466
1775
  import { homedir as homedir2 } from "os";
1467
1776
  import { v4 as uuidv4 } from "uuid";
@@ -1588,7 +1897,7 @@ function findProjectConfigDir(startDir = process.cwd()) {
1588
1897
  while (currentDir !== root) {
1589
1898
  const gitPath = path4.join(currentDir, ".git");
1590
1899
  try {
1591
- if (existsSync2(gitPath)) {
1900
+ if (existsSync3(gitPath)) {
1592
1901
  return currentDir;
1593
1902
  }
1594
1903
  } catch {
@@ -2196,7 +2505,7 @@ var ReActAgent = class extends EventEmitter {
2196
2505
  /**
2197
2506
  * Run the agent to completion
2198
2507
  *
2199
- * @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)
2200
2509
  * @param options - Optional overrides for this run
2201
2510
  * @returns Agent result with final answer and all steps
2202
2511
  */
@@ -7673,7 +7982,7 @@ var planetVisibilityTool = {
7673
7982
 
7674
7983
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
7675
7984
  import { promises as fs7 } from "fs";
7676
- import { existsSync as existsSync3, statSync as statSync3 } from "fs";
7985
+ import { existsSync as existsSync4, statSync as statSync4 } from "fs";
7677
7986
  import path6 from "path";
7678
7987
  var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
7679
7988
  async function readFileContent(params) {
@@ -7684,10 +7993,10 @@ async function readFileContent(params) {
7684
7993
  if (!resolvedPath.startsWith(cwd)) {
7685
7994
  throw new Error(`Access denied: Cannot read files outside of current working directory`);
7686
7995
  }
7687
- if (!existsSync3(resolvedPath)) {
7996
+ if (!existsSync4(resolvedPath)) {
7688
7997
  throw new Error(`File not found: ${filePath}`);
7689
7998
  }
7690
- const stats = statSync3(resolvedPath);
7999
+ const stats = statSync4(resolvedPath);
7691
8000
  if (stats.isDirectory()) {
7692
8001
  throw new Error(`Path is a directory, not a file: ${filePath}`);
7693
8002
  }
@@ -7730,7 +8039,7 @@ var fileReadTool = {
7730
8039
  context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
7731
8040
  try {
7732
8041
  const content = await readFileContent(params);
7733
- const stats = statSync3(path6.resolve(process.cwd(), path6.normalize(params.path)));
8042
+ const stats = statSync4(path6.resolve(process.cwd(), path6.normalize(params.path)));
7734
8043
  context.logger.info("\u2705 FileRead: Success", {
7735
8044
  path: params.path,
7736
8045
  size: stats.size,
@@ -7770,7 +8079,7 @@ var fileReadTool = {
7770
8079
 
7771
8080
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
7772
8081
  import { promises as fs8 } from "fs";
7773
- import { existsSync as existsSync4 } from "fs";
8082
+ import { existsSync as existsSync5 } from "fs";
7774
8083
  import path7 from "path";
7775
8084
  async function createFile(params) {
7776
8085
  const { path: filePath, content, createDirectories = true } = params;
@@ -7780,7 +8089,7 @@ async function createFile(params) {
7780
8089
  if (!resolvedPath.startsWith(cwd)) {
7781
8090
  throw new Error(`Access denied: Cannot create files outside of current working directory`);
7782
8091
  }
7783
- const fileExists = existsSync4(resolvedPath);
8092
+ const fileExists = existsSync5(resolvedPath);
7784
8093
  const action = fileExists ? "overwritten" : "created";
7785
8094
  if (createDirectories) {
7786
8095
  const dir = path7.dirname(resolvedPath);
@@ -7798,7 +8107,7 @@ var createFileTool = {
7798
8107
  implementation: (context) => ({
7799
8108
  toolFn: async (value) => {
7800
8109
  const params = value;
7801
- const fileExists = existsSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
8110
+ const fileExists = existsSync5(path7.resolve(process.cwd(), path7.normalize(params.path)));
7802
8111
  context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
7803
8112
  path: params.path,
7804
8113
  size: params.content.length
@@ -7839,7 +8148,7 @@ var createFileTool = {
7839
8148
 
7840
8149
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
7841
8150
  import { readdir } from "fs/promises";
7842
- import { statSync as statSync4 } from "fs";
8151
+ import { statSync as statSync5 } from "fs";
7843
8152
  import path8 from "path";
7844
8153
  function matchGlob(filePath, pattern) {
7845
8154
  let regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, options) => `(${options.split(",").join("|")})`).replace(/\[([^\]]+)\]/g, "[$1]");
@@ -7887,7 +8196,7 @@ async function listFiles(params) {
7887
8196
  for (const match of matches) {
7888
8197
  try {
7889
8198
  const fullPath = path8.join(targetCwd, match);
7890
- const stats = statSync4(fullPath);
8199
+ const stats = statSync5(fullPath);
7891
8200
  filesInfo.push({
7892
8201
  path: match,
7893
8202
  size: stats.size,
@@ -7968,7 +8277,7 @@ var globFilesTool = {
7968
8277
 
7969
8278
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
7970
8279
  import { readdir as readdir2 } from "fs/promises";
7971
- import { readFileSync as readFileSync2, statSync as statSync5 } from "fs";
8280
+ import { readFileSync as readFileSync3, statSync as statSync6 } from "fs";
7972
8281
  import path9 from "path";
7973
8282
  var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
7974
8283
  function matchGlob2(filePath, pattern) {
@@ -8020,7 +8329,7 @@ async function searchFiles2(params) {
8020
8329
  for (const match of filesToSearch) {
8021
8330
  const fullPath = path9.join(targetCwd, match);
8022
8331
  try {
8023
- const stats = statSync5(fullPath);
8332
+ const stats = statSync6(fullPath);
8024
8333
  if (stats.isDirectory() || stats.size > MAX_FILE_SIZE3) {
8025
8334
  continue;
8026
8335
  }
@@ -8044,7 +8353,7 @@ async function searchFiles2(params) {
8044
8353
  filesSearched++;
8045
8354
  const fullPath = path9.join(targetCwd, file);
8046
8355
  try {
8047
- const content = readFileSync2(fullPath, "utf-8");
8356
+ const content = readFileSync3(fullPath, "utf-8");
8048
8357
  const lines = content.split("\n");
8049
8358
  for (let i = 0; i < lines.length; i++) {
8050
8359
  if (matches.length >= maxResults) {
@@ -8178,7 +8487,7 @@ var grepSearchTool = {
8178
8487
 
8179
8488
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
8180
8489
  import { promises as fs9 } from "fs";
8181
- import { existsSync as existsSync5, statSync as statSync6 } from "fs";
8490
+ import { existsSync as existsSync6, statSync as statSync7 } from "fs";
8182
8491
  import path10 from "path";
8183
8492
  async function deleteFile(params) {
8184
8493
  const { path: filePath, recursive = false } = params;
@@ -8188,10 +8497,10 @@ async function deleteFile(params) {
8188
8497
  if (!resolvedPath.startsWith(cwd)) {
8189
8498
  throw new Error(`Access denied: Cannot delete files outside of current working directory`);
8190
8499
  }
8191
- if (!existsSync5(resolvedPath)) {
8500
+ if (!existsSync6(resolvedPath)) {
8192
8501
  throw new Error(`File or directory not found: ${filePath}`);
8193
8502
  }
8194
- const stats = statSync6(resolvedPath);
8503
+ const stats = statSync7(resolvedPath);
8195
8504
  const isDirectory = stats.isDirectory();
8196
8505
  const size = stats.size;
8197
8506
  if (isDirectory && !recursive) {
@@ -8212,7 +8521,7 @@ var deleteFileTool = {
8212
8521
  toolFn: async (value) => {
8213
8522
  const params = value;
8214
8523
  const resolvedPath = path10.resolve(process.cwd(), path10.normalize(params.path));
8215
- const isDirectory = existsSync5(resolvedPath) && statSync6(resolvedPath).isDirectory();
8524
+ const isDirectory = existsSync6(resolvedPath) && statSync7(resolvedPath).isDirectory();
8216
8525
  context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
8217
8526
  path: params.path,
8218
8527
  recursive: params.recursive
@@ -8502,10 +8811,10 @@ import jwt2 from "jsonwebtoken";
8502
8811
  // src/utils/diffPreview.ts
8503
8812
  import * as Diff from "diff";
8504
8813
  import { readFile } from "fs/promises";
8505
- import { existsSync as existsSync6 } from "fs";
8814
+ import { existsSync as existsSync7 } from "fs";
8506
8815
  async function generateFileDiffPreview(args) {
8507
8816
  try {
8508
- if (!existsSync6(args.path)) {
8817
+ if (!existsSync7(args.path)) {
8509
8818
  const lines2 = args.content.split("\n");
8510
8819
  const preview = lines2.slice(0, 20).join("\n");
8511
8820
  const hasMore = lines2.length > 20;
@@ -8535,7 +8844,7 @@ ${preview}${hasMore ? `
8535
8844
  }
8536
8845
  async function generateFileDeletePreview(args) {
8537
8846
  try {
8538
- if (!existsSync6(args.path)) {
8847
+ if (!existsSync7(args.path)) {
8539
8848
  return `[File does not exist: ${args.path}]`;
8540
8849
  }
8541
8850
  const stats = await import("fs/promises").then((fs11) => fs11.stat(args.path));
@@ -9197,7 +9506,7 @@ var logger = Logger2.getInstance();
9197
9506
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
9198
9507
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
9199
9508
  import path12 from "path";
9200
- import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
9509
+ import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
9201
9510
  var MCPClient = class {
9202
9511
  // Note: This class handles MCP server communication with repository filtering
9203
9512
  mcp;
@@ -9248,7 +9557,7 @@ var MCPClient = class {
9248
9557
  path12.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
9249
9558
  path12.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
9250
9559
  ];
9251
- const serverScriptPath = candidatePaths.find((p) => existsSync7(p));
9560
+ const serverScriptPath = candidatePaths.find((p) => existsSync8(p));
9252
9561
  if (!serverScriptPath) {
9253
9562
  const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
9254
9563
  console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
@@ -9446,6 +9755,190 @@ var McpManager = class {
9446
9755
  }
9447
9756
  };
9448
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
+
9449
9942
  // src/llm/ServerLlmBackend.ts
9450
9943
  import { createParser } from "eventsource-parser";
9451
9944
  import { isAxiosError } from "axios";
@@ -9863,7 +10356,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
9863
10356
  // package.json
9864
10357
  var package_default = {
9865
10358
  name: "@bike4mind/cli",
9866
- version: "0.2.6-fix-cli-mcp-type-mismatch.17119+0906e1e34",
10359
+ version: "0.2.7-basic-cli-shortcuts.17131+b79f34724",
9867
10360
  type: "module",
9868
10361
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
9869
10362
  license: "UNLICENSED",
@@ -9924,6 +10417,7 @@ var package_default = {
9924
10417
  "async-mutex": "^0.5.0",
9925
10418
  axios: "^1.13.2",
9926
10419
  bcryptjs: "^2.4.3",
10420
+ "better-sqlite3": "^12.5.0",
9927
10421
  cheerio: "1.0.0-rc.12",
9928
10422
  "csv-parse": "^5.5.6",
9929
10423
  dayjs: "^1.11.13",
@@ -9949,7 +10443,7 @@ var package_default = {
9949
10443
  "p-limit": "^6.2.0",
9950
10444
  qrcode: "^1.5.4",
9951
10445
  react: "^19.2.3",
9952
- sharp: "^0.32.6",
10446
+ sharp: "^0.34.5",
9953
10447
  speakeasy: "^2.0.0",
9954
10448
  tiktoken: "^1.0.16",
9955
10449
  turndown: "^7.2.0",
@@ -9965,10 +10459,11 @@ var package_default = {
9965
10459
  },
9966
10460
  devDependencies: {
9967
10461
  "@bike4mind/agents": "0.1.0",
9968
- "@bike4mind/common": "2.38.1-fix-cli-mcp-type-mismatch.17119+0906e1e34",
9969
- "@bike4mind/mcp": "1.20.1-fix-cli-mcp-type-mismatch.17119+0906e1e34",
9970
- "@bike4mind/services": "2.33.1-fix-cli-mcp-type-mismatch.17119+0906e1e34",
9971
- "@bike4mind/utils": "2.1.2-fix-cli-mcp-type-mismatch.17119+0906e1e34",
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",
9972
10467
  "@types/diff": "^5.0.9",
9973
10468
  "@types/jsonwebtoken": "^9.0.4",
9974
10469
  "@types/node": "^22.9.0",
@@ -9980,7 +10475,7 @@ var package_default = {
9980
10475
  typescript: "^5.9.3",
9981
10476
  vitest: "^3.2.4"
9982
10477
  },
9983
- gitHead: "0906e1e34709a52074b386d9b51a94776db1d33d"
10478
+ gitHead: "b79f34724ba9a60aae19010065d78ac75c2f8396"
9984
10479
  };
9985
10480
 
9986
10481
  // src/config/constants.ts
@@ -10001,20 +10496,26 @@ var EXIT_TIMEOUT_MS = 2e3;
10001
10496
  var usageCache = null;
10002
10497
  function CliApp() {
10003
10498
  const { exit } = useApp();
10004
- const [state, setState] = useState4({
10499
+ const imageRenderer = new ImageRenderer();
10500
+ const [state, setState] = useState5({
10005
10501
  session: null,
10006
10502
  sessionStore: new SessionStore(),
10007
10503
  configStore: new ConfigStore(),
10008
10504
  commandHistoryStore: new CommandHistoryStore(),
10505
+ imageStore: null,
10506
+ // Lazy-loaded on first use
10507
+ imageRenderer,
10508
+ messageBuilder: null,
10009
10509
  agent: null,
10010
10510
  mcpManager: null,
10011
10511
  permissionManager: null,
10012
10512
  permissionPrompt: null,
10013
10513
  trustLocationSelector: null
10014
10514
  });
10015
- const [isInitialized, setIsInitialized] = useState4(false);
10016
- const [initError, setInitError] = useState4(null);
10017
- const [commandHistory, setCommandHistory] = useState4([]);
10515
+ const [isInitialized, setIsInitialized] = useState5(false);
10516
+ const [initError, setInitError] = useState5(null);
10517
+ const [commandHistory, setCommandHistory] = useState5([]);
10518
+ const imageStoreInitPromise = useRef(null);
10018
10519
  const setStoreSession = useCliStore((state2) => state2.setSession);
10019
10520
  const setPermissionPrompt = useCliStore((state2) => state2.setPermissionPrompt);
10020
10521
  const setShowConfigEditor = useCliStore((state2) => state2.setShowConfigEditor);
@@ -10038,6 +10539,13 @@ function CliApp() {
10038
10539
  if (state.agent) {
10039
10540
  state.agent.removeAllListeners();
10040
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
+ }
10041
10549
  await Promise.race([
10042
10550
  Promise.all(cleanupTasks),
10043
10551
  new Promise((resolve3) => setTimeout(resolve3, 500))
@@ -10046,8 +10554,8 @@ function CliApp() {
10046
10554
  setTimeout(() => {
10047
10555
  process.exit(0);
10048
10556
  }, 100);
10049
- }, [state.session, state.sessionStore, state.mcpManager, state.agent]);
10050
- useInput3((input, key) => {
10557
+ }, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
10558
+ useInput4((input, key) => {
10051
10559
  if (key.ctrl && input === "c") {
10052
10560
  const now = Date.now();
10053
10561
  if (exitTimestamp && now - exitTimestamp < EXIT_TIMEOUT_MS) {
@@ -10306,9 +10814,16 @@ Remember: Use context from previous messages to understand follow-up questions.`
10306
10814
  return;
10307
10815
  }
10308
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
+ }
10309
10824
  const userMessage = {
10310
10825
  role: "user",
10311
- content: message,
10826
+ content: userMessageContent,
10312
10827
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
10313
10828
  };
10314
10829
  const recentMessages = state.session.messages.slice(-20);
@@ -10316,7 +10831,7 @@ Remember: Use context from previous messages to understand follow-up questions.`
10316
10831
  role: msg.role,
10317
10832
  content: msg.content
10318
10833
  }));
10319
- const result = await state.agent.run(message, {
10834
+ const result = await state.agent.run(messageContent, {
10320
10835
  previousMessages: previousMessages.length > 0 ? previousMessages : void 0
10321
10836
  });
10322
10837
  const permissionDenied = result.finalAnswer.startsWith("Permission denied for tool");
@@ -10365,6 +10880,35 @@ Remember: Use context from previous messages to understand follow-up questions.`
10365
10880
  console.error("Error processing message:", error);
10366
10881
  }
10367
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
+ };
10368
10912
  const handleCommand = async (command, args) => {
10369
10913
  switch (command) {
10370
10914
  case "help":
@@ -10801,12 +11345,12 @@ No usage data available for the last ${USAGE_DAYS} days.`);
10801
11345
  setState((prev) => ({ ...prev, config: updatedConfig }));
10802
11346
  };
10803
11347
  if (initError) {
10804
- return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, "\u274C Initialization Error"), /* @__PURE__ */ React13.createElement(Text12, null, initError), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "\n", "Tip: Run /config to set up your API keys"));
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"));
10805
11349
  }
10806
11350
  if (state.trustLocationSelector) {
10807
11351
  const projectDir = state.configStore.getProjectConfigDir();
10808
11352
  const inProject = projectDir !== null;
10809
- return /* @__PURE__ */ React13.createElement(
11353
+ return /* @__PURE__ */ React14.createElement(
10810
11354
  TrustLocationSelector,
10811
11355
  {
10812
11356
  inProject,
@@ -10825,7 +11369,7 @@ No usage data available for the last ${USAGE_DAYS} days.`);
10825
11369
  }
10826
11370
  if (state.showLoginFlow) {
10827
11371
  const loginApiUrl = getApiUrl(state.config?.apiConfig);
10828
- return /* @__PURE__ */ React13.createElement(
11372
+ return /* @__PURE__ */ React14.createElement(
10829
11373
  LoginFlow,
10830
11374
  {
10831
11375
  apiUrl: loginApiUrl,
@@ -10848,13 +11392,14 @@ No usage data available for the last ${USAGE_DAYS} days.`);
10848
11392
  );
10849
11393
  }
10850
11394
  if (!isInitialized) {
10851
- return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "\u{1F680} Initializing..."));
11395
+ return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React14.createElement(Text13, null, "\u{1F680} Initializing..."));
10852
11396
  }
10853
- return /* @__PURE__ */ React13.createElement(
11397
+ return /* @__PURE__ */ React14.createElement(
10854
11398
  App,
10855
11399
  {
10856
11400
  onMessage: handleMessage,
10857
11401
  onCommand: handleCommand,
11402
+ onImageDetected: handleImageDetected,
10858
11403
  commandHistory,
10859
11404
  config: state.config,
10860
11405
  availableModels: state.availableModels,
@@ -10884,6 +11429,6 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
10884
11429
  if (isDevMode) {
10885
11430
  console.log("\u{1F527} Running in development mode (using TypeScript source)\n");
10886
11431
  }
10887
- render(/* @__PURE__ */ React13.createElement(CliApp, null), {
11432
+ render(/* @__PURE__ */ React14.createElement(CliApp, null), {
10888
11433
  exitOnCtrlC: false
10889
11434
  });