bashbros 0.1.2 → 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 (65) 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-XCZMQRSX.js → chunk-7OEWYFN3.js} +745 -541
  6. package/dist/chunk-7OEWYFN3.js.map +1 -0
  7. package/dist/{chunk-SQCP6IYB.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-KYDMPE4N.js +224 -0
  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-YUMNBQAY.js → chunk-RDNSS3ME.js} +587 -12
  22. package/dist/chunk-RDNSS3ME.js.map +1 -0
  23. package/dist/{chunk-BW6XCOJH.js → chunk-RTZ4QWG2.js} +2 -2
  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 +1182 -251
  30. package/dist/cli.js.map +1 -1
  31. package/dist/{config-JLLOTFLI.js → config-I5NCK3RJ.js} +2 -2
  32. package/dist/copilot-cli-5WJWK5YT.js +9 -0
  33. package/dist/{db-OBKEXRTP.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-6LZ2HBCU.js → display-UH7KEHOW.js} +3 -3
  37. package/dist/display-UH7KEHOW.js.map +1 -0
  38. package/dist/gemini-cli-3563EELZ.js +9 -0
  39. package/dist/gemini-cli-3563EELZ.js.map +1 -0
  40. package/dist/index.d.ts +195 -72
  41. package/dist/index.js +119 -398
  42. package/dist/index.js.map +1 -1
  43. package/dist/{ollama-HY35OHW4.js → ollama-5JVKNFOV.js} +2 -2
  44. package/dist/ollama-5JVKNFOV.js.map +1 -0
  45. package/dist/opencode-DRCY275R.js +9 -0
  46. package/dist/opencode-DRCY275R.js.map +1 -0
  47. package/dist/profiles-7CLN6TAT.js +9 -0
  48. package/dist/profiles-7CLN6TAT.js.map +1 -0
  49. package/dist/setup-YS27MOPE.js +124 -0
  50. package/dist/setup-YS27MOPE.js.map +1 -0
  51. package/dist/static/index.html +4815 -2007
  52. package/dist/store-WJ5Y7MOE.js +9 -0
  53. package/dist/store-WJ5Y7MOE.js.map +1 -0
  54. package/dist/writer-3NAVABN6.js +12 -0
  55. package/dist/writer-3NAVABN6.js.map +1 -0
  56. package/package.json +77 -68
  57. package/dist/chunk-BW6XCOJH.js.map +0 -1
  58. package/dist/chunk-DLP2O6PN.js.map +0 -1
  59. package/dist/chunk-SQCP6IYB.js.map +0 -1
  60. package/dist/chunk-XCZMQRSX.js.map +0 -1
  61. package/dist/chunk-YUMNBQAY.js.map +0 -1
  62. /package/dist/{config-JLLOTFLI.js.map → adapters-JAZGGNVP.js.map} +0 -0
  63. /package/dist/{db-OBKEXRTP.js.map → config-I5NCK3RJ.js.map} +0 -0
  64. /package/dist/{display-6LZ2HBCU.js.map → copilot-cli-5WJWK5YT.js.map} +0 -0
  65. /package/dist/{ollama-HY35OHW4.js.map → db-ETWTBXAE.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,30 +1,54 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- DashboardDB
4
- } from "./chunk-YUMNBQAY.js";
5
2
  import {
6
3
  formatAllAgentsInfo,
7
4
  formatPermissionsTable
8
- } from "./chunk-SQCP6IYB.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";
9
22
  import {
23
+ ContextStore
24
+ } from "./chunk-J6ONXY6N.js";
25
+ import {
26
+ AnomalyDetector,
10
27
  BashBro,
11
28
  BashBros,
12
- ClaudeCodeHooks,
13
29
  CostEstimator,
14
30
  LoopDetector,
15
- MetricsCollector,
31
+ OutputScanner,
16
32
  ReportGenerator,
17
33
  UndoStack,
18
- gateCommand,
19
34
  getBashgymIntegration
20
- } from "./chunk-XCZMQRSX.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";
21
43
  import "./chunk-SG752FZC.js";
