@bike4mind/cli 0.2.5-ja-fix-confluence-table-data-5752.17103 → 0.2.7-basic-cli-shortcuts.17131

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