@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.
- package/dist/index.js +591 -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,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
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
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
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
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
|
-
|
|
1736
|
+
throw error;
|
|
1508
1737
|
}
|
|
1509
1738
|
}
|
|
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
|
-
};
|
|
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
|
|
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 (!
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
8438
|
+
import { existsSync as existsSync6 } from "fs";
|
|
8133
8439
|
async function generateFileDiffPreview(args) {
|
|
8134
8440
|
try {
|
|
8135
|
-
if (!
|
|
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 (!
|
|
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(
|
|
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
|
|
8429
|
-
* 2. It's not in the
|
|
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
|
|
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) =>
|
|
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-
|
|
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-
|
|
9503
|
-
"@bike4mind/mcp": "1.19.5-feat-cli-
|
|
9504
|
-
"@bike4mind/services": "2.31.1-feat-cli-
|
|
9505
|
-
"@bike4mind/utils": "2.0.4-feat-cli-
|
|
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: "
|
|
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(
|
|
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
|
|
10005
|
-
if (
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
10700
|
+
return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "\u{1F680} Initializing..."));
|
|
10195
10701
|
}
|
|
10196
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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-
|
|
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-
|
|
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.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": "
|
|
120
|
+
"gitHead": "c671bc110915b78abc187065ea6f65d2db6369df"
|
|
121
121
|
}
|