22
- import "./chunk-DLP2O6PN.js";
44
+ import "./chunk-EMLEJVJZ.js";
45
+ import "./chunk-4XZ64P4V.js";
46
+ import "./chunk-LJE4EPIU.js";
23
47
  import {
24
48
  findConfig,
25
49
  getDefaultConfig,
26
50
  loadConfig
27
- } from "./chunk-BW6XCOJH.js";
51
+ } from "./chunk-RTZ4QWG2.js";
28
52
  import "./chunk-QWZGB4V3.js";
29
53
  import {
30
54
  allowForSession
@@ -33,12 +57,8 @@ import {
33
57
  RiskScorer
34
58
  } from "./chunk-DEAF6PYM.js";
35
59
  import {
36
- formatRedactedConfig,
37
- parseAgentConfig
38
- } from "./chunk-ID2O2QTI.js";
39
- import {
40
- MoltbotHooks
41
- } from "./chunk-J37RHCFJ.js";
60
+ DashboardDB
61
+ } from "./chunk-RDNSS3ME.js";
42
62
  import "./chunk-7OCVIDC7.js";
43
63
 
44
64
  // src/cli.ts
@@ -66,6 +86,7 @@ async function runOnboarding() {
66
86
  { name: "Moltbot (clawd.bot)", value: "moltbot" },
67
87
  { name: "Clawdbot (legacy)", value: "clawdbot" },
68
88
  { name: "Gemini CLI", value: "gemini-cli" },
89
+ { name: "Copilot CLI", value: "copilot-cli" },
69
90
  { name: "Aider", value: "aider" },
70
91
  { name: "OpenCode", value: "opencode" },
71
92
  { name: "Other (custom)", value: "custom" }
@@ -294,6 +315,9 @@ var AGENT_CONFIG_PATHS = {
294
315
  "gemini-cli": [
295
316
  join2(homedir2(), ".config", "gemini-cli", "config.json")
296
317
  ],
318
+ "copilot-cli": [
319
+ join2(homedir2(), ".config", "github-copilot", "config.json")
320
+ ],
297
321
  "opencode": [
298
322
  join2(homedir2(), ".opencode", "config.yml"),
299
323
  join2(homedir2(), ".config", "opencode", "config.yml")
@@ -306,6 +330,7 @@ var AGENT_COMMANDS = {
306
330
  "moltbot": "moltbot",
307
331
  "aider": "aider",
308
332
  "gemini-cli": "gemini",
333
+ "copilot-cli": "copilot",
309
334
  "opencode": "opencode",
310
335
  "custom": ""
311
336
  };
@@ -1015,7 +1040,7 @@ var EgressPatternMatcher = class {
1015
1040
  // src/policy/ward/egress.ts
1016
1041
  var DashboardDB2 = null;
1017
1042
  try {
1018
- const dbModule = await import("./db-OBKEXRTP.js");
1043
+ const dbModule = await import("./db-ETWTBXAE.js");
1019
1044
  DashboardDB2 = dbModule.DashboardDB;
1020
1045
  } catch {
1021
1046
  }
@@ -1425,169 +1450,31 @@ ${passed} passed, ${failed} failed. Fix issues above.
1425
1450
  import chalk3 from "chalk";
1426
1451
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1427
1452
  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
1453
  var dashboardWriter = null;
1585
1454
  var riskScorer = null;
1455
+ var contextStore = null;
1456
+ var contextCommandCount = 0;
1457
+ var contextSessionStartTime = null;
1586
1458
  function cleanup() {
1587
1459
  if (process.stdin.isTTY) {
1588
1460
  process.stdin.setRawMode(false);
1589
1461
  }
1590
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
+ }
1591
1478
  async function startWatch(options) {
1592
1479
  const configPath = findConfig();
1593
1480
  if (!configPath) {
@@ -1602,6 +1489,7 @@ async function startWatch(options) {
1602
1489
  console.log();
1603
1490
  const bashbros = new BashBros(configPath);
1604
1491
  const config = bashbros.getConfig();
1492
+ const fullConfig = loadConfig(configPath);
1605
1493
  try {
1606
1494
  dashboardWriter = new DashboardWriter();
1607
1495
  riskScorer = new RiskScorer();
@@ -1614,6 +1502,33 @@ async function startWatch(options) {
1614
1502
  console.log(chalk3.yellow(" Dashboard recording disabled"));
1615
1503
  }
1616
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;
1617
1532
  let pendingCommand = null;
1618
1533
  let awaitingPromptResponse = false;
1619
1534
  function showBlockedPrompt(command, violations) {
@@ -1653,6 +1568,11 @@ async function startWatch(options) {
1653
1568
  break;
1654
1569
  case "p":
1655
1570
  try {
1571
+ if (!configPath) {
1572
+ console.log(chalk3.red(" \u2717 No config file found"));
1573
+ console.log();
1574
+ break;
1575
+ }
1656
1576
  const content = readFileSync2(configPath, "utf-8");
1657
1577
  const config2 = parse(content);
1658
1578
  if (!config2.commands) {
@@ -1692,14 +1612,50 @@ async function startWatch(options) {
1692
1612
  const risk = riskScorer.score(result.command);
1693
1613
  dashboardWriter.recordCommand(result.command, true, risk, [], result.duration);
1694
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
+ }
1695
1628
  if (options.verbose) {
1696
1629
  console.log(chalk3.green("\u2713"), chalk3.dim(result.command));
1697
1630
  }
1698
1631
  });
1699
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
+ }
1700
1645
  process.stdout.write(data);
1701
1646
  });
1702
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
+ }
1703
1659
  console.error(chalk3.red("Error:"), error.message);
1704
1660
  });
1705
1661
  bashbros.on("exit", (exitCode) => {
@@ -1707,6 +1663,7 @@ async function startWatch(options) {
1707
1663
  dashboardWriter.endSession();
1708
1664
  dashboardWriter.close();
1709
1665
  }
1666
+ finalizeContextStore(config.agent);
1710
1667
  cleanup();
1711
1668
  process.exit(exitCode ?? 0);
1712
1669
  });
@@ -1718,6 +1675,7 @@ async function startWatch(options) {
1718
1675
  dashboardWriter.endSession();
1719
1676
  dashboardWriter.close();
1720
1677
  }
1678
+ finalizeContextStore(config.agent);
1721
1679
  bashbros.stop();
1722
1680
  process.exit(0);
1723
1681
  });
@@ -1727,6 +1685,7 @@ async function startWatch(options) {
1727
1685
  dashboardWriter.endSession();
1728
1686
  dashboardWriter.close();
1729
1687
  }
1688
+ finalizeContextStore(config.agent);
1730
1689
  bashbros.stop();
1731
1690
  process.exit(0);
1732
1691
  });
