bashbros 0.1.0 → 0.1.2
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/{chunk-SB4JS3GU.js → chunk-BW6XCOJH.js} +171 -21
- package/dist/chunk-BW6XCOJH.js.map +1 -0
- package/dist/chunk-FRMAIRQ2.js +89 -0
- package/dist/chunk-FRMAIRQ2.js.map +1 -0
- package/dist/{chunk-GD5VNHIN.js → chunk-QWZGB4V3.js} +4 -85
- package/dist/chunk-QWZGB4V3.js.map +1 -0
- package/dist/{chunk-4R4GV5V2.js → chunk-SQCP6IYB.js} +2 -2
- package/dist/{chunk-43W3RVEL.js → chunk-XCZMQRSX.js} +125 -10
- package/dist/chunk-XCZMQRSX.js.map +1 -0
- package/dist/chunk-YUMNBQAY.js +766 -0
- package/dist/chunk-YUMNBQAY.js.map +1 -0
- package/dist/cli.js +633 -29
- package/dist/cli.js.map +1 -1
- package/dist/{config-CZMIGNPF.js → config-JLLOTFLI.js} +2 -2
- package/dist/{db-EHQDB5OL.js → db-OBKEXRTP.js} +2 -2
- package/dist/{display-IN4NRJJS.js → display-6LZ2HBCU.js} +3 -3
- package/dist/engine-EGPAS2EX.js +10 -0
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/session-Y4MICATZ.js +15 -0
- package/dist/session-Y4MICATZ.js.map +1 -0
- package/dist/static/index.html +1873 -276
- package/package.json +1 -1
- package/dist/chunk-43W3RVEL.js.map +0 -1
- package/dist/chunk-CSRPOGHY.js +0 -354
- package/dist/chunk-CSRPOGHY.js.map +0 -1
- package/dist/chunk-GD5VNHIN.js.map +0 -1
- package/dist/chunk-SB4JS3GU.js.map +0 -1
- package/dist/engine-PKLXW6OF.js +0 -9
- /package/dist/{chunk-4R4GV5V2.js.map → chunk-SQCP6IYB.js.map} +0 -0
- /package/dist/{config-CZMIGNPF.js.map → config-JLLOTFLI.js.map} +0 -0
- /package/dist/{db-EHQDB5OL.js.map → db-OBKEXRTP.js.map} +0 -0
- /package/dist/{display-IN4NRJJS.js.map → display-6LZ2HBCU.js.map} +0 -0
- /package/dist/{engine-PKLXW6OF.js.map → engine-EGPAS2EX.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DashboardDB
|
|
4
|
+
} from "./chunk-YUMNBQAY.js";
|
|
2
5
|
import {
|
|
3
6
|
formatAllAgentsInfo,
|
|
4
7
|
formatPermissionsTable
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SQCP6IYB.js";
|
|
6
9
|
import {
|
|
7
10
|
BashBro,
|
|
8
11
|
BashBros,
|
|
@@ -14,17 +17,18 @@ import {
|
|
|
14
17
|
UndoStack,
|
|
15
18
|
gateCommand,
|
|
16
19
|
getBashgymIntegration
|
|
17
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-XCZMQRSX.js";
|
|
18
21
|
import "./chunk-SG752FZC.js";
|
|
19
22
|
import "./chunk-DLP2O6PN.js";
|
|
20
23
|
import {
|
|
21
24
|
findConfig,
|
|
22
25
|
getDefaultConfig,
|
|
23
26
|
loadConfig
|
|
24
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-BW6XCOJH.js";
|
|
28
|
+
import "./chunk-QWZGB4V3.js";
|
|
25
29
|
import {
|
|
26
30
|
allowForSession
|
|
27
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-FRMAIRQ2.js";
|
|
28
32
|
import {
|
|
29
33
|
RiskScorer
|
|
30
34
|
} from "./chunk-DEAF6PYM.js";
|
|
@@ -35,9 +39,6 @@ import {
|
|
|
35
39
|
import {
|
|
36
40
|
MoltbotHooks
|
|
37
41
|
} from "./chunk-J37RHCFJ.js";
|
|
38
|
-
import {
|
|
39
|
-
DashboardDB
|
|
40
|
-
} from "./chunk-CSRPOGHY.js";
|
|
41
42
|
import "./chunk-7OCVIDC7.js";
|
|
42
43
|
|
|
43
44
|
// src/cli.ts
|
|
@@ -1014,7 +1015,7 @@ var EgressPatternMatcher = class {
|
|
|
1014
1015
|
// src/policy/ward/egress.ts
|
|
1015
1016
|
var DashboardDB2 = null;
|
|
1016
1017
|
try {
|
|
1017
|
-
const dbModule = await import("./db-
|
|
1018
|
+
const dbModule = await import("./db-OBKEXRTP.js");
|
|
1018
1019
|
DashboardDB2 = dbModule.DashboardDB;
|
|
1019
1020
|
} catch {
|
|
1020
1021
|
}
|
|
@@ -1422,6 +1423,171 @@ ${passed} passed, ${failed} failed. Fix issues above.
|
|
|
1422
1423
|
|
|
1423
1424
|
// src/watch.ts
|
|
1424
1425
|
import chalk3 from "chalk";
|
|
1426
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1427
|
+
import { parse, stringify as stringify2 } from "yaml";
|
|
1428
|
+
|
|
1429
|
+
// src/dashboard/writer.ts
|
|
1430
|
+
import { homedir as homedir4 } from "os";
|
|
1431
|
+
import { join as join4 } from "path";
|
|
1432
|
+
import { mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
|
|
1433
|
+
function getDefaultDbPath() {
|
|
1434
|
+
const bashbrosDir = join4(homedir4(), ".bashbros");
|
|
1435
|
+
if (!existsSync5(bashbrosDir)) {
|
|
1436
|
+
mkdirSync2(bashbrosDir, { recursive: true });
|
|
1437
|
+
}
|
|
1438
|
+
return join4(bashbrosDir, "dashboard.db");
|
|
1439
|
+
}
|
|
1440
|
+
var DashboardWriter = class {
|
|
1441
|
+
db;
|
|
1442
|
+
sessionId = null;
|
|
1443
|
+
commandCount = 0;
|
|
1444
|
+
blockedCount = 0;
|
|
1445
|
+
totalRiskScore = 0;
|
|
1446
|
+
constructor(dbPath) {
|
|
1447
|
+
const path = dbPath ?? getDefaultDbPath();
|
|
1448
|
+
this.db = new DashboardDB(path);
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Start a new watch session
|
|
1452
|
+
*/
|
|
1453
|
+
startSession(agent, workingDir) {
|
|
1454
|
+
this.sessionId = this.db.insertSession({
|
|
1455
|
+
agent,
|
|
1456
|
+
pid: process.pid,
|
|
1457
|
+
workingDir
|
|
1458
|
+
});
|
|
1459
|
+
this.commandCount = 0;
|
|
1460
|
+
this.blockedCount = 0;
|
|
1461
|
+
this.totalRiskScore = 0;
|
|
1462
|
+
return this.sessionId;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* End the current session
|
|
1466
|
+
*/
|
|
1467
|
+
endSession() {
|
|
1468
|
+
if (!this.sessionId) return;
|
|
1469
|
+
const avgRiskScore = this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0;
|
|
1470
|
+
this.db.updateSession(this.sessionId, {
|
|
1471
|
+
endTime: /* @__PURE__ */ new Date(),
|
|
1472
|
+
status: "completed",
|
|
1473
|
+
commandCount: this.commandCount,
|
|
1474
|
+
blockedCount: this.blockedCount,
|
|
1475
|
+
avgRiskScore
|
|
1476
|
+
});
|
|
1477
|
+
this.sessionId = null;
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Mark session as crashed (for unexpected exits)
|
|
1481
|
+
*/
|
|
1482
|
+
crashSession() {
|
|
1483
|
+
if (!this.sessionId) return;
|
|
1484
|
+
const avgRiskScore = this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0;
|
|
1485
|
+
this.db.updateSession(this.sessionId, {
|
|
1486
|
+
endTime: /* @__PURE__ */ new Date(),
|
|
1487
|
+
status: "crashed",
|
|
1488
|
+
commandCount: this.commandCount,
|
|
1489
|
+
blockedCount: this.blockedCount,
|
|
1490
|
+
avgRiskScore
|
|
1491
|
+
});
|
|
1492
|
+
this.sessionId = null;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Record a command execution
|
|
1496
|
+
*/
|
|
1497
|
+
recordCommand(command, allowed, riskScore, violations, durationMs) {
|
|
1498
|
+
if (!this.sessionId) return null;
|
|
1499
|
+
const input = {
|
|
1500
|
+
sessionId: this.sessionId,
|
|
1501
|
+
command,
|
|
1502
|
+
allowed,
|
|
1503
|
+
riskScore: riskScore.score,
|
|
1504
|
+
riskLevel: riskScore.level,
|
|
1505
|
+
riskFactors: riskScore.factors,
|
|
1506
|
+
durationMs,
|
|
1507
|
+
violations: violations.map((v) => v.message)
|
|
1508
|
+
};
|
|
1509
|
+
const id = this.db.insertCommand(input);
|
|
1510
|
+
this.commandCount++;
|
|
1511
|
+
this.totalRiskScore += riskScore.score;
|
|
1512
|
+
if (!allowed) {
|
|
1513
|
+
this.blockedCount++;
|
|
1514
|
+
}
|
|
1515
|
+
if (this.commandCount % 10 === 0) {
|
|
1516
|
+
const avgRiskScore = this.totalRiskScore / this.commandCount;
|
|
1517
|
+
this.db.updateSession(this.sessionId, {
|
|
1518
|
+
commandCount: this.commandCount,
|
|
1519
|
+
blockedCount: this.blockedCount,
|
|
1520
|
+
avgRiskScore
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
return id;
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Record a Bash Bro AI event
|
|
1527
|
+
*/
|
|
1528
|
+
recordBroEvent(input) {
|
|
1529
|
+
const dbInput = {
|
|
1530
|
+
sessionId: this.sessionId ?? void 0,
|
|
1531
|
+
eventType: input.eventType,
|
|
1532
|
+
inputContext: input.inputContext,
|
|
1533
|
+
outputSummary: input.outputSummary,
|
|
1534
|
+
modelUsed: input.modelUsed,
|
|
1535
|
+
latencyMs: input.latencyMs,
|
|
1536
|
+
success: input.success
|
|
1537
|
+
};
|
|
1538
|
+
return this.db.insertBroEvent(dbInput);
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Update Bash Bro status
|
|
1542
|
+
*/
|
|
1543
|
+
updateBroStatus(status) {
|
|
1544
|
+
const dbInput = {
|
|
1545
|
+
ollamaAvailable: status.ollamaAvailable,
|
|
1546
|
+
ollamaModel: status.ollamaModel,
|
|
1547
|
+
platform: status.platform,
|
|
1548
|
+
shell: status.shell,
|
|
1549
|
+
projectType: status.projectType
|
|
1550
|
+
};
|
|
1551
|
+
return this.db.updateBroStatus(dbInput);
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Get current session ID
|
|
1555
|
+
*/
|
|
1556
|
+
getSessionId() {
|
|
1557
|
+
return this.sessionId;
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Get current session stats
|
|
1561
|
+
*/
|
|
1562
|
+
getSessionStats() {
|
|
1563
|
+
return {
|
|
1564
|
+
commandCount: this.commandCount,
|
|
1565
|
+
blockedCount: this.blockedCount,
|
|
1566
|
+
avgRiskScore: this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Close database connection
|
|
1571
|
+
*/
|
|
1572
|
+
close() {
|
|
1573
|
+
this.db.close();
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Get the underlying database instance (for advanced use)
|
|
1577
|
+
*/
|
|
1578
|
+
getDB() {
|
|
1579
|
+
return this.db;
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
// src/watch.ts
|
|
1584
|
+
var dashboardWriter = null;
|
|
1585
|
+
var riskScorer = null;
|
|
1586
|
+
function cleanup() {
|
|
1587
|
+
if (process.stdin.isTTY) {
|
|
1588
|
+
process.stdin.setRawMode(false);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1425
1591
|
async function startWatch(options) {
|
|
1426
1592
|
const configPath = findConfig();
|
|
1427
1593
|
if (!configPath) {
|
|
@@ -1435,7 +1601,24 @@ async function startWatch(options) {
|
|
|
1435
1601
|
console.log(chalk3.dim(" Press Ctrl+C to stop"));
|
|
1436
1602
|
console.log();
|
|
1437
1603
|
const bashbros = new BashBros(configPath);
|
|
1438
|
-
bashbros.
|
|
1604
|
+
const config = bashbros.getConfig();
|
|
1605
|
+
try {
|
|
1606
|
+
dashboardWriter = new DashboardWriter();
|
|
1607
|
+
riskScorer = new RiskScorer();
|
|
1608
|
+
const sessionId = dashboardWriter.startSession(config.agent, process.cwd());
|
|
1609
|
+
if (options.verbose) {
|
|
1610
|
+
console.log(chalk3.dim(` Session: ${sessionId}`));
|
|
1611
|
+
}
|
|
1612
|
+
} catch (error) {
|
|
1613
|
+
if (options.verbose) {
|
|
1614
|
+
console.log(chalk3.yellow(" Dashboard recording disabled"));
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
let pendingCommand = null;
|
|
1618
|
+
let awaitingPromptResponse = false;
|
|
1619
|
+
function showBlockedPrompt(command, violations) {
|
|
1620
|
+
pendingCommand = command;
|
|
1621
|
+
awaitingPromptResponse = true;
|
|
1439
1622
|
console.log();
|
|
1440
1623
|
console.log(chalk3.red("\u{1F6E1}\uFE0F BashBros blocked a command"));
|
|
1441
1624
|
console.log();
|
|
@@ -1443,12 +1626,72 @@ async function startWatch(options) {
|
|
|
1443
1626
|
console.log(chalk3.dim(" Reason:"), violations[0].message);
|
|
1444
1627
|
console.log(chalk3.dim(" Policy:"), violations[0].rule);
|
|
1445
1628
|
console.log();
|
|
1446
|
-
console.log(chalk3.
|
|
1447
|
-
console.log(chalk3.cyan(
|
|
1448
|
-
console.log(chalk3.cyan(
|
|
1449
|
-
console.log();
|
|
1629
|
+
console.log(chalk3.yellow(" Allow this command?"));
|
|
1630
|
+
console.log(chalk3.cyan(" [y]"), "Allow once");
|
|
1631
|
+
console.log(chalk3.cyan(" [s]"), "Allow for session");
|
|
1632
|
+
console.log(chalk3.cyan(" [p]"), "Allow permanently");
|
|
1633
|
+
console.log(chalk3.cyan(" [n]"), "Block (default)");
|
|
1634
|
+
process.stdout.write(chalk3.dim("\n Choice: "));
|
|
1635
|
+
}
|
|
1636
|
+
function handlePromptResponse(choice) {
|
|
1637
|
+
if (!pendingCommand) return;
|
|
1638
|
+
const command = pendingCommand;
|
|
1639
|
+
pendingCommand = null;
|
|
1640
|
+
awaitingPromptResponse = false;
|
|
1641
|
+
console.log(choice);
|
|
1642
|
+
switch (choice.toLowerCase()) {
|
|
1643
|
+
case "y":
|
|
1644
|
+
console.log(chalk3.green(" \u2713 Allowed once"));
|
|
1645
|
+
console.log();
|
|
1646
|
+
bashbros.write(command + "\r");
|
|
1647
|
+
break;
|
|
1648
|
+
case "s":
|
|
1649
|
+
allowForSession(command);
|
|
1650
|
+
console.log(chalk3.green(" \u2713 Allowed for session"));
|
|
1651
|
+
console.log();
|
|
1652
|
+
bashbros.write(command + "\r");
|
|
1653
|
+
break;
|
|
1654
|
+
case "p":
|
|
1655
|
+
try {
|
|
1656
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
1657
|
+
const config2 = parse(content);
|
|
1658
|
+
if (!config2.commands) {
|
|
1659
|
+
config2.commands = { allow: [], block: [] };
|
|
1660
|
+
}
|
|
1661
|
+
if (!config2.commands.allow) {
|
|
1662
|
+
config2.commands.allow = [];
|
|
1663
|
+
}
|
|
1664
|
+
if (!config2.commands.allow.includes(command)) {
|
|
1665
|
+
config2.commands.allow.push(command);
|
|
1666
|
+
writeFileSync2(configPath, stringify2(config2));
|
|
1667
|
+
}
|
|
1668
|
+
console.log(chalk3.green(" \u2713 Added to allowlist permanently"));
|
|
1669
|
+
console.log();
|
|
1670
|
+
bashbros.write(command + "\r");
|
|
1671
|
+
} catch (error) {
|
|
1672
|
+
console.log(chalk3.red(" \u2717 Failed to update config"));
|
|
1673
|
+
console.log();
|
|
1674
|
+
}
|
|
1675
|
+
break;
|
|
1676
|
+
case "n":
|
|
1677
|
+
default:
|
|
1678
|
+
console.log(chalk3.yellow(" \u2717 Blocked"));
|
|
1679
|
+
console.log();
|
|
1680
|
+
break;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
bashbros.on("blocked", (command, violations) => {
|
|
1684
|
+
if (dashboardWriter && riskScorer) {
|
|
1685
|
+
const risk = riskScorer.score(command);
|
|
1686
|
+
dashboardWriter.recordCommand(command, false, risk, violations, 0);
|
|
1687
|
+
}
|
|
1688
|
+
showBlockedPrompt(command, violations);
|
|
1450
1689
|
});
|
|
1451
1690
|
bashbros.on("allowed", (result) => {
|
|
1691
|
+
if (dashboardWriter && riskScorer) {
|
|
1692
|
+
const risk = riskScorer.score(result.command);
|
|
1693
|
+
dashboardWriter.recordCommand(result.command, true, risk, [], result.duration);
|
|
1694
|
+
}
|
|
1452
1695
|
if (options.verbose) {
|
|
1453
1696
|
console.log(chalk3.green("\u2713"), chalk3.dim(result.command));
|
|
1454
1697
|
}
|
|
@@ -1459,25 +1702,119 @@ async function startWatch(options) {
|
|
|
1459
1702
|
bashbros.on("error", (error) => {
|
|
1460
1703
|
console.error(chalk3.red("Error:"), error.message);
|
|
1461
1704
|
});
|
|
1705
|
+
bashbros.on("exit", (exitCode) => {
|
|
1706
|
+
if (dashboardWriter) {
|
|
1707
|
+
dashboardWriter.endSession();
|
|
1708
|
+
dashboardWriter.close();
|
|
1709
|
+
}
|
|
1710
|
+
cleanup();
|
|
1711
|
+
process.exit(exitCode ?? 0);
|
|
1712
|
+
});
|
|
1462
1713
|
process.on("SIGINT", () => {
|
|
1714
|
+
cleanup();
|
|
1463
1715
|
console.log();
|
|
1464
1716
|
console.log(chalk3.yellow("Stopping BashBros..."));
|
|
1717
|
+
if (dashboardWriter) {
|
|
1718
|
+
dashboardWriter.endSession();
|
|
1719
|
+
dashboardWriter.close();
|
|
1720
|
+
}
|
|
1465
1721
|
bashbros.stop();
|
|
1466
1722
|
process.exit(0);
|
|
1467
1723
|
});
|
|
1468
1724
|
process.on("SIGTERM", () => {
|
|
1725
|
+
cleanup();
|
|
1726
|
+
if (dashboardWriter) {
|
|
1727
|
+
dashboardWriter.endSession();
|
|
1728
|
+
dashboardWriter.close();
|
|
1729
|
+
}
|
|
1469
1730
|
bashbros.stop();
|
|
1470
1731
|
process.exit(0);
|
|
1471
1732
|
});
|
|
1733
|
+
process.on("uncaughtException", (error) => {
|
|
1734
|
+
console.error(chalk3.red("Unexpected error:"), error.message);
|
|
1735
|
+
if (dashboardWriter) {
|
|
1736
|
+
dashboardWriter.crashSession();
|
|
1737
|
+
dashboardWriter.close();
|
|
1738
|
+
}
|
|
1739
|
+
cleanup();
|
|
1740
|
+
bashbros.stop();
|
|
1741
|
+
process.exit(1);
|
|
1742
|
+
});
|
|
1472
1743
|
bashbros.start();
|
|
1744
|
+
let commandBuffer = "";
|
|
1745
|
+
if (process.stdout.isTTY) {
|
|
1746
|
+
bashbros.resize(process.stdout.columns, process.stdout.rows);
|
|
1747
|
+
}
|
|
1748
|
+
process.stdout.on("resize", () => {
|
|
1749
|
+
if (process.stdout.isTTY) {
|
|
1750
|
+
bashbros.resize(process.stdout.columns, process.stdout.rows);
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
if (process.stdin.isTTY) {
|
|
1754
|
+
process.stdin.setRawMode(true);
|
|
1755
|
+
}
|
|
1756
|
+
process.stdin.resume();
|
|
1757
|
+
process.stdin.on("data", (data) => {
|
|
1758
|
+
const str = data.toString();
|
|
1759
|
+
for (const char of str) {
|
|
1760
|
+
const code = char.charCodeAt(0);
|
|
1761
|
+
if (awaitingPromptResponse) {
|
|
1762
|
+
if (code === 3) {
|
|
1763
|
+
pendingCommand = null;
|
|
1764
|
+
awaitingPromptResponse = false;
|
|
1765
|
+
console.log(chalk3.yellow("Cancelled"));
|
|
1766
|
+
console.log();
|
|
1767
|
+
} else if (char === "\r" || char === "\n") {
|
|
1768
|
+
handlePromptResponse("n");
|
|
1769
|
+
} else if (code >= 32) {
|
|
1770
|
+
handlePromptResponse(char);
|
|
1771
|
+
}
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
if (char === "\r" || char === "\n") {
|
|
1775
|
+
const command = commandBuffer.trim();
|
|
1776
|
+
commandBuffer = "";
|
|
1777
|
+
if (command) {
|
|
1778
|
+
const violations = bashbros.validateOnly(command);
|
|
1779
|
+
if (violations.length > 0) {
|
|
1780
|
+
bashbros.write("");
|
|
1781
|
+
bashbros.write("\r");
|
|
1782
|
+
bashbros.emit("blocked", command, violations);
|
|
1783
|
+
} else {
|
|
1784
|
+
bashbros.write("\r");
|
|
1785
|
+
}
|
|
1786
|
+
} else {
|
|
1787
|
+
bashbros.write("\r");
|
|
1788
|
+
}
|
|
1789
|
+
} else if (code === 127 || code === 8) {
|
|
1790
|
+
if (commandBuffer.length > 0) {
|
|
1791
|
+
commandBuffer = commandBuffer.slice(0, -1);
|
|
1792
|
+
}
|
|
1793
|
+
bashbros.write(char);
|
|
1794
|
+
} else if (code === 27) {
|
|
1795
|
+
bashbros.write(char);
|
|
1796
|
+
} else if (code === 3) {
|
|
1797
|
+
commandBuffer = "";
|
|
1798
|
+
bashbros.write(char);
|
|
1799
|
+
} else if (code === 21) {
|
|
1800
|
+
commandBuffer = "";
|
|
1801
|
+
bashbros.write(char);
|
|
1802
|
+
} else if (code >= 32 || char === " ") {
|
|
1803
|
+
commandBuffer += char;
|
|
1804
|
+
bashbros.write(char);
|
|
1805
|
+
} else {
|
|
1806
|
+
bashbros.write(char);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1473
1810
|
await new Promise(() => {
|
|
1474
1811
|
});
|
|
1475
1812
|
}
|
|
1476
1813
|
|
|
1477
1814
|
// src/allow.ts
|
|
1478
1815
|
import chalk4 from "chalk";
|
|
1479
|
-
import { readFileSync as
|
|
1480
|
-
import { parse, stringify as
|
|
1816
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1817
|
+
import { parse as parse2, stringify as stringify3 } from "yaml";
|
|
1481
1818
|
async function handleAllow(command, options) {
|
|
1482
1819
|
if (options.once) {
|
|
1483
1820
|
allowForSession(command);
|
|
@@ -1492,8 +1829,8 @@ async function handleAllow(command, options) {
|
|
|
1492
1829
|
process.exit(1);
|
|
1493
1830
|
}
|
|
1494
1831
|
try {
|
|
1495
|
-
const content =
|
|
1496
|
-
const config =
|
|
1832
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
1833
|
+
const config = parse2(content);
|
|
1497
1834
|
if (!config.commands) {
|
|
1498
1835
|
config.commands = { allow: [], block: [] };
|
|
1499
1836
|
}
|
|
@@ -1502,7 +1839,7 @@ async function handleAllow(command, options) {
|
|
|
1502
1839
|
}
|
|
1503
1840
|
if (!config.commands.allow.includes(command)) {
|
|
1504
1841
|
config.commands.allow.push(command);
|
|
1505
|
-
|
|
1842
|
+
writeFileSync3(configPath, stringify3(config));
|
|
1506
1843
|
console.log(chalk4.green("\u2713"), `Added to allowlist: ${command}`);
|
|
1507
1844
|
console.log(chalk4.dim(` Updated ${configPath}`));
|
|
1508
1845
|
} else {
|
|
@@ -1528,7 +1865,17 @@ import express from "express";
|
|
|
1528
1865
|
import { WebSocketServer } from "ws";
|
|
1529
1866
|
import { createServer } from "http";
|
|
1530
1867
|
import { fileURLToPath } from "url";
|
|
1531
|
-
import { dirname, join as
|
|
1868
|
+
import { dirname, join as join5 } from "path";
|
|
1869
|
+
import { homedir as homedir5 } from "os";
|
|
1870
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
1871
|
+
import { parse as parse3, stringify as stringify4 } from "yaml";
|
|
1872
|
+
function getDefaultDbPath2() {
|
|
1873
|
+
const bashbrosDir = join5(homedir5(), ".bashbros");
|
|
1874
|
+
if (!existsSync6(bashbrosDir)) {
|
|
1875
|
+
mkdirSync3(bashbrosDir, { recursive: true });
|
|
1876
|
+
}
|
|
1877
|
+
return join5(bashbrosDir, "dashboard.db");
|
|
1878
|
+
}
|
|
1532
1879
|
var DashboardServer = class {
|
|
1533
1880
|
app;
|
|
1534
1881
|
server = null;
|
|
@@ -1540,7 +1887,7 @@ var DashboardServer = class {
|
|
|
1540
1887
|
constructor(config = {}) {
|
|
1541
1888
|
this.port = config.port ?? 17800;
|
|
1542
1889
|
this.bind = config.bind ?? "127.0.0.1";
|
|
1543
|
-
this.db = new DashboardDB(config.dbPath ??
|
|
1890
|
+
this.db = new DashboardDB(config.dbPath ?? getDefaultDbPath2());
|
|
1544
1891
|
this.app = express();
|
|
1545
1892
|
this.setupMiddleware();
|
|
1546
1893
|
this.setupRoutes();
|
|
@@ -1639,12 +1986,202 @@ var DashboardServer = class {
|
|
|
1639
1986
|
res.status(500).json({ error: "Failed to deny block" });
|
|
1640
1987
|
}
|
|
1641
1988
|
});
|
|
1989
|
+
this.app.get("/api/sessions", (req, res) => {
|
|
1990
|
+
try {
|
|
1991
|
+
const filter = {};
|
|
1992
|
+
if (req.query.status) filter.status = req.query.status;
|
|
1993
|
+
if (req.query.agent) filter.agent = req.query.agent;
|
|
1994
|
+
if (req.query.limit) filter.limit = parseInt(req.query.limit, 10);
|
|
1995
|
+
if (req.query.offset) filter.offset = parseInt(req.query.offset, 10);
|
|
1996
|
+
if (req.query.since) filter.since = new Date(req.query.since);
|
|
1997
|
+
if (req.query.until) filter.until = new Date(req.query.until);
|
|
1998
|
+
const sessions = this.db.getSessions(filter);
|
|
1999
|
+
res.json(sessions);
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
res.status(500).json({ error: "Failed to fetch sessions" });
|
|
2002
|
+
}
|
|
2003
|
+
});
|
|
2004
|
+
this.app.get("/api/sessions/active", (_req, res) => {
|
|
2005
|
+
try {
|
|
2006
|
+
const session = this.db.getActiveSession();
|
|
2007
|
+
if (!session) {
|
|
2008
|
+
res.json(null);
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
res.json(session);
|
|
2012
|
+
} catch (error) {
|
|
2013
|
+
res.status(500).json({ error: "Failed to fetch active session" });
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
this.app.get("/api/sessions/:id", (req, res) => {
|
|
2017
|
+
try {
|
|
2018
|
+
const session = this.db.getSession(req.params.id);
|
|
2019
|
+
if (!session) {
|
|
2020
|
+
res.status(404).json({ error: "Session not found" });
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
res.json(session);
|
|
2024
|
+
} catch (error) {
|
|
2025
|
+
res.status(500).json({ error: "Failed to fetch session" });
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
this.app.get("/api/sessions/:id/commands", (req, res) => {
|
|
2029
|
+
try {
|
|
2030
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
|
|
2031
|
+
const commands = this.db.getCommandsBySession(req.params.id, limit);
|
|
2032
|
+
res.json(commands);
|
|
2033
|
+
} catch (error) {
|
|
2034
|
+
res.status(500).json({ error: "Failed to fetch session commands" });
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2037
|
+
this.app.get("/api/sessions/:id/metrics", (req, res) => {
|
|
2038
|
+
try {
|
|
2039
|
+
const session = this.db.getSession(req.params.id);
|
|
2040
|
+
if (!session) {
|
|
2041
|
+
res.status(404).json({ error: "Session not found" });
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
const metrics = this.db.getSessionMetrics(req.params.id);
|
|
2045
|
+
res.json(metrics);
|
|
2046
|
+
} catch (error) {
|
|
2047
|
+
res.status(500).json({ error: "Failed to fetch session metrics" });
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
2050
|
+
this.app.get("/api/commands/live", (req, res) => {
|
|
2051
|
+
try {
|
|
2052
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 20;
|
|
2053
|
+
const commands = this.db.getLiveCommands(limit);
|
|
2054
|
+
res.json(commands);
|
|
2055
|
+
} catch (error) {
|
|
2056
|
+
res.status(500).json({ error: "Failed to fetch live commands" });
|
|
2057
|
+
}
|
|
2058
|
+
});
|
|
2059
|
+
this.app.get("/api/commands", (req, res) => {
|
|
2060
|
+
try {
|
|
2061
|
+
const filter = {};
|
|
2062
|
+
if (req.query.sessionId) filter.sessionId = req.query.sessionId;
|
|
2063
|
+
if (req.query.allowed !== void 0) filter.allowed = req.query.allowed === "true";
|
|
2064
|
+
if (req.query.riskLevel) filter.riskLevel = req.query.riskLevel;
|
|
2065
|
+
if (req.query.afterId) filter.afterId = req.query.afterId;
|
|
2066
|
+
if (req.query.since) filter.since = new Date(req.query.since);
|
|
2067
|
+
if (req.query.limit) filter.limit = parseInt(req.query.limit, 10);
|
|
2068
|
+
if (req.query.offset) filter.offset = parseInt(req.query.offset, 10);
|
|
2069
|
+
const commands = this.db.getCommands(filter);
|
|
2070
|
+
res.json(commands);
|
|
2071
|
+
} catch (error) {
|
|
2072
|
+
res.status(500).json({ error: "Failed to fetch commands" });
|
|
2073
|
+
}
|
|
2074
|
+
});
|
|
2075
|
+
this.app.get("/api/bro/status", (_req, res) => {
|
|
2076
|
+
try {
|
|
2077
|
+
const status = this.db.getLatestBroStatus();
|
|
2078
|
+
res.json(status);
|
|
2079
|
+
} catch (error) {
|
|
2080
|
+
res.status(500).json({ error: "Failed to fetch Bro status" });
|
|
2081
|
+
}
|
|
2082
|
+
});
|
|
2083
|
+
this.app.get("/api/bro/events", (req, res) => {
|
|
2084
|
+
try {
|
|
2085
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
|
|
2086
|
+
const sessionId = req.query.sessionId;
|
|
2087
|
+
const events = this.db.getBroEvents(limit, sessionId);
|
|
2088
|
+
res.json(events);
|
|
2089
|
+
} catch (error) {
|
|
2090
|
+
res.status(500).json({ error: "Failed to fetch Bro events" });
|
|
2091
|
+
}
|
|
2092
|
+
});
|
|
2093
|
+
this.app.get("/api/bro/models", async (_req, res) => {
|
|
2094
|
+
try {
|
|
2095
|
+
const controller = new AbortController();
|
|
2096
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
2097
|
+
const response = await fetch("http://localhost:11434/api/tags", {
|
|
2098
|
+
signal: controller.signal
|
|
2099
|
+
});
|
|
2100
|
+
clearTimeout(timeout);
|
|
2101
|
+
if (!response.ok) {
|
|
2102
|
+
res.json({ available: false, models: [] });
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
const data = await response.json();
|
|
2106
|
+
const models = data.models?.map((m) => m.name) || [];
|
|
2107
|
+
res.json({ available: true, models });
|
|
2108
|
+
} catch (error) {
|
|
2109
|
+
res.json({ available: false, models: [] });
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
this.app.post("/api/bro/model", (req, res) => {
|
|
2113
|
+
try {
|
|
2114
|
+
const { model } = req.body;
|
|
2115
|
+
if (!model) {
|
|
2116
|
+
res.status(400).json({ error: "Model name required" });
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
const controlPath = join5(homedir5(), ".bashbros", "model-control.json");
|
|
2120
|
+
writeFileSync4(controlPath, JSON.stringify({
|
|
2121
|
+
model,
|
|
2122
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2123
|
+
}), "utf-8");
|
|
2124
|
+
res.json({ success: true, model });
|
|
2125
|
+
} catch (error) {
|
|
2126
|
+
res.status(500).json({ error: "Failed to change model" });
|
|
2127
|
+
}
|
|
2128
|
+
});
|
|
2129
|
+
this.app.post("/api/bro/scan", (_req, res) => {
|
|
2130
|
+
try {
|
|
2131
|
+
const controlPath = join5(homedir5(), ".bashbros", "scan-control.json");
|
|
2132
|
+
writeFileSync4(controlPath, JSON.stringify({
|
|
2133
|
+
action: "scan",
|
|
2134
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2135
|
+
}), "utf-8");
|
|
2136
|
+
res.json({ success: true, message: "Scan requested" });
|
|
2137
|
+
} catch (error) {
|
|
2138
|
+
res.status(500).json({ error: "Failed to trigger scan" });
|
|
2139
|
+
}
|
|
2140
|
+
});
|
|
2141
|
+
this.app.get("/api/exposures", (req, res) => {
|
|
2142
|
+
try {
|
|
2143
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
|
|
2144
|
+
const exposures = this.db.getRecentExposures(limit);
|
|
2145
|
+
res.json(exposures);
|
|
2146
|
+
} catch (error) {
|
|
2147
|
+
res.status(500).json({ error: "Failed to fetch exposures" });
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
this.app.get("/api/config", (_req, res) => {
|
|
2151
|
+
try {
|
|
2152
|
+
const configPath = findConfig();
|
|
2153
|
+
if (!configPath) {
|
|
2154
|
+
res.status(404).json({ error: "No config file found" });
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
2158
|
+
const config = parse3(content);
|
|
2159
|
+
res.json(config);
|
|
2160
|
+
} catch (error) {
|
|
2161
|
+
res.status(500).json({ error: "Failed to load config" });
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
this.app.post("/api/config", (req, res) => {
|
|
2165
|
+
try {
|
|
2166
|
+
const configPath = findConfig();
|
|
2167
|
+
if (!configPath) {
|
|
2168
|
+
res.status(404).json({ error: "No config file found" });
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
const config = req.body;
|
|
2172
|
+
const content = stringify4(config);
|
|
2173
|
+
writeFileSync4(configPath, content, "utf-8");
|
|
2174
|
+
res.json({ success: true });
|
|
2175
|
+
} catch (error) {
|
|
2176
|
+
res.status(500).json({ error: "Failed to save config" });
|
|
2177
|
+
}
|
|
2178
|
+
});
|
|
1642
2179
|
const __filename = fileURLToPath(import.meta.url);
|
|
1643
2180
|
const __dirname = dirname(__filename);
|
|
1644
|
-
const staticPath =
|
|
2181
|
+
const staticPath = join5(__dirname, "static");
|
|
1645
2182
|
this.app.use(express.static(staticPath));
|
|
1646
2183
|
this.app.get("/{*path}", (_req, res) => {
|
|
1647
|
-
res.sendFile(
|
|
2184
|
+
res.sendFile(join5(staticPath, "index.html"));
|
|
1648
2185
|
});
|
|
1649
2186
|
}
|
|
1650
2187
|
setupWebSocket() {
|
|
@@ -1775,7 +2312,7 @@ program.command("scan").description("Scan your system and project environment").
|
|
|
1775
2312
|
console.log(bro.getSystemContext());
|
|
1776
2313
|
console.log();
|
|
1777
2314
|
console.log(chalk5.bold("\n## Agent Configurations\n"));
|
|
1778
|
-
const { formatAgentSummary } = await import("./display-
|
|
2315
|
+
const { formatAgentSummary } = await import("./display-6LZ2HBCU.js");
|
|
1779
2316
|
const agents = await getAllAgentConfigs();
|
|
1780
2317
|
console.log(formatAgentSummary(agents));
|
|
1781
2318
|
console.log();
|
|
@@ -1905,8 +2442,8 @@ program.command("script <description>").description("Generate a shell script fro
|
|
|
1905
2442
|
if (script) {
|
|
1906
2443
|
console.log(chalk5.cyan(script));
|
|
1907
2444
|
if (options.output) {
|
|
1908
|
-
const { writeFileSync:
|
|
1909
|
-
|
|
2445
|
+
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
2446
|
+
writeFileSync5(options.output, script, { mode: 493 });
|
|
1910
2447
|
console.log(chalk5.green(`
|
|
1911
2448
|
\u2713 Saved to ${options.output}`));
|
|
1912
2449
|
}
|
|
@@ -2134,7 +2671,7 @@ program.command("agent-info [agent]").description("Show detailed info about inst
|
|
|
2134
2671
|
return;
|
|
2135
2672
|
}
|
|
2136
2673
|
const info = await getAgentConfigInfo(agent);
|
|
2137
|
-
const { formatAgentInfo: formatAgentInfo2 } = await import("./display-
|
|
2674
|
+
const { formatAgentInfo: formatAgentInfo2 } = await import("./display-6LZ2HBCU.js");
|
|
2138
2675
|
console.log();
|
|
2139
2676
|
console.log(formatAgentInfo2(info));
|
|
2140
2677
|
if (options.raw && info.configExists && info.configPath) {
|
|
@@ -2166,11 +2703,78 @@ program.command("permissions").description("Show combined permissions view acros
|
|
|
2166
2703
|
console.log(formatPermissionsTable(agents));
|
|
2167
2704
|
console.log();
|
|
2168
2705
|
});
|
|
2169
|
-
program.command("gate <command>").description("Check if a command should be allowed (used by hooks)").action(async (command) => {
|
|
2706
|
+
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 (command, options) => {
|
|
2170
2707
|
const result = await gateCommand(command);
|
|
2171
2708
|
if (!result.allowed) {
|
|
2172
|
-
|
|
2173
|
-
|
|
2709
|
+
if (process.stdin.isTTY && !options.yes) {
|
|
2710
|
+
const { allowForSession: allowForSession2 } = await import("./session-Y4MICATZ.js");
|
|
2711
|
+
const { readFileSync: readFileSync5, writeFileSync: writeFileSync5 } = await import("fs");
|
|
2712
|
+
const { parse: parse4, stringify: stringify5 } = await import("yaml");
|
|
2713
|
+
const { findConfig: findConfig2 } = await import("./config-JLLOTFLI.js");
|
|
2714
|
+
console.error();
|
|
2715
|
+
console.error(chalk5.red("\u{1F6E1}\uFE0F BashBros blocked a command"));
|
|
2716
|
+
console.error();
|
|
2717
|
+
console.error(chalk5.dim(" Command:"), command);
|
|
2718
|
+
console.error(chalk5.dim(" Reason:"), result.reason);
|
|
2719
|
+
console.error();
|
|
2720
|
+
console.error(chalk5.yellow(" Allow this command?"));
|
|
2721
|
+
console.error(chalk5.cyan(" [y]"), "Allow once");
|
|
2722
|
+
console.error(chalk5.cyan(" [s]"), "Allow for session");
|
|
2723
|
+
console.error(chalk5.cyan(" [p]"), "Allow permanently");
|
|
2724
|
+
console.error(chalk5.cyan(" [n]"), "Block (default)");
|
|
2725
|
+
process.stderr.write(chalk5.dim("\n Choice: "));
|
|
2726
|
+
const choice = await new Promise((resolve) => {
|
|
2727
|
+
if (process.stdin.isTTY) {
|
|
2728
|
+
process.stdin.setRawMode(true);
|
|
2729
|
+
}
|
|
2730
|
+
process.stdin.resume();
|
|
2731
|
+
process.stdin.once("data", (data) => {
|
|
2732
|
+
if (process.stdin.isTTY) {
|
|
2733
|
+
process.stdin.setRawMode(false);
|
|
2734
|
+
}
|
|
2735
|
+
const char = data.toString().toLowerCase();
|
|
2736
|
+
console.error(char);
|
|
2737
|
+
resolve(char);
|
|
2738
|
+
});
|
|
2739
|
+
});
|
|
2740
|
+
switch (choice) {
|
|
2741
|
+
case "y":
|
|
2742
|
+
console.error(chalk5.green(" \u2713 Allowed once"));
|
|
2743
|
+
process.exit(0);
|
|
2744
|
+
break;
|
|
2745
|
+
case "s":
|
|
2746
|
+
allowForSession2(command);
|
|
2747
|
+
console.error(chalk5.green(" \u2713 Allowed for session"));
|
|
2748
|
+
process.exit(0);
|
|
2749
|
+
break;
|
|
2750
|
+
case "p":
|
|
2751
|
+
try {
|
|
2752
|
+
const configPath = findConfig2();
|
|
2753
|
+
if (configPath) {
|
|
2754
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
2755
|
+
const config = parse4(content);
|
|
2756
|
+
if (!config.commands) config.commands = { allow: [], block: [] };
|
|
2757
|
+
if (!config.commands.allow) config.commands.allow = [];
|
|
2758
|
+
if (!config.commands.allow.includes(command)) {
|
|
2759
|
+
config.commands.allow.push(command);
|
|
2760
|
+
writeFileSync5(configPath, stringify5(config));
|
|
2761
|
+
}
|
|
2762
|
+
console.error(chalk5.green(" \u2713 Added to allowlist permanently"));
|
|
2763
|
+
process.exit(0);
|
|
2764
|
+
}
|
|
2765
|
+
} catch {
|
|
2766
|
+
console.error(chalk5.red(" \u2717 Failed to update config"));
|
|
2767
|
+
}
|
|
2768
|
+
process.exit(2);
|
|
2769
|
+
break;
|
|
2770
|
+
default:
|
|
2771
|
+
console.error(chalk5.yellow(" \u2717 Blocked"));
|
|
2772
|
+
process.exit(2);
|
|
2773
|
+
}
|
|
2774
|
+
} else {
|
|
2775
|
+
console.error(`Blocked: ${result.reason}`);
|
|
2776
|
+
process.exit(2);
|
|
2777
|
+
}
|
|
2174
2778
|
}
|
|
2175
2779
|
process.exit(0);
|
|
2176
2780
|
});
|