@bike4mind/cli 0.2.3-feat-cli-clear-command.17058 → 0.2.3-feat-cli-project-scoped-config.17063

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.
Files changed (2) hide show
  1. package/dist/index.js +838 -166
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -79,13 +79,13 @@ import {
79
79
  } from "./chunk-PDX44BCA.js";
80
80
 
81
81
  // src/index.tsx
82
- import React11, { useState as useState3, useEffect as useEffect3, useCallback } from "react";
83
- import { render, Box as Box11, Text as Text10, useApp, useInput as useInput2 } from "ink";
82
+ import React13, { useState as useState4, useEffect as useEffect3, useCallback } from "react";
83
+ import { render, Box as Box13, Text as Text12, useApp, useInput as useInput3 } from "ink";
84
84
  import { v4 as uuidv49 } from "uuid";
85
85
 
86
86
  // src/components/App.tsx
87
- import React8 from "react";
88
- import { Box as Box8, Text as Text7, Static } from "ink";
87
+ import React9 from "react";
88
+ import { Box as Box9, Text as Text8, Static } from "ink";
89
89
 
90
90
  // src/components/StatusBar.tsx
91
91
  import React from "react";
@@ -352,7 +352,7 @@ var COMMANDS = [
352
352
  },
353
353
  {
354
354
  name: "config",
355
- description: "Show configuration"
355
+ description: "Open interactive configuration editor"
356
356
  },
357
357
  {
358
358
  name: "set-api",
@@ -384,11 +384,6 @@ var COMMANDS = [
384
384
  {
385
385
  name: "usage",
386
386
  description: "Show credit usage and balance"
387
- },
388
- {
389
- name: "clear",
390
- description: "Start a new session",
391
- aliases: ["new"]
392
387
  }
393
388
  ];
394
389
 
@@ -609,6 +604,9 @@ var useCliStore = create((set) => ({
609
604
  // Permission prompt
610
605
  permissionPrompt: null,
611
606
  setPermissionPrompt: (prompt) => set({ permissionPrompt: prompt }),
607
+ // Config editor
608
+ showConfigEditor: false,
609
+ setShowConfigEditor: (show) => set({ showConfigEditor: show }),
612
610
  // Exit handling
613
611
  exitRequested: false,
614
612
  setExitRequested: (requested) => set({ exitRequested: requested })
@@ -663,6 +661,211 @@ function PermissionPrompt({
663
661
  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) })));
664
662
  }
665
663
 
664
+ // src/components/ConfigEditor.tsx
665
+ import React8, { useState as useState2, useMemo as useMemo2 } from "react";
666
+ import { Box as Box8, Text as Text7, useInput as useInput2 } from "ink";
667
+ var MAX_ITERATIONS_OPTIONS = [
668
+ { label: "10", value: 10 },
669
+ { label: "20", value: 20 },
670
+ { label: "30", value: 30 },
671
+ { label: "40", value: 40 },
672
+ { label: "50", value: 50 },
673
+ { label: "Infinite", value: null }
674
+ ];
675
+ var THEME_OPTIONS = [
676
+ { label: "dark", value: "dark" },
677
+ { label: "light", value: "light" }
678
+ ];
679
+ var EXPORT_FORMAT_OPTIONS = [
680
+ { label: "markdown", value: "markdown" },
681
+ { label: "json", value: "json" }
682
+ ];
683
+ var CONFIG_ITEMS = [
684
+ {
685
+ key: "maxIterations",
686
+ label: "Max Iterations",
687
+ type: "select",
688
+ options: MAX_ITERATIONS_OPTIONS,
689
+ getValue: (config) => config.preferences.maxIterations,
690
+ setValue: (config, value) => ({
691
+ ...config,
692
+ preferences: {
693
+ ...config.preferences,
694
+ maxIterations: value
695
+ }
696
+ })
697
+ },
698
+ {
699
+ key: "maxTokens",
700
+ label: "Max Tokens",
701
+ type: "number",
702
+ min: 256,
703
+ max: 128e3,
704
+ step: 256,
705
+ getValue: (config) => config.preferences.maxTokens,
706
+ setValue: (config, value) => ({
707
+ ...config,
708
+ preferences: {
709
+ ...config.preferences,
710
+ maxTokens: value
711
+ }
712
+ })
713
+ },
714
+ {
715
+ key: "temperature",
716
+ label: "Temperature",
717
+ type: "number",
718
+ min: 0,
719
+ max: 2,
720
+ step: 0.1,
721
+ getValue: (config) => config.preferences.temperature,
722
+ setValue: (config, value) => ({
723
+ ...config,
724
+ preferences: {
725
+ ...config.preferences,
726
+ temperature: value
727
+ }
728
+ })
729
+ },
730
+ {
731
+ key: "autoSave",
732
+ label: "Auto Save",
733
+ type: "boolean",
734
+ getValue: (config) => config.preferences.autoSave,
735
+ setValue: (config, value) => ({
736
+ ...config,
737
+ preferences: {
738
+ ...config.preferences,
739
+ autoSave: value
740
+ }
741
+ })
742
+ },
743
+ {
744
+ key: "theme",
745
+ label: "Theme",
746
+ type: "select",
747
+ options: THEME_OPTIONS,
748
+ getValue: (config) => config.preferences.theme,
749
+ setValue: (config, value) => ({
750
+ ...config,
751
+ preferences: {
752
+ ...config.preferences,
753
+ theme: value
754
+ }
755
+ })
756
+ },
757
+ {
758
+ key: "exportFormat",
759
+ label: "Export Format",
760
+ type: "select",
761
+ options: EXPORT_FORMAT_OPTIONS,
762
+ getValue: (config) => config.preferences.exportFormat,
763
+ setValue: (config, value) => ({
764
+ ...config,
765
+ preferences: {
766
+ ...config.preferences,
767
+ exportFormat: value
768
+ }
769
+ })
770
+ }
771
+ ];
772
+ function ConfigEditor({ config, onSave, onClose }) {
773
+ const [selectedIndex, setSelectedIndex] = useState2(0);
774
+ const [editedConfig, setEditedConfig] = useState2(config);
775
+ const [saveError, setSaveError] = useState2(null);
776
+ const [isSaving, setIsSaving] = useState2(false);
777
+ const hasChanges = useMemo2(() => {
778
+ return JSON.stringify(config.preferences) !== JSON.stringify(editedConfig.preferences);
779
+ }, [config.preferences, editedConfig.preferences]);
780
+ const handleSaveAndClose = async () => {
781
+ if (hasChanges) {
782
+ try {
783
+ setSaveError(null);
784
+ setIsSaving(true);
785
+ await onSave(editedConfig);
786
+ } catch (error) {
787
+ setSaveError(error instanceof Error ? error.message : "Failed to save");
788
+ setIsSaving(false);
789
+ return;
790
+ }
791
+ setIsSaving(false);
792
+ }
793
+ onClose();
794
+ };
795
+ useInput2((input, key) => {
796
+ const currentItem = CONFIG_ITEMS[selectedIndex];
797
+ if (key.upArrow) {
798
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : CONFIG_ITEMS.length - 1);
799
+ return;
800
+ }
801
+ if (key.downArrow) {
802
+ setSelectedIndex((prev) => prev < CONFIG_ITEMS.length - 1 ? prev + 1 : 0);
803
+ return;
804
+ }
805
+ if ((key.escape || input === "q") && !isSaving) {
806
+ handleSaveAndClose();
807
+ return;
808
+ }
809
+ if (currentItem.type === "select" && currentItem.options) {
810
+ const currentValue = currentItem.getValue(editedConfig);
811
+ const currentIdx = currentItem.options.findIndex((opt) => opt.value === currentValue);
812
+ if (key.leftArrow || input === " ") {
813
+ let newIdx;
814
+ if (input === " ") {
815
+ newIdx = (currentIdx + 1) % currentItem.options.length;
816
+ } else {
817
+ newIdx = currentIdx > 0 ? currentIdx - 1 : currentItem.options.length - 1;
818
+ }
819
+ const newValue = currentItem.options[newIdx].value;
820
+ setEditedConfig(currentItem.setValue(editedConfig, newValue));
821
+ } else if (key.rightArrow) {
822
+ const newIdx = (currentIdx + 1) % currentItem.options.length;
823
+ const newValue = currentItem.options[newIdx].value;
824
+ setEditedConfig(currentItem.setValue(editedConfig, newValue));
825
+ }
826
+ } else if (currentItem.type === "boolean") {
827
+ if (input === " " || key.leftArrow || key.rightArrow) {
828
+ const currentValue = currentItem.getValue(editedConfig);
829
+ setEditedConfig(currentItem.setValue(editedConfig, !currentValue));
830
+ }
831
+ } else if (currentItem.type === "number") {
832
+ const currentValue = currentItem.getValue(editedConfig);
833
+ const step = currentItem.step || 1;
834
+ const min = currentItem.min ?? 0;
835
+ const max = currentItem.max ?? Infinity;
836
+ if (key.leftArrow || input === "-") {
837
+ const newValue = Math.max(min, currentValue - step);
838
+ const rounded = Math.round(newValue * 10) / 10;
839
+ setEditedConfig(currentItem.setValue(editedConfig, rounded));
840
+ } else if (key.rightArrow || input === "+" || input === "=") {
841
+ const newValue = Math.min(max, currentValue + step);
842
+ const rounded = Math.round(newValue * 10) / 10;
843
+ setEditedConfig(currentItem.setValue(editedConfig, rounded));
844
+ }
845
+ }
846
+ });
847
+ const formatValue = (item, value) => {
848
+ if (item.type === "boolean") {
849
+ return value ? "On" : "Off";
850
+ }
851
+ if (item.type === "select" && item.options) {
852
+ const option = item.options.find((opt) => opt.value === value);
853
+ return option?.label || String(value);
854
+ }
855
+ if (item.type === "number") {
856
+ return String(value);
857
+ }
858
+ return String(value);
859
+ };
860
+ 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) => {
861
+ const isSelected = index === selectedIndex;
862
+ const value = item.getValue(editedConfig);
863
+ const displayValue = formatValue(item, value);
864
+ const hasOptions = item.type === "select" || item.type === "boolean" || item.type === "number";
865
+ 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 }, " >")));
866
+ })), /* @__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")));
867
+ }
868
+
666
869
  // src/utils/processFileReferences.ts
667
870
  import * as fs2 from "fs";
668
871
  import * as path2 from "path";
@@ -778,16 +981,25 @@ function hasFileReferences(message) {
778
981
  }
779
982
 
780
983
  // src/components/App.tsx
781
- function App({ onMessage, onCommand, onPermissionResponse, commandHistory = [] }) {
984
+ function App({
985
+ onMessage,
986
+ onCommand,
987
+ onPermissionResponse,
988
+ commandHistory = [],
989
+ config,
990
+ onSaveConfig
991
+ }) {
782
992
  const messages = useCliStore((state) => state.session?.messages || []);
783
993
  const sessionName = useCliStore((state) => state.session?.name || "New Session");
784
994
  const currentModel = useCliStore((state) => state.session?.model || "claude-sonnet-4-5-20250929");
785
995
  const totalTokens = useCliStore((state) => state.session?.metadata.totalTokens || 0);
786
996
  const isThinking = useCliStore((state) => state.isThinking);
787
997
  const permissionPrompt = useCliStore((state) => state.permissionPrompt);
998
+ const showConfigEditor = useCliStore((state) => state.showConfigEditor);
999
+ const setShowConfigEditor = useCliStore((state) => state.setShowConfigEditor);
788
1000
  const exitRequested = useCliStore((state) => state.exitRequested);
789
1001
  const setIsThinking = useCliStore((state) => state.setIsThinking);
790
- const handleSubmit = React8.useCallback(
1002
+ const handleSubmit = React9.useCallback(
791
1003
  async (input) => {
792
1004
  const trimmed = input.trim();
793
1005
  if (!trimmed) return;
@@ -816,7 +1028,7 @@ ${errorBlock}`;
816
1028
  },
817
1029
  [onMessage, onCommand, setIsThinking]
818
1030
  );
819
- return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column" }, /* @__PURE__ */ React8.createElement(StatusBar, { sessionName, model: currentModel, tokenUsage: totalTokens }), permissionPrompt ? /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React8.createElement(
1031
+ 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(
820
1032
  PermissionPrompt,
821
1033
  {
822
1034
  toolName: permissionPrompt.toolName,
@@ -825,39 +1037,39 @@ ${errorBlock}`;
825
1037
  canBeTrusted: permissionPrompt.canBeTrusted,
826
1038
  onResponse: onPermissionResponse
827
1039
  }
828
- )) : /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Static, { items: messages }, (message, index) => /* @__PURE__ */ React8.createElement(Box8, { key: index, flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React8.createElement(MessageItem, { message }))), /* @__PURE__ */ React8.createElement(AgentThinking, null), exitRequested && /* @__PURE__ */ React8.createElement(Box8, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: "yellow", bold: true }, "\u26A0\uFE0F Press Ctrl+C again to exit")), /* @__PURE__ */ React8.createElement(Box8, { borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React8.createElement(InputPrompt, { onSubmit: handleSubmit, disabled: isThinking, history: commandHistory }))));
1040
+ )) : /* @__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 }))));
829
1041
  }
830
1042
  function MessageItem({ message }) {
831
- return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Box8, null, /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: message.role === "user" ? "cyan" : "green" }, message.role === "user" ? "\u{1F464} You" : "\u{1F916} Assistant"), /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), /* @__PURE__ */ React8.createElement(Box8, { paddingLeft: 2 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React8.createElement(Text7, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React8.createElement(Text7, null, message.content)), message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React8.createElement(Box8, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text7, { 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__ */ React8.createElement(Text7, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
1043
+ 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) => {
832
1044
  if (step.type === "thought") {
833
- return /* @__PURE__ */ React8.createElement(Box8, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text7, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, ` ${step.content.slice(0, 200)}${step.content.length > 200 ? "..." : ""}`));
1045
+ 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 ? "..." : ""}`));
834
1046
  }
