@bike4mind/cli 0.2.3-feat-cli-interactive-config-editor.17058 → 0.2.3-feat-cli-project-scoped-config.17064

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 +591 -85
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -79,8 +79,8 @@ import {
79
79
  } from "./chunk-PDX44BCA.js";
80
80
 
81
81
  // src/index.tsx
82
- import 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 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
@@ -1094,9 +1094,42 @@ var MessageList = React10.memo(
1094
1094
  }
1095
1095
  );
1096
1096
 
1097
- // src/components/LoginFlow.tsx
1098
- import React11, { useState as useState3, useEffect as useEffect2 } from "react";
1097
+ // src/components/TrustLocationSelector.tsx
1098
+ import React11 from "react";
1099
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
+
1130
+ // src/components/LoginFlow.tsx
1131
+ import React12, { useState as useState3, useEffect as useEffect2 } from "react";
1132
+ import { Box as Box12, Text as Text11 } from "ink";
1100
1133
  import Spinner2 from "ink-spinner";
1101
1134
  import jwt from "jsonwebtoken";
1102
1135
  import open from "open";
@@ -1259,15 +1292,15 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
1259
1292
  }
1260
1293
  }, [deviceFlow, status]);
1261
1294
  if (status === "initiating") {
1262
- 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...")));
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...")));
1263
1296
  }
1264
1297
  if (status === "error") {
1265
- 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)));
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)));
1266
1299
  }
1267
1300
  if (status === "success") {
1268
- 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.")));
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.")));
1269
1302
  }
1270
- 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")));
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")));
1271
1304
  }
1272
1305
 
1273
1306
  // src/storage/SessionStore.ts
@@ -1389,7 +1422,7 @@ var SessionStore = class {
1389
1422
  };
1390
1423
 
1391
1424
  // src/storage/ConfigStore.ts
1392
- import { promises as fs4 } from "fs";
1425
+ import { promises as fs4, existsSync as existsSync2 } from "fs";
1393
1426
  import path4 from "path";
1394
1427
  import { homedir as homedir2 } from "os";
1395
1428
  import { v4 as uuidv4 } from "uuid";
@@ -1437,6 +1470,53 @@ var CliConfigSchema = z.object({
1437
1470
  }),
1438
1471
  trustedTools: z.array(z.string()).optional().default([])