@@ -1736,6 +1695,7 @@ async function startWatch(options) {
1736
1695
  dashboardWriter.crashSession();
1737
1696
  dashboardWriter.close();
1738
1697
  }
1698
+ finalizeContextStore(config.agent);
1739
1699
  cleanup();
1740
1700
  bashbros.stop();
1741
1701
  process.exit(1);
@@ -1776,6 +1736,48 @@ async function startWatch(options) {
1776
1736
  commandBuffer = "";
1777
1737
  if (command) {
1778
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
+ }
1779
1781
  if (violations.length > 0) {
1780
1782
  bashbros.write("");
1781
1783
  bashbros.write("\r");
@@ -1865,16 +1867,16 @@ import express from "express";
1865
1867
  import { WebSocketServer } from "ws";
1866
1868
  import { createServer } from "http";
1867
1869
  import { fileURLToPath } from "url";
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";
1870
+ import { dirname, join as join4 } from "path";
1871
+ import { homedir as homedir4 } from "os";
1872
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
1871
1873
  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 });
1874
+ function getDefaultDbPath() {
1875
+ const bashbrosDir = join4(homedir4(), ".bashbros");
1876
+ if (!existsSync5(bashbrosDir)) {
1877
+ mkdirSync2(bashbrosDir, { recursive: true });
1876
1878
  }
1877
- return join5(bashbrosDir, "dashboard.db");
1879
+ return join4(bashbrosDir, "dashboard.db");
1878
1880
  }