835
1047
  if (step.type === "action") {
836
1048
  const toolName = step.metadata?.toolName || "unknown";
837
1049
  const toolInput = step.metadata?.toolInput;
838
1050
  const observationStep = message.metadata.steps[idx + 1];
839
1051
  const result = observationStep?.type === "observation" ? observationStep.content : null;
840
- return /* @__PURE__ */ React8.createElement(Box8, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text7, { color: "yellow" }, "\u{1F527} Action: ", toolName), toolInput && /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, ` Input: ${typeof toolInput === "string" ? toolInput : JSON.stringify(toolInput).slice(0, 100)}`), result && /* @__PURE__ */ React8.createElement(Text7, { 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 ? "..." : ""}`));
1052
+ 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 ? "..." : ""}`));
841
1053
  }
842
1054
  return null;
843
- }).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React8.createElement(Box8, { paddingLeft: 2 }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)));
1055
+ }).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`)));
844
1056
  }
845
1057
 
846
1058
  // src/components/MessageList.tsx
847
- import React9 from "react";
848
- import { Box as Box9, Text as Text8 } from "ink";
1059
+ import React10 from "react";
1060
+ import { Box as Box10, Text as Text9 } from "ink";
849
1061
  function stripThinkingTags(content) {
850
1062
  return content.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
851
1063
  }
852
- var MessageList = React9.memo(
1064
+ var MessageList = React10.memo(
853
1065
  function MessageList2({ messages }) {
854
1066
  if (messages.length === 0) {
855
- return /* @__PURE__ */ React9.createElement(Box9, { paddingY: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "No messages yet. Type a message to start!"));
1067
+ return /* @__PURE__ */ React10.createElement(Box10, { paddingY: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "No messages yet. Type a message to start!"));
856
1068
  }
857
- return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", gap: 1, paddingY: 1 }, messages.map((message, index) => /* @__PURE__ */ React9.createElement(Box9, { key: index, 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 ", stripThinkingTags(message.content)) : /* @__PURE__ */ React9.createElement(Text8, null, stripThinkingTags(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) => {
1069
+ 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) => {
858
1070
  if (step.type === "thought") {
859
- 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(
860
- Text8,
1071
+ 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(
1072
+ Text9,
861
1073
  {
862
1074
  dimColor: true
863
1075
  },
@@ -869,10 +1081,10 @@ var MessageList = React9.memo(
869
1081
  const toolInput = step.metadata?.toolInput;
870
1082
  const observationStep = message.metadata.steps[idx + 1];
871
1083
  const result = observationStep?.type === "observation" ? observationStep.content : null;
872
- 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 ? "..." : ""}`));
1084
+ 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 ? "..." : ""}`));
873
1085
  }
874
1086
  return null;
875
- }).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`)))));
1087
+ }).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`)))));
876
1088
  },
877
1089
  (prevProps, nextProps) => {
878
1090
  if (prevProps.messages.length !== nextProps.messages.length) {
@@ -882,9 +1094,42 @@ var MessageList = React9.memo(
882
1094
  }
883
1095
  );
884
1096
 
1097
+ // src/components/TrustLocationSelector.tsx
1098
+ import React11 from "react";
1099
+ import { Box as Box11, Text as Text10 } from "ink";
1100
+ import SelectInput2 from "ink-select-input";
1101
+ function TrustLocationSelector({ inProject, onSelect, onCancel }) {
1102
+ const items = [];
1103
+ if (inProject) {
1104
+ items.push({
1105
+ label: "Project settings (local) - Saved in .bike4mind/local.json (not committed)",
1106
+ value: "local"
1107
+ });
1108
+ items.push({
1109
+ label: "Project settings (team-wide) - Checked in at .bike4mind/config.json",
1110
+ value: "project"
1111
+ });
1112
+ }
1113
+ items.push({
1114
+ label: "User settings - Saved at ~/.bike4mind/config.json",
1115
+ value: "global"
1116
+ });
1117
+ const handleSelect = (item) => {
1118
+ onSelect(item.value);
1119
+ };
1120
+ return /* @__PURE__ */ React11.createElement(Box11, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React11.createElement(Box11, { marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "Where should this rule be saved?")), /* @__PURE__ */ React11.createElement(
1121
+ SelectInput2,
1122
+ {
1123
+ items,
1124
+ onSelect: handleSelect,
1125
+ itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React11.createElement(Box11, null, /* @__PURE__ */ React11.createElement(Text10, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
1126
+ }
1127
+ ), /* @__PURE__ */ React11.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Ctrl+C to cancel")));
1128
+ }
1129
+
885
1130
  // src/components/LoginFlow.tsx
886
- import React10, { useState as useState2, useEffect as useEffect2 } from "react";
887
- import { Box as Box10, Text as Text9 } from "ink";
1131
+ import React12, { useState as useState3, useEffect as useEffect2 } from "react";
1132
+ import { Box as Box12, Text as Text11 } from "ink";
888
1133
  import Spinner2 from "ink-spinner";
889
1134
  import jwt from "jsonwebtoken";
890
1135
  import open from "open";
@@ -1000,10 +1245,10 @@ var OAuthClient = class {
1000
1245
 
1001
1246
  // src/components/LoginFlow.tsx
1002
1247
  function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
1003
- const [status, setStatus] = useState2("initiating");
1004
- const [deviceFlow, setDeviceFlow] = useState2(null);
1005
- const [statusMessage, setStatusMessage] = useState2("Initiating device authorization...");
1006
- const [error, setError] = useState2(null);
1248
+ const [status, setStatus] = useState3("initiating");
1249
+ const [deviceFlow, setDeviceFlow] = useState3(null);
1250
+ const [statusMessage, setStatusMessage] = useState3("Initiating device authorization...");
1251
+ const [error, setError] = useState3(null);
1007
1252
  useEffect2(() => {
1008
1253
  const runLoginFlow = async () => {
1009
1254
  const oauth = new OAuthClient(apiUrl);
@@ -1047,15 +1292,15 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
1047
1292
  }
1048
1293
  }, [deviceFlow, status]);
1049
1294
  if (status === "initiating") {
1050
- return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React10.createElement(Box10, null, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, /* @__PURE__ */ React10.createElement(Spinner2, { type: "dots" }), " Initiating device authorization...")));
1295
+ return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, /* @__PURE__ */ React12.createElement(Spinner2, { type: "dots" }), " Initiating device authorization...")));
1051
1296
  }
1052
1297
  if (status === "error") {
1053
- return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React10.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "red", bold: true }, "\u2716 Authentication Failed")), /* @__PURE__ */ React10.createElement(Box10, null, /* @__PURE__ */ React10.createElement(Text9, { color: "red" }, error)));
1298
+ return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "red", bold: true }, "\u2716 Authentication Failed")), /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: "red" }, error)));
1054
1299
  }
1055
1300
  if (status === "success") {
1056
- return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React10.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "green", bold: true }, "\u2714 Successfully authenticated!")), /* @__PURE__ */ React10.createElement(Box10, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "You can now use B4M CLI with your account.")));
1301
+ return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "green", bold: true }, "\u2714 Successfully authenticated!")), /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "You can now use B4M CLI with your account.")));
1057
1302
  }
1058
- return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" }, /* @__PURE__ */ React10.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan", bold: true }, "\u{1F510} Device Authorization")), /* @__PURE__ */ React10.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Text9, null, "Opening browser automatically... If it doesn't open, please visit:")), /* @__PURE__ */ React10.createElement(Box10, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { color: "blue", bold: true }, deviceFlow?.verification_uri)), /* @__PURE__ */ React10.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Text9, null, "And enter this code when prompted:")), /* @__PURE__ */ React10.createElement(Box10, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow", bold: true }, deviceFlow?.user_code)), /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, /* @__PURE__ */ React10.createElement(Spinner2, { type: "dots" }), " ", statusMessage)), /* @__PURE__ */ React10.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Expires in ", deviceFlow ? Math.floor(deviceFlow.expires_in / 60) : 0, " minutes")));
1303
+ return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan", bold: true }, "\u{1F510} Device Authorization")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "Opening browser automatically... If it doesn't open, please visit:")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "blue", bold: true }, deviceFlow?.verification_uri)), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "And enter this code when prompted:")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "yellow", bold: true }, deviceFlow?.user_code)), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, /* @__PURE__ */ React12.createElement(Spinner2, { type: "dots" }), " ", statusMessage)), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Expires in ", deviceFlow ? Math.floor(deviceFlow.expires_in / 60) : 0, " minutes")));
1059
1304
  }
1060
1305
 
1061
1306
  // src/storage/SessionStore.ts
@@ -1177,7 +1422,7 @@ var SessionStore = class {
1177
1422
  };
1178
1423
 
1179
1424
  // src/storage/ConfigStore.ts
1180
- import { promises as fs4 } from "fs";
1425
+ import { promises as fs4, existsSync as existsSync2 } from "fs";
1181
1426
  import path4 from "path";
1182
1427
  import { homedir as homedir2 } from "os";
1183
1428
  import { v4 as uuidv4 } from "uuid";
@@ -1215,7 +1460,8 @@ var CliConfigSchema = z.object({
1215
1460
  temperature: z.number(),
1216
1461
  autoSave: z.boolean(),
1217
1462
  theme: z.enum(["light", "dark"]),
1218
- exportFormat: z.enum(["markdown", "json"])
1463
+ exportFormat: z.enum(["markdown", "json"]),
1464
+ maxIterations: z.number().nullable().default(10)
1219
1465
  }),
1220
1466
  tools: z.object({
1221
1467
  enabled: z.array(z.string()),
@@ -1224,6 +1470,53 @@ var CliConfigSchema = z.object({
1224
1470
  }),
1225
1471
  trustedTools: z.array(z.string()).optional().default([])
1226
1472
  });
1473
+ var ProjectConfigSchema = z.object({
1474
+ tools: z.object({
1475
+ enabled: z.array(z.string()).optional(),
1476
+ denied: z.array(z.string()).optional(),
1477
+ config: z.record(z.any()).optional()
1478
+ }).optional(),
1479
+ defaultModel: z.string().optional(),
1480
+ mcpServers: z.array(
1481
+ z.object({
1482
+ name: z.string(),
1483
+ command: z.string(),
1484
+ args: z.array(z.string()),
1485
+ env: z.record(z.string()),
1486
+ enabled: z.boolean()
1487
+ })
1488
+ ).optional(),
1489
+ preferences: z.object({
1490
+ maxTokens: z.number().optional(),
1491
+ temperature: z.number().optional(),
1492
+ autoSave: z.boolean().optional(),
1493
+ theme: z.enum(["light", "dark"]).optional(),
1494
+ exportFormat: z.enum(["markdown", "json"]).optional()
1495
+ }).optional()
1496
+ });
1497
+ var ProjectLocalConfigSchema = z.object({
1498
+ trustedTools: z.array(z.string()).optional(),
1499
+ toolApiKeys: z.object({
1500
+ openweather: z.string().optional(),
1501
+ serper: z.string().optional()
1502
+ }).optional(),
1503
+ preferences: z.object({
1504
+ maxTokens: z.number().optional(),
1505
+ temperature: z.number().optional(),
1506
+ autoSave: z.boolean().optional(),
1507
+ theme: z.enum(["light", "dark"]).optional(),
1508
+ exportFormat: z.enum(["markdown", "json"]).optional()
1509
+ }).optional(),
1510
+ mcpServers: z.array(
1511
+ z.object({
1512
+ name: z.string(),
1513
+ command: z.string(),
1514
+ args: z.array(z.string()),
1515
+ env: z.record(z.string()),
1516
+ enabled: z.boolean()
1517
+ })
1518
+ ).optional()
1519
+ });
1227
1520
  var DEFAULT_CONFIG = {
1228
1521
  version: "0.1.0",
1229
1522
  userId: uuidv4(),
@@ -1238,7 +1531,8 @@ var DEFAULT_CONFIG = {
1238
1531
  temperature: 0.7,
1239
1532
  autoSave: true,
1240
1533
  theme: "dark",
1241
- exportFormat: "markdown"
1534
+ exportFormat: "markdown",
1535
+ maxIterations: 10
1242
1536
  },
1243
1537
  tools: {
1244
1538
  enabled: [],
@@ -1249,9 +1543,123 @@ var DEFAULT_CONFIG = {
1249
1543
  trustedTools: []
1250
1544
  // No tools trusted by default
1251
1545
  };
1546
+ function findProjectConfigDir(startDir = process.cwd()) {
1547
+ let currentDir = startDir;
1548
+ const { root } = path4.parse(currentDir);
1549
+ while (currentDir !== root) {
1550
+ const gitPath = path4.join(currentDir, ".git");
1551
+ try {
1552
+ if (existsSync2(gitPath)) {
1553
+ return currentDir;
1554
+ }
1555
+ } catch {
1556
+ }
1557
+ currentDir = path4.dirname(currentDir);
1558
+ }
1559
+ return process.cwd();
1560
+ }
1561
+ async function loadProjectConfig(projectDir) {
1562
+ const configPath = path4.join(projectDir, ".bike4mind", "config.json");
1563
+ try {
1564
+ const data = await fs4.readFile(configPath, "utf-8");
1565
+ const rawConfig = JSON.parse(data);
1566
+ const validated = ProjectConfigSchema.parse(rawConfig);
1567
+ return validated;
1568
+ } catch (error) {
1569
+ if (error.code === "ENOENT") {
1570
+ return null;
1571
+ }
1572
+ if (error instanceof z.ZodError) {
1573
+ console.error("Project config validation error:", error.errors);
1574
+ return null;
1575
+ }
1576
+ console.error("Failed to load project config:", error);
1577
+ return null;
1578
+ }
1579
+ }
1580
+ async function loadProjectLocalConfig(projectDir) {
1581
+ const configPath = path4.join(projectDir, ".bike4mind", "local.json");
1582
+ try {
1583
+ const data = await fs4.readFile(configPath, "utf-8");
1584
+ const rawConfig = JSON.parse(data);
1585
+ const validated = ProjectLocalConfigSchema.parse(rawConfig);
1586
+ return validated;
1587
+ } catch (error) {
1588
+ if (error.code === "ENOENT") {
1589
+ return null;
1590
+ }
1591
+ if (error instanceof z.ZodError) {
1592
+ console.error("Project local config validation error:", error.errors);
1593
+ return null;
1594
+ }
1595
+ console.error("Failed to load project local config:", error);
1596
+ return null;
1597
+ }
1598
+ }
1599
+ function mergeMcpServers(...serverArrays) {
1600
+ const serverMap = /* @__PURE__ */ new Map();
1601
+ for (const servers of serverArrays) {
1602
+ if (servers) {
1603
+ for (const server of servers) {
1604
+ serverMap.set(server.name, server);
1605
+ }
1606
+ }
1607
+ }
1608
+ return Array.from(serverMap.values());
1609
+ }
1610
+ function mergeConfigs(global, project, local) {
1611
+ const merged = { ...global };
1612
+ if (project) {
1613
+ if (project.defaultModel) {
1614
+ merged.defaultModel = project.defaultModel;
1615
+ }
1616
+ if (project.preferences) {
1617
+ merged.preferences = {
1618
+ ...merged.preferences,
1619
+ ...project.preferences
1620
+ };
1621
+ }
1622
+ if (project.tools) {
1623
+ merged.tools = {
1624
+ ...merged.tools,
1625
+ enabled: project.tools.enabled || merged.tools.enabled,
1626
+ disabled: [...merged.tools.disabled, ...project.tools.denied || []],
1627
+ config: {
1628
+ ...merged.tools.config,
1629
+ ...project.tools.config
1630
+ }
1631
+ };
1632
+ }
1633
+ if (project.mcpServers) {
1634
+ merged.mcpServers = mergeMcpServers(merged.mcpServers, project.mcpServers);
1635
+ }
1636
+ }
1637
+ if (local) {
1638
+ if (local.trustedTools) {
1639
+ merged.trustedTools = [...merged.trustedTools || [], ...local.trustedTools];
1640
+ }
1641
+ if (local.toolApiKeys) {
1642
+ merged.toolApiKeys = {
1643
+ ...merged.toolApiKeys,
1644
+ ...local.toolApiKeys
1645
+ };
1646
+ }
1647
+ if (local.preferences) {
1648
+ merged.preferences = {
1649
+ ...merged.preferences,
1650
+ ...local.preferences
1651
+ };
1652
+ }
1653
+ if (local.mcpServers) {
1654
+ merged.mcpServers = mergeMcpServers(merged.mcpServers, local.mcpServers);
1655
+ }
1656
+ }
1657
+ return merged;
1658
+ }
1252
1659
  var ConfigStore = class {
1253
1660
  constructor(configPath) {
1254
1661
  this.config = null;
1662
+ this.projectConfigDir = null;
1255
1663
  this.configPath = configPath || path4.join(homedir2(), ".bike4mind", "config.json");
1256
1664
  }
1257
1665
  /**
@@ -1268,51 +1676,76 @@ var ConfigStore = class {
1268
1676
  }
1269
1677
  /**
1270
1678
  * Load configuration from disk with Zod validation
1679
+ * Merges global → project → local configs
1271
1680
  */
1272
1681
  async load() {
1273
1682
  if (this.config) {
1274
1683
  return this.config;
1275
1684
  }
1276
1685
  try {
1686
+ let globalConfig;
1277
1687
  try {
1278
- const stats = await fs4.stat(this.configPath);
1279
- const mode = stats.mode & 511;
1280
- if (mode !== 384) {
1281
- console.warn(`\u26A0\uFE0F Config file has insecure permissions (${mode.toString(8)}). Setting to 0600...`);
1282
- await fs4.chmod(this.configPath, 384);
1688
+ try {
1689
+ const stats = await fs4.stat(this.configPath);
1690
+ const mode = stats.mode & 511;
1691
+ if (mode !== 384) {
1692
+ console.warn(`\u26A0\uFE0F Config file has insecure permissions (${mode.toString(8)}). Setting to 0600...`);
1693
+ await fs4.chmod(this.configPath, 384);
1694
+ }
1695
+ } catch (statError) {
1283
1696
  }
1284
- } catch (statError) {
1285
- }
1286
- const data = await fs4.readFile(this.configPath, "utf-8");
1287
- const rawConfig = JSON.parse(data);
1288
- if (rawConfig.apiConfig && "environment" in rawConfig.apiConfig) {
1289
- const oldApiConfig = rawConfig.apiConfig;
1290
- if (oldApiConfig.environment === "custom" && oldApiConfig.customUrl) {
1291
- rawConfig.apiConfig = { customUrl: oldApiConfig.customUrl };
1697
+ const data = await fs4.readFile(this.configPath, "utf-8");
1698
+ const rawConfig = JSON.parse(data);
1699
+ if (rawConfig.apiConfig && "environment" in rawConfig.apiConfig) {
1700
+ const oldApiConfig = rawConfig.apiConfig;
1701
+ if (oldApiConfig.environment === "custom" && oldApiConfig.customUrl) {
1702
+ rawConfig.apiConfig = { customUrl: oldApiConfig.customUrl };
1703
+ } else {
1704
+ rawConfig.apiConfig = {};
1705
+ }
1706
+ }
1707
+ const validated = CliConfigSchema.parse(rawConfig);
1708
+ globalConfig = {
1709
+ ...DEFAULT_CONFIG,
1710
+ ...validated,
1711
+ auth: validated.auth,
1712
+ // Explicitly preserve auth field
1713
+ preferences: {
1714
+ ...DEFAULT_CONFIG.preferences,
1715
+ ...validated.preferences
1716
+ },
1717
+ tools: {
1718
+ ...DEFAULT_CONFIG.tools,
1719
+ ...validated.tools
1720
+ },
1721
+ toolApiKeys: {
1722
+ ...DEFAULT_CONFIG.toolApiKeys,
1723
+ ...validated.toolApiKeys || {}
1724
+ },
1725
+ trustedTools: validated.trustedTools || []
1726
+ };
1727
+ } catch (error) {
1728
+ if (error.code === "ENOENT") {
1729
+ globalConfig = { ...DEFAULT_CONFIG };
1730
+ } else if (error instanceof z.ZodError) {
1731
+ console.error("Global config validation error:", error.errors);
1732
+ console.error("Using default configuration");
1733
+ globalConfig = { ...DEFAULT_CONFIG };
1292
1734
  } else {
1293
- rawConfig.apiConfig = {};
1735
+ throw error;
1294
1736
  }
1295
1737
  }
1296
- const validated = CliConfigSchema.parse(rawConfig);
1297
- this.config = {
1298
- ...DEFAULT_CONFIG,
1299
- ...validated,
1300
- auth: validated.auth,
1301
- // Explicitly preserve auth field
1302
- preferences: {
1303
- ...DEFAULT_CONFIG.preferences,
1304
- ...validated.preferences
1305
- },
1306
- tools: {
1307
- ...DEFAULT_CONFIG.tools,
1308
- ...validated.tools
1309
- },
1310
- toolApiKeys: {
1311
- ...DEFAULT_CONFIG.toolApiKeys,
1312
- ...validated.toolApiKeys || {}
1313
- },
1314
- trustedTools: validated.trustedTools || []
1315
- };
1738
+ this.projectConfigDir = findProjectConfigDir();
1739
+ let projectConfig = null;
1740
+ let projectLocalConfig = null;
1741
+ if (this.projectConfigDir) {
1742
+ projectConfig = await loadProjectConfig(this.projectConfigDir);
1743
+ projectLocalConfig = await loadProjectLocalConfig(this.projectConfigDir);
1744
+ if (projectConfig) {
1745
+ console.log(`\u{1F4C1} Project config loaded from: ${this.projectConfigDir}/.bike4mind/`);
1746
+ }
1747
+ }
1748
+ this.config = mergeConfigs(globalConfig, projectConfig, projectLocalConfig);
1316
1749
  return this.config;
1317
1750
  } catch (error) {
1318
1751
  if (error.code === "ENOENT") {
@@ -1501,6 +1934,92 @@ var ConfigStore = class {
1501
1934
  }
1502
1935
  await this.save(config);
1503
1936
  }
1937
+ /**
1938
+ * Get project config directory (if any)
1939
+ */
1940
+ getProjectConfigDir() {
1941
+ return this.projectConfigDir;
1942
+ }
1943
+ /**
1944
+ * Initialize project config directory
1945
+ * Creates .bike4mind/ directory and ensures local.json is gitignored
1946
+ * Does NOT auto-create config.json (user creates that manually)
1947
+ */
1948
+ async initProjectConfig() {
1949
+ const projectDir = this.projectConfigDir || findProjectConfigDir();
1950
+ if (!projectDir) {
1951
+ return;
1952
+ }
1953
+ const configDir = path4.join(projectDir, ".bike4mind");
1954
+ await fs4.mkdir(configDir, { recursive: true });
1955
+ await this.ensureGitignore(projectDir);
1956
+ }
1957
+ /**
1958
+ * Ensure .gitignore includes .bike4mind/local.json
1959
+ */
1960
+ async ensureGitignore(projectDir) {
1961
+ const gitignorePath = path4.join(projectDir, ".gitignore");
1962
+ const entryToAdd = ".bike4mind/local.json";
1963
+ try {
1964
+ let gitignoreContent = "";
1965
+ try {
1966
+ gitignoreContent = await fs4.readFile(gitignorePath, "utf-8");
1967
+ } catch {
1968
+ }
1969
+ if (gitignoreContent.includes(entryToAdd)) {
1970
+ return;
1971
+ }
1972
+ const newContent = gitignoreContent.trim() + (gitignoreContent ? "\n" : "") + `
1973
+ # Bike4Mind local config (developer-specific)
1974
+ ${entryToAdd}
1975
+ `;
1976
+ await fs4.writeFile(gitignorePath, newContent, "utf-8");
1977
+ console.log(`\u2705 Added ${entryToAdd} to .gitignore`);
1978
+ } catch (error) {
1979
+ console.warn(`\u26A0\uFE0F Failed to update .gitignore:`, error);
1980
+ }
1981
+ }
1982
+ /**
1983
+ * Save project config to .bike4mind/config.json
1984
+ */
1985
+ async saveProjectConfig(config, projectDir) {
1986
+ const targetDir = projectDir || this.projectConfigDir || process.cwd();
1987
+ const configPath = path4.join(targetDir, ".bike4mind", "config.json");
1988
+ await fs4.mkdir(path4.dirname(configPath), { recursive: true });
1989
+ const validated = ProjectConfigSchema.parse(config);
1990
+ await fs4.writeFile(configPath, JSON.stringify(validated, null, 2), "utf-8");
1991
+ console.log(`\u2705 Saved project config to: ${configPath}`);
1992
+ }
1993
+ /**
1994
+ * Save project-local config to .bike4mind/local.json
1995
+ */
1996
+ async saveProjectLocalConfig(config, projectDir) {
1997
+ const targetDir = projectDir || this.projectConfigDir || process.cwd();
1998
+ const configPath = path4.join(targetDir, ".bike4mind", "local.json");
1999
+ await fs4.mkdir(path4.dirname(configPath), { recursive: true });
2000
+ const validated = ProjectLocalConfigSchema.parse(config);
2001
+ await fs4.writeFile(configPath, JSON.stringify(validated, null, 2), "utf-8");
2002
+ await fs4.chmod(configPath, 384);
2003
+ console.log(`\u2705 Saved project-local config to: ${configPath}`);
2004
+ }
2005
+ /**
2006
+ * Load raw project config (without merging)
2007
+ */
2008
+ async loadRawProjectConfig() {
2009
+ if (!this.projectConfigDir) {
2010
+ return null;
2011
+ }
2012
+ return loadProjectConfig(this.projectConfigDir);
2013
+ }
2014
+ /**
2015
+ * Load raw project-local config (without merging)
2016
+ */
2017
+ async loadRawProjectLocalConfig() {
2018
+ if (!this.projectConfigDir) {
2019
+ return null;
2020
+ }
2021
+ return loadProjectLocalConfig(this.projectConfigDir);
2022
+ }
1504
2023
  };
1505
2024
 
1506
2025
  // src/storage/CommandHistoryStore.ts
@@ -7086,7 +7605,7 @@ var planetVisibilityTool = {
7086
7605
 
7087
7606
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
7088
7607
  import { promises as fs7 } from "fs";
7089
- import { existsSync as existsSync2, statSync as statSync3 } from "fs";
7608
+ import { existsSync as existsSync3, statSync as statSync3 } from "fs";
7090
7609
  import path6 from "path";
7091
7610
  var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
7092
7611
  async function readFileContent(params) {
@@ -7097,7 +7616,7 @@ async function readFileContent(params) {
7097
7616
  if (!resolvedPath.startsWith(cwd)) {
7098
7617
  throw new Error(`Access denied: Cannot read files outside of current working directory`);
7099
7618
  }
7100
- if (!existsSync2(resolvedPath)) {
7619
+ if (!existsSync3(resolvedPath)) {
7101
7620
  throw new Error(`File not found: ${filePath}`);
7102
7621
  }
7103
7622
  const stats = statSync3(resolvedPath);
@@ -7183,7 +7702,7 @@ var fileReadTool = {
7183
7702
 
7184
7703
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
7185
7704
  import { promises as fs8 } from "fs";
7186
- import { existsSync as existsSync3 } from "fs";
7705
+ import { existsSync as existsSync4 } from "fs";
7187
7706
  import path7 from "path";
7188
7707
  async function createFile(params) {
7189
7708
  const { path: filePath, content, createDirectories = true } = params;
@@ -7193,7 +7712,7 @@ async function createFile(params) {
7193
7712
  if (!resolvedPath.startsWith(cwd)) {
7194
7713
  throw new Error(`Access denied: Cannot create files outside of current working directory`);
7195
7714
  }
7196
- const fileExists = existsSync3(resolvedPath);
7715
+ const fileExists = existsSync4(resolvedPath);
7197
7716
  const action = fileExists ? "overwritten" : "created";
7198
7717
  if (createDirectories) {
7199
7718
  const dir = path7.dirname(resolvedPath);
@@ -7211,7 +7730,7 @@ var createFileTool = {
7211
7730
  implementation: (context) => ({
7212
7731
  toolFn: async (value) => {
7213
7732
  const params = value;
7214
- const fileExists = existsSync3(path7.resolve(process.cwd(), path7.normalize(params.path)));
7733
+ const fileExists = existsSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
7215
7734
  context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
7216
7735
  path: params.path,
7217
7736
  size: params.content.length
@@ -7591,7 +8110,7 @@ var grepSearchTool = {
7591
8110
 
7592
8111
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
7593
8112
  import { promises as fs9 } from "fs";
7594
- import { existsSync as existsSync4, statSync as statSync6 } from "fs";
8113
+ import { existsSync as existsSync5, statSync as statSync6 } from "fs";
7595
8114
  import path10 from "path";
7596
8115
  async function deleteFile(params) {
7597
8116
  const { path: filePath, recursive = false } = params;
@@ -7601,7 +8120,7 @@ async function deleteFile(params) {
7601
8120
  if (!resolvedPath.startsWith(cwd)) {
7602
8121
  throw new Error(`Access denied: Cannot delete files outside of current working directory`);
7603
8122
  }
7604
- if (!existsSync4(resolvedPath)) {
8123
+ if (!existsSync5(resolvedPath)) {
7605
8124
  throw new Error(`File or directory not found: ${filePath}`);
7606
8125
  }
7607
8126
  const stats = statSync6(resolvedPath);
@@ -7625,7 +8144,7 @@ var deleteFileTool = {
7625
8144
  toolFn: async (value) => {
7626
8145
  const params = value;
7627
8146
  const resolvedPath = path10.resolve(process.cwd(), path10.normalize(params.path));
7628
- const isDirectory = existsSync4(resolvedPath) && statSync6(resolvedPath).isDirectory();
8147
+ const isDirectory = existsSync5(resolvedPath) && statSync6(resolvedPath).isDirectory();
7629
8148
  context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
7630
8149
  path: params.path,
7631
8150
  recursive: params.recursive
@@ -7915,10 +8434,10 @@ import jwt2 from "jsonwebtoken";
7915
8434
  // src/utils/diffPreview.ts
7916
8435
  import * as Diff from "diff";
7917
8436
  import { readFile } from "fs/promises";
7918
- import { existsSync as existsSync5 } from "fs";
8437
+ import { existsSync as existsSync6 } from "fs";
7919
8438
  async function generateFileDiffPreview(args) {
7920
8439
  try {
7921
- if (!existsSync5(args.path)) {
8440
+ if (!existsSync6(args.path)) {
7922
8441
  const lines2 = args.content.split("\n");
7923
8442
  const preview = lines2.slice(0, 20).join("\n");
7924
8443
  const hasMore = lines2.length > 20;
@@ -7948,7 +8467,7 @@ ${preview}${hasMore ? `
7948
8467
  }