1439
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
+ });
1440
1520
  var DEFAULT_CONFIG = {
1441
1521
  version: "0.1.0",
1442
1522
  userId: uuidv4(),
@@ -1463,9 +1543,124 @@ var DEFAULT_CONFIG = {
1463
1543
  trustedTools: []
1464
1544
  // No tools trusted by default
1465
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: [...merged.tools.enabled || [], ...project.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
+ const trustedSet = /* @__PURE__ */ new Set([...merged.trustedTools || [], ...local.trustedTools]);
1640
+ merged.trustedTools = Array.from(trustedSet);
1641
+ }
1642
+ if (local.toolApiKeys) {
1643
+ merged.toolApiKeys = {
1644
+ ...merged.toolApiKeys,
1645
+ ...local.toolApiKeys
1646
+ };
1647
+ }
1648
+ if (local.preferences) {
1649
+ merged.preferences = {
1650
+ ...merged.preferences,
1651
+ ...local.preferences
1652
+ };
1653
+ }
1654
+ if (local.mcpServers) {
1655
+ merged.mcpServers = mergeMcpServers(merged.mcpServers, local.mcpServers);
1656
+ }
1657
+ }
1658
+ return merged;
1659
+ }
1466
1660
  var ConfigStore = class {
1467
1661
  constructor(configPath) {
1468
1662
  this.config = null;
1663
+ this.projectConfigDir = null;
1469
1664
  this.configPath = configPath || path4.join(homedir2(), ".bike4mind", "config.json");
1470
1665
  }
1471
1666
  /**
@@ -1482,51 +1677,76 @@ var ConfigStore = class {
1482
1677
  }
1483
1678
  /**
1484
1679
  * Load configuration from disk with Zod validation
1680
+ * Merges global → project → local configs
1485
1681
  */
1486
1682
  async load() {
1487
1683
  if (this.config) {
1488
1684
  return this.config;
1489
1685
  }
1490
1686
  try {
1687
+ let globalConfig;
1491
1688
  try {
1492
- const stats = await fs4.stat(this.configPath);
1493
- const mode = stats.mode & 511;
1494
- if (mode !== 384) {
1495
- console.warn(`\u26A0\uFE0F Config file has insecure permissions (${mode.toString(8)}). Setting to 0600...`);
1496
- await fs4.chmod(this.configPath, 384);
1689
+ try {
1690
+ const stats = await fs4.stat(this.configPath);
1691
+ const mode = stats.mode & 511;
1692
+ if (mode !== 384) {
1693
+ console.warn(`\u26A0\uFE0F Config file has insecure permissions (${mode.toString(8)}). Setting to 0600...`);
1694
+ await fs4.chmod(this.configPath, 384);
1695
+ }
1696
+ } catch (statError) {
1497
1697
  }
1498
- } catch (statError) {
1499
- }
1500
- const data = await fs4.readFile(this.configPath, "utf-8");
1501
- const rawConfig = JSON.parse(data);
1502
- if (rawConfig.apiConfig && "environment" in rawConfig.apiConfig) {
1503
- const oldApiConfig = rawConfig.apiConfig;
1504
- if (oldApiConfig.environment === "custom" && oldApiConfig.customUrl) {
1505
- rawConfig.apiConfig = { customUrl: oldApiConfig.customUrl };
1698
+ const data = await fs4.readFile(this.configPath, "utf-8");
1699
+ const rawConfig = JSON.parse(data);
1700
+ if (rawConfig.apiConfig && "environment" in rawConfig.apiConfig) {
1701
+ const oldApiConfig = rawConfig.apiConfig;
1702
+ if (oldApiConfig.environment === "custom" && oldApiConfig.customUrl) {
1703
+ rawConfig.apiConfig = { customUrl: oldApiConfig.customUrl };
1704
+ } else {
1705
+ rawConfig.apiConfig = {};
1706
+ }
1707
+ }
1708
+ const validated = CliConfigSchema.parse(rawConfig);
1709
+ globalConfig = {
1710
+ ...DEFAULT_CONFIG,
1711
+ ...validated,
1712
+ auth: validated.auth,
1713
+ // Explicitly preserve auth field
1714
+ preferences: {
1715
+ ...DEFAULT_CONFIG.preferences,
1716
+ ...validated.preferences
1717
+ },
1718
+ tools: {
1719
+ ...DEFAULT_CONFIG.tools,
1720
+ ...validated.tools
1721
+ },
1722
+ toolApiKeys: {
1723
+ ...DEFAULT_CONFIG.toolApiKeys,
1724
+ ...validated.toolApiKeys || {}
1725
+ },
1726
+ trustedTools: validated.trustedTools || []
1727
+ };
1728
+ } catch (error) {
1729
+ if (error.code === "ENOENT") {
1730
+ globalConfig = { ...DEFAULT_CONFIG };
1731
+ } else if (error instanceof z.ZodError) {
1732
+ console.error("Global config validation error:", error.errors);
1733
+ console.error("Using default configuration");
1734
+ globalConfig = { ...DEFAULT_CONFIG };
1506
1735
  } else {
1507
- rawConfig.apiConfig = {};
1736
+ throw error;
1508
1737
  }
1509
1738
  }
1510
- const validated = CliConfigSchema.parse(rawConfig);
1511
- this.config = {
1512
- ...DEFAULT_CONFIG,
1513
- ...validated,
1514
- auth: validated.auth,
1515
- // Explicitly preserve auth field
1516
- preferences: {
1517
- ...DEFAULT_CONFIG.preferences,
1518
- ...validated.preferences
1519
- },
1520
- tools: {
1521
- ...DEFAULT_CONFIG.tools,
1522
- ...validated.tools
1523
- },
1524
- toolApiKeys: {
1525
- ...DEFAULT_CONFIG.toolApiKeys,
1526
- ...validated.toolApiKeys || {}
1527
- },
1528
- trustedTools: validated.trustedTools || []
1529
- };
1739
+ this.projectConfigDir = findProjectConfigDir();
1740
+ let projectConfig = null;
1741
+ let projectLocalConfig = null;
1742
+ if (this.projectConfigDir) {
1743
+ projectConfig = await loadProjectConfig(this.projectConfigDir);
1744
+ projectLocalConfig = await loadProjectLocalConfig(this.projectConfigDir);
1745
+ if (projectConfig) {
1746
+ console.log(`\u{1F4C1} Project config loaded from: ${this.projectConfigDir}/.bike4mind/`);
1747
+ }
1748
+ }
1749
+ this.config = mergeConfigs(globalConfig, projectConfig, projectLocalConfig);
1530
1750
  return this.config;
1531
1751
  } catch (error) {
1532
1752
  if (error.code === "ENOENT") {
@@ -1715,6 +1935,92 @@ var ConfigStore = class {
1715
1935
  }
1716
1936
  await this.save(config);
1717
1937
  }
1938
+ /**
1939
+ * Get project config directory (if any)
1940
+ */
1941
+ getProjectConfigDir() {
1942
+ return this.projectConfigDir;
1943
+ }
1944
+ /**
1945
+ * Initialize project config directory
1946
+ * Creates .bike4mind/ directory and ensures local.json is gitignored
1947
+ * Does NOT auto-create config.json (user creates that manually)
1948
+ */
1949
+ async initProjectConfig() {
1950
+ const projectDir = this.projectConfigDir || findProjectConfigDir();
1951
+ if (!projectDir) {
1952
+ return;
1953
+ }
1954
+ const configDir = path4.join(projectDir, ".bike4mind");
1955
+ await fs4.mkdir(configDir, { recursive: true });
1956
+ await this.ensureGitignore(projectDir);
1957
+ }
1958
+ /**
1959
+ * Ensure .gitignore includes .bike4mind/local.json
1960
+ */
1961
+ async ensureGitignore(projectDir) {
1962
+ const gitignorePath = path4.join(projectDir, ".gitignore");
1963
+ const entryToAdd = ".bike4mind/local.json";
1964
+ try {
1965
+ let gitignoreContent = "";
1966
+ try {
1967
+ gitignoreContent = await fs4.readFile(gitignorePath, "utf-8");
1968
+ } catch {
1969
+ }
1970
+ if (gitignoreContent.includes(entryToAdd)) {
1971
+ return;
1972
+ }
1973
+ const newContent = gitignoreContent.trim() + (gitignoreContent ? "\n" : "") + `
1974
+ # Bike4Mind local config (developer-specific)
1975
+ ${entryToAdd}
1976
+ `;
1977
+ await fs4.writeFile(gitignorePath, newContent, "utf-8");
1978
+ console.log(`\u2705 Added ${entryToAdd} to .gitignore`);
1979
+ } catch (error) {
1980
+ console.warn(`\u26A0\uFE0F Failed to update .gitignore:`, error);
1981
+ }
1982
+ }
1983
+ /**
1984
+ * Save project config to .bike4mind/config.json
1985
+ */
1986
+ async saveProjectConfig(config, projectDir) {
1987
+ const targetDir = projectDir || this.projectConfigDir || process.cwd();
1988
+ const configPath = path4.join(targetDir, ".bike4mind", "config.json");
1989
+ await fs4.mkdir(path4.dirname(configPath), { recursive: true });
1990
+ const validated = ProjectConfigSchema.parse(config);
1991
+ await fs4.writeFile(configPath, JSON.stringify(validated, null, 2), "utf-8");
1992
+ console.log(`\u2705 Saved project config to: ${configPath}`);
1993
+ }
1994
+ /**
1995
+ * Save project-local config to .bike4mind/local.json
1996
+ */
1997
+ async saveProjectLocalConfig(config, projectDir) {
1998
+ const targetDir = projectDir || this.projectConfigDir || process.cwd();
1999
+ const configPath = path4.join(targetDir, ".bike4mind", "local.json");
2000
+ await fs4.mkdir(path4.dirname(configPath), { recursive: true });
2001
+ const validated = ProjectLocalConfigSchema.parse(config);
2002
+ await fs4.writeFile(configPath, JSON.stringify(validated, null, 2), "utf-8");
2003
+ await fs4.chmod(configPath, 384);
2004
+ console.log(`\u2705 Saved project-local config to: ${configPath}`);
2005
+ }
2006
+ /**
2007
+ * Load raw project config (without merging)
2008
+ */
2009
+ async loadRawProjectConfig() {
2010
+ if (!this.projectConfigDir) {
2011
+ return null;
2012
+ }
2013
+ return loadProjectConfig(this.projectConfigDir);
2014
+ }
2015
+ /**
2016
+ * Load raw project-local config (without merging)
2017
+ */
2018
+ async loadRawProjectLocalConfig() {
2019
+ if (!this.projectConfigDir) {
2020
+ return null;
2021
+ }
2022
+ return loadProjectLocalConfig(this.projectConfigDir);
2023
+ }
1718
2024
  };
1719
2025
 
1720
2026
  // src/storage/CommandHistoryStore.ts
@@ -7300,7 +7606,7 @@ var planetVisibilityTool = {
7300
7606
 
7301
7607
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
7302
7608
  import { promises as fs7 } from "fs";
7303
- import { existsSync as existsSync2, statSync as statSync3 } from "fs";
7609
+ import { existsSync as existsSync3, statSync as statSync3 } from "fs";
7304
7610
  import path6 from "path";
7305
7611
  var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
7306
7612
  async function readFileContent(params) {
@@ -7311,7 +7617,7 @@ async function readFileContent(params) {
7311
7617
  if (!resolvedPath.startsWith(cwd)) {
7312
7618
  throw new Error(`Access denied: Cannot read files outside of current working directory`);
7313
7619
  }
7314
- if (!existsSync2(resolvedPath)) {
7620
+ if (!existsSync3(resolvedPath)) {
7315
7621
  throw new Error(`File not found: ${filePath}`);
7316
7622
  }
7317
7623
  const stats = statSync3(resolvedPath);
@@ -7397,7 +7703,7 @@ var fileReadTool = {
7397
7703
 
7398
7704
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
7399
7705
  import { promises as fs8 } from "fs";
7400
- import { existsSync as existsSync3 } from "fs";
7706
+ import { existsSync as existsSync4 } from "fs";
7401
7707
  import path7 from "path";
7402
7708
  async function createFile(params) {
7403
7709
  const { path: filePath, content, createDirectories = true } = params;
@@ -7407,7 +7713,7 @@ async function createFile(params) {
7407
7713
  if (!resolvedPath.startsWith(cwd)) {
7408
7714
  throw new Error(`Access denied: Cannot create files outside of current working directory`);
7409
7715
  }
7410
- const fileExists = existsSync3(resolvedPath);
7716
+ const fileExists = existsSync4(resolvedPath);
7411
7717
  const action = fileExists ? "overwritten" : "created";
7412
7718
  if (createDirectories) {
7413
7719
  const dir = path7.dirname(resolvedPath);
@@ -7425,7 +7731,7 @@ var createFileTool = {
7425
7731
  implementation: (context) => ({
7426
7732
  toolFn: async (value) => {
7427
7733
  const params = value;
7428
- const fileExists = existsSync3(path7.resolve(process.cwd(), path7.normalize(params.path)));
7734
+ const fileExists = existsSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
7429
7735
  context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
7430
7736
  path: params.path,
7431
7737
  size: params.content.length
@@ -7805,7 +8111,7 @@ var grepSearchTool = {
7805
8111
 
7806
8112
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
7807
8113
  import { promises as fs9 } from "fs";
7808
- import { existsSync as existsSync4, statSync as statSync6 } from "fs";
8114
+ import { existsSync as existsSync5, statSync as statSync6 } from "fs";
7809
8115
  import path10 from "path";
7810
8116
  async function deleteFile(params) {
7811
8117
  const { path: filePath, recursive = false } = params;
@@ -7815,7 +8121,7 @@ async function deleteFile(params) {
7815
8121
  if (!resolvedPath.startsWith(cwd)) {
7816
8122
  throw new Error(`Access denied: Cannot delete files outside of current working directory`);
7817
8123
  }
7818
- if (!existsSync4(resolvedPath)) {
8124
+ if (!existsSync5(resolvedPath)) {
7819
8125
  throw new Error(`File or directory not found: ${filePath}`);
7820
8126
  }
7821
8127
  const stats = statSync6(resolvedPath);
@@ -7839,7 +8145,7 @@ var deleteFileTool = {
7839
8145
  toolFn: async (value) => {
7840
8146
  const params = value;
7841
8147
  const resolvedPath = path10.resolve(process.cwd(), path10.normalize(params.path));
7842
- const isDirectory = existsSync4(resolvedPath) && statSync6(resolvedPath).isDirectory();
8148
+ const isDirectory = existsSync5(resolvedPath) && statSync6(resolvedPath).isDirectory();
7843
8149
  context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
7844
8150
  path: params.path,
7845
8151
  recursive: params.recursive
@@ -8129,10 +8435,10 @@ import jwt2 from "jsonwebtoken";
8129
8435
  // src/utils/diffPreview.ts
8130
8436
  import * as Diff from "diff";
8131
8437
  import { readFile } from "fs/promises";
8132
- import { existsSync as existsSync5 } from "fs";
8438
+ import { existsSync as existsSync6 } from "fs";
8133
8439
  async function generateFileDiffPreview(args) {
8134
8440
  try {
8135
- if (!existsSync5(args.path)) {
8441
+ if (!existsSync6(args.path)) {
8136
8442
  const lines2 = args.content.split("\n");
8137
8443
  const preview = lines2.slice(0, 20).join("\n");
8138
8444
  const hasMore = lines2.length > 20;
@@ -8162,7 +8468,7 @@ ${preview}${hasMore ? `
8162
8468
  }
8163
8469
  async function generateFileDeletePreview(args) {
8164
8470
  try {
8165
- if (!existsSync5(args.path)) {
8471
+ if (!existsSync6(args.path)) {
8166
8472
  return `[File does not exist: ${args.path}]`;
8167
8473
  }
8168
8474
  const stats = await import("fs/promises").then((fs11) => fs11.stat(args.path));
@@ -8215,7 +8521,7 @@ var CliLogger = class extends Logger {
8215
8521
  log(...args) {
8216
8522
  }
8217
8523
  };
8218
- function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext) {
8524
+ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore) {
8219
8525
  const originalFn = tool.toolFn;
8220
8526
  const toolName = tool.toolSchema.name;
8221
8527
  return {
@@ -8259,7 +8565,25 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
8259
8565
  throw new PermissionDeniedError(toolName, args);
8260
8566
  }
8261
8567
  if (response.action === "allow-always") {
8262
- permissionManager.trustTool(toolName);
8568
+ const canTrust = permissionManager.trustTool(toolName);
8569
+ if (canTrust) {
8570
+ const projectDir = configStore.getProjectConfigDir();
8571
+ if (projectDir) {
8572
+ try {
8573
+ await configStore.initProjectConfig();
8574
+ const existingLocal = await configStore.loadRawProjectLocalConfig() || {};
8575
+ const updatedLocal = {
8576
+ ...existingLocal,
8577
+ trustedTools: [...existingLocal.trustedTools || [], toolName]
8578
+ };
8579
+ await configStore.saveProjectLocalConfig(updatedLocal);
8580
+ } catch (error) {
8581
+ await configStore.trustTool(toolName);
8582
+ }
8583
+ } else {
8584
+ await configStore.trustTool(toolName);
8585
+ }
8586
+ }
8263
8587
  }
8264
8588
  const result = await originalFn(args);
8265
8589
  agentContext.observationQueue.push({ toolName, result });
@@ -8267,7 +8591,7 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
8267
8591
  }
8268
8592
  };
8269
8593
  }
8270
- function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, toolApiKeys) {
8594
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, toolApiKeys) {
8271
8595
  const logger2 = new CliLogger();
8272
8596
  const storage = new NoOpStorage();
8273
8597
  const user = {
@@ -8351,7 +8675,9 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
8351
8675
  toolConfig,
8352
8676
  model
8353
8677
  );
8354
- const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext));
8678
+ const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
8679
+ ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore)
8680
+ );
8355
8681
  return { tools: tools2, agentContext };
8356
8682
  }
8357
8683
 
@@ -8416,23 +8742,30 @@ function canTrustTool(toolName, customCategories) {
8416
8742
 
8417
8743
  // src/utils/PermissionManager.ts
8418
8744
  var PermissionManager = class {
8419
- constructor(trustedTools = [], customCategories) {
8745
+ constructor(trustedTools = [], customCategories, deniedTools) {
8420
8746
  this.trustedTools = /* @__PURE__ */ new Set();
8747
+ this.deniedTools = /* @__PURE__ */ new Set();
8421
8748
  this.trustedTools = new Set(trustedTools);
8422
8749
  this.customCategories = new Map(Object.entries(customCategories || {}));
8750
+ this.deniedTools = new Set(deniedTools || []);
8423
8751
  }
8424
8752
  /**
8425
8753
  * Check if a tool needs permission before execution
8426
8754
  *
8427
8755
  * A tool needs permission if:
8428
- * 1. It's not in the auto_approve category, AND
8429
- * 2. It's not in the trusted tools set
8756
+ * 1. It's in the denied tools list (from project config), OR
8757
+ * 2. It's not in the auto_approve category, AND
8758
+ * 3. It's not in the trusted tools set
8430
8759
  *
8431
8760
  * Note: prompt_always tools CANNOT be trusted, so they always need permission
8761
+ * Note: denied tools from project config ALWAYS need permission (cannot be overridden locally)
8432
8762
  */
8433
8763
  needsPermission(toolName) {
8434
8764
  const categoryMap = Object.fromEntries(this.customCategories);
8435
8765
  const category = getToolCategory(toolName, categoryMap);
8766
+ if (this.deniedTools.has(toolName)) {
8767
+ return true;
8768
+ }
8436
8769
  if (category === "auto_approve") {
8437
8770
  return false;
8438
8771
  }
@@ -8443,10 +8776,13 @@ var PermissionManager = class {
8443
8776
  }
8444
8777
  /**
8445
8778
  * Add a tool to the trusted list
8446
- * Returns false if the tool cannot be trusted (prompt_always category)
8779
+ * Returns false if the tool cannot be trusted (prompt_always category or denied by project)
8447
8780
  */
8448
8781
  trustTool(toolName) {
8449
8782
  const categoryMap = Object.fromEntries(this.customCategories);
8783
+ if (this.deniedTools.has(toolName)) {
8784
+ return false;
8785
+ }
8450
8786
  if (!canTrustTool(toolName, categoryMap)) {
8451
8787
  return false;
8452
8788
  }
@@ -8479,12 +8815,27 @@ var PermissionManager = class {
8479
8815
  return getToolCategory(toolName, categoryMap);
8480
8816
  }
8481
8817
  /**
8482
- * Check if a tool can be trusted (not in prompt_always category)
8818
+ * Check if a tool can be trusted (not in prompt_always category or denied by project)
8483
8819
  */
8484
8820
  canBeTrusted(toolName) {
8485
8821
  const categoryMap = Object.fromEntries(this.customCategories);
8822
+ if (this.deniedTools.has(toolName)) {
8823
+ return false;
8824
+ }
8486
8825
  return canTrustTool(toolName, categoryMap);
8487
8826
  }
8827
+ /**
8828
+ * Check if a tool is denied by project config
8829
+ */
8830
+ isDenied(toolName) {
8831
+ return this.deniedTools.has(toolName);
8832
+ }
8833
+ /**
8834
+ * Get list of denied tools
8835
+ */
8836
+ getDeniedTools() {
8837
+ return Array.from(this.deniedTools).sort();
8838
+ }
8488
8839
  /**
8489
8840
  * Clear all trusted tools
8490
8841
  */
@@ -8779,7 +9130,7 @@ var logger = Logger2.getInstance();
8779
9130
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
8780
9131
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
8781
9132
  import path12 from "path";
8782
- import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
9133
+ import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
8783
9134
  var MCPClient = class {
8784
9135
  // Note: This class handles MCP server communication with repository filtering
8785
9136
  mcp;
@@ -8815,7 +9166,7 @@ var MCPClient = class {
8815
9166
  path12.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
8816
9167
  path12.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
8817
9168
  ];
8818
- const serverScriptPath = candidatePaths.find((p) => existsSync6(p));
9169
+ const serverScriptPath = candidatePaths.find((p) => existsSync7(p));
8819
9170
  if (!serverScriptPath) {
8820
9171
  const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
8821
9172
  console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
@@ -9397,7 +9748,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
9397
9748
  // package.json
9398
9749
  var package_default = {
9399
9750
  name: "@bike4mind/cli",
9400
- version: "0.2.3-feat-cli-interactive-config-editor.17058+291ffcbb6",
9751
+ version: "0.2.3-feat-cli-project-scoped-config.17064+c671bc110",
9401
9752
  type: "module",
9402
9753
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
9403
9754
  license: "UNLICENSED",
@@ -9499,10 +9850,10 @@ var package_default = {
9499
9850
  },
9500
9851
  devDependencies: {
9501
9852
  "@bike4mind/agents": "0.1.0",
9502
- "@bike4mind/common": "2.37.1-feat-cli-interactive-config-editor.17058+291ffcbb6",
9503
- "@bike4mind/mcp": "1.19.5-feat-cli-interactive-config-editor.17058+291ffcbb6",
9504
- "@bike4mind/services": "2.31.1-feat-cli-interactive-config-editor.17058+291ffcbb6",
9505
- "@bike4mind/utils": "2.0.4-feat-cli-interactive-config-editor.17058+291ffcbb6",
9853
+ "@bike4mind/common": "2.37.1-feat-cli-project-scoped-config.17064+c671bc110",
9854
+ "@bike4mind/mcp": "1.19.5-feat-cli-project-scoped-config.17064+c671bc110",
9855
+ "@bike4mind/services": "2.31.1-feat-cli-project-scoped-config.17064+c671bc110",
9856
+ "@bike4mind/utils": "2.0.4-feat-cli-project-scoped-config.17064+c671bc110",
9506
9857
  "@types/diff": "^5.0.9",
9507
9858
  "@types/jsonwebtoken": "^9.0.4",
9508
9859
  "@types/node": "^22.9.0",
@@ -9514,7 +9865,7 @@ var package_default = {
9514
9865
  typescript: "^5.9.3",
9515
9866
  vitest: "^3.2.4"
9516
9867
  },
9517
- gitHead: "291ffcbb62b8a9f48f7f9bc29a5be2750818a147"
9868
+ gitHead: "c671bc110915b78abc187065ea6f65d2db6369df"
9518
9869
  };
9519
9870
 
9520
9871
  // src/config/constants.ts
@@ -9543,7 +9894,8 @@ function CliApp() {
9543
9894
  agent: null,
9544
9895
  mcpManager: null,
9545
9896
  permissionManager: null,
9546
- permissionPrompt: null
9897
+ permissionPrompt: null,
9898
+ trustLocationSelector: null
9547
9899
  });
9548
9900
  const [isInitialized, setIsInitialized] = useState4(false);
9549
9901
  const [initError, setInitError] = useState4(null);
@@ -9705,7 +10057,13 @@ function CliApp() {
9705
10057
  debug: () => {
9706
10058
  }
9707
10059
  };
9708
- const permissionManager = new PermissionManager(config.trustedTools || []);
10060
+ const permissionManager = new PermissionManager(
10061
+ config.trustedTools || [],
10062
+ void 0,
10063
+ // customCategories (not used yet)
10064
+ config.tools.disabled || []
10065
+ // denied tools from project config
10066
+ );
9709
10067
  const promptFn = (toolName, args, preview) => {
9710
10068
  return new Promise((resolve3) => {
9711
10069
  const canBeTrusted = permissionManager.canBeTrusted(toolName);
@@ -9733,6 +10091,7 @@ function CliApp() {
9733
10091
  permissionManager,
9734
10092
  promptFn,
9735
10093
  agentContext,
10094
+ state.configStore,
9736
10095
  config.toolApiKeys
9737
10096
  );
9738
10097
  console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools.length} B4M tool(s)`);
@@ -9913,6 +10272,9 @@ Tool Permissions:
9913
10272
  /trust <tool-name> - Trust a tool (won't ask permission again)
9914
10273
  /untrust <tool-name> - Remove tool from trusted list
9915
10274
  /trusted - List all trusted tools
10275
+
10276
+ Project Configuration:
10277
+ /project-config - Show merged project configuration
9916
10278
  `);
9917
10279
  break;
9918
10280
  case "exit":
@@ -9991,7 +10353,7 @@ Tool Permissions:
9991
10353
  console.log("");
9992
10354
  break;
9993
10355
  }
9994
- case "trust":
10356
+ case "trust": {
9995
10357
  if (!state.permissionManager) {
9996
10358
  console.log("Permission manager not initialized");
9997
10359
  return;
@@ -9999,16 +10361,97 @@ Tool Permissions:
9999
10361
  const toolToTrust = args[0];
10000
10362
  if (!toolToTrust) {
10001
10363
  console.log("Usage: /trust <tool-name>");
10364
+ console.log("");
10365
+ console.log("Example:");
10366
+ console.log(" /trust file_read - Opens interactive location selector");
10367
+ console.log("");
10002
10368
  return;
10003
10369
  }
10004
- const trusted = state.permissionManager.trustTool(toolToTrust);
10005
- if (trusted) {
10006
- await state.configStore.trustTool(toolToTrust);
10007
- console.log(`\u2705 Tool '${toolToTrust}' is now trusted and won't require permission`);
10008
- } else {
10009
- console.log(`\u274C Tool '${toolToTrust}' cannot be trusted (it's in the 'prompt_always' category)`);
10370
+ const canTrust = state.permissionManager.trustTool(toolToTrust);
10371
+ if (!canTrust) {
10372
+ if (state.permissionManager.isDenied(toolToTrust)) {
10373
+ console.log(`\u274C Tool '${toolToTrust}' is denied by project configuration and cannot be trusted`);
10374
+ } else {
10375
+ console.log(`\u274C Tool '${toolToTrust}' cannot be trusted (it's in the 'prompt_always' category)`);
10376
+ }
10377
+ return;
10010
10378
  }
10379
+ state.permissionManager.untrustTool(toolToTrust);
10380
+ const handleLocationSelect = async (location) => {
10381
+ setState((prev) => ({ ...prev, trustLocationSelector: null }));
10382
+ if (!location) {
10383
+ console.log("\u274C Trust operation cancelled");
10384
+ return;
10385
+ }
10386
+ const projectDir = state.configStore.getProjectConfigDir();
10387
+ switch (location) {
10388
+ case "local": {
10389
+ if (!projectDir) {
10390
+ console.log('\u274C No project found. Use "global" to save to ~/.bike4mind/config.json');
10391
+ return;
10392
+ }
10393
+ try {
10394
+ await state.configStore.initProjectConfig();
10395
+ const existingLocal = await state.configStore.loadRawProjectLocalConfig() || {};
10396
+ const updatedLocal = {
10397
+ ...existingLocal,
10398
+ trustedTools: [.../* @__PURE__ */ new Set([...existingLocal.trustedTools || [], toolToTrust])]
10399
+ };
10400
+ await state.configStore.saveProjectLocalConfig(updatedLocal);
10401
+ state.permissionManager?.trustTool(toolToTrust);
10402
+ console.log(`\u2705 Tool '${toolToTrust}' trusted for this project only`);
10403
+ console.log(` Saved in: ${projectDir}/.bike4mind/local.json`);
10404
+ } catch (error) {
10405
+ console.error(`\u274C Failed to save project-local config:`, error);
10406
+ }
10407
+ break;
10408
+ }
10409
+ case "project": {
10410
+ if (!projectDir) {
10411
+ console.log('\u274C No project found. Use "global" to save to ~/.bike4mind/config.json');
10412
+ return;
10413
+ }
10414
+ try {
10415
+ const existingProject = await state.configStore.loadRawProjectConfig() || {};
10416
+ const updatedProject = {
10417
+ ...existingProject,
10418
+ tools: {
10419
+ ...existingProject.tools,
10420
+ enabled: [.../* @__PURE__ */ new Set([...existingProject.tools?.enabled || [], toolToTrust])]
10421
+ }
10422
+ };
10423
+ await state.configStore.saveProjectConfig(updatedProject);
10424
+ state.permissionManager?.trustTool(toolToTrust);
10425
+ console.log(`\u2705 Tool '${toolToTrust}' trusted for entire team`);
10426
+ console.log(` Saved in: ${projectDir}/.bike4mind/config.json`);
10427
+ console.log(" \u26A0\uFE0F Remember to commit this file to share with your team!");
10428
+ } catch (error) {
10429
+ console.error(`\u274C Failed to save project config:`, error);
10430
+ }
10431
+ break;
10432
+ }
10433
+ case "global": {
10434
+ try {
10435
+ await state.configStore.trustTool(toolToTrust);
10436
+ state.permissionManager?.trustTool(toolToTrust);
10437
+ console.log(`\u2705 Tool '${toolToTrust}' trusted globally (all projects)`);
10438
+ console.log(" Saved in: ~/.bike4mind/config.json");
10439
+ } catch (error) {
10440
+ console.error(`\u274C Failed to save global config:`, error);
10441
+ }
10442
+ break;
10443
+ }
10444
+ }
10445
+ };
10446
+ setState((prev) => ({
10447
+ ...prev,
10448
+ trustLocationSelector: {
10449
+ toolName: toolToTrust,
10450
+ resolve: handleLocationSelect
10451
+ }
10452
+ }));
10011
10453
  break;
10454
+ }
10012
10455
  case "untrust":
10013
10456
  if (!state.permissionManager) {
10014
10457
  console.log("Permission manager not initialized");
@@ -10151,6 +10594,49 @@ Usage by Model (Last ${USAGE_DAYS} days):`);
10151
10594
  console.log(`
10152
10595
  No usage data available for the last ${USAGE_DAYS} days.`);
10153
10596
  }
10597
+ break;
10598
+ }
10599
+ case "project-config": {
10600
+ const projectDir = state.configStore.getProjectConfigDir();
10601
+ const config = await state.configStore.get();
10602
+ console.log("\n\u{1F4C1} Project Configuration:\n");
10603
+ if (projectDir) {
10604
+ console.log(`Project Directory: ${projectDir}/.bike4mind/`);
10605
+ console.log("");
10606
+ const projectConfig = await state.configStore.loadRawProjectConfig();
10607
+ const localConfig = await state.configStore.loadRawProjectLocalConfig();
10608
+ if (projectConfig) {
10609
+ console.log("Team Config (.bike4mind/config.json):");
10610
+ if (projectConfig.defaultModel) {
10611
+ console.log(` Default Model: ${projectConfig.defaultModel}`);
10612
+ }
10613
+ if (projectConfig.tools?.denied && projectConfig.tools.denied.length > 0) {
10614
+ console.log(` Denied Tools: ${projectConfig.tools.denied.join(", ")}`);
10615
+ }
10616
+ if (projectConfig.mcpServers && projectConfig.mcpServers.length > 0) {
10617
+ console.log(` MCP Servers: ${projectConfig.mcpServers.map((s) => s.name).join(", ")}`);
10618
+ }
10619
+ console.log("");
10620
+ }
10621
+ if (localConfig) {
10622
+ console.log("Local Config (.bike4mind/local.json):");
10623
+ if (localConfig.trustedTools && localConfig.trustedTools.length > 0) {
10624
+ console.log(` Trusted Tools: ${localConfig.trustedTools.join(", ")}`);
10625
+ }
10626
+ if (localConfig.mcpServers && localConfig.mcpServers.length > 0) {
10627
+ console.log(` MCP Servers: ${localConfig.mcpServers.map((s) => s.name).join(", ")}`);
10628
+ }
10629
+ console.log("");
10630
+ }
10631
+ console.log("Merged Configuration (what you're using):");
10632
+ console.log(` Default Model: ${config.defaultModel}`);
10633
+ console.log(` Trusted Tools: ${config.trustedTools?.length || 0} tools`);
10634
+ console.log(` Denied Tools: ${config.tools.disabled.length} tools`);
10635
+ console.log(` MCP Servers: ${config.mcpServers.length} servers`);
10636
+ } else {
10637
+ console.log("No project configuration found.");
10638
+ console.log("Tip: Create .bike4mind/config.json in your project root for team-wide settings.");
10639
+ }
10154
10640
  console.log("");
10155
10641
  break;
10156
10642
  }
@@ -10164,11 +10650,31 @@ No usage data available for the last ${USAGE_DAYS} days.`);
10164
10650
  setState((prev) => ({ ...prev, config: updatedConfig }));
10165
10651
  };
10166
10652
  if (initError) {
10167
- 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"));
10653
+ 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"));
10654
+ }
10655
+ if (state.trustLocationSelector) {
10656
+ const projectDir = state.configStore.getProjectConfigDir();
10657
+ const inProject = projectDir !== null;
10658
+ return /* @__PURE__ */ React13.createElement(
10659
+ TrustLocationSelector,
10660
+ {
10661
+ inProject,
10662
+ onSelect: (location) => {
10663
+ if (state.trustLocationSelector) {
10664
+ state.trustLocationSelector.resolve(location);
10665
+ }
10666
+ },
10667
+ onCancel: () => {
10668
+ if (state.trustLocationSelector) {
10669
+ state.trustLocationSelector.resolve(null);
10670
+ }
10671
+ }
10672
+ }
10673
+ );
10168
10674
  }
10169
10675
  if (state.showLoginFlow) {
10170
10676
  const loginApiUrl = getApiUrl(state.config?.apiConfig);
10171
- return /* @__PURE__ */ React12.createElement(
10677
+ return /* @__PURE__ */ React13.createElement(
10172
10678
  LoginFlow,
10173
10679
  {
10174
10680
  apiUrl: loginApiUrl,
@@ -10191,9 +10697,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
10191
10697
  );
10192
10698
  }
10193
10699
  if (!isInitialized) {
10194
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "\u{1F680} Initializing..."));
10700
+ return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "\u{1F680} Initializing..."));
10195
10701
  }
10196
- return /* @__PURE__ */ React12.createElement(
10702
+ return /* @__PURE__ */ React13.createElement(
10197
10703
  App,
10198
10704
  {
10199
10705
  onMessage: handleMessage,
@@ -10226,6 +10732,6 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
10226
10732
  if (isDevMode) {
10227
10733
  console.log("\u{1F527} Running in development mode (using TypeScript source)\n");
10228
10734
  }
10229
- render(/* @__PURE__ */ React12.createElement(CliApp, null), {
10735
+ render(/* @__PURE__ */ React13.createElement(CliApp, null), {
10230
10736
  exitOnCtrlC: false
10231
10737
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "0.2.3-feat-cli-interactive-config-editor.17058+291ffcbb6",
3
+ "version": "0.2.3-feat-cli-project-scoped-config.17064+c671bc110",
4
4
  "type": "module",
5
5
  "description": "Interactive CLI tool for Bike4Mind with ReAct agents",
6
6
  "license": "UNLICENSED",
@@ -102,10 +102,10 @@
102
102
  },
103
103
  "devDependencies": {
104
104
  "@bike4mind/agents": "0.1.0",
105
- "@bike4mind/common": "2.37.1-feat-cli-interactive-config-editor.17058+291ffcbb6",
106
- "@bike4mind/mcp": "1.19.5-feat-cli-interactive-config-editor.17058+291ffcbb6",
107
- "@bike4mind/services": "2.31.1-feat-cli-interactive-config-editor.17058+291ffcbb6",
108
- "@bike4mind/utils": "2.0.4-feat-cli-interactive-config-editor.17058+291ffcbb6",
105
+ "@bike4mind/common": "2.37.1-feat-cli-project-scoped-config.17064+c671bc110",
106
+ "@bike4mind/mcp": "1.19.5-feat-cli-project-scoped-config.17064+c671bc110",
107
+ "@bike4mind/services": "2.31.1-feat-cli-project-scoped-config.17064+c671bc110",
108
+ "@bike4mind/utils": "2.0.4-feat-cli-project-scoped-config.17064+c671bc110",
109
109
  "@types/diff": "^5.0.9",
110
110
  "@types/jsonwebtoken": "^9.0.4",
111
111
  "@types/node": "^22.9.0",
@@ -117,5 +117,5 @@
117
117
  "typescript": "^5.9.3",
118
118
  "vitest": "^3.2.4"
119
119
  },
120
- "gitHead": "291ffcbb62b8a9f48f7f9bc29a5be2750818a147"
120
+ "gitHead": "c671bc110915b78abc187065ea6f65d2db6369df"
121
121
  }