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.
- package/README.md +727 -265
- package/dist/adapters-JAZGGNVP.js +9 -0
- package/dist/chunk-4XZ64P4V.js +47 -0
- package/dist/chunk-4XZ64P4V.js.map +1 -0
- package/dist/{chunk-XCZMQRSX.js → chunk-7OEWYFN3.js} +745 -541
- package/dist/chunk-7OEWYFN3.js.map +1 -0
- package/dist/{chunk-SQCP6IYB.js → chunk-CG6VEHJM.js} +3 -2
- package/dist/chunk-CG6VEHJM.js.map +1 -0
- package/dist/{chunk-DLP2O6PN.js → chunk-EMLEJVJZ.js} +102 -1
- package/dist/chunk-EMLEJVJZ.js.map +1 -0
- package/dist/chunk-IUUBCPMV.js +166 -0
- package/dist/chunk-IUUBCPMV.js.map +1 -0
- package/dist/chunk-J6ONXY6N.js +146 -0
- package/dist/chunk-J6ONXY6N.js.map +1 -0
- package/dist/chunk-KYDMPE4N.js +224 -0
- package/dist/chunk-KYDMPE4N.js.map +1 -0
- package/dist/chunk-LJE4EPIU.js +56 -0
- package/dist/chunk-LJE4EPIU.js.map +1 -0
- package/dist/chunk-LZYW7XQO.js +339 -0
- package/dist/chunk-LZYW7XQO.js.map +1 -0
- package/dist/{chunk-YUMNBQAY.js → chunk-RDNSS3ME.js} +587 -12
- package/dist/chunk-RDNSS3ME.js.map +1 -0
- package/dist/{chunk-BW6XCOJH.js → chunk-RTZ4QWG2.js} +2 -2
- package/dist/chunk-RTZ4QWG2.js.map +1 -0
- package/dist/chunk-SDN6TAGD.js +157 -0
- package/dist/chunk-SDN6TAGD.js.map +1 -0
- package/dist/chunk-T5ONCUHZ.js +198 -0
- package/dist/chunk-T5ONCUHZ.js.map +1 -0
- package/dist/cli.js +1182 -251
- package/dist/cli.js.map +1 -1
- package/dist/{config-JLLOTFLI.js → config-I5NCK3RJ.js} +2 -2
- package/dist/copilot-cli-5WJWK5YT.js +9 -0
- package/dist/{db-OBKEXRTP.js → db-ETWTBXAE.js} +2 -2
- package/dist/db-checks-2YOVECD4.js +133 -0
- package/dist/db-checks-2YOVECD4.js.map +1 -0
- package/dist/{display-6LZ2HBCU.js → display-UH7KEHOW.js} +3 -3
- package/dist/display-UH7KEHOW.js.map +1 -0
- package/dist/gemini-cli-3563EELZ.js +9 -0
- package/dist/gemini-cli-3563EELZ.js.map +1 -0
- package/dist/index.d.ts +195 -72
- package/dist/index.js +119 -398
- package/dist/index.js.map +1 -1
- package/dist/{ollama-HY35OHW4.js → ollama-5JVKNFOV.js} +2 -2
- package/dist/ollama-5JVKNFOV.js.map +1 -0
- package/dist/opencode-DRCY275R.js +9 -0
- package/dist/opencode-DRCY275R.js.map +1 -0
- package/dist/profiles-7CLN6TAT.js +9 -0
- package/dist/profiles-7CLN6TAT.js.map +1 -0
- package/dist/setup-YS27MOPE.js +124 -0
- package/dist/setup-YS27MOPE.js.map +1 -0
- package/dist/static/index.html +4815 -2007
- package/dist/store-WJ5Y7MOE.js +9 -0
- package/dist/store-WJ5Y7MOE.js.map +1 -0
- package/dist/writer-3NAVABN6.js +12 -0
- package/dist/writer-3NAVABN6.js.map +1 -0
- package/package.json +77 -68
- package/dist/chunk-BW6XCOJH.js.map +0 -1
- package/dist/chunk-DLP2O6PN.js.map +0 -1
- package/dist/chunk-SQCP6IYB.js.map +0 -1
- package/dist/chunk-XCZMQRSX.js.map +0 -1
- package/dist/chunk-YUMNBQAY.js.map +0 -1
- /package/dist/{config-JLLOTFLI.js.map → adapters-JAZGGNVP.js.map} +0 -0
- /package/dist/{db-OBKEXRTP.js.map → config-I5NCK3RJ.js.map} +0 -0
- /package/dist/{display-6LZ2HBCU.js.map → copilot-cli-5WJWK5YT.js.map} +0 -0
- /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-
|
|
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
|
-
|
|
31
|
+
OutputScanner,
|
|
16
32
|
ReportGenerator,
|
|
17
33
|
UndoStack,
|
|
18
|
-
gateCommand,
|
|
19
34
|
getBashgymIntegration
|
|
20
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-7OEWYFN3.js";
|
|
36
|
+
import {
|
|
37
|
+
ClaudeCodeHooks,
|
|
38
|
+
gateCommand
|
|
39
|
+
} from "./chunk-LZYW7XQO.js";
|
|
40
|
+
import {
|
|
41
|
+
MoltbotHooks
|
|
42
|
+
} from "./chunk-J37RHCFJ.js";
|
|
21
43
|
import "./chunk-SG752FZC.js";
|
|
22
|
-
import "./chunk-
|
|
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-
|
|
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
|
-
|
|
37
|
-
|
|
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-
|
|
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
|
|
1869
|
-
import { homedir as
|
|
1870
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as
|
|
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
|
|
1873
|
-
const bashbrosDir =
|
|
1874
|
-
if (!
|
|
1875
|
-
|
|
1874
|
+
function getDefaultDbPath() {
|
|
1875
|
+
const bashbrosDir = join4(homedir4(), ".bashbros");
|
|
1876
|
+
if (!existsSync5(bashbrosDir)) {
|
|
1877
|
+
mkdirSync2(bashbrosDir, { recursive: true });
|
|
1876
1878
|
}
|
|
1877
|
-
return
|
|
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 ??
|
|
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
|
|
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
|
|
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
|
-
|
|
2054
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
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(
|
|
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
|
-
|
|
2276
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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:
|
|
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-
|
|
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 =
|
|
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
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
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
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|