7949
8468
  async function generateFileDeletePreview(args) {
7950
8469
  try {
7951
- if (!existsSync5(args.path)) {
8470
+ if (!existsSync6(args.path)) {
7952
8471
  return `[File does not exist: ${args.path}]`;
7953
8472
  }
7954
8473
  const stats = await import("fs/promises").then((fs11) => fs11.stat(args.path));
@@ -8001,7 +8520,7 @@ var CliLogger = class extends Logger {
8001
8520
  log(...args) {
8002
8521
  }
8003
8522
  };
8004
- function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext) {
8523
+ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore) {
8005
8524
  const originalFn = tool.toolFn;
8006
8525
  const toolName = tool.toolSchema.name;
8007
8526
  return {
@@ -8045,7 +8564,25 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
8045
8564
  throw new PermissionDeniedError(toolName, args);
8046
8565
  }
8047
8566
  if (response.action === "allow-always") {
8048
- permissionManager.trustTool(toolName);
8567
+ const canTrust = permissionManager.trustTool(toolName);
8568
+ if (canTrust) {
8569
+ const projectDir = configStore.getProjectConfigDir();
8570
+ if (projectDir) {
8571
+ try {
8572
+ await configStore.initProjectConfig();
8573
+ const existingLocal = await configStore.loadRawProjectLocalConfig() || {};
8574
+ const updatedLocal = {
8575
+ ...existingLocal,
8576
+ trustedTools: [...existingLocal.trustedTools || [], toolName]
8577
+ };
8578
+ await configStore.saveProjectLocalConfig(updatedLocal);
8579
+ } catch (error) {
8580
+ await configStore.trustTool(toolName);
8581
+ }
8582
+ } else {
8583
+ await configStore.trustTool(toolName);
8584
+ }
8585
+ }
8049
8586
  }
8050
8587
  const result = await originalFn(args);
8051
8588
  agentContext.observationQueue.push({ toolName, result });
@@ -8053,7 +8590,7 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
8053
8590
  }
8054
8591
  };
8055
8592
  }