1879
1881
  var DashboardServer = class {
1880
1882
  app;
@@ -1887,7 +1889,7 @@ var DashboardServer = class {
1887
1889
  constructor(config = {}) {
1888
1890
  this.port = config.port ?? 17800;
1889
1891
  this.bind = config.bind ?? "127.0.0.1";
1890
- this.db = new DashboardDB(config.dbPath ?? getDefaultDbPath2());
1892
+ this.db = new DashboardDB(config.dbPath ?? getDefaultDbPath());
1891
1893
  this.app = express();
1892
1894
  this.setupMiddleware();
1893
1895
  this.setupRoutes();
@@ -1896,7 +1898,7 @@ var DashboardServer = class {
1896
1898
  this.app.use(express.json());
1897
1899
  this.app.use((_req, res, next) => {
1898
1900
  res.header("Access-Control-Allow-Origin", "*");
1899
- res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1901
+ res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
1900
1902
  res.header("Access-Control-Allow-Headers", "Content-Type");
1901
1903
  next();
1902
1904
  });
@@ -1940,7 +1942,7 @@ var DashboardServer = class {
1940
1942
  this.app.get("/api/connectors/:name/events", (req, res) => {
1941
1943
  try {
1942
1944
  const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
1943
- const events = this.db.getConnectorEvents(req.params.name, limit);
1945
+ const events = this.db.getConnectorEvents(String(req.params.name), limit);
1944
1946
  res.json(events);
1945
1947
  } catch (error) {
1946
1948
  res.status(500).json({ error: "Failed to fetch connector events" });
@@ -1956,7 +1958,7 @@ var DashboardServer = class {
1956
1958
  });
1957
1959
  this.app.post("/api/blocked/:id/approve", (req, res) => {
1958
1960
  try {
1959
- const { id } = req.params;
1961
+ const id = String(req.params.id);
1960
1962
  const approvedBy = req.body?.approvedBy ?? "dashboard-user";
1961
1963
  const block = this.db.getBlock(id);
1962
1964
  if (!block) {
@@ -1972,7 +1974,7 @@ var DashboardServer = class {
1972
1974
  });
1973
1975
  this.app.post("/api/blocked/:id/deny", (req, res) => {
1974
1976
  try {
1975
- const { id } = req.params;
1977
+ const id = String(req.params.id);
1976
1978
  const deniedBy = req.body?.deniedBy ?? "dashboard-user";
1977
1979
  const block = this.db.getBlock(id);
1978
1980
  if (!block) {
@@ -2013,9 +2015,17 @@ var DashboardServer = class {
2013
2015
  res.status(500).json({ error: "Failed to fetch active session" });
2014
2016
  }
2015
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
+ });
2016
2026
  this.app.get("/api/sessions/:id", (req, res) => {
2017
2027
  try {
2018
- const session = this.db.getSession(req.params.id);
2028
+ const session = this.db.getSession(String(req.params.id));
2019
2029
  if (!session) {
2020
2030
  res.status(404).json({ error: "Session not found" });
2021
2031
  return;
@@ -2028,7 +2038,7 @@ var DashboardServer = class {
2028
2038
  this.app.get("/api/sessions/:id/commands", (req, res) => {
2029
2039
  try {
2030
2040
  const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
2031
- const commands = this.db.getCommandsBySession(req.params.id, limit);
2041
+ const commands = this.db.getCommandsBySession(String(req.params.id), limit);
2032
2042
  res.json(commands);
2033
2043
  } catch (error) {
2034
2044
  res.status(500).json({ error: "Failed to fetch session commands" });
@@ -2036,12 +2046,12 @@ var DashboardServer = class {
2036
2046
  });
2037
2047
  this.app.get("/api/sessions/:id/metrics", (req, res) => {
2038
2048
  try {
2039
- const session = this.db.getSession(req.params.id);
2049
+ const session = this.db.getSession(String(req.params.id));
2040
2050
  if (!session) {
2041
2051
  res.status(404).json({ error: "Session not found" });
2042
2052
  return;
2043
2053
  }
2044
- const metrics = this.db.getSessionMetrics(req.params.id);
2054
+ const metrics = this.db.getSessionMetrics(String(req.params.id));
2045
2055
  res.json(metrics);
2046
2056
  } catch (error) {
2047
2057
  res.status(500).json({ error: "Failed to fetch session metrics" });
@@ -2050,8 +2060,16 @@ var DashboardServer = class {
2050
2060
  this.app.get("/api/commands/live", (req, res) => {
2051
2061
  try {
2052
2062
  const limit = req.query.limit ? parseInt(req.query.limit, 10) : 20;
2053
- const commands = this.db.getLiveCommands(limit);
2054
- 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
+ }
2055
2073
  } catch (error) {
2056
2074
  res.status(500).json({ error: "Failed to fetch live commands" });
2057
2075
  }
@@ -2072,6 +2090,37 @@ var DashboardServer = class {
2072
2090
  res.status(500).json({ error: "Failed to fetch commands" });
2073
2091
  }
2074
2092
  });
2093
+ this.app.get("/api/tools/live", (req, res) => {
2094
+ try {
2095
+ const limit = req.query.limit ? parseInt(req.query.limit, 10) : 50;
2096
+ const tools = this.db.getLiveToolUses(limit);
2097
+ res.json(tools);
2098
+ } catch (error) {
2099
+ res.status(500).json({ error: "Failed to fetch live tool uses" });
2100
+ }
2101
+ });
2102
+ this.app.get("/api/tools", (req, res) => {
2103
+ try {
2104
+ const filter = {};
2105
+ if (req.query.toolName) filter.toolName = req.query.toolName;
2106
+ if (req.query.sessionId) filter.sessionId = req.query.sessionId;
2107
+ if (req.query.since) filter.since = new Date(req.query.since);
2108
+ if (req.query.limit) filter.limit = parseInt(req.query.limit, 10);
2109
+ if (req.query.offset) filter.offset = parseInt(req.query.offset, 10);
2110
+ const tools = this.db.getToolUses(filter);
2111
+ res.json(tools);
2112
+ } catch (error) {
2113
+ res.status(500).json({ error: "Failed to fetch tool uses" });
2114
+ }
2115
+ });
2116
+ this.app.get("/api/tools/stats", (_req, res) => {
2117
+ try {
2118
+ const stats = this.db.getToolUseStats();
2119
+ res.json(stats);
2120
+ } catch (error) {
2121
+ res.status(500).json({ error: "Failed to fetch tool use stats" });
2122
+ }
2123
+ });
2075
2124
  this.app.get("/api/bro/status", (_req, res) => {
2076
2125
  try {
2077
2126
  const status = this.db.getLatestBroStatus();
@@ -2116,7 +2165,7 @@ var DashboardServer = class {
2116
2165
  res.status(400).json({ error: "Model name required" });
2117
2166
  return;
2118
2167
  }
2119
- const controlPath = join5(homedir5(), ".bashbros", "model-control.json");
2168
+ const controlPath = join4(homedir4(), ".bashbros", "model-control.json");
2120
2169
  writeFileSync4(controlPath, JSON.stringify({
2121
2170
  model,
2122
2171
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2128,7 +2177,7 @@ var DashboardServer = class {
2128
2177
  });
2129
2178
  this.app.post("/api/bro/scan", (_req, res) => {
2130
2179
  try {
2131
- const controlPath = join5(homedir5(), ".bashbros", "scan-control.json");
2180
+ const controlPath = join4(homedir4(), ".bashbros", "scan-control.json");
2132
2181
  writeFileSync4(controlPath, JSON.stringify({
2133
2182
  action: "scan",
2134
2183
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2138,6 +2187,217 @@ var DashboardServer = class {
2138
2187
  res.status(500).json({ error: "Failed to trigger scan" });
2139
2188
  }
2140
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
+ });
2141
2401
  this.app.get("/api/exposures", (req, res) => {
2142
2402
  try {
2143
2403
  const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
@@ -2176,12 +2436,73 @@ var DashboardServer = class {
2176
2436
  res.status(500).json({ error: "Failed to save config" });
2177
2437
  }
2178
2438
  });
2179
- const __filename = fileURLToPath(import.meta.url);
2180
- const __dirname = dirname(__filename);
2181
- const staticPath = join5(__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");
2182
2503
  this.app.use(express.static(staticPath));
2183
2504
  this.app.get("/{*path}", (_req, res) => {
2184
- res.sendFile(join5(staticPath, "index.html"));
2505
+ res.sendFile(join4(staticPath, "index.html"));
2185
2506
  });
2186
2507
  }
2187
2508
  setupWebSocket() {
@@ -2272,16 +2593,53 @@ var DashboardServer = class {
2272
2593
  };
2273
2594
 
2274
2595
  // src/cli.ts
2275
- var metricsCollector = null;
2276
- 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";
2277
2599
  var loopDetector = null;
2278
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
+ }
2279
2628
  var logo = `
2280
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
2281
2630
  \u{1F91D} Your Friendly Bash Agent Helper
2282
2631
  `;
2283
2632
  var program = new Command();
2284
- 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);
2285
2643
  program.command("init").description("Set up BashBros for your project").action(async () => {
2286
2644
  console.log(chalk5.cyan(logo));
2287
2645
  await runOnboarding();
@@ -2312,7 +2670,7 @@ program.command("scan").description("Scan your system and project environment").
2312
2670
  console.log(bro.getSystemContext());
2313
2671
  console.log();
2314
2672
  console.log(chalk5.bold("\n## Agent Configurations\n"));
2315
- const { formatAgentSummary } = await import("./display-6LZ2HBCU.js");
2673
+ const { formatAgentSummary } = await import("./display-UH7KEHOW.js");
2316
2674
  const agents = await getAllAgentConfigs();
2317
2675
  console.log(formatAgentSummary(agents));
2318
2676
  console.log();
@@ -2515,7 +2873,7 @@ program.command("do <description>").description("Convert natural language to a c
2515
2873
  });
2516
2874
  program.command("models").description("List available Ollama models").action(async () => {
2517
2875
  console.log(chalk5.cyan(logo));
2518
- const { OllamaClient } = await import("./ollama-HY35OHW4.js");
2876
+ const { OllamaClient } = await import("./ollama-5JVKNFOV.js");
2519
2877
  const ollama = new OllamaClient();
2520
2878
  const available = await ollama.isAvailable();
2521
2879
  if (!available) {
@@ -2559,11 +2917,30 @@ hookCmd.command("status").description("Check Claude Code hook status").action(()
2559
2917
  console.log();
2560
2918
  console.log(` Claude Code: ${status.claudeInstalled ? chalk5.green("installed") : chalk5.yellow("not found")}`);
2561
2919
  console.log(` BashBros hooks: ${status.hooksInstalled ? chalk5.green("active") : chalk5.dim("not installed")}`);
2920
+ console.log(` All-tools recording: ${status.allToolsInstalled ? chalk5.green("active") : chalk5.dim("not installed")}`);
2562
2921
  if (status.hooks.length > 0) {
2563
2922
  console.log(` Active hooks: ${status.hooks.join(", ")}`);
2564
2923
  }
2565
2924
  console.log();
2566
2925
  });
2926
+ hookCmd.command("install-all-tools").description("Install hook to record ALL Claude Code tool uses (not just Bash)").action(() => {
2927
+ const result = ClaudeCodeHooks.installAllTools();
2928
+ if (result.success) {
2929
+ console.log(chalk5.green("\u2713"), result.message);
2930
+ } else {
2931
+ console.log(chalk5.red("\u2717"), result.message);
2932
+ process.exit(1);
2933
+ }
2934
+ });
2935
+ hookCmd.command("uninstall-all-tools").description("Remove all-tools recording hook").action(() => {
2936
+ const result = ClaudeCodeHooks.uninstallAllTools();
2937
+ if (result.success) {
2938
+ console.log(chalk5.green("\u2713"), result.message);
2939
+ } else {
2940
+ console.log(chalk5.red("\u2717"), result.message);
2941
+ process.exit(1);
2942
+ }
2943
+ });
2567
2944
  var moltbotCmd = program.command("moltbot").alias("clawdbot").description("Manage Moltbot/Clawdbot integration");
2568
2945
  moltbotCmd.command("install").description("Install BashBros hooks into Moltbot").action(() => {
2569
2946
  const result = MoltbotHooks.install();
@@ -2664,14 +3041,14 @@ moltbotCmd.command("audit").description("Run Moltbot security audit").option("--
2664
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) => {
2665
3042
  console.log(chalk5.cyan(logo));
2666
3043
  if (agent) {
2667
- const validAgents = ["claude-code", "moltbot", "clawdbot", "aider", "gemini-cli", "opencode"];
3044
+ const validAgents = ["claude-code", "moltbot", "clawdbot", "copilot-cli", "aider", "gemini-cli", "opencode"];
2668
3045
  if (!validAgents.includes(agent)) {
2669
3046
  console.log(chalk5.red(`Unknown agent: ${agent}`));
2670
3047
  console.log(chalk5.dim(`Valid agents: ${validAgents.join(", ")}`));
2671
3048
  return;
2672
3049
  }
2673
3050
  const info = await getAgentConfigInfo(agent);
2674
- const { formatAgentInfo: formatAgentInfo2 } = await import("./display-6LZ2HBCU.js");
3051
+ const { formatAgentInfo: formatAgentInfo2 } = await import("./display-UH7KEHOW.js");
2675
3052
  console.log();
2676
3053
  console.log(formatAgentInfo2(info));
2677
3054
  if (options.raw && info.configExists && info.configPath) {
@@ -2703,14 +3080,60 @@ program.command("permissions").description("Show combined permissions view acros
2703
3080
  console.log(formatPermissionsTable(agents));
2704
3081
  console.log();
2705
3082
  });
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) => {
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
+ }
2707
3112
  const result = await gateCommand(command);
2708
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
+ }
2709
3132
  if (process.stdin.isTTY && !options.yes) {
2710
3133
  const { allowForSession: allowForSession2 } = await import("./session-Y4MICATZ.js");
2711
- const { readFileSync: readFileSync5, writeFileSync: writeFileSync5 } = await import("fs");
3134
+ const { readFileSync: readFileSync6, writeFileSync: writeFileSync5 } = await import("fs");
2712
3135
  const { parse: parse4, stringify: stringify5 } = await import("yaml");
2713
- const { findConfig: findConfig2 } = await import("./config-JLLOTFLI.js");
3136
+ const { findConfig: findConfig2 } = await import("./config-I5NCK3RJ.js");
2714
3137
  console.error();
2715
3138
  console.error(chalk5.red("\u{1F6E1}\uFE0F BashBros blocked a command"));
2716
3139
  console.error();
@@ -2751,7 +3174,7 @@ program.command("gate <command>").description("Check if a command should be allo
2751
3174
  try {
2752
3175
  const configPath = findConfig2();
2753
3176
  if (configPath) {
2754
- const content = readFileSync5(configPath, "utf-8");
3177
+ const content = readFileSync6(configPath, "utf-8");
2755
3178
  const config = parse4(content);
2756
3179
  if (!config.commands) config.commands = { allow: [], block: [] };
2757
3180
  if (!config.commands.allow) config.commands.allow = [];
@@ -2778,61 +3201,317 @@ program.command("gate <command>").description("Check if a command should be allo
2778
3201
  }
2779
3202
  process.exit(0);
2780
3203
  });
2781
- 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) => {
2782
- if (!metricsCollector) metricsCollector = new MetricsCollector();
2783
- if (!costEstimator) costEstimator = new CostEstimator();
2784
- if (!loopDetector) loopDetector = new LoopDetector();
2785
- if (!undoStack) undoStack = new UndoStack();
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 () => {
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
+ }
3215
+ if (!eventJson) {
3216
+ return;
3217
+ }
3218
+ try {
3219
+ const event = JSON.parse(eventJson);
3220
+ const events = Array.isArray(event) ? event : [event];
3221
+ const { DashboardWriter: DashboardWriter2 } = await import("./writer-3NAVABN6.js");
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));
3226
+ for (const evt of events) {
3227
+ const toolName = evt.tool_name || evt.tool || "unknown";
3228
+ const toolInput = evt.tool_input || evt.input || {};
3229
+ const toolOutput = evt.tool_output || evt.output || "";
3230
+ let inputStr;
3231
+ if (typeof toolInput === "string") {
3232
+ inputStr = toolInput;
3233
+ } else if (typeof toolInput === "object") {
3234
+ inputStr = JSON.stringify(toolInput, null, 2);
3235
+ } else {
3236
+ inputStr = String(toolInput);
3237
+ }
3238
+ let outputStr;
3239
+ let exitCode = null;
3240
+ let success = null;
3241
+ if (typeof toolOutput === "object" && toolOutput !== null) {
3242
+ outputStr = (toolOutput.stdout || "") + (toolOutput.stderr || "");
3243
+ exitCode = toolOutput.exit_code ?? toolOutput.exitCode ?? null;
3244
+ if (exitCode !== null) {
3245
+ success = exitCode === 0;
3246
+ }
3247
+ } else {
3248
+ outputStr = String(toolOutput || "");
3249
+ }
3250
+ const repoName = evt.repo?.name ?? null;
3251
+ const repoPath = evt.repo?.path ?? null;
3252
+ writer.recordToolUse({
3253
+ toolName,
3254
+ toolInput: inputStr,
3255
+ toolOutput: outputStr,
3256
+ exitCode,
3257
+ success,
3258
+ cwd: evt.cwd || process.cwd(),
3259
+ repoName,
3260
+ repoPath
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
+ }
3275
+ const preview = inputStr.substring(0, 40).replace(/\n/g, " ");
3276
+ console.log(`[BashBros] ${toolName}: ${preview}${inputStr.length > 40 ? "..." : ""}`);
3277
+ }
3278
+ writer.close();
3279
+ } catch (e) {
3280
+ console.error(`[BashBros] Error recording tool: ${e instanceof Error ? e.message : e}`);
3281
+ }
3282
+ });
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);
2786
3313
  const scorer = new RiskScorer();
2787
3314
  const risk = scorer.score(command);
2788
- metricsCollector.record({
2789
- command,
2790
- timestamp: /* @__PURE__ */ new Date(),
2791
- duration: 0,
2792
- // Not available in hook
2793
- allowed: true,
2794
- riskScore: risk,
2795
- violations: [],
2796
- exitCode: parseInt(options.exitCode) || 0
2797
- });
2798
- costEstimator.recordToolCall(command, options.output || "");
2799
3315
  const loopAlert = loopDetector.check(command);
2800
3316
  if (loopAlert) {
2801
3317
  console.error(chalk5.yellow(`\u26A0 Loop detected: ${loopAlert.message}`));
2802
3318
  }
2803
- const paths = command.match(/(?:^|\s)(\.\/|\.\.\/|\/|~\/)[^\s]+/g) || [];
2804
- const cleanPaths = paths.map((p) => p.trim());
2805
- if (cleanPaths.length > 0) {
2806
- 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
+ }
2807
3366
  }
2808
3367
  });
2809
- program.command("session-end").description("Generate session report (used by hooks)").option("-f, --format <format>", "Output format (text, markdown, json)", "text").action((options) => {
2810
- 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 {
2811
3443
  console.log(chalk5.dim("No session data to report."));
2812
- return;
2813
3444
  }
2814
- const metrics = metricsCollector.getMetrics();
2815
- const cost = costEstimator?.getEstimate();
2816
- const report = ReportGenerator.generate(metrics, cost, { format: options.format });
2817
- console.log();
2818
- console.log(report);
2819
- console.log();
2820
3445
  });
2821
- 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) => {
2822
- 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 {
2823
3513
  console.log(chalk5.dim("No session data. Run some commands first."));
2824
- return;
2825
3514
  }
2826
- const metrics = metricsCollector.getMetrics();
2827
- const cost = options.cost ? costEstimator?.getEstimate() : void 0;
2828
- const report = ReportGenerator.generate(metrics, cost, {
2829
- format: options.format,
2830
- showCost: options.cost,
2831
- showRisk: options.risk
2832
- });
2833
- console.log();
2834
- console.log(report);
2835
- console.log();
2836
3515
  });
2837
3516
  program.command("risk <command>").description("Score a command for security risk").action((command) => {
2838
3517
  const scorer = new RiskScorer();
@@ -3048,5 +3727,257 @@ patternsCmd.command("test <text>").description("Test if text matches any detecti
3048
3727
  }
3049
3728
  console.log();
3050
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
+ });
3051
3982
  program.parse();
3052
3983
  //# sourceMappingURL=cli.js.map