@bike4mind/cli 0.2.3-feat-cli-interactive-config-editor.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.
- package/dist/index.js +578 -85
- 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
|
|
83
|
-
import { render, Box as
|
|
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/
|
|
1098
|
-
import React11
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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,123 @@ 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: 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
|
+
}
|
|
1466
1659
|
var ConfigStore = class {
|
|
1467
1660
|
constructor(configPath) {
|
|
1468
1661
|
this.config = null;
|
|
1662
|
+
this.projectConfigDir = null;
|
|
1469
1663
|
this.configPath = configPath || path4.join(homedir2(), ".bike4mind", "config.json");
|
|
1470
1664
|
}
|
|
1471
1665
|
/**
|
|
@@ -1482,51 +1676,76 @@ var ConfigStore = class {
|
|
|
1482
1676
|
}
|
|
1483
1677
|
/**
|
|
1484
1678
|
* Load configuration from disk with Zod validation
|
|
1679
|
+
* Merges global → project → local configs
|
|
1485
1680
|
*/
|
|
1486
1681
|
async load() {
|
|
1487
1682
|
if (this.config) {
|
|
1488
1683
|
return this.config;
|
|
1489
1684
|
}
|
|
1490
1685
|
try {
|
|
1686
|
+
let globalConfig;
|
|
1491
1687
|
try {
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
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) {
|
|
1497
1696
|
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
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 };
|
|
1506
1734
|
} else {
|
|
1507
|
-
|
|
1735
|
+
throw error;
|
|
1508
1736
|
}
|
|
1509
1737
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
...DEFAULT_CONFIG.tools,
|
|
1522
|
-
...validated.tools
|
|
1523
|
-
},
|
|
1524
|
-
toolApiKeys: {
|
|
1525
|
-
...DEFAULT_CONFIG.toolApiKeys,
|
|
1526
|
-
...validated.toolApiKeys || {}
|
|
1527
|
-
},
|
|
1528
|
-
trustedTools: validated.trustedTools || []
|
|
1529
|
-
};
|
|
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);
|
|
1530
1749
|
return this.config;
|
|
1531
1750
|
} catch (error) {
|
|
1532
1751
|
if (error.code === "ENOENT") {
|
|
@@ -1715,6 +1934,92 @@ var ConfigStore = class {
|
|
|
1715
1934
|
}
|
|
1716
1935
|
await this.save(config);
|
|
1717
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
|
+
}
|
|
1718
2023
|
};
|
|
1719
2024
|
|
|
1720
2025
|
// src/storage/CommandHistoryStore.ts
|
|
@@ -7300,7 +7605,7 @@ var planetVisibilityTool = {
|
|
|
7300
7605
|
|
|
7301
7606
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
|
|
7302
7607
|
import { promises as fs7 } from "fs";
|
|
7303
|
-
import { existsSync as
|
|
7608
|
+
import { existsSync as existsSync3, statSync as statSync3 } from "fs";
|
|
7304
7609
|
import path6 from "path";
|
|
7305
7610
|
var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
|
|
7306
7611
|
async function readFileContent(params) {
|
|
@@ -7311,7 +7616,7 @@ async function readFileContent(params) {
|
|
|
7311
7616
|
if (!resolvedPath.startsWith(cwd)) {
|
|
7312
7617
|
throw new Error(`Access denied: Cannot read files outside of current working directory`);
|
|
7313
7618
|
}
|
|
7314
|
-
if (!
|
|
7619
|
+
if (!existsSync3(resolvedPath)) {
|
|
7315
7620
|
throw new Error(`File not found: ${filePath}`);
|
|
7316
7621
|
}
|
|
7317
7622
|
const stats = statSync3(resolvedPath);
|
|
@@ -7397,7 +7702,7 @@ var fileReadTool = {
|
|
|
7397
7702
|
|
|
7398
7703
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
|
|
7399
7704
|
import { promises as fs8 } from "fs";
|
|
7400
|
-
import { existsSync as
|
|
7705
|
+
import { existsSync as existsSync4 } from "fs";
|
|
7401
7706
|
import path7 from "path";
|
|
7402
7707
|
async function createFile(params) {
|
|
7403
7708
|
const { path: filePath, content, createDirectories = true } = params;
|
|
@@ -7407,7 +7712,7 @@ async function createFile(params) {
|
|
|
7407
7712
|
if (!resolvedPath.startsWith(cwd)) {
|
|
7408
7713
|
throw new Error(`Access denied: Cannot create files outside of current working directory`);
|
|
7409
7714
|
}
|
|
7410
|
-
const fileExists =
|
|
7715
|
+
const fileExists = existsSync4(resolvedPath);
|
|
7411
7716
|
const action = fileExists ? "overwritten" : "created";
|
|
7412
7717
|
if (createDirectories) {
|
|
7413
7718
|
const dir = path7.dirname(resolvedPath);
|
|
@@ -7425,7 +7730,7 @@ var createFileTool = {
|
|
|
7425
7730
|
implementation: (context) => ({
|
|
7426
7731
|
toolFn: async (value) => {
|
|
7427
7732
|
const params = value;
|
|
7428
|
-
const fileExists =
|
|
7733
|
+
const fileExists = existsSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
|
|
7429
7734
|
context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
|
|
7430
7735
|
path: params.path,
|
|
7431
7736
|
size: params.content.length
|
|
@@ -7805,7 +8110,7 @@ var grepSearchTool = {
|
|
|
7805
8110
|
|
|
7806
8111
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
|
|
7807
8112
|
import { promises as fs9 } from "fs";
|
|
7808
|
-
import { existsSync as
|
|
8113
|
+
import { existsSync as existsSync5, statSync as statSync6 } from "fs";
|
|
7809
8114
|
import path10 from "path";
|
|
7810
8115
|
async function deleteFile(params) {
|
|
7811
8116
|
const { path: filePath, recursive = false } = params;
|
|
@@ -7815,7 +8120,7 @@ async function deleteFile(params) {
|
|
|
7815
8120
|
if (!resolvedPath.startsWith(cwd)) {
|
|
7816
8121
|
throw new Error(`Access denied: Cannot delete files outside of current working directory`);
|
|
7817
8122
|
}
|
|
7818
|
-
if (!
|
|
8123
|
+
if (!existsSync5(resolvedPath)) {
|
|
7819
8124
|
throw new Error(`File or directory not found: ${filePath}`);
|
|
7820
8125
|
}
|
|
7821
8126
|
const stats = statSync6(resolvedPath);
|
|
@@ -7839,7 +8144,7 @@ var deleteFileTool = {
|
|
|
7839
8144
|
toolFn: async (value) => {
|
|
7840
8145
|
const params = value;
|
|
7841
8146
|
const resolvedPath = path10.resolve(process.cwd(), path10.normalize(params.path));
|
|
7842
|
-
const isDirectory =
|
|
8147
|
+
const isDirectory = existsSync5(resolvedPath) && statSync6(resolvedPath).isDirectory();
|
|
7843
8148
|
context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
|
|
7844
8149
|
path: params.path,
|
|
7845
8150
|
recursive: params.recursive
|
|
@@ -8129,10 +8434,10 @@ import jwt2 from "jsonwebtoken";
|
|
|
8129
8434
|
// src/utils/diffPreview.ts
|
|
8130
8435
|
import * as Diff from "diff";
|
|
8131
8436
|
import { readFile } from "fs/promises";
|
|
8132
|
-
import { existsSync as
|
|
8437
|
+
import { existsSync as existsSync6 } from "fs";
|
|
8133
8438
|
async function generateFileDiffPreview(args) {
|
|
8134
8439
|
try {
|
|
8135
|
-
if (!
|
|
8440
|
+
if (!existsSync6(args.path)) {
|
|
8136
8441
|
const lines2 = args.content.split("\n");
|
|
8137
8442
|
const preview = lines2.slice(0, 20).join("\n");
|
|
8138
8443
|
const hasMore = lines2.length > 20;
|
|
@@ -8162,7 +8467,7 @@ ${preview}${hasMore ? `
|
|
|
8162
8467
|
}
|
|
8163
8468
|
async function generateFileDeletePreview(args) {
|
|
8164
8469
|
try {
|
|
8165
|
-
if (!
|
|
8470
|
+
if (!existsSync6(args.path)) {
|
|
8166
8471
|
return `[File does not exist: ${args.path}]`;
|
|
8167
8472
|
}
|
|
8168
8473
|
const stats = await import("fs/promises").then((fs11) => fs11.stat(args.path));
|
|
@@ -8215,7 +8520,7 @@ var CliLogger = class extends Logger {
|
|
|
8215
8520
|
log(...args) {
|
|
8216
8521
|
}
|
|
8217
8522
|
};
|
|
8218
|
-
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext) {
|
|
8523
|
+
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore) {
|
|
8219
8524
|
const originalFn = tool.toolFn;
|
|
8220
8525
|
const toolName = tool.toolSchema.name;
|
|
8221
8526
|
return {
|
|
@@ -8259,7 +8564,25 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
|
|
|
8259
8564
|
throw new PermissionDeniedError(toolName, args);
|
|
8260
8565
|
}
|
|
8261
8566
|
if (response.action === "allow-always") {
|
|
8262
|
-
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
|
+
}
|
|
8263
8586
|
}
|
|
8264
8587
|
const result = await originalFn(args);
|
|
8265
8588
|
agentContext.observationQueue.push({ toolName, result });
|
|
@@ -8267,7 +8590,7 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
|
|
|
8267
8590
|
}
|
|
8268
8591
|
};
|
|
8269
8592
|
}
|
|
8270
|
-
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, toolApiKeys) {
|
|
8593
|
+
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, toolApiKeys) {
|
|
8271
8594
|
const logger2 = new CliLogger();
|
|
8272
8595
|
const storage = new NoOpStorage();
|
|
8273
8596
|
const user = {
|
|
@@ -8351,7 +8674,9 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
8351
8674
|
toolConfig,
|
|
8352
8675
|
model
|
|
8353
8676
|
);
|
|
8354
|
-
const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
|
|
8677
|
+
const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
|
|
8678
|
+
([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore)
|
|
8679
|
+
);
|
|
8355
8680
|
return { tools: tools2, agentContext };
|
|
8356
8681
|
}
|
|
8357
8682
|
|
|
@@ -8416,23 +8741,30 @@ function canTrustTool(toolName, customCategories) {
|
|
|
8416
8741
|
|
|
8417
8742
|
// src/utils/PermissionManager.ts
|
|
8418
8743
|
var PermissionManager = class {
|
|
8419
|
-
constructor(trustedTools = [], customCategories) {
|
|
8744
|
+
constructor(trustedTools = [], customCategories, deniedTools) {
|
|
8420
8745
|
this.trustedTools = /* @__PURE__ */ new Set();
|
|
8746
|
+
this.deniedTools = /* @__PURE__ */ new Set();
|
|
8421
8747
|
this.trustedTools = new Set(trustedTools);
|
|
8422
8748
|
this.customCategories = new Map(Object.entries(customCategories || {}));
|
|
8749
|
+
this.deniedTools = new Set(deniedTools || []);
|
|
8423
8750
|
}
|
|
8424
8751
|
/**
|
|
8425
8752
|
* Check if a tool needs permission before execution
|
|
8426
8753
|
*
|
|
8427
8754
|
* A tool needs permission if:
|
|
8428
|
-
* 1. It's
|
|
8429
|
-
* 2. It's not in the
|
|
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
|
|
8430
8758
|
*
|
|
8431
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)
|
|
8432
8761
|
*/
|
|
8433
8762
|
needsPermission(toolName) {
|
|
8434
8763
|
const categoryMap = Object.fromEntries(this.customCategories);
|
|
8435
8764
|
const category = getToolCategory(toolName, categoryMap);
|
|
8765
|
+
if (this.deniedTools.has(toolName)) {
|
|
8766
|
+
return true;
|
|
8767
|
+
}
|
|
8436
8768
|
if (category === "auto_approve") {
|
|
8437
8769
|
return false;
|
|
8438
8770
|
}
|
|
@@ -8443,10 +8775,13 @@ var PermissionManager = class {
|
|
|
8443
8775
|
}
|
|
8444
8776
|
/**
|
|
8445
8777
|
* Add a tool to the trusted list
|
|
8446
|
-
* 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)
|
|
8447
8779
|
*/
|
|
8448
8780
|
trustTool(toolName) {
|
|
8449
8781
|
const categoryMap = Object.fromEntries(this.customCategories);
|
|
8782
|
+
if (this.deniedTools.has(toolName)) {
|
|
8783
|
+
return false;
|
|
8784
|
+
}
|
|
8450
8785
|
if (!canTrustTool(toolName, categoryMap)) {
|
|
8451
8786
|
return false;
|
|
8452
8787
|
}
|
|
@@ -8479,12 +8814,27 @@ var PermissionManager = class {
|
|
|
8479
8814
|
return getToolCategory(toolName, categoryMap);
|
|
8480
8815
|
}
|
|
8481
8816
|
/**
|
|
8482
|
-
* 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)
|
|
8483
8818
|
*/
|
|
8484
8819
|
canBeTrusted(toolName) {
|
|
8485
8820
|
const categoryMap = Object.fromEntries(this.customCategories);
|
|
8821
|
+
if (this.deniedTools.has(toolName)) {
|
|
8822
|
+
return false;
|
|
8823
|
+
}
|
|
8486
8824
|
return canTrustTool(toolName, categoryMap);
|
|
8487
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
|
+
}
|
|
8488
8838
|
/**
|
|
8489
8839
|
* Clear all trusted tools
|
|
8490
8840
|
*/
|
|
@@ -8779,7 +9129,7 @@ var logger = Logger2.getInstance();
|
|
|
8779
9129
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
8780
9130
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8781
9131
|
import path12 from "path";
|
|
8782
|
-
import { existsSync as
|
|
9132
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
|
|
8783
9133
|
var MCPClient = class {
|
|
8784
9134
|
// Note: This class handles MCP server communication with repository filtering
|
|
8785
9135
|
mcp;
|
|
@@ -8815,7 +9165,7 @@ var MCPClient = class {
|
|
|
8815
9165
|
path12.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
8816
9166
|
path12.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
|
|
8817
9167
|
];
|
|
8818
|
-
const serverScriptPath = candidatePaths.find((p) =>
|
|
9168
|
+
const serverScriptPath = candidatePaths.find((p) => existsSync7(p));
|
|
8819
9169
|
if (!serverScriptPath) {
|
|
8820
9170
|
const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
8821
9171
|
console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
|
|
@@ -9397,7 +9747,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
9397
9747
|
// package.json
|
|
9398
9748
|
var package_default = {
|
|
9399
9749
|
name: "@bike4mind/cli",
|
|
9400
|
-
version: "0.2.3-feat-cli-
|
|
9750
|
+
version: "0.2.3-feat-cli-project-scoped-config.17063+4e29d03e0",
|
|
9401
9751
|
type: "module",
|
|
9402
9752
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
9403
9753
|
license: "UNLICENSED",
|
|
@@ -9499,10 +9849,10 @@ var package_default = {
|
|
|
9499
9849
|
},
|
|
9500
9850
|
devDependencies: {
|
|
9501
9851
|
"@bike4mind/agents": "0.1.0",
|
|
9502
|
-
"@bike4mind/common": "2.37.1-feat-cli-
|
|
9503
|
-
"@bike4mind/mcp": "1.19.5-feat-cli-
|
|
9504
|
-
"@bike4mind/services": "2.31.1-feat-cli-
|
|
9505
|
-
"@bike4mind/utils": "2.0.4-feat-cli-
|
|
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",
|
|
9506
9856
|
"@types/diff": "^5.0.9",
|
|
9507
9857
|
"@types/jsonwebtoken": "^9.0.4",
|
|
9508
9858
|
"@types/node": "^22.9.0",
|
|
@@ -9514,7 +9864,7 @@ var package_default = {
|
|
|
9514
9864
|
typescript: "^5.9.3",
|
|
9515
9865
|
vitest: "^3.2.4"
|
|
9516
9866
|
},
|
|
9517
|
-
gitHead: "
|
|
9867
|
+
gitHead: "4e29d03e04acb4714606940d6557b9601325ed73"
|
|
9518
9868
|
};
|
|
9519
9869
|
|
|
9520
9870
|
// src/config/constants.ts
|
|
@@ -9543,7 +9893,8 @@ function CliApp() {
|
|
|
9543
9893
|
agent: null,
|
|
9544
9894
|
mcpManager: null,
|
|
9545
9895
|
permissionManager: null,
|
|
9546
|
-
permissionPrompt: null
|
|
9896
|
+
permissionPrompt: null,
|
|
9897
|
+
trustLocationSelector: null
|
|
9547
9898
|
});
|
|
9548
9899
|
const [isInitialized, setIsInitialized] = useState4(false);
|
|
9549
9900
|
const [initError, setInitError] = useState4(null);
|
|
@@ -9705,7 +10056,13 @@ function CliApp() {
|
|
|
9705
10056
|
debug: () => {
|
|
9706
10057
|
}
|
|
9707
10058
|
};
|
|
9708
|
-
const permissionManager = new PermissionManager(
|
|
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
|
+
);
|
|
9709
10066
|
const promptFn = (toolName, args, preview) => {
|
|
9710
10067
|
return new Promise((resolve3) => {
|
|
9711
10068
|
const canBeTrusted = permissionManager.canBeTrusted(toolName);
|
|
@@ -9733,6 +10090,7 @@ function CliApp() {
|
|
|
9733
10090
|
permissionManager,
|
|
9734
10091
|
promptFn,
|
|
9735
10092
|
agentContext,
|
|
10093
|
+
state.configStore,
|
|
9736
10094
|
config.toolApiKeys
|
|
9737
10095
|
);
|
|
9738
10096
|
console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools.length} B4M tool(s)`);
|
|
@@ -9913,6 +10271,9 @@ Tool Permissions:
|
|
|
9913
10271
|
/trust <tool-name> - Trust a tool (won't ask permission again)
|
|
9914
10272
|
/untrust <tool-name> - Remove tool from trusted list
|
|
9915
10273
|
/trusted - List all trusted tools
|
|
10274
|
+
|
|
10275
|
+
Project Configuration:
|
|
10276
|
+
/project-config - Show merged project configuration
|
|
9916
10277
|
`);
|
|
9917
10278
|
break;
|
|
9918
10279
|
case "exit":
|
|
@@ -9991,7 +10352,7 @@ Tool Permissions:
|
|
|
9991
10352
|
console.log("");
|
|
9992
10353
|
break;
|
|
9993
10354
|
}
|
|
9994
|
-
case "trust":
|
|
10355
|
+
case "trust": {
|
|
9995
10356
|
if (!state.permissionManager) {
|
|
9996
10357
|
console.log("Permission manager not initialized");
|
|
9997
10358
|
return;
|
|
@@ -9999,16 +10360,85 @@ Tool Permissions:
|
|
|
9999
10360
|
const toolToTrust = args[0];
|
|
10000
10361
|
if (!toolToTrust) {
|
|
10001
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("");
|
|
10002
10367
|
return;
|
|
10003
10368
|
}
|
|
10004
|
-
const
|
|
10005
|
-
if (
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
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;
|
|
10010
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
|
+
}));
|
|
10011
10440
|
break;
|
|
10441
|
+
}
|
|
10012
10442
|
case "untrust":
|
|
10013
10443
|
if (!state.permissionManager) {
|
|
10014
10444
|
console.log("Permission manager not initialized");
|
|
@@ -10151,6 +10581,49 @@ Usage by Model (Last ${USAGE_DAYS} days):`);
|
|
|
10151
10581
|
console.log(`
|
|
10152
10582
|
No usage data available for the last ${USAGE_DAYS} days.`);
|
|
10153
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
|
+
}
|
|
10154
10627
|
console.log("");
|
|
10155
10628
|
break;
|
|
10156
10629
|
}
|
|
@@ -10164,11 +10637,31 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
10164
10637
|
setState((prev) => ({ ...prev, config: updatedConfig }));
|
|
10165
10638
|
};
|
|
10166
10639
|
if (initError) {
|
|
10167
|
-
return /* @__PURE__ */
|
|
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
|
+
);
|
|
10168
10661
|
}
|
|
10169
10662
|
if (state.showLoginFlow) {
|
|
10170
10663
|
const loginApiUrl = getApiUrl(state.config?.apiConfig);
|
|
10171
|
-
return /* @__PURE__ */
|
|
10664
|
+
return /* @__PURE__ */ React13.createElement(
|
|
10172
10665
|
LoginFlow,
|
|
10173
10666
|
{
|
|
10174
10667
|
apiUrl: loginApiUrl,
|
|
@@ -10191,9 +10684,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
10191
10684
|
);
|
|
10192
10685
|
}
|
|
10193
10686
|
if (!isInitialized) {
|
|
10194
|
-
return /* @__PURE__ */
|
|
10687
|
+
return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "\u{1F680} Initializing..."));
|
|
10195
10688
|
}
|
|
10196
|
-
return /* @__PURE__ */
|
|
10689
|
+
return /* @__PURE__ */ React13.createElement(
|
|
10197
10690
|
App,
|
|
10198
10691
|
{
|
|
10199
10692
|
onMessage: handleMessage,
|
|
@@ -10226,6 +10719,6 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
|
|
|
10226
10719
|
if (isDevMode) {
|
|
10227
10720
|
console.log("\u{1F527} Running in development mode (using TypeScript source)\n");
|
|
10228
10721
|
}
|
|
10229
|
-
render(/* @__PURE__ */
|
|
10722
|
+
render(/* @__PURE__ */ React13.createElement(CliApp, null), {
|
|
10230
10723
|
exitOnCtrlC: false
|
|
10231
10724
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bike4mind/cli",
|
|
3
|
-
"version": "0.2.3-feat-cli-
|
|
3
|
+
"version": "0.2.3-feat-cli-project-scoped-config.17063+4e29d03e0",
|
|
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-
|
|
106
|
-
"@bike4mind/mcp": "1.19.5-feat-cli-
|
|
107
|
-
"@bike4mind/services": "2.31.1-feat-cli-
|
|
108
|
-
"@bike4mind/utils": "2.0.4-feat-cli-
|
|
105
|
+
"@bike4mind/common": "2.37.1-feat-cli-project-scoped-config.17063+4e29d03e0",
|
|
106
|
+
"@bike4mind/mcp": "1.19.5-feat-cli-project-scoped-config.17063+4e29d03e0",
|
|
107
|
+
"@bike4mind/services": "2.31.1-feat-cli-project-scoped-config.17063+4e29d03e0",
|
|
108
|
+
"@bike4mind/utils": "2.0.4-feat-cli-project-scoped-config.17063+4e29d03e0",
|
|
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": "
|
|
120
|
+
"gitHead": "4e29d03e04acb4714606940d6557b9601325ed73"
|
|
121
121
|
}
|