8056
- function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, toolApiKeys) {
8593
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, toolApiKeys) {
8057
8594
  const logger2 = new CliLogger();
8058
8595
  const storage = new NoOpStorage();
8059
8596
  const user = {
@@ -8137,7 +8674,9 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
8137
8674
  toolConfig,
8138
8675
  model
8139
8676
  );
8140
- const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext));
8677
+ const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
8678
+ ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore)
8679
+ );
8141
8680
  return { tools: tools2, agentContext };
8142
8681
  }
8143
8682
 
@@ -8202,23 +8741,30 @@ function canTrustTool(toolName, customCategories) {
8202
8741
 
8203
8742
  // src/utils/PermissionManager.ts
8204
8743
  var PermissionManager = class {
8205
- constructor(trustedTools = [], customCategories) {
8744
+ constructor(trustedTools = [], customCategories, deniedTools) {
8206
8745
  this.trustedTools = /* @__PURE__ */ new Set();
8746
+ this.deniedTools = /* @__PURE__ */ new Set();
8207
8747
  this.trustedTools = new Set(trustedTools);
8208
8748
  this.customCategories = new Map(Object.entries(customCategories || {}));
8749
+ this.deniedTools = new Set(deniedTools || []);
8209
8750
  }
8210
8751
  /**
8211
8752
  * Check if a tool needs permission before execution
8212
8753
  *
8213
8754
  * A tool needs permission if:
8214
- * 1. It's not in the auto_approve category, AND
8215
- * 2. It's not in the trusted tools set
8755
+ * 1. It's in the denied tools list (from project config), OR
8756
+ * 2. It's not in the auto_approve category, AND
8757
+ * 3. It's not in the trusted tools set
8216
8758
  *
8217
8759
  * Note: prompt_always tools CANNOT be trusted, so they always need permission
8760
+ * Note: denied tools from project config ALWAYS need permission (cannot be overridden locally)
8218
8761
  */
8219
8762
  needsPermission(toolName) {
8220
8763
  const categoryMap = Object.fromEntries(this.customCategories);
8221
8764
  const category = getToolCategory(toolName, categoryMap);
8765
+ if (this.deniedTools.has(toolName)) {
8766
+ return true;
8767
+ }
8222
8768
  if (category === "auto_approve") {
8223
8769
  return false;
8224
8770
  }
@@ -8229,10 +8775,13 @@ var PermissionManager = class {
8229
8775
  }
8230
8776
  /**
8231
8777
  * Add a tool to the trusted list
8232
- * Returns false if the tool cannot be trusted (prompt_always category)
8778
+ * Returns false if the tool cannot be trusted (prompt_always category or denied by project)
8233
8779
  */
8234
8780
  trustTool(toolName) {
8235
8781
  const categoryMap = Object.fromEntries(this.customCategories);
8782
+ if (this.deniedTools.has(toolName)) {
8783
+ return false;
8784
+ }
8236
8785
  if (!canTrustTool(toolName, categoryMap)) {
8237
8786
  return false;
8238
8787
  }
@@ -8265,12 +8814,27 @@ var PermissionManager = class {
8265
8814
  return getToolCategory(toolName, categoryMap);
8266
8815
  }
8267
8816
  /**
8268
- * Check if a tool can be trusted (not in prompt_always category)
8817
+ * Check if a tool can be trusted (not in prompt_always category or denied by project)
8269
8818
  */
8270
8819
  canBeTrusted(toolName) {
8271
8820
  const categoryMap = Object.fromEntries(this.customCategories);
8821
+ if (this.deniedTools.has(toolName)) {
8822
+ return false;
8823
+ }
8272
8824
  return canTrustTool(toolName, categoryMap);
8273
8825
  }
8826
+ /**
8827
+ * Check if a tool is denied by project config
8828
+ */
8829
+ isDenied(toolName) {
8830
+ return this.deniedTools.has(toolName);
8831
+ }
8832
+ /**
8833
+ * Get list of denied tools
8834
+ */
8835
+ getDeniedTools() {
8836
+ return Array.from(this.deniedTools).sort();
8837
+ }
8274
8838
  /**
8275
8839
  * Clear all trusted tools
8276
8840
  */
@@ -8565,7 +9129,7 @@ var logger = Logger2.getInstance();
8565
9129
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
8566
9130
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
8567
9131
  import path12 from "path";
8568
- import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
9132
+ import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
8569
9133
  var MCPClient = class {
8570
9134
  // Note: This class handles MCP server communication with repository filtering
8571
9135
  mcp;
@@ -8601,7 +9165,7 @@ var MCPClient = class {
8601
9165
  path12.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
8602
9166
  path12.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
8603
9167
  ];
8604
- const serverScriptPath = candidatePaths.find((p) => existsSync6(p));
9168
+ const serverScriptPath = candidatePaths.find((p) => existsSync7(p));
8605
9169
  if (!serverScriptPath) {
8606
9170
  const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
8607
9171
  console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
@@ -9183,7 +9747,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
9183
9747
  // package.json
9184
9748
  var package_default = {
9185
9749
  name: "@bike4mind/cli",
9186
- version: "0.2.3-feat-cli-clear-command.17058+9fe45e4bf",
9750
+ version: "0.2.3-feat-cli-project-scoped-config.17063+4e29d03e0",
9187
9751
  type: "module",
9188
9752
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
9189
9753
  license: "UNLICENSED",
@@ -9285,10 +9849,10 @@ var package_default = {
9285
9849
  },
9286
9850
  devDependencies: {
9287
9851
  "@bike4mind/agents": "0.1.0",
9288
- "@bike4mind/common": "2.37.1-feat-cli-clear-command.17058+9fe45e4bf",
9289
- "@bike4mind/mcp": "1.19.5-feat-cli-clear-command.17058+9fe45e4bf",
9290
- "@bike4mind/services": "2.31.1-feat-cli-clear-command.17058+9fe45e4bf",
9291
- "@bike4mind/utils": "2.0.4-feat-cli-clear-command.17058+9fe45e4bf",
9852
+ "@bike4mind/common": "2.37.1-feat-cli-project-scoped-config.17063+4e29d03e0",
9853
+ "@bike4mind/mcp": "1.19.5-feat-cli-project-scoped-config.17063+4e29d03e0",
9854
+ "@bike4mind/services": "2.31.1-feat-cli-project-scoped-config.17063+4e29d03e0",
9855
+ "@bike4mind/utils": "2.0.4-feat-cli-project-scoped-config.17063+4e29d03e0",
9292
9856
  "@types/diff": "^5.0.9",
9293
9857
  "@types/jsonwebtoken": "^9.0.4",
9294
9858
  "@types/node": "^22.9.0",
@@ -9300,7 +9864,7 @@ var package_default = {
9300
9864
  typescript: "^5.9.3",
9301
9865
  vitest: "^3.2.4"
9302
9866
  },
9303
- gitHead: "9fe45e4bfb34840cd5b5cb738bb276b0520e89c6"
9867
+ gitHead: "4e29d03e04acb4714606940d6557b9601325ed73"
9304
9868
  };
9305
9869
 
9306
9870
  // src/config/constants.ts
@@ -9321,7 +9885,7 @@ var EXIT_TIMEOUT_MS = 2e3;
9321
9885
  var usageCache = null;
9322
9886
  function CliApp() {
9323
9887
  const { exit } = useApp();
9324
- const [state, setState] = useState3({
9888
+ const [state, setState] = useState4({
9325
9889
  session: null,
9326
9890
  sessionStore: new SessionStore(),
9327
9891
  configStore: new ConfigStore(),
@@ -9329,13 +9893,15 @@ function CliApp() {
9329
9893
  agent: null,
9330
9894
  mcpManager: null,
9331
9895
  permissionManager: null,
9332
- permissionPrompt: null
9896
+ permissionPrompt: null,
9897
+ trustLocationSelector: null
9333
9898
  });
9334
- const [isInitialized, setIsInitialized] = useState3(false);
9335
- const [initError, setInitError] = useState3(null);
9336
- const [commandHistory, setCommandHistory] = useState3([]);
9899
+ const [isInitialized, setIsInitialized] = useState4(false);
9900
+ const [initError, setInitError] = useState4(null);
9901
+ const [commandHistory, setCommandHistory] = useState4([]);
9337
9902
  const setStoreSession = useCliStore((state2) => state2.setSession);
9338
9903
  const setPermissionPrompt = useCliStore((state2) => state2.setPermissionPrompt);
9904
+ const setShowConfigEditor = useCliStore((state2) => state2.setShowConfigEditor);
9339
9905
  const setExitRequested = useCliStore((state2) => state2.setExitRequested);
9340
9906
  const performCleanup = useCallback(async () => {
9341
9907
  const cleanupTasks = [];
@@ -9365,7 +9931,7 @@ function CliApp() {
9365
9931
  process.exit(0);
9366
9932
  }, 100);
9367
9933
  }, [state.session, state.sessionStore, state.mcpManager, state.agent]);
9368
- useInput2((input, key) => {
9934
+ useInput3((input, key) => {
9369
9935
  if (key.ctrl && input === "c") {
9370
9936
  const now = Date.now();
9371
9937
  if (exitTimestamp && now - exitTimestamp < EXIT_TIMEOUT_MS) {
@@ -9490,7 +10056,13 @@ function CliApp() {
9490
10056
  debug: () => {
9491
10057
  }
9492
10058
  };
9493
- const permissionManager = new PermissionManager(config.trustedTools || []);
10059
+ const permissionManager = new PermissionManager(
10060
+ config.trustedTools || [],
10061
+ void 0,
10062
+ // customCategories (not used yet)
10063
+ config.tools.disabled || []
10064
+ // denied tools from project config
10065
+ );
9494
10066
  const promptFn = (toolName, args, preview) => {
9495
10067
  return new Promise((resolve3) => {
9496
10068
  const canBeTrusted = permissionManager.canBeTrusted(toolName);
@@ -9518,6 +10090,7 @@ function CliApp() {
9518
10090
  permissionManager,
9519
10091
  promptFn,
9520
10092
  agentContext,
10093
+ state.configStore,
9521
10094
  config.toolApiKeys
9522
10095
  );
9523
10096
  console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools.length} B4M tool(s)`);
@@ -9562,13 +10135,14 @@ EXAMPLES:
9562
10135
  - "what packages installed?" \u2192 glob_files "**/package.json" \u2192 file_read
9563
10136
 
9564
10137
  Remember: Use context from previous messages to understand follow-up questions.`;
10138
+ const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
9565
10139
  const agent = new ReActAgent({
9566
10140
  userId: config.userId,
9567
10141
  logger: silentLogger,
9568
10142
  llm,
9569
10143
  model: modelInfo.id,
9570
10144
  tools: allTools,
9571
- maxIterations: 10,
10145
+ maxIterations,
9572
10146
  maxTokens: config.preferences.maxTokens,
9573
10147
  temperature: config.preferences.temperature,
9574
10148
  systemPrompt: cliSystemPrompt
@@ -9680,7 +10254,6 @@ Remember: Use context from previous messages to understand follow-up questions.`
9680
10254
  Available commands:
9681
10255
  /help - Show this help message
9682
10256
  /exit - Exit the CLI
9683
- /clear - Start a new session
9684
10257
  /login - Authenticate with your B4M account
9685
10258
  /logout - Clear authentication and sign out
9686
10259
  /whoami - Show current authenticated user
@@ -9698,6 +10271,9 @@ Tool Permissions:
9698
10271
  /trust <tool-name> - Trust a tool (won't ask permission again)
9699
10272
  /untrust <tool-name> - Remove tool from trusted list
9700
10273
  /trusted - List all trusted tools
10274
+
10275
+ Project Configuration:
10276
+ /project-config - Show merged project configuration
9701
10277
  `);
9702
10278
  break;
9703
10279
  case "exit":
@@ -9726,20 +10302,7 @@ Tool Permissions:
9726
10302
  console.log("");
9727
10303
  break;
9728
10304
  case "config": {
9729
- const config = await state.configStore.get();
9730
- console.log("\n\u2699\uFE0F Configuration:\n");
9731
- console.log(`Default Model: ${config.defaultModel}`);
9732
- console.log(`Max Tokens: ${config.preferences.maxTokens}`);
9733
- console.log(`Temperature: ${config.preferences.temperature}`);
9734
- const apiUrl = getApiUrl(config.apiConfig);
9735
- const apiType = getEnvironmentName(config.apiConfig);
9736
- console.log(`API: ${apiType} (${apiUrl})`);
9737
- console.log("");
9738
- console.log("Related commands:");
9739
- console.log(" /api-info - Show detailed API configuration");
9740
- console.log(" /set-api - Connect to self-hosted instance");
9741
- console.log(" /reset-api - Reset to Bike4Mind main service");
9742
- console.log("");
10305
+ setShowConfigEditor(true);
9743
10306
  break;
9744
10307
  }
9745
10308
  case "set-api": {
@@ -9789,7 +10352,7 @@ Tool Permissions:
9789
10352
  console.log("");
9790
10353
  break;
9791
10354
  }
9792
- case "trust":
10355
+ case "trust": {
9793
10356
  if (!state.permissionManager) {
9794
10357
  console.log("Permission manager not initialized");
9795
10358
  return;
@@ -9797,16 +10360,85 @@ Tool Permissions:
9797
10360
  const toolToTrust = args[0];
9798
10361
  if (!toolToTrust) {
9799
10362
  console.log("Usage: /trust <tool-name>");
10363
+ console.log("");
10364
+ console.log("Example:");
10365
+ console.log(" /trust file_read - Opens interactive location selector");
10366
+ console.log("");
9800
10367
  return;
9801
10368
  }
9802
- const trusted = state.permissionManager.trustTool(toolToTrust);
9803
- if (trusted) {
9804
- await state.configStore.trustTool(toolToTrust);
9805
- console.log(`\u2705 Tool '${toolToTrust}' is now trusted and won't require permission`);
9806
- } else {
9807
- console.log(`\u274C Tool '${toolToTrust}' cannot be trusted (it's in the 'prompt_always' category)`);
10369
+ const canTrust = state.permissionManager.trustTool(toolToTrust);
10370
+ if (!canTrust) {
10371
+ if (state.permissionManager.isDenied(toolToTrust)) {
10372
+ console.log(`\u274C Tool '${toolToTrust}' is denied by project configuration and cannot be trusted`);
10373
+ } else {
10374
+ console.log(`\u274C Tool '${toolToTrust}' cannot be trusted (it's in the 'prompt_always' category)`);
10375
+ }
10376
+ return;
9808
10377
  }
10378
+ state.permissionManager.untrustTool(toolToTrust);
10379
+ const handleLocationSelect = async (location) => {
10380
+ setState((prev) => ({ ...prev, trustLocationSelector: null }));
10381
+ if (!location) {
10382
+ console.log("\u274C Trust operation cancelled");
10383
+ return;
10384
+ }
10385
+ const projectDir = state.configStore.getProjectConfigDir();
10386
+ switch (location) {
10387
+ case "local": {
10388
+ if (!projectDir) {
10389
+ console.log('\u274C No project found. Use "global" to save to ~/.bike4mind/config.json');
10390
+ return;
10391
+ }
10392
+ await state.configStore.initProjectConfig();
10393
+ const existingLocal = await state.configStore.loadRawProjectLocalConfig() || {};
10394
+ const updatedLocal = {
10395
+ ...existingLocal,
10396
+ trustedTools: [...existingLocal.trustedTools || [], toolToTrust]
10397
+ };
10398
+ await state.configStore.saveProjectLocalConfig(updatedLocal);
10399
+ state.permissionManager?.trustTool(toolToTrust);
10400
+ console.log(`\u2705 Tool '${toolToTrust}' trusted for this project only`);
10401
+ console.log(` Saved in: ${projectDir}/.bike4mind/local.json`);
10402
+ break;
10403
+ }
10404
+ case "project": {
10405
+ if (!projectDir) {
10406
+ console.log('\u274C No project found. Use "global" to save to ~/.bike4mind/config.json');
10407
+ return;
10408
+ }
10409
+ const existingProject = await state.configStore.loadRawProjectConfig() || {};
10410
+ const updatedProject = {
10411
+ ...existingProject,
10412
+ tools: {
10413
+ ...existingProject.tools,
10414
+ enabled: [...existingProject.tools?.enabled || [], toolToTrust]
10415
+ }
10416
+ };
10417
+ await state.configStore.saveProjectConfig(updatedProject);
10418
+ state.permissionManager?.trustTool(toolToTrust);
10419
+ console.log(`\u2705 Tool '${toolToTrust}' trusted for entire team`);
10420
+ console.log(` Saved in: ${projectDir}/.bike4mind/config.json`);
10421
+ console.log(" \u26A0\uFE0F Remember to commit this file to share with your team!");
10422
+ break;
10423
+ }
10424
+ case "global": {
10425
+ await state.configStore.trustTool(toolToTrust);
10426
+ state.permissionManager?.trustTool(toolToTrust);
10427
+ console.log(`\u2705 Tool '${toolToTrust}' trusted globally (all projects)`);
10428
+ console.log(" Saved in: ~/.bike4mind/config.json");
10429
+ break;
10430
+ }
10431
+ }
10432
+ };
10433
+ setState((prev) => ({
10434
+ ...prev,
10435
+ trustLocationSelector: {
10436
+ toolName: toolToTrust,
10437
+ resolve: handleLocationSelect
10438
+ }
10439
+ }));
9809
10440
  break;
10441
+ }
9810
10442
  case "untrust":
9811
10443
  if (!state.permissionManager) {
9812
10444
  console.log("Permission manager not initialized");
@@ -9855,35 +10487,6 @@ Tool Permissions:
9855
10487
  console.log(`Expires: ${new Date(authTokens.expiresAt).toLocaleString()}`);
9856
10488
  console.log("");
9857
10489
  break;
9858
- case "clear":
9859
- case "new": {
9860
- console.clear();
9861
- const model = state.session?.model || state.config?.defaultModel || "claude-sonnet";
9862
- const newSession = {
9863
- id: uuidv49(),
9864
- name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
9865
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
9866
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9867
- model,
9868
- messages: [],
9869
- metadata: {
9870
- totalTokens: 0,
9871
- totalCost: 0,
9872
- toolCallCount: 0
9873
- }
9874
- };
9875
- await logger.initialize(newSession.id);
9876
- logger.debug("=== New Session Started via /clear ===");
9877
- setState((prev) => ({ ...prev, session: newSession }));
9878
- setStoreSession(newSession);
9879
- useCliStore.getState().clearAgentSteps();
9880
- usageCache = null;
9881
- console.log("New session started.");
9882
- console.log(`
9883
- \u{1F4DD} Session: ${newSession.name} | \u{1F916} Model: ${newSession.model} | \u{1F4CA} Tokens: 0
9884
- `);
9885
- break;
9886
- }
9887
10490
  case "usage": {
9888
10491
  const usageAuthTokens = await state.configStore.getAuthTokens();
9889
10492
  if (!usageAuthTokens || new Date(usageAuthTokens.expiresAt) <= /* @__PURE__ */ new Date()) {
@@ -9978,6 +10581,49 @@ Usage by Model (Last ${USAGE_DAYS} days):`);
9978
10581
  console.log(`
9979
10582
  No usage data available for the last ${USAGE_DAYS} days.`);
9980
10583
  }
10584
+ break;
10585
+ }
10586
+ case "project-config": {
10587
+ const projectDir = state.configStore.getProjectConfigDir();
10588
+ const config = await state.configStore.get();
10589
+ console.log("\n\u{1F4C1} Project Configuration:\n");
10590
+ if (projectDir) {
10591
+ console.log(`Project Directory: ${projectDir}/.bike4mind/`);
10592
+ console.log("");
10593
+ const projectConfig = await state.configStore.loadRawProjectConfig();
10594
+ const localConfig = await state.configStore.loadRawProjectLocalConfig();
10595
+ if (projectConfig) {
10596
+ console.log("Team Config (.bike4mind/config.json):");
10597
+ if (projectConfig.defaultModel) {
10598
+ console.log(` Default Model: ${projectConfig.defaultModel}`);
10599
+ }
10600
+ if (projectConfig.tools?.denied && projectConfig.tools.denied.length > 0) {
10601
+ console.log(` Denied Tools: ${projectConfig.tools.denied.join(", ")}`);
10602
+ }
10603
+ if (projectConfig.mcpServers && projectConfig.mcpServers.length > 0) {
10604
+ console.log(` MCP Servers: ${projectConfig.mcpServers.map((s) => s.name).join(", ")}`);
10605
+ }
10606
+ console.log("");
10607
+ }
10608
+ if (localConfig) {
10609
+ console.log("Local Config (.bike4mind/local.json):");
10610
+ if (localConfig.trustedTools && localConfig.trustedTools.length > 0) {
10611
+ console.log(` Trusted Tools: ${localConfig.trustedTools.join(", ")}`);
10612
+ }
10613
+ if (localConfig.mcpServers && localConfig.mcpServers.length > 0) {
10614
+ console.log(` MCP Servers: ${localConfig.mcpServers.map((s) => s.name).join(", ")}`);
10615
+ }
10616
+ console.log("");
10617
+ }
10618
+ console.log("Merged Configuration (what you're using):");
10619
+ console.log(` Default Model: ${config.defaultModel}`);
10620
+ console.log(` Trusted Tools: ${config.trustedTools?.length || 0} tools`);
10621
+ console.log(` Denied Tools: ${config.tools.disabled.length} tools`);
10622
+ console.log(` MCP Servers: ${config.mcpServers.length} servers`);
10623
+ } else {
10624
+ console.log("No project configuration found.");
10625
+ console.log("Tip: Create .bike4mind/config.json in your project root for team-wide settings.");
10626
+ }
9981
10627
  console.log("");
9982
10628
  break;
9983
10629
  }
@@ -9986,12 +10632,36 @@ No usage data available for the last ${USAGE_DAYS} days.`);
9986
10632
  console.log("Type /help for available commands");
9987
10633
  }
9988
10634
  };
10635
+ const handleSaveConfig = async (updatedConfig) => {
10636
+ await state.configStore.save(updatedConfig);
10637
+ setState((prev) => ({ ...prev, config: updatedConfig }));
10638
+ };
9989
10639
  if (initError) {
9990
- return /* @__PURE__ */ React11.createElement(Box11, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, "\u274C Initialization Error"), /* @__PURE__ */ React11.createElement(Text10, null, initError), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "\n", "Tip: Run /config to set up your API keys"));
10640
+ return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, "\u274C Initialization Error"), /* @__PURE__ */ React13.createElement(Text12, null, initError), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "\n", "Tip: Run /config to set up your API keys"));
10641
+ }
10642
+ if (state.trustLocationSelector) {
10643
+ const projectDir = state.configStore.getProjectConfigDir();
10644
+ const inProject = projectDir !== null;
10645
+ return /* @__PURE__ */ React13.createElement(
10646
+ TrustLocationSelector,
10647
+ {
10648
+ inProject,
10649
+ onSelect: (location) => {
10650
+ if (state.trustLocationSelector) {
10651
+ state.trustLocationSelector.resolve(location);
10652
+ }
10653
+ },
10654
+ onCancel: () => {
10655
+ if (state.trustLocationSelector) {
10656
+ state.trustLocationSelector.resolve(null);
10657
+ }
10658
+ }
10659
+ }
10660
+ );
9991
10661
  }
9992
10662
  if (state.showLoginFlow) {
9993
10663
  const loginApiUrl = getApiUrl(state.config?.apiConfig);
9994
- return /* @__PURE__ */ React11.createElement(
10664
+ return /* @__PURE__ */ React13.createElement(
9995
10665
  LoginFlow,
9996
10666
  {
9997
10667
  apiUrl: loginApiUrl,
@@ -10014,14 +10684,16 @@ No usage data available for the last ${USAGE_DAYS} days.`);
10014
10684
  );
10015
10685
  }
10016
10686
  if (!isInitialized) {
10017
- return /* @__PURE__ */ React11.createElement(Box11, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React11.createElement(Text10, null, "\u{1F680} Initializing..."));
10687
+ return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "\u{1F680} Initializing..."));
10018
10688
  }
10019
- return /* @__PURE__ */ React11.createElement(
10689
+ return /* @__PURE__ */ React13.createElement(
10020
10690
  App,
10021
10691
  {
10022
10692
  onMessage: handleMessage,
10023
10693
  onCommand: handleCommand,
10024
10694
  commandHistory,
10695
+ config: state.config,
10696
+ onSaveConfig: handleSaveConfig,
10025
10697
  onPermissionResponse: (response) => {
10026
10698
  if (state.permissionPrompt) {
10027
10699
  state.permissionPrompt.resolve({ action: response });
@@ -10047,6 +10719,6 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
10047
10719
  if (isDevMode) {
10048
10720
  console.log("\u{1F527} Running in development mode (using TypeScript source)\n");
10049
10721
  }
10050
- render(/* @__PURE__ */ React11.createElement(CliApp, null), {
10722
+ render(/* @__PURE__ */ React13.createElement(CliApp, null), {
10051
10723
  exitOnCtrlC: false
10052
10724
  });