bashbros 0.1.3 → 0.1.4
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/README.md +727 -265
- package/dist/adapters-JAZGGNVP.js +9 -0
- package/dist/chunk-4XZ64P4V.js +47 -0
- package/dist/chunk-4XZ64P4V.js.map +1 -0
- package/dist/{chunk-2RPTM6EQ.js → chunk-7OEWYFN3.js} +745 -629
- package/dist/chunk-7OEWYFN3.js.map +1 -0
- package/dist/{chunk-WPJJZLT6.js → chunk-CG6VEHJM.js} +3 -2
- package/dist/chunk-CG6VEHJM.js.map +1 -0
- package/dist/{chunk-DLP2O6PN.js → chunk-EMLEJVJZ.js} +102 -1
- package/dist/chunk-EMLEJVJZ.js.map +1 -0
- package/dist/chunk-IUUBCPMV.js +166 -0
- package/dist/chunk-IUUBCPMV.js.map +1 -0
- package/dist/chunk-J6ONXY6N.js +146 -0
- package/dist/chunk-J6ONXY6N.js.map +1 -0
- package/dist/{chunk-EYO44OMN.js → chunk-KYDMPE4N.js} +60 -17
- package/dist/chunk-KYDMPE4N.js.map +1 -0
- package/dist/chunk-LJE4EPIU.js +56 -0
- package/dist/chunk-LJE4EPIU.js.map +1 -0
- package/dist/chunk-LZYW7XQO.js +339 -0
- package/dist/chunk-LZYW7XQO.js.map +1 -0
- package/dist/{chunk-JYWQT2B4.js → chunk-RDNSS3ME.js} +489 -14
- package/dist/chunk-RDNSS3ME.js.map +1 -0
- package/dist/{chunk-A535VV7N.js → chunk-RTZ4QWG2.js} +5 -4
- package/dist/chunk-RTZ4QWG2.js.map +1 -0
- package/dist/chunk-SDN6TAGD.js +157 -0
- package/dist/chunk-SDN6TAGD.js.map +1 -0
- package/dist/chunk-T5ONCUHZ.js +198 -0
- package/dist/chunk-T5ONCUHZ.js.map +1 -0
- package/dist/cli.js +1069 -88
- package/dist/cli.js.map +1 -1
- package/dist/{config-43SK6SFI.js → config-I5NCK3RJ.js} +2 -2
- package/dist/copilot-cli-5WJWK5YT.js +9 -0
- package/dist/{db-SWJUUSFX.js → db-ETWTBXAE.js} +2 -2
- package/dist/db-checks-2YOVECD4.js +133 -0
- package/dist/db-checks-2YOVECD4.js.map +1 -0
- package/dist/{display-HFIFXOOL.js → display-UH7KEHOW.js} +3 -3
- package/dist/gemini-cli-3563EELZ.js +9 -0
- package/dist/gemini-cli-3563EELZ.js.map +1 -0
- package/dist/index.d.ts +176 -72
- package/dist/index.js +119 -398
- package/dist/index.js.map +1 -1
- package/dist/{ollama-HY35OHW4.js → ollama-5JVKNFOV.js} +2 -2
- package/dist/ollama-5JVKNFOV.js.map +1 -0
- package/dist/opencode-DRCY275R.js +9 -0
- package/dist/opencode-DRCY275R.js.map +1 -0
- package/dist/profiles-7CLN6TAT.js +9 -0
- package/dist/profiles-7CLN6TAT.js.map +1 -0
- package/dist/setup-YS27MOPE.js +124 -0
- package/dist/setup-YS27MOPE.js.map +1 -0
- package/dist/static/index.html +4815 -2007
- package/dist/store-WJ5Y7MOE.js +9 -0
- package/dist/store-WJ5Y7MOE.js.map +1 -0
- package/dist/{writer-4ZEAKUFD.js → writer-3NAVABN6.js} +3 -3
- package/dist/writer-3NAVABN6.js.map +1 -0
- package/package.json +77 -68
- package/dist/chunk-2RPTM6EQ.js.map +0 -1
- package/dist/chunk-A535VV7N.js.map +0 -1
- package/dist/chunk-DLP2O6PN.js.map +0 -1
- package/dist/chunk-EYO44OMN.js.map +0 -1
- package/dist/chunk-JYWQT2B4.js.map +0 -1
- package/dist/chunk-WPJJZLT6.js.map +0 -1
- /package/dist/{config-43SK6SFI.js.map → adapters-JAZGGNVP.js.map} +0 -0
- /package/dist/{db-SWJUUSFX.js.map → config-I5NCK3RJ.js.map} +0 -0
- /package/dist/{display-HFIFXOOL.js.map → copilot-cli-5WJWK5YT.js.map} +0 -0
- /package/dist/{ollama-HY35OHW4.js.map → db-ETWTBXAE.js.map} +0 -0
- /package/dist/{writer-4ZEAKUFD.js.map → display-UH7KEHOW.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,33 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
DashboardWriter
|
|
4
|
-
} from "./chunk-EYO44OMN.js";
|
|
5
|
-
import {
|
|
6
|
-
DashboardDB
|
|
7
|
-
} from "./chunk-JYWQT2B4.js";
|
|
8
2
|
import {
|
|
9
3
|
formatAllAgentsInfo,
|
|
10
4
|
formatPermissionsTable
|
|
11
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CG6VEHJM.js";
|
|
6
|
+
import {
|
|
7
|
+
GeminiCLIHooks
|
|
8
|
+
} from "./chunk-T5ONCUHZ.js";
|
|
9
|
+
import {
|
|
10
|
+
CopilotCLIHooks
|
|
11
|
+
} from "./chunk-SDN6TAGD.js";
|
|
12
|
+
import {
|
|
13
|
+
OpenCodeHooks
|
|
14
|
+
} from "./chunk-IUUBCPMV.js";
|
|
15
|
+
import {
|
|
16
|
+
formatRedactedConfig,
|
|
17
|
+
parseAgentConfig
|
|
18
|
+
} from "./chunk-ID2O2QTI.js";
|
|
19
|
+
import {
|
|
20
|
+
DashboardWriter
|
|
21
|
+
} from "./chunk-KYDMPE4N.js";
|
|
12
22
|
import {
|
|
23
|
+
ContextStore
|
|
24
|
+
} from "./chunk-J6ONXY6N.js";
|
|
25
|
+
import {
|
|
26
|
+
AnomalyDetector,
|
|
13
27
|
BashBro,
|
|
14
28
|
BashBros,
|
|
15
|
-
ClaudeCodeHooks,
|
|
16
29
|
CostEstimator,
|
|
17
30
|
LoopDetector,
|
|
18
|
-
|
|
31
|
+
OutputScanner,
|
|
19
32
|
ReportGenerator,
|
|
20
33
|
UndoStack,
|
|
21
|
-
gateCommand,
|
|
22
34
|
getBashgymIntegration
|
|
23
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-7OEWYFN3.js";
|
|
36
|
+
import {
|
|
37
|
+
ClaudeCodeHooks,
|
|
38
|
+
gateCommand
|
|
39
|
+
} from "./chunk-LZYW7XQO.js";
|
|
40
|
+
import {
|
|
41
|
+
MoltbotHooks
|
|
42
|
+
} from "./chunk-J37RHCFJ.js";
|
|
24
43
|
import "./chunk-SG752FZC.js";
|
|
25
|
-
import "./chunk-
|
|
44
|
+
import "./chunk-EMLEJVJZ.js";
|
|
45
|
+
import "./chunk-4XZ64P4V.js";
|
|
46
|
+
import "./chunk-LJE4EPIU.js";
|
|
26
47
|
import {
|
|
27
48
|
findConfig,
|
|
28
49
|
getDefaultConfig,
|
|
29
50
|
loadConfig
|
|
30
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-RTZ4QWG2.js";
|
|
31
52
|
import "./chunk-QWZGB4V3.js";
|
|
32
53
|
import {
|
|
33
54
|
allowForSession
|
|
@@ -36,12 +57,8 @@ import {
|
|
|
36
57
|
RiskScorer
|
|
37
58
|
} from "./chunk-DEAF6PYM.js";
|
|
38
59
|
import {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} from "./chunk-ID2O2QTI.js";
|
|
42
|
-
import {
|
|
43
|
-
MoltbotHooks
|
|
44
|
-
} from "./chunk-J37RHCFJ.js";
|
|
60
|
+
DashboardDB
|
|
61
|
+
} from "./chunk-RDNSS3ME.js";
|
|
45
62
|
import "./chunk-7OCVIDC7.js";
|
|
46
63
|
|
|
47
64
|
// src/cli.ts
|
|
@@ -69,6 +86,7 @@ async function runOnboarding() {
|
|
|
69
86
|
{ name: "Moltbot (clawd.bot)", value: "moltbot" },
|
|
70
87
|
{ name: "Clawdbot (legacy)", value: "clawdbot" },
|
|
71
88
|
{ name: "Gemini CLI", value: "gemini-cli" },
|
|
89
|
+
{ name: "Copilot CLI", value: "copilot-cli" },
|
|
72
90
|
{ name: "Aider", value: "aider" },
|
|
73
91
|
{ name: "OpenCode", value: "opencode" },
|
|
74
92
|
{ name: "Other (custom)", value: "custom" }
|
|
@@ -297,6 +315,9 @@ var AGENT_CONFIG_PATHS = {
|
|
|
297
315
|
"gemini-cli": [
|
|
298
316
|
join2(homedir2(), ".config", "gemini-cli", "config.json")
|
|
299
317
|
],
|
|
318
|
+
"copilot-cli": [
|
|
319
|
+
join2(homedir2(), ".config", "github-copilot", "config.json")
|
|
320
|
+
],
|
|
300
321
|
"opencode": [
|
|
301
322
|
join2(homedir2(), ".opencode", "config.yml"),
|
|
302
323
|
join2(homedir2(), ".config", "opencode", "config.yml")
|
|
@@ -309,6 +330,7 @@ var AGENT_COMMANDS = {
|
|
|
309
330
|
"moltbot": "moltbot",
|
|
310
331
|
"aider": "aider",
|
|
311
332
|
"gemini-cli": "gemini",
|
|
333
|
+
"copilot-cli": "copilot",
|
|
312
334
|
"opencode": "opencode",
|
|
313
335
|
"custom": ""
|
|
314
336
|
};
|
|
@@ -1018,7 +1040,7 @@ var EgressPatternMatcher = class {
|
|
|
1018
1040
|
// src/policy/ward/egress.ts
|
|
1019
1041
|
var DashboardDB2 = null;
|
|
1020
1042
|
try {
|
|
1021
|
-
const dbModule = await import("./db-
|
|
1043
|
+
const dbModule = await import("./db-ETWTBXAE.js");
|
|
1022
1044
|
DashboardDB2 = dbModule.DashboardDB;
|
|
1023
1045
|
} catch {
|
|
1024
1046
|
}
|
|
@@ -1430,11 +1452,29 @@ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "
|
|
|
1430
1452
|
import { parse, stringify as stringify2 } from "yaml";
|
|
1431
1453
|
var dashboardWriter = null;
|
|
1432
1454
|
var riskScorer = null;
|
|
1455
|
+
var contextStore = null;
|
|
1456
|
+
var contextCommandCount = 0;
|
|
1457
|
+
var contextSessionStartTime = null;
|
|
1433
1458
|
function cleanup() {
|
|
1434
1459
|
if (process.stdin.isTTY) {
|
|
1435
1460
|
process.stdin.setRawMode(false);
|
|
1436
1461
|
}
|
|
1437
1462
|
}
|
|
1463
|
+
function finalizeContextStore(agent) {
|
|
1464
|
+
if (!contextStore) return;
|
|
1465
|
+
try {
|
|
1466
|
+
contextStore.writeSession({
|
|
1467
|
+
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1468
|
+
agent,
|
|
1469
|
+
startTime: contextSessionStartTime || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1470
|
+
endTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1471
|
+
commandCount: contextCommandCount,
|
|
1472
|
+
summary: `Watch session with ${contextCommandCount} commands`
|
|
1473
|
+
});
|
|
1474
|
+
contextStore.updateIndex();
|
|
1475
|
+
} catch {
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1438
1478
|
async function startWatch(options) {
|
|
1439
1479
|
const configPath = findConfig();
|
|
1440
1480
|
if (!configPath) {
|
|
@@ -1449,6 +1489,7 @@ async function startWatch(options) {
|
|
|
1449
1489
|
console.log();
|
|
1450
1490
|
const bashbros = new BashBros(configPath);
|
|
1451
1491
|
const config = bashbros.getConfig();
|
|
1492
|
+
const fullConfig = loadConfig(configPath);
|
|
1452
1493
|
try {
|
|
1453
1494
|
dashboardWriter = new DashboardWriter();
|
|
1454
1495
|
riskScorer = new RiskScorer();
|
|
@@ -1461,6 +1502,33 @@ async function startWatch(options) {
|
|
|
1461
1502
|
console.log(chalk3.yellow(" Dashboard recording disabled"));
|
|
1462
1503
|
}
|
|
1463
1504
|
}
|
|
1505
|
+
try {
|
|
1506
|
+
contextStore = new ContextStore(process.cwd());
|
|
1507
|
+
contextStore.initialize();
|
|
1508
|
+
contextSessionStartTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1509
|
+
contextCommandCount = 0;
|
|
1510
|
+
if (options.verbose) {
|
|
1511
|
+
console.log(chalk3.dim(" Context store initialized"));
|
|
1512
|
+
}
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
if (options.verbose) {
|
|
1515
|
+
console.log(chalk3.yellow(" Context store disabled"));
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
const loopDetector2 = new LoopDetector(fullConfig.loopDetection);
|
|
1519
|
+
const anomalyDetector = new AnomalyDetector({
|
|
1520
|
+
workingHours: fullConfig.anomalyDetection.workingHours,
|
|
1521
|
+
typicalCommandsPerMinute: fullConfig.anomalyDetection.typicalCommandsPerMinute,
|
|
1522
|
+
suspiciousPatterns: fullConfig.anomalyDetection.suspiciousPatterns.map((p) => {
|
|
1523
|
+
try {
|
|
1524
|
+
return new RegExp(p, "i");
|
|
1525
|
+
} catch {
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
}).filter((p) => p !== null),
|
|
1529
|
+
enabled: fullConfig.anomalyDetection.enabled
|
|
1530
|
+
});
|
|
1531
|
+
const outputScanner = fullConfig.outputScanning.enabled ? new OutputScanner(fullConfig.outputScanning) : null;
|
|
1464
1532
|
let pendingCommand = null;
|
|
1465
1533
|
let awaitingPromptResponse = false;
|
|
1466
1534
|
function showBlockedPrompt(command, violations) {
|
|
@@ -1500,6 +1568,11 @@ async function startWatch(options) {
|
|
|
1500
1568
|
break;
|
|
1501
1569
|
case "p":
|
|
1502
1570
|
try {
|
|
1571
|
+
if (!configPath) {
|
|
1572
|
+
console.log(chalk3.red(" \u2717 No config file found"));
|
|
1573
|
+
console.log();
|
|
1574
|
+
break;
|
|
1575
|
+
}
|
|
1503
1576
|
const content = readFileSync2(configPath, "utf-8");
|
|
1504
1577
|
const config2 = parse(content);
|
|
1505
1578
|
if (!config2.commands) {
|
|
@@ -1539,14 +1612,50 @@ async function startWatch(options) {
|
|
|
1539
1612
|
const risk = riskScorer.score(result.command);
|
|
1540
1613
|
dashboardWriter.recordCommand(result.command, true, risk, [], result.duration);
|
|
1541
1614
|
}
|
|
1615
|
+
if (contextStore) {
|
|
1616
|
+
try {
|
|
1617
|
+
contextStore.appendCommand({
|
|
1618
|
+
command: result.command,
|
|
1619
|
+
output: (result.output || "").slice(0, 500),
|
|
1620
|
+
agent: config.agent,
|
|
1621
|
+
exitCode: result.exitCode ?? 0,
|
|
1622
|
+
cwd: process.cwd()
|
|
1623
|
+
});
|
|
1624
|
+
contextCommandCount++;
|
|
1625
|
+
} catch {
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1542
1628
|
if (options.verbose) {
|
|
1543
1629
|
console.log(chalk3.green("\u2713"), chalk3.dim(result.command));
|
|
1544
1630
|
}
|
|
1545
1631
|
});
|
|
1546
1632
|
bashbros.on("output", (data) => {
|
|
1633
|
+
if (outputScanner) {
|
|
1634
|
+
try {
|
|
1635
|
+
const text = typeof data === "string" ? data : data.toString();
|
|
1636
|
+
const scanResult = outputScanner.scan(text);
|
|
1637
|
+
if (scanResult.hasSecrets) {
|
|
1638
|
+
for (const f of scanResult.findings.filter((f2) => f2.type === "secret")) {
|
|
1639
|
+
process.stderr.write(chalk3.yellow(`[BashBros] Output warning: ${f.message}`) + "\n");
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
} catch {
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1547
1645
|
process.stdout.write(data);
|
|
1548
1646
|
});
|
|
1549
1647
|
bashbros.on("error", (error) => {
|
|
1648
|
+
if (contextStore) {
|
|
1649
|
+
try {
|
|
1650
|
+
contextStore.appendError({
|
|
1651
|
+
command: "",
|
|
1652
|
+
error: error.message,
|
|
1653
|
+
agent: config.agent,
|
|
1654
|
+
resolved: false
|
|
1655
|
+
});
|
|
1656
|
+
} catch {
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1550
1659
|
console.error(chalk3.red("Error:"), error.message);
|
|
1551
1660
|
});
|
|
1552
1661
|
bashbros.on("exit", (exitCode) => {
|
|
@@ -1554,6 +1663,7 @@ async function startWatch(options) {
|
|
|
1554
1663
|
dashboardWriter.endSession();
|
|
1555
1664
|
dashboardWriter.close();
|
|
1556
1665
|
}
|
|
1666
|
+
finalizeContextStore(config.agent);
|
|
1557
1667
|
cleanup();
|
|
1558
1668
|
process.exit(exitCode ?? 0);
|
|
1559
1669
|
});
|
|
@@ -1565,6 +1675,7 @@ async function startWatch(options) {
|
|
|
1565
1675
|
dashboardWriter.endSession();
|
|
1566
1676
|
dashboardWriter.close();
|
|
1567
1677
|
}
|
|
1678
|
+
finalizeContextStore(config.agent);
|
|
1568
1679
|
bashbros.stop();
|
|
1569
1680
|
process.exit(0);
|
|
1570
1681
|
});
|
|
@@ -1574,6 +1685,7 @@ async function startWatch(options) {
|
|
|
1574
1685
|
dashboardWriter.endSession();
|
|
1575
1686
|
dashboardWriter.close();
|
|
1576
1687
|
}
|
|
1688
|
+
finalizeContextStore(config.agent);
|
|
1577
1689
|
bashbros.stop();
|
|
1578
1690
|
process.exit(0);
|
|
1579
1691
|
});
|
|
@@ -1583,6 +1695,7 @@ async function startWatch(options) {
|
|
|
1583
1695
|
dashboardWriter.crashSession();
|
|
1584
1696
|
dashboardWriter.close();
|
|
1585
1697
|
}
|
|
1698
|
+
finalizeContextStore(config.agent);
|
|
1586
1699
|
cleanup();
|
|
1587
1700
|
bashbros.stop();
|
|
1588
1701
|
process.exit(1);
|
|
@@ -1623,6 +1736,48 @@ async function startWatch(options) {
|
|
|
1623
1736
|
commandBuffer = "";
|
|
1624
1737
|
if (command) {
|
|
1625
1738
|
const violations = bashbros.validateOnly(command);
|
|
1739
|
+
if (violations.length === 0 && riskScorer && fullConfig.riskScoring.enabled) {
|
|
1740
|
+
const risk = riskScorer.score(command);
|
|
1741
|
+
if (risk.score >= fullConfig.riskScoring.blockThreshold) {
|
|
1742
|
+
violations.push({
|
|
1743
|
+
type: "risk_score",
|
|
1744
|
+
rule: "risk_threshold",
|
|
1745
|
+
message: `Risk score ${risk.score} >= block threshold ${fullConfig.riskScoring.blockThreshold}: ${risk.factors.join(", ")}`
|
|
1746
|
+
});
|
|
1747
|
+
} else if (risk.score >= fullConfig.riskScoring.warnThreshold) {
|
|
1748
|
+
process.stderr.write(chalk3.yellow(`[BashBros] Warning: risk score ${risk.score} (${risk.factors.join(", ")})`) + "\n");
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
if (violations.length === 0 && fullConfig.loopDetection.enabled) {
|
|
1752
|
+
const loopAlert = loopDetector2.check(command);
|
|
1753
|
+
if (loopAlert) {
|
|
1754
|
+
if (fullConfig.loopDetection.action === "block") {
|
|
1755
|
+
violations.push({
|
|
1756
|
+
type: "loop",
|
|
1757
|
+
rule: loopAlert.type,
|
|
1758
|
+
message: loopAlert.message
|
|
1759
|
+
});
|
|
1760
|
+
} else {
|
|
1761
|
+
process.stderr.write(chalk3.yellow(`[BashBros] Loop warning: ${loopAlert.message}`) + "\n");
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
if (violations.length === 0 && fullConfig.anomalyDetection.enabled) {
|
|
1766
|
+
const anomalies = anomalyDetector.check(command);
|
|
1767
|
+
const significant = anomalies.filter((a) => a.severity === "warning" || a.severity === "alert");
|
|
1768
|
+
if (significant.length > 0) {
|
|
1769
|
+
const msg = significant.map((a) => a.message).join("; ");
|
|
1770
|
+
if (fullConfig.anomalyDetection.action === "block") {
|
|
1771
|
+
violations.push({
|
|
1772
|
+
type: "anomaly",
|
|
1773
|
+
rule: "anomaly_detection",
|
|
1774
|
+
message: `Anomaly: ${msg}`
|
|
1775
|
+
});
|
|
1776
|
+
} else {
|
|
1777
|
+
process.stderr.write(chalk3.yellow(`[BashBros] Anomaly: ${msg}`) + "\n");
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1626
1781
|
if (violations.length > 0) {
|
|
1627
1782
|
bashbros.write("");
|
|
1628
1783
|
bashbros.write("\r");
|
|
@@ -1743,7 +1898,7 @@ var DashboardServer = class {
|
|
|
1743
1898
|
this.app.use(express.json());
|
|
1744
1899
|
this.app.use((_req, res, next) => {
|
|
1745
1900
|
res.header("Access-Control-Allow-Origin", "*");
|
|
1746
|
-
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1901
|
+
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
1747
1902
|
res.header("Access-Control-Allow-Headers", "Content-Type");
|
|
1748
1903
|
next();
|
|
1749
1904
|
});
|
|
@@ -1787,7 +1942,7 @@ var DashboardServer = class {
|
|
|
1787
1942
|
this.app.get("/api/connectors/:name/events", (req, res) => {
|
|
1788
1943
|
try {
|
|
1789
1944
|
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
|
|
1790
|
-
const events = this.db.getConnectorEvents(req.params.name, limit);
|
|
1945
|
+
const events = this.db.getConnectorEvents(String(req.params.name), limit);
|
|
1791
1946
|
res.json(events);
|
|
1792
1947
|
} catch (error) {
|
|
1793
1948
|
res.status(500).json({ error: "Failed to fetch connector events" });
|
|
@@ -1803,7 +1958,7 @@ var DashboardServer = class {
|
|
|
1803
1958
|
});
|
|
1804
1959
|
this.app.post("/api/blocked/:id/approve", (req, res) => {
|
|
1805
1960
|
try {
|
|
1806
|
-
const
|
|
1961
|
+
const id = String(req.params.id);
|
|
1807
1962
|
const approvedBy = req.body?.approvedBy ?? "dashboard-user";
|
|
1808
1963
|
const block = this.db.getBlock(id);
|
|
1809
1964
|
if (!block) {
|
|
@@ -1819,7 +1974,7 @@ var DashboardServer = class {
|
|
|
1819
1974
|
});
|
|
1820
1975
|
this.app.post("/api/blocked/:id/deny", (req, res) => {
|
|
1821
1976
|
try {
|
|
1822
|
-
const
|
|
1977
|
+
const id = String(req.params.id);
|
|
1823
1978
|
const deniedBy = req.body?.deniedBy ?? "dashboard-user";
|
|
1824
1979
|
const block = this.db.getBlock(id);
|
|
1825
1980
|
if (!block) {
|
|
@@ -1860,9 +2015,17 @@ var DashboardServer = class {
|
|
|
1860
2015
|
res.status(500).json({ error: "Failed to fetch active session" });
|
|
1861
2016
|
}
|
|
1862
2017
|
});
|
|
2018
|
+
this.app.get("/api/sessions/active-all", (_req, res) => {
|
|
2019
|
+
try {
|
|
2020
|
+
const sessions = this.db.getActiveSessions();
|
|
2021
|
+
res.json(sessions);
|
|
2022
|
+
} catch (error) {
|
|
2023
|
+
res.status(500).json({ error: "Failed to fetch active sessions" });
|
|
2024
|
+
}
|
|
2025
|
+
});
|
|
1863
2026
|
this.app.get("/api/sessions/:id", (req, res) => {
|
|
1864
2027
|
try {
|
|
1865
|
-
const session = this.db.getSession(req.params.id);
|
|
2028
|
+
const session = this.db.getSession(String(req.params.id));
|
|
1866
2029
|
if (!session) {
|
|
1867
2030
|
res.status(404).json({ error: "Session not found" });
|
|
1868
2031
|
return;
|
|
@@ -1875,7 +2038,7 @@ var DashboardServer = class {
|
|
|
1875
2038
|
this.app.get("/api/sessions/:id/commands", (req, res) => {
|
|
1876
2039
|
try {
|
|
1877
2040
|
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
|
|
1878
|
-
const commands = this.db.getCommandsBySession(req.params.id, limit);
|
|
2041
|
+
const commands = this.db.getCommandsBySession(String(req.params.id), limit);
|
|
1879
2042
|
res.json(commands);
|
|
1880
2043
|
} catch (error) {
|
|
1881
2044
|
res.status(500).json({ error: "Failed to fetch session commands" });
|
|
@@ -1883,12 +2046,12 @@ var DashboardServer = class {
|
|
|
1883
2046
|
});
|
|
1884
2047
|
this.app.get("/api/sessions/:id/metrics", (req, res) => {
|
|
1885
2048
|
try {
|
|
1886
|
-
const session = this.db.getSession(req.params.id);
|
|
2049
|
+
const session = this.db.getSession(String(req.params.id));
|
|
1887
2050
|
if (!session) {
|
|
1888
2051
|
res.status(404).json({ error: "Session not found" });
|
|
1889
2052
|
return;
|
|
1890
2053
|
}
|
|
1891
|
-
const metrics = this.db.getSessionMetrics(req.params.id);
|
|
2054
|
+
const metrics = this.db.getSessionMetrics(String(req.params.id));
|
|
1892
2055
|
res.json(metrics);
|
|
1893
2056
|
} catch (error) {
|
|
1894
2057
|
res.status(500).json({ error: "Failed to fetch session metrics" });
|
|
@@ -1897,8 +2060,16 @@ var DashboardServer = class {
|
|
|
1897
2060
|
this.app.get("/api/commands/live", (req, res) => {
|
|
1898
2061
|
try {
|
|
1899
2062
|
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 20;
|
|
1900
|
-
|
|
1901
|
-
|
|
2063
|
+
if (req.query.sessionId) {
|
|
2064
|
+
const commands = this.db.getCommands({
|
|
2065
|
+
sessionId: req.query.sessionId,
|
|
2066
|
+
limit
|
|
2067
|
+
});
|
|
2068
|
+
res.json(commands);
|
|
2069
|
+
} else {
|
|
2070
|
+
const commands = this.db.getLiveCommands(limit);
|
|
2071
|
+
res.json(commands);
|
|
2072
|
+
}
|
|
1902
2073
|
} catch (error) {
|
|
1903
2074
|
res.status(500).json({ error: "Failed to fetch live commands" });
|
|
1904
2075
|
}
|
|
@@ -1932,6 +2103,7 @@ var DashboardServer = class {
|
|
|
1932
2103
|
try {
|
|
1933
2104
|
const filter = {};
|
|
1934
2105
|
if (req.query.toolName) filter.toolName = req.query.toolName;
|
|
2106
|
+
if (req.query.sessionId) filter.sessionId = req.query.sessionId;
|
|
1935
2107
|
if (req.query.since) filter.since = new Date(req.query.since);
|
|
1936
2108
|
if (req.query.limit) filter.limit = parseInt(req.query.limit, 10);
|
|
1937
2109
|
if (req.query.offset) filter.offset = parseInt(req.query.offset, 10);
|
|
@@ -2015,6 +2187,217 @@ var DashboardServer = class {
|
|
|
2015
2187
|
res.status(500).json({ error: "Failed to trigger scan" });
|
|
2016
2188
|
}
|
|
2017
2189
|
});
|
|
2190
|
+
this.app.get("/api/bro/models/running", async (_req, res) => {
|
|
2191
|
+
try {
|
|
2192
|
+
const controller = new AbortController();
|
|
2193
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
2194
|
+
const response = await fetch("http://localhost:11434/api/ps", { signal: controller.signal });
|
|
2195
|
+
clearTimeout(timeout);
|
|
2196
|
+
if (!response.ok) {
|
|
2197
|
+
res.json({ models: [] });
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
const data = await response.json();
|
|
2201
|
+
res.json(data);
|
|
2202
|
+
} catch {
|
|
2203
|
+
res.json({ models: [] });
|
|
2204
|
+
}
|
|
2205
|
+
});
|
|
2206
|
+
this.app.get("/api/bro/models/:name", async (req, res) => {
|
|
2207
|
+
try {
|
|
2208
|
+
const name = decodeURIComponent(String(req.params.name));
|
|
2209
|
+
const controller = new AbortController();
|
|
2210
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
2211
|
+
const response = await fetch("http://localhost:11434/api/show", {
|
|
2212
|
+
method: "POST",
|
|
2213
|
+
headers: { "Content-Type": "application/json" },
|
|
2214
|
+
body: JSON.stringify({ name }),
|
|
2215
|
+
signal: controller.signal
|
|
2216
|
+
});
|
|
2217
|
+
clearTimeout(timeout);
|
|
2218
|
+
if (!response.ok) {
|
|
2219
|
+
res.status(404).json({ error: "Model not found" });
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
res.json(await response.json());
|
|
2223
|
+
} catch {
|
|
2224
|
+
res.status(500).json({ error: "Failed to fetch model details" });
|
|
2225
|
+
}
|
|
2226
|
+
});
|
|
2227
|
+
this.app.post("/api/bro/models/pull", async (req, res) => {
|
|
2228
|
+
try {
|
|
2229
|
+
const { name } = req.body;
|
|
2230
|
+
if (!name) {
|
|
2231
|
+
res.status(400).json({ error: "Model name required" });
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
this.broadcast({ type: "model:pull:start", name });
|
|
2235
|
+
const response = await fetch("http://localhost:11434/api/pull", {
|
|
2236
|
+
method: "POST",
|
|
2237
|
+
headers: { "Content-Type": "application/json" },
|
|
2238
|
+
body: JSON.stringify({ name, stream: false })
|
|
2239
|
+
});
|
|
2240
|
+
if (response.ok) {
|
|
2241
|
+
this.broadcast({ type: "model:pull:complete", name });
|
|
2242
|
+
res.json({ success: true });
|
|
2243
|
+
} else {
|
|
2244
|
+
this.broadcast({ type: "model:pull:error", name });
|
|
2245
|
+
res.status(500).json({ error: "Pull failed" });
|
|
2246
|
+
}
|
|
2247
|
+
} catch {
|
|
2248
|
+
res.status(500).json({ error: "Failed to pull model" });
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
this.app.delete("/api/bro/models/:name", async (req, res) => {
|
|
2252
|
+
try {
|
|
2253
|
+
const name = decodeURIComponent(String(req.params.name));
|
|
2254
|
+
const response = await fetch("http://localhost:11434/api/delete", {
|
|
2255
|
+
method: "DELETE",
|
|
2256
|
+
headers: { "Content-Type": "application/json" },
|
|
2257
|
+
body: JSON.stringify({ name })
|
|
2258
|
+
});
|
|
2259
|
+
res.json({ success: response.ok });
|
|
2260
|
+
} catch {
|
|
2261
|
+
res.status(500).json({ error: "Failed to delete model" });
|
|
2262
|
+
}
|
|
2263
|
+
});
|
|
2264
|
+
this.app.get("/api/bro/adapters", async (_req, res) => {
|
|
2265
|
+
try {
|
|
2266
|
+
const { AdapterRegistry } = await import("./adapters-JAZGGNVP.js");
|
|
2267
|
+
const registry = new AdapterRegistry();
|
|
2268
|
+
res.json(registry.discover());
|
|
2269
|
+
} catch {
|
|
2270
|
+
res.json([]);
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
this.app.get("/api/bro/adapters/events", (_req, res) => {
|
|
2274
|
+
try {
|
|
2275
|
+
res.json(this.db.getAdapterEvents());
|
|
2276
|
+
} catch {
|
|
2277
|
+
res.json([]);
|
|
2278
|
+
}
|
|
2279
|
+
});
|
|
2280
|
+
this.app.post("/api/bro/adapters/:name/activate", async (req, res) => {
|
|
2281
|
+
try {
|
|
2282
|
+
const adapterName = String(req.params.name);
|
|
2283
|
+
const { AdapterRegistry } = await import("./adapters-JAZGGNVP.js");
|
|
2284
|
+
const registry = new AdapterRegistry();
|
|
2285
|
+
const adapters = registry.discover();
|
|
2286
|
+
const adapter = adapters.find((a) => a.name === adapterName);
|
|
2287
|
+
if (!adapter) {
|
|
2288
|
+
res.status(404).json({ error: "Adapter not found" });
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
const modelfile = registry.generateModelfile(adapter);
|
|
2292
|
+
const ollamaName = registry.ollamaModelName(adapterName);
|
|
2293
|
+
const response = await fetch("http://localhost:11434/api/create", {
|
|
2294
|
+
method: "POST",
|
|
2295
|
+
headers: { "Content-Type": "application/json" },
|
|
2296
|
+
body: JSON.stringify({ name: ollamaName, modelfile, stream: false })
|
|
2297
|
+
});
|
|
2298
|
+
if (response.ok) {
|
|
2299
|
+
this.db.insertAdapterEvent({
|
|
2300
|
+
adapterName,
|
|
2301
|
+
baseModel: adapter.baseModel,
|
|
2302
|
+
purpose: adapter.purpose,
|
|
2303
|
+
action: "activated",
|
|
2304
|
+
success: true
|
|
2305
|
+
});
|
|
2306
|
+
this.broadcast({ type: "adapter:activated", name: adapterName });
|
|
2307
|
+
res.json({ success: true, ollamaModel: ollamaName });
|
|
2308
|
+
} else {
|
|
2309
|
+
res.status(500).json({ error: "Failed to create Ollama model from adapter" });
|
|
2310
|
+
}
|
|
2311
|
+
} catch {
|
|
2312
|
+
res.status(500).json({ error: "Failed to activate adapter" });
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2315
|
+
this.app.get("/api/bro/profiles", async (_req, res) => {
|
|
2316
|
+
try {
|
|
2317
|
+
const { ProfileManager } = await import("./profiles-7CLN6TAT.js");
|
|
2318
|
+
res.json(new ProfileManager().list());
|
|
2319
|
+
} catch {
|
|
2320
|
+
res.json([]);
|
|
2321
|
+
}
|
|
2322
|
+
});
|
|
2323
|
+
this.app.post("/api/bro/profiles", async (req, res) => {
|
|
2324
|
+
try {
|
|
2325
|
+
const { ProfileManager } = await import("./profiles-7CLN6TAT.js");
|
|
2326
|
+
new ProfileManager().save(req.body);
|
|
2327
|
+
res.json({ success: true });
|
|
2328
|
+
} catch {
|
|
2329
|
+
res.status(500).json({ error: "Failed to save profile" });
|
|
2330
|
+
}
|
|
2331
|
+
});
|
|
2332
|
+
this.app.delete("/api/bro/profiles/:name", async (req, res) => {
|
|
2333
|
+
try {
|
|
2334
|
+
const { ProfileManager } = await import("./profiles-7CLN6TAT.js");
|
|
2335
|
+
new ProfileManager().delete(String(req.params.name));
|
|
2336
|
+
res.json({ success: true });
|
|
2337
|
+
} catch {
|
|
2338
|
+
res.status(500).json({ error: "Failed to delete profile" });
|
|
2339
|
+
}
|
|
2340
|
+
});
|
|
2341
|
+
this.app.get("/api/context/index", async (_req, res) => {
|
|
2342
|
+
try {
|
|
2343
|
+
const { ContextStore: ContextStore2 } = await import("./store-WJ5Y7MOE.js");
|
|
2344
|
+
res.json(new ContextStore2(process.cwd()).getIndex());
|
|
2345
|
+
} catch {
|
|
2346
|
+
res.json({ lastUpdated: "", agents: [], sessionCount: 0, commandFileCount: 0, errorFileCount: 0 });
|
|
2347
|
+
}
|
|
2348
|
+
});
|
|
2349
|
+
this.app.get("/api/context/memory", async (_req, res) => {
|
|
2350
|
+
try {
|
|
2351
|
+
const { ContextStore: ContextStore2 } = await import("./store-WJ5Y7MOE.js");
|
|
2352
|
+
const store = new ContextStore2(process.cwd());
|
|
2353
|
+
const files = store.listMemoryFiles();
|
|
2354
|
+
const result = {};
|
|
2355
|
+
for (const file of files) {
|
|
2356
|
+
result[file] = store.readMemory(file);
|
|
2357
|
+
}
|
|
2358
|
+
res.json(result);
|
|
2359
|
+
} catch {
|
|
2360
|
+
res.json({});
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
this.app.put("/api/context/memory/:name", async (req, res) => {
|
|
2364
|
+
try {
|
|
2365
|
+
const { ContextStore: ContextStore2 } = await import("./store-WJ5Y7MOE.js");
|
|
2366
|
+
const store = new ContextStore2(process.cwd());
|
|
2367
|
+
store.writeMemory(String(req.params.name), req.body.content);
|
|
2368
|
+
this.broadcast({ type: "context:updated", file: req.params.name });
|
|
2369
|
+
res.json({ success: true });
|
|
2370
|
+
} catch {
|
|
2371
|
+
res.status(500).json({ error: "Failed to write memory file" });
|
|
2372
|
+
}
|
|
2373
|
+
});
|
|
2374
|
+
this.app.get("/api/achievements", (_req, res) => {
|
|
2375
|
+
try {
|
|
2376
|
+
const stats = this.db.getAchievementStats();
|
|
2377
|
+
const badges = this.db.computeAchievements(stats);
|
|
2378
|
+
const xp = this.db.computeXP(stats, badges);
|
|
2379
|
+
res.json({ stats, badges, xp });
|
|
2380
|
+
} catch (error) {
|
|
2381
|
+
res.status(500).json({ error: "Failed to fetch achievements" });
|
|
2382
|
+
}
|
|
2383
|
+
});
|
|
2384
|
+
this.app.get("/api/security/summary", (_req, res) => {
|
|
2385
|
+
try {
|
|
2386
|
+
const summary = this.db.getSecuritySummary();
|
|
2387
|
+
res.json(summary);
|
|
2388
|
+
} catch (error) {
|
|
2389
|
+
res.status(500).json({ error: "Failed to fetch security summary" });
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
this.app.get("/api/security/blocked-commands", (req, res) => {
|
|
2393
|
+
try {
|
|
2394
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 25;
|
|
2395
|
+
const commands = this.db.getBlockedCommandsRecent(limit);
|
|
2396
|
+
res.json(commands);
|
|
2397
|
+
} catch (error) {
|
|
2398
|
+
res.status(500).json({ error: "Failed to fetch blocked commands" });
|
|
2399
|
+
}
|
|
2400
|
+
});
|
|
2018
2401
|
this.app.get("/api/exposures", (req, res) => {
|
|
2019
2402
|
try {
|
|
2020
2403
|
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
|
|
@@ -2053,9 +2436,70 @@ var DashboardServer = class {
|
|
|
2053
2436
|
res.status(500).json({ error: "Failed to save config" });
|
|
2054
2437
|
}
|
|
2055
2438
|
});
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2439
|
+
this.app.get("/api/agents/status", async (_req, res) => {
|
|
2440
|
+
try {
|
|
2441
|
+
const claude = ClaudeCodeHooks.getStatus();
|
|
2442
|
+
const moltbot = MoltbotHooks.getStatus();
|
|
2443
|
+
const gemini = GeminiCLIHooks.getStatus();
|
|
2444
|
+
const copilot = CopilotCLIHooks.getStatus();
|
|
2445
|
+
const opencode = OpenCodeHooks.getStatus();
|
|
2446
|
+
let gatewayRunning = false;
|
|
2447
|
+
let sandboxMode = null;
|
|
2448
|
+
try {
|
|
2449
|
+
const gw = await MoltbotHooks.getGatewayStatus();
|
|
2450
|
+
gatewayRunning = gw.running;
|
|
2451
|
+
sandboxMode = gw.sandboxMode ? "strict" : null;
|
|
2452
|
+
} catch {
|
|
2453
|
+
gatewayRunning = moltbot.gatewayRunning;
|
|
2454
|
+
sandboxMode = moltbot.sandboxMode;
|
|
2455
|
+
}
|
|
2456
|
+
const agents = [
|
|
2457
|
+
{
|
|
2458
|
+
name: "Claude Code",
|
|
2459
|
+
key: "claude-code",
|
|
2460
|
+
installed: claude.claudeInstalled,
|
|
2461
|
+
hooksInstalled: claude.hooksInstalled,
|
|
2462
|
+
hooks: claude.hooks,
|
|
2463
|
+
extra: { allToolsRecording: claude.allToolsInstalled }
|
|
2464
|
+
},
|
|
2465
|
+
{
|
|
2466
|
+
name: "Moltbot",
|
|
2467
|
+
key: "moltbot",
|
|
2468
|
+
installed: moltbot.moltbotInstalled || moltbot.clawdbotInstalled,
|
|
2469
|
+
hooksInstalled: moltbot.hooksInstalled,
|
|
2470
|
+
hooks: moltbot.hooks,
|
|
2471
|
+
extra: { gatewayRunning, sandboxMode }
|
|
2472
|
+
},
|
|
2473
|
+
{
|
|
2474
|
+
name: "Gemini CLI",
|
|
2475
|
+
key: "gemini-cli",
|
|
2476
|
+
installed: gemini.geminiInstalled,
|
|
2477
|
+
hooksInstalled: gemini.hooksInstalled,
|
|
2478
|
+
hooks: gemini.hooks
|
|
2479
|
+
},
|
|
2480
|
+
{
|
|
2481
|
+
name: "GitHub Copilot CLI",
|
|
2482
|
+
key: "copilot-cli",
|
|
2483
|
+
installed: copilot.copilotInstalled,
|
|
2484
|
+
hooksInstalled: copilot.hooksInstalled,
|
|
2485
|
+
hooks: copilot.hooks
|
|
2486
|
+
},
|
|
2487
|
+
{
|
|
2488
|
+
name: "OpenCode",
|
|
2489
|
+
key: "opencode",
|
|
2490
|
+
installed: opencode.openCodeInstalled,
|
|
2491
|
+
hooksInstalled: opencode.pluginInstalled,
|
|
2492
|
+
hooks: opencode.pluginInstalled ? ["plugin (gate + record)"] : []
|
|
2493
|
+
}
|
|
2494
|
+
];
|
|
2495
|
+
res.json({ agents });
|
|
2496
|
+
} catch (error) {
|
|
2497
|
+
res.status(500).json({ error: "Failed to fetch agent status" });
|
|
2498
|
+
}
|
|
2499
|
+
});
|
|
2500
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
2501
|
+
const __dirname2 = dirname(__filename2);
|
|
2502
|
+
const staticPath = join4(__dirname2, "static");
|
|
2059
2503
|
this.app.use(express.static(staticPath));
|
|
2060
2504
|
this.app.get("/{*path}", (_req, res) => {
|
|
2061
2505
|
res.sendFile(join4(staticPath, "index.html"));
|
|
@@ -2149,16 +2593,53 @@ var DashboardServer = class {
|
|
|
2149
2593
|
};
|
|
2150
2594
|
|
|
2151
2595
|
// src/cli.ts
|
|
2152
|
-
|
|
2153
|
-
|
|
2596
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2597
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2598
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
2154
2599
|
var loopDetector = null;
|
|
2155
2600
|
var undoStack = null;
|
|
2601
|
+
function extractHookSessionId(event) {
|
|
2602
|
+
if (typeof event.session_id === "string" && event.session_id) {
|
|
2603
|
+
return event.session_id;
|
|
2604
|
+
}
|
|
2605
|
+
if (process.env.CLAUDE_SESSION_ID) {
|
|
2606
|
+
return process.env.CLAUDE_SESSION_ID;
|
|
2607
|
+
}
|
|
2608
|
+
return `ppid-${process.ppid}`;
|
|
2609
|
+
}
|
|
2610
|
+
function extractRepoName(event) {
|
|
2611
|
+
const repo = event.repo;
|
|
2612
|
+
if (repo && typeof repo.name === "string") return repo.name;
|
|
2613
|
+
return null;
|
|
2614
|
+
}
|
|
2615
|
+
async function readStdinJSON() {
|
|
2616
|
+
const chunks = [];
|
|
2617
|
+
for await (const chunk of process.stdin) {
|
|
2618
|
+
chunks.push(chunk);
|
|
2619
|
+
}
|
|
2620
|
+
const data = Buffer.concat(chunks).toString("utf-8").trim();
|
|
2621
|
+
if (!data) return {};
|
|
2622
|
+
try {
|
|
2623
|
+
return JSON.parse(data);
|
|
2624
|
+
} catch {
|
|
2625
|
+
return {};
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2156
2628
|
var logo = `
|
|
2157
2629
|
\u2571BashBros \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
2158
2630
|
\u{1F91D} Your Friendly Bash Agent Helper
|
|
2159
2631
|
`;
|
|
2160
2632
|
var program = new Command();
|
|
2161
|
-
|
|
2633
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
2634
|
+
var __dirname = dirname2(__filename);
|
|
2635
|
+
var version = "0.1.3";
|
|
2636
|
+
try {
|
|
2637
|
+
const pkgPath = join5(__dirname, "..", "package.json");
|
|
2638
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
2639
|
+
version = pkg.version;
|
|
2640
|
+
} catch {
|
|
2641
|
+
}
|
|
2642
|
+
program.name("bashbros").description("The Bash Agent Helper").version(version);
|
|
2162
2643
|
program.command("init").description("Set up BashBros for your project").action(async () => {
|
|
2163
2644
|
console.log(chalk5.cyan(logo));
|
|
2164
2645
|
await runOnboarding();
|
|
@@ -2189,7 +2670,7 @@ program.command("scan").description("Scan your system and project environment").
|
|
|
2189
2670
|
console.log(bro.getSystemContext());
|
|
2190
2671
|
console.log();
|
|
2191
2672
|
console.log(chalk5.bold("\n## Agent Configurations\n"));
|
|
2192
|
-
const { formatAgentSummary } = await import("./display-
|
|
2673
|
+
const { formatAgentSummary } = await import("./display-UH7KEHOW.js");
|
|
2193
2674
|
const agents = await getAllAgentConfigs();
|
|
2194
2675
|
console.log(formatAgentSummary(agents));
|
|
2195
2676
|
console.log();
|
|
@@ -2392,7 +2873,7 @@ program.command("do <description>").description("Convert natural language to a c
|
|
|
2392
2873
|
});
|
|
2393
2874
|
program.command("models").description("List available Ollama models").action(async () => {
|
|
2394
2875
|
console.log(chalk5.cyan(logo));
|
|
2395
|
-
const { OllamaClient } = await import("./ollama-
|
|
2876
|
+
const { OllamaClient } = await import("./ollama-5JVKNFOV.js");
|
|
2396
2877
|
const ollama = new OllamaClient();
|
|
2397
2878
|
const available = await ollama.isAvailable();
|
|
2398
2879
|
if (!available) {
|
|
@@ -2560,14 +3041,14 @@ moltbotCmd.command("audit").description("Run Moltbot security audit").option("--
|
|
|
2560
3041
|
program.command("agent-info [agent]").description("Show detailed info about installed agents and their configurations").option("-r, --raw", "Show raw (redacted) config contents").action(async (agent, options) => {
|
|
2561
3042
|
console.log(chalk5.cyan(logo));
|
|
2562
3043
|
if (agent) {
|
|
2563
|
-
const validAgents = ["claude-code", "moltbot", "clawdbot", "aider", "gemini-cli", "opencode"];
|
|
3044
|
+
const validAgents = ["claude-code", "moltbot", "clawdbot", "copilot-cli", "aider", "gemini-cli", "opencode"];
|
|
2564
3045
|
if (!validAgents.includes(agent)) {
|
|
2565
3046
|
console.log(chalk5.red(`Unknown agent: ${agent}`));
|
|
2566
3047
|
console.log(chalk5.dim(`Valid agents: ${validAgents.join(", ")}`));
|
|
2567
3048
|
return;
|
|
2568
3049
|
}
|
|
2569
3050
|
const info = await getAgentConfigInfo(agent);
|
|
2570
|
-
const { formatAgentInfo: formatAgentInfo2 } = await import("./display-
|
|
3051
|
+
const { formatAgentInfo: formatAgentInfo2 } = await import("./display-UH7KEHOW.js");
|
|
2571
3052
|
console.log();
|
|
2572
3053
|
console.log(formatAgentInfo2(info));
|
|
2573
3054
|
if (options.raw && info.configExists && info.configPath) {
|
|
@@ -2599,14 +3080,60 @@ program.command("permissions").description("Show combined permissions view acros
|
|
|
2599
3080
|
console.log(formatPermissionsTable(agents));
|
|
2600
3081
|
console.log();
|
|
2601
3082
|
});
|
|
2602
|
-
program.command("gate
|
|
3083
|
+
program.command("gate [command]").description("Check if a command should be allowed (used by hooks)").option("-y, --yes", "Skip interactive prompt and block").action(async (rawInput, options) => {
|
|
3084
|
+
let command = "";
|
|
3085
|
+
let hookEvent = {};
|
|
3086
|
+
try {
|
|
3087
|
+
const chunks = [];
|
|
3088
|
+
for await (const chunk of process.stdin) {
|
|
3089
|
+
chunks.push(chunk);
|
|
3090
|
+
}
|
|
3091
|
+
const stdinData = Buffer.concat(chunks).toString("utf-8").trim();
|
|
3092
|
+
if (stdinData) {
|
|
3093
|
+
const event = JSON.parse(stdinData);
|
|
3094
|
+
hookEvent = event;
|
|
3095
|
+
if (event.tool_input?.command) {
|
|
3096
|
+
command = event.tool_input.command;
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
} catch {
|
|
3100
|
+
}
|
|
3101
|
+
if (!command && rawInput && rawInput !== "$TOOL_INPUT") {
|
|
3102
|
+
try {
|
|
3103
|
+
const parsed = JSON.parse(rawInput);
|
|
3104
|
+
if (parsed?.command) command = parsed.command;
|
|
3105
|
+
} catch {
|
|
3106
|
+
command = rawInput;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
if (!command) {
|
|
3110
|
+
process.exit(0);
|
|
3111
|
+
}
|
|
2603
3112
|
const result = await gateCommand(command);
|
|
2604
3113
|
if (!result.allowed) {
|
|
3114
|
+
try {
|
|
3115
|
+
const { DashboardWriter: DashboardWriter2 } = await import("./writer-3NAVABN6.js");
|
|
3116
|
+
const { RiskScorer: RiskScorer2 } = await import("./risk-scorer-Y6KF2XCZ.js");
|
|
3117
|
+
const scorer = new RiskScorer2();
|
|
3118
|
+
const risk = scorer.score(command);
|
|
3119
|
+
const writer = new DashboardWriter2();
|
|
3120
|
+
const hookSessionId = extractHookSessionId(hookEvent);
|
|
3121
|
+
writer.ensureHookSession(hookSessionId, process.cwd(), extractRepoName(hookEvent));
|
|
3122
|
+
writer.recordCommand(
|
|
3123
|
+
command,
|
|
3124
|
+
false,
|
|
3125
|
+
risk,
|
|
3126
|
+
[{ type: "command", rule: "gate", message: result.reason || "Blocked" }],
|
|
3127
|
+
0
|
|
3128
|
+
);
|
|
3129
|
+
writer.close();
|
|
3130
|
+
} catch {
|
|
3131
|
+
}
|
|
2605
3132
|
if (process.stdin.isTTY && !options.yes) {
|
|
2606
3133
|
const { allowForSession: allowForSession2 } = await import("./session-Y4MICATZ.js");
|
|
2607
|
-
const { readFileSync:
|
|
3134
|
+
const { readFileSync: readFileSync6, writeFileSync: writeFileSync5 } = await import("fs");
|
|
2608
3135
|
const { parse: parse4, stringify: stringify5 } = await import("yaml");
|
|
2609
|
-
const { findConfig: findConfig2 } = await import("./config-
|
|
3136
|
+
const { findConfig: findConfig2 } = await import("./config-I5NCK3RJ.js");
|
|
2610
3137
|
console.error();
|
|
2611
3138
|
console.error(chalk5.red("\u{1F6E1}\uFE0F BashBros blocked a command"));
|
|
2612
3139
|
console.error();
|
|
@@ -2647,7 +3174,7 @@ program.command("gate <command>").description("Check if a command should be allo
|
|
|
2647
3174
|
try {
|
|
2648
3175
|
const configPath = findConfig2();
|
|
2649
3176
|
if (configPath) {
|
|
2650
|
-
const content =
|
|
3177
|
+
const content = readFileSync6(configPath, "utf-8");
|
|
2651
3178
|
const config = parse4(content);
|
|
2652
3179
|
if (!config.commands) config.commands = { allow: [], block: [] };
|
|
2653
3180
|
if (!config.commands.allow) config.commands.allow = [];
|
|
@@ -2675,15 +3202,27 @@ program.command("gate <command>").description("Check if a command should be allo
|
|
|
2675
3202
|
process.exit(0);
|
|
2676
3203
|
});
|
|
2677
3204
|
program.command("record-tool").description("Record a Claude Code tool execution (used by hooks)").option("--marker <marker>", "Hook marker (ignored, used for identification)").action(async () => {
|
|
2678
|
-
const
|
|
3205
|
+
const cfg = loadConfig();
|
|
3206
|
+
let eventJson = "";
|
|
3207
|
+
try {
|
|
3208
|
+
const chunks = [];
|
|
3209
|
+
for await (const chunk of process.stdin) {
|
|
3210
|
+
chunks.push(chunk);
|
|
3211
|
+
}
|
|
3212
|
+
eventJson = Buffer.concat(chunks).toString("utf-8").trim();
|
|
3213
|
+
} catch {
|
|
3214
|
+
}
|
|
2679
3215
|
if (!eventJson) {
|
|
2680
3216
|
return;
|
|
2681
3217
|
}
|
|
2682
3218
|
try {
|
|
2683
3219
|
const event = JSON.parse(eventJson);
|
|
2684
3220
|
const events = Array.isArray(event) ? event : [event];
|
|
2685
|
-
const { DashboardWriter: DashboardWriter2 } = await import("./writer-
|
|
3221
|
+
const { DashboardWriter: DashboardWriter2 } = await import("./writer-3NAVABN6.js");
|
|
2686
3222
|
const writer = new DashboardWriter2();
|
|
3223
|
+
const firstEvent = events[0] || {};
|
|
3224
|
+
const hookSessionId = extractHookSessionId(firstEvent);
|
|
3225
|
+
writer.ensureHookSession(hookSessionId, firstEvent.cwd || process.cwd(), extractRepoName(firstEvent));
|
|
2687
3226
|
for (const evt of events) {
|
|
2688
3227
|
const toolName = evt.tool_name || evt.tool || "unknown";
|
|
2689
3228
|
const toolInput = evt.tool_input || evt.input || {};
|
|
@@ -2720,6 +3259,19 @@ program.command("record-tool").description("Record a Claude Code tool execution
|
|
|
2720
3259
|
repoName,
|
|
2721
3260
|
repoPath
|
|
2722
3261
|
});
|
|
3262
|
+
if (cfg.outputScanning.enabled && outputStr) {
|
|
3263
|
+
try {
|
|
3264
|
+
const scanner = new OutputScanner(cfg.outputScanning);
|
|
3265
|
+
const scanResult = scanner.scan(outputStr);
|
|
3266
|
+
if (scanResult.hasSecrets) {
|
|
3267
|
+
for (const f of scanResult.findings.filter((f2) => f2.type === "secret")) {
|
|
3268
|
+
process.stderr.write(`[BashBros] Output warning (${toolName}): ${f.message}
|
|
3269
|
+
`);
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
} catch {
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
2723
3275
|
const preview = inputStr.substring(0, 40).replace(/\n/g, " ");
|
|
2724
3276
|
console.log(`[BashBros] ${toolName}: ${preview}${inputStr.length > 40 ? "..." : ""}`);
|
|
2725
3277
|
}
|
|
@@ -2728,61 +3280,238 @@ program.command("record-tool").description("Record a Claude Code tool execution
|
|
|
2728
3280
|
console.error(`[BashBros] Error recording tool: ${e instanceof Error ? e.message : e}`);
|
|
2729
3281
|
}
|
|
2730
3282
|
});
|
|
2731
|
-
program.command("record
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
3283
|
+
program.command("record [rawInput]").description("Record a command execution (used by hooks)").option("-o, --output <output>", "Command output").option("-e, --exit-code <code>", "Exit code", "0").action(async (rawInput, options) => {
|
|
3284
|
+
let command = "";
|
|
3285
|
+
let stdinData = "";
|
|
3286
|
+
let hookEvent = {};
|
|
3287
|
+
try {
|
|
3288
|
+
const chunks = [];
|
|
3289
|
+
for await (const chunk of process.stdin) {
|
|
3290
|
+
chunks.push(chunk);
|
|
3291
|
+
}
|
|
3292
|
+
stdinData = Buffer.concat(chunks).toString("utf-8").trim();
|
|
3293
|
+
if (stdinData) {
|
|
3294
|
+
const event = JSON.parse(stdinData);
|
|
3295
|
+
hookEvent = event;
|
|
3296
|
+
if (event.tool_input?.command) {
|
|
3297
|
+
command = event.tool_input.command;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
} catch {
|
|
3301
|
+
}
|
|
3302
|
+
if (!command && rawInput && rawInput !== "$TOOL_INPUT") {
|
|
3303
|
+
try {
|
|
3304
|
+
const parsed = JSON.parse(rawInput);
|
|
3305
|
+
if (parsed?.command) command = parsed.command;
|
|
3306
|
+
} catch {
|
|
3307
|
+
command = rawInput;
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
if (!command) return;
|
|
3311
|
+
const cfg = loadConfig();
|
|
3312
|
+
if (!loopDetector) loopDetector = new LoopDetector(cfg.loopDetection);
|
|
2736
3313
|
const scorer = new RiskScorer();
|
|
2737
3314
|
const risk = scorer.score(command);
|
|
2738
|
-
metricsCollector.record({
|
|
2739
|
-
command,
|
|
2740
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
2741
|
-
duration: 0,
|
|
2742
|
-
// Not available in hook
|
|
2743
|
-
allowed: true,
|
|
2744
|
-
riskScore: risk,
|
|
2745
|
-
violations: [],
|
|
2746
|
-
exitCode: parseInt(options.exitCode) || 0
|
|
2747
|
-
});
|
|
2748
|
-
costEstimator.recordToolCall(command, options.output || "");
|
|
2749
3315
|
const loopAlert = loopDetector.check(command);
|
|
2750
3316
|
if (loopAlert) {
|
|
2751
3317
|
console.error(chalk5.yellow(`\u26A0 Loop detected: ${loopAlert.message}`));
|
|
2752
3318
|
}
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
3319
|
+
try {
|
|
3320
|
+
const { DashboardWriter: DashboardWriter2 } = await import("./writer-3NAVABN6.js");
|
|
3321
|
+
const writer = new DashboardWriter2();
|
|
3322
|
+
const hookSessionId = extractHookSessionId(hookEvent);
|
|
3323
|
+
writer.ensureHookSession(hookSessionId, process.cwd(), extractRepoName(hookEvent));
|
|
3324
|
+
writer.recordCommand(
|
|
3325
|
+
command,
|
|
3326
|
+
true,
|
|
3327
|
+
// allowed (if we're recording post-execution, it was allowed)
|
|
3328
|
+
risk,
|
|
3329
|
+
[],
|
|
3330
|
+
// no violations (it passed the gate)
|
|
3331
|
+
0
|
|
3332
|
+
// duration not available in hook mode
|
|
3333
|
+
);
|
|
3334
|
+
writer.close();
|
|
3335
|
+
} catch (e) {
|
|
3336
|
+
console.error(`[BashBros] Error persisting record: ${e instanceof Error ? e.message : e}`);
|
|
3337
|
+
}
|
|
3338
|
+
if (cfg.outputScanning.enabled && stdinData) {
|
|
3339
|
+
try {
|
|
3340
|
+
const event = JSON.parse(stdinData);
|
|
3341
|
+
const toolOutput = event.tool_output;
|
|
3342
|
+
let outputStr = "";
|
|
3343
|
+
if (typeof toolOutput === "string") {
|
|
3344
|
+
outputStr = toolOutput;
|
|
3345
|
+
} else if (typeof toolOutput === "object" && toolOutput !== null) {
|
|
3346
|
+
outputStr = (toolOutput.stdout || "") + (toolOutput.stderr || "");
|
|
3347
|
+
}
|
|
3348
|
+
if (outputStr) {
|
|
3349
|
+
const scanner = new OutputScanner(cfg.outputScanning);
|
|
3350
|
+
const scanResult = scanner.scan(outputStr);
|
|
3351
|
+
if (scanResult.hasSecrets) {
|
|
3352
|
+
for (const f of scanResult.findings.filter((f2) => f2.type === "secret")) {
|
|
3353
|
+
process.stderr.write(`[BashBros] Output warning: ${f.message}
|
|
3354
|
+
`);
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
if (scanResult.hasErrors) {
|
|
3358
|
+
for (const f of scanResult.findings.filter((f2) => f2.type === "error")) {
|
|
3359
|
+
process.stderr.write(`[BashBros] Output notice: ${f.message}
|
|
3360
|
+
`);
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
} catch {
|
|
3365
|
+
}
|
|
2757
3366
|
}
|
|
2758
3367
|
});
|
|
2759
|
-
program.command("session-end").description("Generate session report (used by hooks)").option("-f, --format <format>", "Output format (text, markdown, json)", "text").action((options) => {
|
|
2760
|
-
|
|
3368
|
+
program.command("session-end").description("Generate session report (used by hooks)").option("-f, --format <format>", "Output format (text, markdown, json)", "text").action(async (options) => {
|
|
3369
|
+
let hookSessionId = null;
|
|
3370
|
+
try {
|
|
3371
|
+
const chunks = [];
|
|
3372
|
+
for await (const chunk of process.stdin) {
|
|
3373
|
+
chunks.push(chunk);
|
|
3374
|
+
}
|
|
3375
|
+
const stdinData = Buffer.concat(chunks).toString("utf-8").trim();
|
|
3376
|
+
if (stdinData) {
|
|
3377
|
+
const event = JSON.parse(stdinData);
|
|
3378
|
+
hookSessionId = extractHookSessionId(event);
|
|
3379
|
+
}
|
|
3380
|
+
} catch {
|
|
3381
|
+
}
|
|
3382
|
+
try {
|
|
3383
|
+
const { DashboardWriter: DashboardWriter2 } = await import("./writer-3NAVABN6.js");
|
|
3384
|
+
const writer = new DashboardWriter2();
|
|
3385
|
+
if (hookSessionId) {
|
|
3386
|
+
writer.endHookSession(hookSessionId);
|
|
3387
|
+
}
|
|
3388
|
+
const db = writer.getDB();
|
|
3389
|
+
const since = new Date(Date.now() - 24 * 60 * 60 * 1e3);
|
|
3390
|
+
const commands = db.getCommands({ since, limit: 1e4 });
|
|
3391
|
+
if (commands.length > 0) {
|
|
3392
|
+
const startTime = commands[commands.length - 1].timestamp;
|
|
3393
|
+
const endTime = commands[0].timestamp;
|
|
3394
|
+
const duration = endTime.getTime() - startTime.getTime();
|
|
3395
|
+
const cmdFreq = /* @__PURE__ */ new Map();
|
|
3396
|
+
for (const cmd of commands) {
|
|
3397
|
+
const base = cmd.command.split(/\s+/)[0];
|
|
3398
|
+
cmdFreq.set(base, (cmdFreq.get(base) || 0) + 1);
|
|
3399
|
+
}
|
|
3400
|
+
const topCommands = [...cmdFreq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
3401
|
+
const violationsByType = {};
|
|
3402
|
+
for (const cmd of commands) {
|
|
3403
|
+
for (const v of cmd.violations) {
|
|
3404
|
+
violationsByType[v] = (violationsByType[v] || 0) + 1;
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
const metrics = {
|
|
3408
|
+
sessionId: "hook-session",
|
|
3409
|
+
startTime,
|
|
3410
|
+
endTime,
|
|
3411
|
+
duration,
|
|
3412
|
+
commandCount: commands.length,
|
|
3413
|
+
blockedCount: commands.filter((c) => !c.allowed).length,
|
|
3414
|
+
uniqueCommands: cmdFreq.size,
|
|
3415
|
+
topCommands,
|
|
3416
|
+
riskDistribution: {
|
|
3417
|
+
safe: commands.filter((c) => c.riskLevel === "safe").length,
|
|
3418
|
+
caution: commands.filter((c) => c.riskLevel === "caution").length,
|
|
3419
|
+
dangerous: commands.filter((c) => c.riskLevel === "dangerous").length,
|
|
3420
|
+
critical: commands.filter((c) => c.riskLevel === "critical").length
|
|
3421
|
+
},
|
|
3422
|
+
avgRiskScore: commands.reduce((sum, c) => sum + c.riskScore, 0) / commands.length,
|
|
3423
|
+
avgExecutionTime: commands.reduce((sum, c) => sum + c.durationMs, 0) / commands.length,
|
|
3424
|
+
totalExecutionTime: commands.reduce((sum, c) => sum + c.durationMs, 0),
|
|
3425
|
+
filesModified: [],
|
|
3426
|
+
pathsAccessed: [],
|
|
3427
|
+
violationsByType
|
|
3428
|
+
};
|
|
3429
|
+
const toolUses = db.getToolUses({ since, limit: 1e4 });
|
|
3430
|
+
const estimator = new CostEstimator();
|
|
3431
|
+
for (const use of toolUses) {
|
|
3432
|
+
estimator.recordToolCall(use.toolInput, use.toolOutput);
|
|
3433
|
+
}
|
|
3434
|
+
const report = ReportGenerator.generate(metrics, estimator.getEstimate(), { format: options.format });
|
|
3435
|
+
console.log();
|
|
3436
|
+
console.log(report);
|
|
3437
|
+
console.log();
|
|
3438
|
+
writer.close();
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
writer.close();
|
|
3442
|
+
} catch {
|
|
2761
3443
|
console.log(chalk5.dim("No session data to report."));
|
|
2762
|
-
return;
|
|
2763
3444
|
}
|
|
2764
|
-
const metrics = metricsCollector.getMetrics();
|
|
2765
|
-
const cost = costEstimator?.getEstimate();
|
|
2766
|
-
const report = ReportGenerator.generate(metrics, cost, { format: options.format });
|
|
2767
|
-
console.log();
|
|
2768
|
-
console.log(report);
|
|
2769
|
-
console.log();
|
|
2770
3445
|
});
|
|
2771
|
-
program.command("report").description("Generate a session report").option("-f, --format <format>", "Output format (text, markdown, json)", "text").option("--no-cost", "Hide cost estimate").option("--no-risk", "Hide risk distribution").action((options) => {
|
|
2772
|
-
|
|
3446
|
+
program.command("report").description("Generate a session report").option("-f, --format <format>", "Output format (text, markdown, json)", "text").option("--no-cost", "Hide cost estimate").option("--no-risk", "Hide risk distribution").action(async (options) => {
|
|
3447
|
+
try {
|
|
3448
|
+
const { DashboardWriter: DashboardWriter2 } = await import("./writer-3NAVABN6.js");
|
|
3449
|
+
const writer = new DashboardWriter2();
|
|
3450
|
+
const db = writer.getDB();
|
|
3451
|
+
const since = new Date(Date.now() - 24 * 60 * 60 * 1e3);
|
|
3452
|
+
const commands = db.getCommands({ since, limit: 1e4 });
|
|
3453
|
+
if (commands.length > 0) {
|
|
3454
|
+
const startTime = commands[commands.length - 1].timestamp;
|
|
3455
|
+
const endTime = commands[0].timestamp;
|
|
3456
|
+
const duration = endTime.getTime() - startTime.getTime();
|
|
3457
|
+
const cmdFreq = /* @__PURE__ */ new Map();
|
|
3458
|
+
for (const cmd of commands) {
|
|
3459
|
+
const base = cmd.command.split(/\s+/)[0];
|
|
3460
|
+
cmdFreq.set(base, (cmdFreq.get(base) || 0) + 1);
|
|
3461
|
+
}
|
|
3462
|
+
const topCommands = [...cmdFreq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
3463
|
+
const violationsByType = {};
|
|
3464
|
+
for (const cmd of commands) {
|
|
3465
|
+
for (const v of cmd.violations) {
|
|
3466
|
+
violationsByType[v] = (violationsByType[v] || 0) + 1;
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
const metrics = {
|
|
3470
|
+
sessionId: "hook-session",
|
|
3471
|
+
startTime,
|
|
3472
|
+
endTime,
|
|
3473
|
+
duration,
|
|
3474
|
+
commandCount: commands.length,
|
|
3475
|
+
blockedCount: commands.filter((c) => !c.allowed).length,
|
|
3476
|
+
uniqueCommands: cmdFreq.size,
|
|
3477
|
+
topCommands,
|
|
3478
|
+
riskDistribution: {
|
|
3479
|
+
safe: commands.filter((c) => c.riskLevel === "safe").length,
|
|
3480
|
+
caution: commands.filter((c) => c.riskLevel === "caution").length,
|
|
3481
|
+
dangerous: commands.filter((c) => c.riskLevel === "dangerous").length,
|
|
3482
|
+
critical: commands.filter((c) => c.riskLevel === "critical").length
|
|
3483
|
+
},
|
|
3484
|
+
avgRiskScore: commands.reduce((sum, c) => sum + c.riskScore, 0) / commands.length,
|
|
3485
|
+
avgExecutionTime: commands.reduce((sum, c) => sum + c.durationMs, 0) / commands.length,
|
|
3486
|
+
totalExecutionTime: commands.reduce((sum, c) => sum + c.durationMs, 0),
|
|
3487
|
+
filesModified: [],
|
|
3488
|
+
pathsAccessed: [],
|
|
3489
|
+
violationsByType
|
|
3490
|
+
};
|
|
3491
|
+
let cost;
|
|
3492
|
+
if (options.cost) {
|
|
3493
|
+
const toolUses = db.getToolUses({ since, limit: 1e4 });
|
|
3494
|
+
const estimator = new CostEstimator();
|
|
3495
|
+
for (const use of toolUses) {
|
|
3496
|
+
estimator.recordToolCall(use.toolInput, use.toolOutput);
|
|
3497
|
+
}
|
|
3498
|
+
cost = estimator.getEstimate();
|
|
3499
|
+
}
|
|
3500
|
+
const report = ReportGenerator.generate(metrics, cost, {
|
|
3501
|
+
format: options.format,
|
|
3502
|
+
showCost: options.cost,
|
|
3503
|
+
showRisk: options.risk
|
|
3504
|
+
});
|
|
3505
|
+
console.log();
|
|
3506
|
+
console.log(report);
|
|
3507
|
+
console.log();
|
|
3508
|
+
writer.close();
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
writer.close();
|
|
3512
|
+
} catch {
|
|
2773
3513
|
console.log(chalk5.dim("No session data. Run some commands first."));
|
|
2774
|
-
return;
|
|
2775
3514
|
}
|
|
2776
|
-
const metrics = metricsCollector.getMetrics();
|
|
2777
|
-
const cost = options.cost ? costEstimator?.getEstimate() : void 0;
|
|
2778
|
-
const report = ReportGenerator.generate(metrics, cost, {
|
|
2779
|
-
format: options.format,
|
|
2780
|
-
showCost: options.cost,
|
|
2781
|
-
showRisk: options.risk
|
|
2782
|
-
});
|
|
2783
|
-
console.log();
|
|
2784
|
-
console.log(report);
|
|
2785
|
-
console.log();
|
|
2786
3515
|
});
|
|
2787
3516
|
program.command("risk <command>").description("Score a command for security risk").action((command) => {
|
|
2788
3517
|
const scorer = new RiskScorer();
|
|
@@ -2998,5 +3727,257 @@ patternsCmd.command("test <text>").description("Test if text matches any detecti
|
|
|
2998
3727
|
}
|
|
2999
3728
|
console.log();
|
|
3000
3729
|
});
|
|
3730
|
+
var geminiCmd = program.command("gemini").description("Manage Gemini CLI integration");
|
|
3731
|
+
geminiCmd.command("install").description("Install BashBros hooks into Gemini CLI").action(async () => {
|
|
3732
|
+
const { GeminiCLIHooks: GeminiCLIHooks2 } = await import("./gemini-cli-3563EELZ.js");
|
|
3733
|
+
const result = GeminiCLIHooks2.install();
|
|
3734
|
+
if (result.success) {
|
|
3735
|
+
console.log(chalk5.green("\u2713"), result.message);
|
|
3736
|
+
} else {
|
|
3737
|
+
console.log(chalk5.red("\u2717"), result.message);
|
|
3738
|
+
process.exit(1);
|
|
3739
|
+
}
|
|
3740
|
+
});
|
|
3741
|
+
geminiCmd.command("uninstall").description("Remove BashBros hooks from Gemini CLI").action(async () => {
|
|
3742
|
+
const { GeminiCLIHooks: GeminiCLIHooks2 } = await import("./gemini-cli-3563EELZ.js");
|
|
3743
|
+
const result = GeminiCLIHooks2.uninstall();
|
|
3744
|
+
if (result.success) {
|
|
3745
|
+
console.log(chalk5.green("\u2713"), result.message);
|
|
3746
|
+
} else {
|
|
3747
|
+
console.log(chalk5.red("\u2717"), result.message);
|
|
3748
|
+
process.exit(1);
|
|
3749
|
+
}
|
|
3750
|
+
});
|
|
3751
|
+
geminiCmd.command("status").description("Check Gemini CLI integration status").action(async () => {
|
|
3752
|
+
const { GeminiCLIHooks: GeminiCLIHooks2 } = await import("./gemini-cli-3563EELZ.js");
|
|
3753
|
+
const status = GeminiCLIHooks2.getStatus();
|
|
3754
|
+
console.log();
|
|
3755
|
+
console.log(chalk5.bold("Gemini CLI Integration Status"));
|
|
3756
|
+
console.log();
|
|
3757
|
+
console.log(` Gemini CLI: ${status.geminiInstalled ? chalk5.green("detected") : chalk5.yellow("not found")}`);
|
|
3758
|
+
console.log(` BashBros hooks: ${status.hooksInstalled ? chalk5.green("active") : chalk5.dim("not installed")}`);
|
|
3759
|
+
if (status.hooks.length > 0) {
|
|
3760
|
+
console.log(` Active hooks: ${status.hooks.join(", ")}`);
|
|
3761
|
+
}
|
|
3762
|
+
console.log(chalk5.dim(" Scope: project (.gemini/settings.json)"));
|
|
3763
|
+
console.log();
|
|
3764
|
+
});
|
|
3765
|
+
var copilotCmd = program.command("copilot").description("Manage GitHub Copilot CLI integration");
|
|
3766
|
+
copilotCmd.command("install").description("Install BashBros hooks into Copilot CLI").action(async () => {
|
|
3767
|
+
const { CopilotCLIHooks: CopilotCLIHooks2 } = await import("./copilot-cli-5WJWK5YT.js");
|
|
3768
|
+
const result = CopilotCLIHooks2.install();
|
|
3769
|
+
if (result.success) {
|
|
3770
|
+
console.log(chalk5.green("\u2713"), result.message);
|
|
3771
|
+
} else {
|
|
3772
|
+
console.log(chalk5.red("\u2717"), result.message);
|
|
3773
|
+
process.exit(1);
|
|
3774
|
+
}
|
|
3775
|
+
});
|
|
3776
|
+
copilotCmd.command("uninstall").description("Remove BashBros hooks from Copilot CLI").action(async () => {
|
|
3777
|
+
const { CopilotCLIHooks: CopilotCLIHooks2 } = await import("./copilot-cli-5WJWK5YT.js");
|
|
3778
|
+
const result = CopilotCLIHooks2.uninstall();
|
|
3779
|
+
if (result.success) {
|
|
3780
|
+
console.log(chalk5.green("\u2713"), result.message);
|
|
3781
|
+
} else {
|
|
3782
|
+
console.log(chalk5.red("\u2717"), result.message);
|
|
3783
|
+
process.exit(1);
|
|
3784
|
+
}
|
|
3785
|
+
});
|
|
3786
|
+
copilotCmd.command("status").description("Check Copilot CLI integration status").action(async () => {
|
|
3787
|
+
const { CopilotCLIHooks: CopilotCLIHooks2 } = await import("./copilot-cli-5WJWK5YT.js");
|
|
3788
|
+
const status = CopilotCLIHooks2.getStatus();
|
|
3789
|
+
console.log();
|
|
3790
|
+
console.log(chalk5.bold("Copilot CLI Integration Status"));
|
|
3791
|
+
console.log();
|
|
3792
|
+
console.log(` Copilot CLI: ${status.copilotInstalled ? chalk5.green("detected") : chalk5.yellow("not found")}`);
|
|
3793
|
+
console.log(` BashBros hooks: ${status.hooksInstalled ? chalk5.green("active") : chalk5.dim("not installed")}`);
|
|
3794
|
+
if (status.hooks.length > 0) {
|
|
3795
|
+
console.log(` Active hooks: ${status.hooks.join(", ")}`);
|
|
3796
|
+
}
|
|
3797
|
+
console.log(chalk5.dim(" Scope: project (.github/hooks/bashbros.json)"));
|
|
3798
|
+
console.log();
|
|
3799
|
+
});
|
|
3800
|
+
var opencodeCmd = program.command("opencode").description("Manage OpenCode integration");
|
|
3801
|
+
opencodeCmd.command("install").description("Install BashBros plugin into OpenCode").action(async () => {
|
|
3802
|
+
const { OpenCodeHooks: OpenCodeHooks2 } = await import("./opencode-DRCY275R.js");
|
|
3803
|
+
const result = OpenCodeHooks2.install();
|
|
3804
|
+
if (result.success) {
|
|
3805
|
+
console.log(chalk5.green("\u2713"), result.message);
|
|
3806
|
+
} else {
|
|
3807
|
+
console.log(chalk5.red("\u2717"), result.message);
|
|
3808
|
+
process.exit(1);
|
|
3809
|
+
}
|
|
3810
|
+
});
|
|
3811
|
+
opencodeCmd.command("uninstall").description("Remove BashBros plugin from OpenCode").action(async () => {
|
|
3812
|
+
const { OpenCodeHooks: OpenCodeHooks2 } = await import("./opencode-DRCY275R.js");
|
|
3813
|
+
const result = OpenCodeHooks2.uninstall();
|
|
3814
|
+
if (result.success) {
|
|
3815
|
+
console.log(chalk5.green("\u2713"), result.message);
|
|
3816
|
+
} else {
|
|
3817
|
+
console.log(chalk5.red("\u2717"), result.message);
|
|
3818
|
+
process.exit(1);
|
|
3819
|
+
}
|
|
3820
|
+
});
|
|
3821
|
+
opencodeCmd.command("status").description("Check OpenCode integration status").action(async () => {
|
|
3822
|
+
const { OpenCodeHooks: OpenCodeHooks2 } = await import("./opencode-DRCY275R.js");
|
|
3823
|
+
const status = OpenCodeHooks2.getStatus();
|
|
3824
|
+
console.log();
|
|
3825
|
+
console.log(chalk5.bold("OpenCode Integration Status"));
|
|
3826
|
+
console.log();
|
|
3827
|
+
console.log(` OpenCode: ${status.openCodeInstalled ? chalk5.green("detected") : chalk5.yellow("not found")}`);
|
|
3828
|
+
console.log(` BashBros plugin: ${status.pluginInstalled ? chalk5.green("active") : chalk5.dim("not installed")}`);
|
|
3829
|
+
console.log(chalk5.dim(" Scope: project (.opencode/plugins/bashbros.ts)"));
|
|
3830
|
+
console.log();
|
|
3831
|
+
});
|
|
3832
|
+
program.command("gemini-gate").description("Gate command for Gemini CLI hooks (internal)").action(async () => {
|
|
3833
|
+
const event = await readStdinJSON();
|
|
3834
|
+
const toolInput = event.tool_input;
|
|
3835
|
+
const command = typeof toolInput?.command === "string" ? toolInput.command : "";
|
|
3836
|
+
if (!command) {
|
|
3837
|
+
console.log(JSON.stringify({ decision: "allow" }));
|
|
3838
|
+
process.exit(0);
|
|
3839
|
+
}
|
|
3840
|
+
const result = await gateCommand(command);
|
|
3841
|
+
if (!result.allowed) {
|
|
3842
|
+
try {
|
|
3843
|
+
const { DashboardWriter: DashboardWriter2 } = await import("./writer-3NAVABN6.js");
|
|
3844
|
+
const writer = new DashboardWriter2();
|
|
3845
|
+
const hookSessionId = extractHookSessionId(event);
|
|
3846
|
+
writer.ensureHookSession(hookSessionId, process.cwd(), extractRepoName(event));
|
|
3847
|
+
const scorer = new RiskScorer();
|
|
3848
|
+
const risk = scorer.score(command);
|
|
3849
|
+
writer.recordCommand(
|
|
3850
|
+
command,
|
|
3851
|
+
false,
|
|
3852
|
+
risk,
|
|
3853
|
+
[{ type: "command", rule: "gate", message: result.reason || "Blocked" }],
|
|
3854
|
+
0
|
|
3855
|
+
);
|
|
3856
|
+
writer.close();
|
|
3857
|
+
} catch {
|
|
3858
|
+
}
|
|
3859
|
+
console.log(JSON.stringify({
|
|
3860
|
+
decision: "deny",
|
|
3861
|
+
reason: result.reason || "Blocked by BashBros"
|
|
3862
|
+
}));
|
|
3863
|
+
process.exit(0);
|
|
3864
|
+
}
|
|
3865
|
+
console.log(JSON.stringify({ decision: "allow" }));
|
|
3866
|
+
process.exit(0);
|
|
3867
|
+
});
|
|
3868
|
+
program.command("gemini-record").description("Record command for Gemini CLI hooks (internal)").action(async () => {
|
|
3869
|
+
const event = await readStdinJSON();
|
|
3870
|
+
const toolInput = event.tool_input;
|
|
3871
|
+
const command = typeof toolInput?.command === "string" ? toolInput.command : "";
|
|
3872
|
+
if (!command) return;
|
|
3873
|
+
const cfg = loadConfig();
|
|
3874
|
+
if (!loopDetector) loopDetector = new LoopDetector(cfg.loopDetection);
|
|
3875
|
+
const scorer = new RiskScorer();
|
|
3876
|
+
const risk = scorer.score(command);
|
|
3877
|
+
const loopAlert = loopDetector.check(command);
|
|
3878
|
+
if (loopAlert) {
|
|
3879
|
+
process.stderr.write(`[BashBros] Loop detected: ${loopAlert.message}
|
|
3880
|
+
`);
|
|
3881
|
+
}
|
|
3882
|
+
try {
|
|
3883
|
+
const { DashboardWriter: DashboardWriter2 } = await import("./writer-3NAVABN6.js");
|
|
3884
|
+
const writer = new DashboardWriter2();
|
|
3885
|
+
const hookSessionId = extractHookSessionId(event);
|
|
3886
|
+
writer.ensureHookSession(hookSessionId, process.cwd(), extractRepoName(event));
|
|
3887
|
+
writer.recordCommand(command, true, risk, [], 0);
|
|
3888
|
+
writer.close();
|
|
3889
|
+
} catch (e) {
|
|
3890
|
+
process.stderr.write(`[BashBros] Error recording: ${e instanceof Error ? e.message : e}
|
|
3891
|
+
`);
|
|
3892
|
+
}
|
|
3893
|
+
if (cfg.outputScanning.enabled) {
|
|
3894
|
+
const toolOutput = event.tool_output;
|
|
3895
|
+
let outputStr = "";
|
|
3896
|
+
if (typeof toolOutput === "string") outputStr = toolOutput;
|
|
3897
|
+
else if (typeof toolOutput === "object" && toolOutput !== null) {
|
|
3898
|
+
outputStr = (toolOutput.stdout || "") + (toolOutput.stderr || "");
|
|
3899
|
+
}
|
|
3900
|
+
if (outputStr) {
|
|
3901
|
+
try {
|
|
3902
|
+
const scanner = new OutputScanner(cfg.outputScanning);
|
|
3903
|
+
const scanResult = scanner.scan(outputStr);
|
|
3904
|
+
if (scanResult.hasSecrets) {
|
|
3905
|
+
for (const f of scanResult.findings.filter((f2) => f2.type === "secret")) {
|
|
3906
|
+
process.stderr.write(`[BashBros] Output warning: ${f.message}
|
|
3907
|
+
`);
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
} catch {
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
});
|
|
3915
|
+
program.command("copilot-gate").description("Gate command for Copilot CLI hooks (internal)").action(async () => {
|
|
3916
|
+
const event = await readStdinJSON();
|
|
3917
|
+
const toolArgs = event.toolArgs;
|
|
3918
|
+
const command = typeof toolArgs?.command === "string" ? toolArgs.command : "";
|
|
3919
|
+
if (!command) {
|
|
3920
|
+
console.log(JSON.stringify({ permissionDecision: "allow" }));
|
|
3921
|
+
process.exit(0);
|
|
3922
|
+
}
|
|
3923
|
+
const result = await gateCommand(command);
|
|
3924
|
+
if (!result.allowed) {
|
|
3925
|
+
try {
|
|
3926
|
+
const { DashboardWriter: DashboardWriter2 } = await import("./writer-3NAVABN6.js");
|
|
3927
|
+
const writer = new DashboardWriter2();
|
|
3928
|
+
const hookSessionId = extractHookSessionId(event);
|
|
3929
|
+
writer.ensureHookSession(hookSessionId, process.cwd(), extractRepoName(event));
|
|
3930
|
+
const scorer = new RiskScorer();
|
|
3931
|
+
const risk = scorer.score(command);
|
|
3932
|
+
writer.recordCommand(
|
|
3933
|
+
command,
|
|
3934
|
+
false,
|
|
3935
|
+
risk,
|
|
3936
|
+
[{ type: "command", rule: "gate", message: result.reason || "Blocked" }],
|
|
3937
|
+
0
|
|
3938
|
+
);
|
|
3939
|
+
writer.close();
|
|
3940
|
+
} catch {
|
|
3941
|
+
}
|
|
3942
|
+
console.log(JSON.stringify({
|
|
3943
|
+
permissionDecision: "deny",
|
|
3944
|
+
permissionDecisionReason: result.reason || "Blocked by BashBros"
|
|
3945
|
+
}));
|
|
3946
|
+
process.exit(1);
|
|
3947
|
+
}
|
|
3948
|
+
console.log(JSON.stringify({ permissionDecision: "allow" }));
|
|
3949
|
+
process.exit(0);
|
|
3950
|
+
});
|
|
3951
|
+
program.command("copilot-record").description("Record command for Copilot CLI hooks (internal)").action(async () => {
|
|
3952
|
+
const event = await readStdinJSON();
|
|
3953
|
+
const toolArgs = event.toolArgs;
|
|
3954
|
+
const command = typeof toolArgs?.command === "string" ? toolArgs.command : "";
|
|
3955
|
+
if (!command) return;
|
|
3956
|
+
const cfg = loadConfig();
|
|
3957
|
+
if (!loopDetector) loopDetector = new LoopDetector(cfg.loopDetection);
|
|
3958
|
+
const scorer = new RiskScorer();
|
|
3959
|
+
const risk = scorer.score(command);
|
|
3960
|
+
const loopAlert = loopDetector.check(command);
|
|
3961
|
+
if (loopAlert) {
|
|
3962
|
+
process.stderr.write(`[BashBros] Loop detected: ${loopAlert.message}
|
|
3963
|
+
`);
|
|
3964
|
+
}
|
|
3965
|
+
try {
|
|
3966
|
+
const { DashboardWriter: DashboardWriter2 } = await import("./writer-3NAVABN6.js");
|
|
3967
|
+
const writer = new DashboardWriter2();
|
|
3968
|
+
const hookSessionId = extractHookSessionId(event);
|
|
3969
|
+
writer.ensureHookSession(hookSessionId, process.cwd(), extractRepoName(event));
|
|
3970
|
+
writer.recordCommand(command, true, risk, [], 0);
|
|
3971
|
+
writer.close();
|
|
3972
|
+
} catch (e) {
|
|
3973
|
+
process.stderr.write(`[BashBros] Error recording: ${e instanceof Error ? e.message : e}
|
|
3974
|
+
`);
|
|
3975
|
+
}
|
|
3976
|
+
});
|
|
3977
|
+
program.command("setup").description("Install BashBros hooks for multiple agents at once").action(async () => {
|
|
3978
|
+
console.log(chalk5.cyan(logo));
|
|
3979
|
+
const { runSetup } = await import("./setup-YS27MOPE.js");
|
|
3980
|
+
await runSetup();
|
|
3981
|
+
});
|
|
3001
3982
|
program.parse();
|
|
3002
3983
|
//# sourceMappingURL=cli.js.map
|