bashbros 0.1.1 → 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.
Files changed (34) hide show
  1. package/dist/{chunk-A535VV7N.js → chunk-BW6XCOJH.js} +4 -3
  2. package/dist/chunk-BW6XCOJH.js.map +1 -0
  3. package/dist/chunk-FRMAIRQ2.js +89 -0
  4. package/dist/chunk-FRMAIRQ2.js.map +1 -0
  5. package/dist/{chunk-GD5VNHIN.js → chunk-QWZGB4V3.js} +4 -85
  6. package/dist/chunk-QWZGB4V3.js.map +1 -0
  7. package/dist/{chunk-WPJJZLT6.js → chunk-SQCP6IYB.js} +2 -2
  8. package/dist/{chunk-VVSCAH2B.js → chunk-XCZMQRSX.js} +125 -10
  9. package/dist/chunk-XCZMQRSX.js.map +1 -0
  10. package/dist/chunk-YUMNBQAY.js +766 -0
  11. package/dist/chunk-YUMNBQAY.js.map +1 -0
  12. package/dist/cli.js +577 -30
  13. package/dist/cli.js.map +1 -1
  14. package/dist/{config-43SK6SFI.js → config-JLLOTFLI.js} +2 -2
  15. package/dist/{db-EHQDB5OL.js → db-OBKEXRTP.js} +2 -2
  16. package/dist/{display-HFIFXOOL.js → display-6LZ2HBCU.js} +3 -3
  17. package/dist/engine-EGPAS2EX.js +10 -0
  18. package/dist/index.js +6 -4
  19. package/dist/index.js.map +1 -1
  20. package/dist/session-Y4MICATZ.js +15 -0
  21. package/dist/session-Y4MICATZ.js.map +1 -0
  22. package/dist/static/index.html +1873 -276
  23. package/package.json +1 -1
  24. package/dist/chunk-A535VV7N.js.map +0 -1
  25. package/dist/chunk-CSRPOGHY.js +0 -354
  26. package/dist/chunk-CSRPOGHY.js.map +0 -1
  27. package/dist/chunk-GD5VNHIN.js.map +0 -1
  28. package/dist/chunk-VVSCAH2B.js.map +0 -1
  29. package/dist/engine-PKLXW6OF.js +0 -9
  30. /package/dist/{chunk-WPJJZLT6.js.map → chunk-SQCP6IYB.js.map} +0 -0
  31. /package/dist/{config-43SK6SFI.js.map → config-JLLOTFLI.js.map} +0 -0
  32. /package/dist/{db-EHQDB5OL.js.map → db-OBKEXRTP.js.map} +0 -0
  33. /package/dist/{display-HFIFXOOL.js.map → display-6LZ2HBCU.js.map} +0 -0
  34. /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-WPJJZLT6.js";
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-VVSCAH2B.js";
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-A535VV7N.js";
27
+ } from "./chunk-BW6XCOJH.js";
28
+ import "./chunk-QWZGB4V3.js";
25
29
  import {
26
30
  allowForSession
27
- } from "./chunk-GD5VNHIN.js";
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-EHQDB5OL.js");
1018
+ const dbModule = await import("./db-OBKEXRTP.js");
1018
1019
  DashboardDB2 = dbModule.DashboardDB;
1019
1020
  } catch {
1020
1021
  }
