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.
Files changed (66) hide show
  1. package/README.md +727 -265
  2. package/dist/adapters-JAZGGNVP.js +9 -0
  3. package/dist/chunk-4XZ64P4V.js +47 -0
  4. package/dist/chunk-4XZ64P4V.js.map +1 -0
  5. package/dist/{chunk-2RPTM6EQ.js → chunk-7OEWYFN3.js} +745 -629
  6. package/dist/chunk-7OEWYFN3.js.map +1 -0
  7. package/dist/{chunk-WPJJZLT6.js → chunk-CG6VEHJM.js} +3 -2
  8. package/dist/chunk-CG6VEHJM.js.map +1 -0
  9. package/dist/{chunk-DLP2O6PN.js → chunk-EMLEJVJZ.js} +102 -1
  10. package/dist/chunk-EMLEJVJZ.js.map +1 -0
  11. package/dist/chunk-IUUBCPMV.js +166 -0
  12. package/dist/chunk-IUUBCPMV.js.map +1 -0
  13. package/dist/chunk-J6ONXY6N.js +146 -0
  14. package/dist/chunk-J6ONXY6N.js.map +1 -0
  15. package/dist/{chunk-EYO44OMN.js → chunk-KYDMPE4N.js} +60 -17
  16. package/dist/chunk-KYDMPE4N.js.map +1 -0
  17. package/dist/chunk-LJE4EPIU.js +56 -0
  18. package/dist/chunk-LJE4EPIU.js.map +1 -0
  19. package/dist/chunk-LZYW7XQO.js +339 -0
  20. package/dist/chunk-LZYW7XQO.js.map +1 -0
  21. package/dist/{chunk-JYWQT2B4.js → chunk-RDNSS3ME.js} +489 -14
  22. package/dist/chunk-RDNSS3ME.js.map +1 -0
  23. package/dist/{chunk-A535VV7N.js → chunk-RTZ4QWG2.js} +5 -4
  24. package/dist/chunk-RTZ4QWG2.js.map +1 -0
  25. package/dist/chunk-SDN6TAGD.js +157 -0
  26. package/dist/chunk-SDN6TAGD.js.map +1 -0
  27. package/dist/chunk-T5ONCUHZ.js +198 -0
  28. package/dist/chunk-T5ONCUHZ.js.map +1 -0
  29. package/dist/cli.js +1069 -88
  30. package/dist/cli.js.map +1 -1
  31. package/dist/{config-43SK6SFI.js → config-I5NCK3RJ.js} +2 -2
  32. package/dist/copilot-cli-5WJWK5YT.js +9 -0
  33. package/dist/{db-SWJUUSFX.js → db-ETWTBXAE.js} +2 -2
  34. package/dist/db-checks-2YOVECD4.js +133 -0
  35. package/dist/db-checks-2YOVECD4.js.map +1 -0
  36. package/dist/{display-HFIFXOOL.js → display-UH7KEHOW.js} +3 -3
  37. package/dist/gemini-cli-3563EELZ.js +9 -0
  38. package/dist/gemini-cli-3563EELZ.js.map +1 -0
  39. package/dist/index.d.ts +176 -72
  40. package/dist/index.js +119 -398
  41. package/dist/index.js.map +1 -1
  42. package/dist/{ollama-HY35OHW4.js → ollama-5JVKNFOV.js} +2 -2
  43. package/dist/ollama-5JVKNFOV.js.map +1 -0
  44. package/dist/opencode-DRCY275R.js +9 -0
  45. package/dist/opencode-DRCY275R.js.map +1 -0
  46. package/dist/profiles-7CLN6TAT.js +9 -0
  47. package/dist/profiles-7CLN6TAT.js.map +1 -0
  48. package/dist/setup-YS27MOPE.js +124 -0
  49. package/dist/setup-YS27MOPE.js.map +1 -0
  50. package/dist/static/index.html +4815 -2007
  51. package/dist/store-WJ5Y7MOE.js +9 -0
  52. package/dist/store-WJ5Y7MOE.js.map +1 -0
  53. package/dist/{writer-4ZEAKUFD.js → writer-3NAVABN6.js} +3 -3
  54. package/dist/writer-3NAVABN6.js.map +1 -0
  55. package/package.json +77 -68
  56. package/dist/chunk-2RPTM6EQ.js.map +0 -1
  57. package/dist/chunk-A535VV7N.js.map +0 -1
  58. package/dist/chunk-DLP2O6PN.js.map +0 -1
  59. package/dist/chunk-EYO44OMN.js.map +0 -1
  60. package/dist/chunk-JYWQT2B4.js.map +0 -1
  61. package/dist/chunk-WPJJZLT6.js.map +0 -1
  62. /package/dist/{config-43SK6SFI.js.map → adapters-JAZGGNVP.js.map} +0 -0
  63. /package/dist/{db-SWJUUSFX.js.map → config-I5NCK3RJ.js.map} +0 -0
  64. /package/dist/{display-HFIFXOOL.js.map → copilot-cli-5WJWK5YT.js.map} +0 -0
  65. /package/dist/{ollama-HY35OHW4.js.map → db-ETWTBXAE.js.map} +0 -0
  66. /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-WPJJZLT6.js";
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
- MetricsCollector,
31
+ OutputScanner,
19
32
  ReportGenerator,
20
33
  UndoStack,
21
- gateCommand,
22
34
  getBashgymIntegration
23
- } from "./chunk-2RPTM6EQ.js";
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-DLP2O6PN.js";
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-A535VV7N.js";
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
- formatRedactedConfig,
40
- parseAgentConfig
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-SWJUUSFX.js");
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 { id } = req.params;
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 { id } = req.params;
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
- const commands = this.db.getLiveCommands(limit);
1901
- res.json(commands);
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
- const __filename = fileURLToPath(import.meta.url);
2057
- const __dirname = dirname(__filename);
2058
- const staticPath = join4(__dirname, "static");
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
- var metricsCollector = null;
2153
- var costEstimator = null;
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
- program.name("bashbros").description("The Bash Agent Helper").version("0.1.0");
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-HFIFXOOL.js");
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-HY35OHW4.js");
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-HFIFXOOL.js");
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 <command>").description("Check if a command should be allowed (used by hooks)").option("-y, --yes", "Skip interactive prompt and block").action(async (command, options) => {
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: readFileSync5, writeFileSync: writeFileSync5 } = await import("fs");
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-43SK6SFI.js");
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 = readFileSync5(configPath, "utf-8");
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 eventJson = process.env.CLAUDE_HOOK_EVENT || "";
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-4ZEAKUFD.js");
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 <command>").description("Record a command execution (used by hooks)").option("-o, --output <output>", "Command output").option("-e, --exit-code <code>", "Exit code", "0").action(async (command, options) => {
2732
- if (!metricsCollector) metricsCollector = new MetricsCollector();
2733
- if (!costEstimator) costEstimator = new CostEstimator();
2734
- if (!loopDetector) loopDetector = new LoopDetector();
2735
- if (!undoStack) undoStack = new UndoStack();
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
- const paths = command.match(/(?:^|\s)(\.\/|\.\.\/|\/|~\/)[^\s]+/g) || [];
2754
- const cleanPaths = paths.map((p) => p.trim());
2755
- if (cleanPaths.length > 0) {
2756
- undoStack.recordFromCommand(command, cleanPaths);
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
- if (!metricsCollector) {
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
- if (!metricsCollector) {
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