@@ -1422,6 +1423,166 @@ ${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;
1425
1586
  function cleanup() {
1426
1587
  if (process.stdin.isTTY) {
1427
1588
  process.stdin.setRawMode(false);
@@ -1440,7 +1601,24 @@ async function startWatch(options) {
1440
1601
  console.log(chalk3.dim(" Press Ctrl+C to stop"));
1441
1602
  console.log();
1442
1603
  const bashbros = new BashBros(configPath);
1443
- bashbros.on("blocked", (command, violations) => {
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;
1444
1622
  console.log();
1445
1623
  console.log(chalk3.red("\u{1F6E1}\uFE0F BashBros blocked a command"));
1446
1624
  console.log();
@@ -1448,12 +1626,72 @@ async function startWatch(options) {
1448
1626
  console.log(chalk3.dim(" Reason:"), violations[0].message);
1449
1627
  console.log(chalk3.dim(" Policy:"), violations[0].rule);
1450
1628
  console.log();
1451
- console.log(chalk3.dim(" To allow this command:"));
1452
- console.log(chalk3.cyan(` bashbros allow "${command}" --once`));
1453
- console.log(chalk3.cyan(` bashbros allow "${command}" --persist`));
1454
- 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);
1455
1689
  });
1456
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
+ }
1457
1695
  if (options.verbose) {
1458
1696
  console.log(chalk3.green("\u2713"), chalk3.dim(result.command));
1459
1697
  }
@@ -1465,6 +1703,10 @@ async function startWatch(options) {
1465
1703
  console.error(chalk3.red("Error:"), error.message);
1466
1704
  });
1467
1705
  bashbros.on("exit", (exitCode) => {
1706
+ if (dashboardWriter) {
1707
+ dashboardWriter.endSession();
1708
+ dashboardWriter.close();
1709
+ }
1468
1710
  cleanup();
1469
1711
  process.exit(exitCode ?? 0);
1470
1712
  });
@@ -1472,14 +1714,32 @@ async function startWatch(options) {
1472
1714
  cleanup();
1473
1715
  console.log();
1474
1716
  console.log(chalk3.yellow("Stopping BashBros..."));
1717
+ if (dashboardWriter) {
1718
+ dashboardWriter.endSession();
1719
+ dashboardWriter.close();
1720
+ }
1475
1721
  bashbros.stop();
1476
1722
  process.exit(0);
1477
1723
  });
1478
1724
  process.on("SIGTERM", () => {
1479
1725
  cleanup();
1726
+ if (dashboardWriter) {
1727
+ dashboardWriter.endSession();
1728
+ dashboardWriter.close();
1729
+ }
1480
1730
  bashbros.stop();
1481
1731
  process.exit(0);
1482
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
+ });
1483
1743
  bashbros.start();
1484
1744
  let commandBuffer = "";
1485
1745
  if (process.stdout.isTTY) {
@@ -1498,11 +1758,31 @@ async function startWatch(options) {
1498
1758
  const str = data.toString();
1499
1759
  for (const char of str) {
1500
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
+ }
1501
1774
  if (char === "\r" || char === "\n") {
1502
1775
  const command = commandBuffer.trim();
1503
1776
  commandBuffer = "";
1504
1777
  if (command) {
1505
- bashbros.execute(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
+ }
1506
1786
  } else {
1507
1787
  bashbros.write("\r");
1508
1788
  }
@@ -1533,8 +1813,8 @@ async function startWatch(options) {
1533
1813
 
1534
1814
  // src/allow.ts
1535
1815
  import chalk4 from "chalk";
1536
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1537
- import { parse, stringify as stringify2 } from "yaml";
1816
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1817
+ import { parse as parse2, stringify as stringify3 } from "yaml";
1538
1818
  async function handleAllow(command, options) {
1539
1819
  if (options.once) {
1540
1820
  allowForSession(command);
@@ -1549,8 +1829,8 @@ async function handleAllow(command, options) {
1549
1829
  process.exit(1);
1550
1830
  }
1551
1831
  try {
1552
- const content = readFileSync2(configPath, "utf-8");
1553
- const config = parse(content);
1832
+ const content = readFileSync3(configPath, "utf-8");
1833
+ const config = parse2(content);
1554
1834
  if (!config.commands) {
1555
1835
  config.commands = { allow: [], block: [] };
1556
1836
  }
@@ -1559,7 +1839,7 @@ async function handleAllow(command, options) {
1559
1839
  }
1560
1840
  if (!config.commands.allow.includes(command)) {
1561
1841
  config.commands.allow.push(command);
1562
- writeFileSync2(configPath, stringify2(config));
1842
+ writeFileSync3(configPath, stringify3(config));
1563
1843
  console.log(chalk4.green("\u2713"), `Added to allowlist: ${command}`);
1564
1844
  console.log(chalk4.dim(` Updated ${configPath}`));
1565
1845
  } else {
@@ -1585,7 +1865,17 @@ import express from "express";
1585
1865
  import { WebSocketServer } from "ws";
1586
1866
  import { createServer } from "http";
1587
1867
  import { fileURLToPath } from "url";
1588
- import { dirname, join as join4 } from "path";
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
+ }
1589
1879
  var DashboardServer = class {
1590
1880
  app;
1591
1881
  server = null;
@@ -1597,7 +1887,7 @@ var DashboardServer = class {
1597
1887
  constructor(config = {}) {
1598
1888
  this.port = config.port ?? 17800;
1599
1889
  this.bind = config.bind ?? "127.0.0.1";
1600
- this.db = new DashboardDB(config.dbPath ?? ":memory:");
1890
+ this.db = new DashboardDB(config.dbPath ?? getDefaultDbPath2());
1601
1891
  this.app = express();
1602
1892
  this.setupMiddleware();
1603
1893
  this.setupRoutes();
@@ -1696,12 +1986,202 @@ var DashboardServer = class {
1696
1986
  res.status(500).json({ error: "Failed to deny block" });
1697
1987
  }
1698
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
+ });
1699
2179
  const __filename = fileURLToPath(import.meta.url);
1700
2180
  const __dirname = dirname(__filename);
1701
- const staticPath = join4(__dirname, "static");
2181
+ const staticPath = join5(__dirname, "static");
1702
2182
  this.app.use(express.static(staticPath));
1703
2183
  this.app.get("/{*path}", (_req, res) => {
1704
- res.sendFile(join4(staticPath, "index.html"));
2184
+ res.sendFile(join5(staticPath, "index.html"));
1705
2185
  });
1706
2186
  }
1707
2187
  setupWebSocket() {
@@ -1832,7 +2312,7 @@ program.command("scan").description("Scan your system and project environment").
1832
2312
  console.log(bro.getSystemContext());
1833
2313
  console.log();
1834
2314
  console.log(chalk5.bold("\n## Agent Configurations\n"));
1835
- const { formatAgentSummary } = await import("./display-HFIFXOOL.js");
2315
+ const { formatAgentSummary } = await import("./display-6LZ2HBCU.js");
1836
2316
  const agents = await getAllAgentConfigs();
1837
2317
  console.log(formatAgentSummary(agents));
1838
2318
  console.log();
@@ -1962,8 +2442,8 @@ program.command("script <description>").description("Generate a shell script fro
1962
2442
  if (script) {
1963
2443
  console.log(chalk5.cyan(script));
1964
2444
  if (options.output) {
1965
- const { writeFileSync: writeFileSync3 } = await import("fs");
1966
- writeFileSync3(options.output, script, { mode: 493 });
2445
+ const { writeFileSync: writeFileSync5 } = await import("fs");
2446
+ writeFileSync5(options.output, script, { mode: 493 });
1967
2447
  console.log(chalk5.green(`
1968
2448
  \u2713 Saved to ${options.output}`));
1969
2449
  }
@@ -2191,7 +2671,7 @@ program.command("agent-info [agent]").description("Show detailed info about inst
2191
2671
  return;
2192
2672
  }
2193
2673
  const info = await getAgentConfigInfo(agent);
2194
- const { formatAgentInfo: formatAgentInfo2 } = await import("./display-HFIFXOOL.js");
2674
+ const { formatAgentInfo: formatAgentInfo2 } = await import("./display-6LZ2HBCU.js");
2195
2675
  console.log();
2196
2676
  console.log(formatAgentInfo2(info));
2197
2677
  if (options.raw && info.configExists && info.configPath) {
@@ -2223,11 +2703,78 @@ program.command("permissions").description("Show combined permissions view acros
2223
2703
  console.log(formatPermissionsTable(agents));
2224
2704
  console.log();
2225
2705
  });
2226
- 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) => {
2227
2707
  const result = await gateCommand(command);
2228
2708
  if (!result.allowed) {
2229
- console.error(`BLOCKED: ${result.reason}`);
2230
- process.exit(2);
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
+ }
2231
2778
  }
2232
2779
  process.exit(0);
2233
2780
  });