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
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DashboardDB
|
|
4
|
+
} from "./chunk-RDNSS3ME.js";
|
|
5
|
+
|
|
6
|
+
// src/dashboard/writer.ts
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { mkdirSync, existsSync } from "fs";
|
|
10
|
+
function getDefaultDbPath() {
|
|
11
|
+
const bashbrosDir = join(homedir(), ".bashbros");
|
|
12
|
+
if (!existsSync(bashbrosDir)) {
|
|
13
|
+
mkdirSync(bashbrosDir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
return join(bashbrosDir, "dashboard.db");
|
|
16
|
+
}
|
|
17
|
+
var DashboardWriter = class {
|
|
18
|
+
db;
|
|
19
|
+
sessionId = null;
|
|
20
|
+
commandCount = 0;
|
|
21
|
+
blockedCount = 0;
|
|
22
|
+
totalRiskScore = 0;
|
|
23
|
+
hookMode = false;
|
|
24
|
+
constructor(dbPath) {
|
|
25
|
+
const path = dbPath ?? getDefaultDbPath();
|
|
26
|
+
this.db = new DashboardDB(path);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Start a new watch session
|
|
30
|
+
*/
|
|
31
|
+
startSession(agent, workingDir) {
|
|
32
|
+
this.sessionId = this.db.insertSession({
|
|
33
|
+
agent,
|
|
34
|
+
pid: process.pid,
|
|
35
|
+
workingDir
|
|
36
|
+
});
|
|
37
|
+
this.commandCount = 0;
|
|
38
|
+
this.blockedCount = 0;
|
|
39
|
+
this.totalRiskScore = 0;
|
|
40
|
+
return this.sessionId;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Ensure a hook-mode session exists for the given external session ID.
|
|
44
|
+
* Idempotent - safe to call multiple times per hook invocation.
|
|
45
|
+
* Uses atomic DB increments so concurrent hook processes don't clobber each other.
|
|
46
|
+
*/
|
|
47
|
+
ensureHookSession(hookSessionId, workingDir, repoName) {
|
|
48
|
+
this.db.insertSessionWithId(hookSessionId, {
|
|
49
|
+
agent: "claude-code",
|
|
50
|
+
pid: process.pid,
|
|
51
|
+
workingDir,
|
|
52
|
+
repoName: repoName ?? null,
|
|
53
|
+
mode: "hook"
|
|
54
|
+
});
|
|
55
|
+
this.sessionId = hookSessionId;
|
|
56
|
+
this.hookMode = true;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* End a hook-mode session by ID
|
|
60
|
+
*/
|
|
61
|
+
endHookSession(hookSessionId) {
|
|
62
|
+
const session = this.db.getSession(hookSessionId);
|
|
63
|
+
if (session && session.status === "active") {
|
|
64
|
+
this.db.updateSession(hookSessionId, {
|
|
65
|
+
endTime: /* @__PURE__ */ new Date(),
|
|
66
|
+
status: "completed"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* End the current session
|
|
72
|
+
*/
|
|
73
|
+
endSession() {
|
|
74
|
+
if (!this.sessionId) return;
|
|
75
|
+
const avgRiskScore = this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0;
|
|
76
|
+
this.db.updateSession(this.sessionId, {
|
|
77
|
+
endTime: /* @__PURE__ */ new Date(),
|
|
78
|
+
status: "completed",
|
|
79
|
+
commandCount: this.commandCount,
|
|
80
|
+
blockedCount: this.blockedCount,
|
|
81
|
+
avgRiskScore
|
|
82
|
+
});
|
|
83
|
+
this.sessionId = null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Mark session as crashed (for unexpected exits)
|
|
87
|
+
*/
|
|
88
|
+
crashSession() {
|
|
89
|
+
if (!this.sessionId) return;
|
|
90
|
+
const avgRiskScore = this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0;
|
|
91
|
+
this.db.updateSession(this.sessionId, {
|
|
92
|
+
endTime: /* @__PURE__ */ new Date(),
|
|
93
|
+
status: "crashed",
|
|
94
|
+
commandCount: this.commandCount,
|
|
95
|
+
blockedCount: this.blockedCount,
|
|
96
|
+
avgRiskScore
|
|
97
|
+
});
|
|
98
|
+
this.sessionId = null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Record a command execution
|
|
102
|
+
*/
|
|
103
|
+
recordCommand(command, allowed, riskScore, violations, durationMs) {
|
|
104
|
+
const input = {
|
|
105
|
+
sessionId: this.sessionId ?? void 0,
|
|
106
|
+
command,
|
|
107
|
+
allowed,
|
|
108
|
+
riskScore: riskScore.score,
|
|
109
|
+
riskLevel: riskScore.level,
|
|
110
|
+
riskFactors: riskScore.factors,
|
|
111
|
+
durationMs,
|
|
112
|
+
violations: violations.map((v) => v.message)
|
|
113
|
+
};
|
|
114
|
+
const id = this.db.insertCommand(input);
|
|
115
|
+
if (this.sessionId) {
|
|
116
|
+
if (this.hookMode) {
|
|
117
|
+
this.db.incrementSessionCommand(this.sessionId, !allowed, riskScore.score);
|
|
118
|
+
} else {
|
|
119
|
+
this.commandCount++;
|
|
120
|
+
this.totalRiskScore += riskScore.score;
|
|
121
|
+
if (!allowed) {
|
|
122
|
+
this.blockedCount++;
|
|
123
|
+
}
|
|
124
|
+
if (this.commandCount % 10 === 0) {
|
|
125
|
+
this.flushSessionStats();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return id;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Record a Bash Bro AI event
|
|
133
|
+
*/
|
|
134
|
+
recordBroEvent(input) {
|
|
135
|
+
const dbInput = {
|
|
136
|
+
sessionId: this.sessionId ?? void 0,
|
|
137
|
+
eventType: input.eventType,
|
|
138
|
+
inputContext: input.inputContext,
|
|
139
|
+
outputSummary: input.outputSummary,
|
|
140
|
+
modelUsed: input.modelUsed,
|
|
141
|
+
latencyMs: input.latencyMs,
|
|
142
|
+
success: input.success
|
|
143
|
+
};
|
|
144
|
+
return this.db.insertBroEvent(dbInput);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Update Bash Bro status
|
|
148
|
+
*/
|
|
149
|
+
updateBroStatus(status) {
|
|
150
|
+
const dbInput = {
|
|
151
|
+
ollamaAvailable: status.ollamaAvailable,
|
|
152
|
+
ollamaModel: status.ollamaModel,
|
|
153
|
+
platform: status.platform,
|
|
154
|
+
shell: status.shell,
|
|
155
|
+
projectType: status.projectType
|
|
156
|
+
};
|
|
157
|
+
return this.db.updateBroStatus(dbInput);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Record a generic tool use (for all Claude Code tools)
|
|
161
|
+
*/
|
|
162
|
+
recordToolUse(input) {
|
|
163
|
+
const dbInput = {
|
|
164
|
+
toolName: input.toolName,
|
|
165
|
+
toolInput: input.toolInput,
|
|
166
|
+
toolOutput: input.toolOutput,
|
|
167
|
+
exitCode: input.exitCode,
|
|
168
|
+
success: input.success,
|
|
169
|
+
cwd: input.cwd,
|
|
170
|
+
repoName: input.repoName,
|
|
171
|
+
repoPath: input.repoPath,
|
|
172
|
+
sessionId: this.sessionId ?? void 0
|
|
173
|
+
};
|
|
174
|
+
return this.db.insertToolUse(dbInput);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get current session ID
|
|
178
|
+
*/
|
|
179
|
+
getSessionId() {
|
|
180
|
+
return this.sessionId;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get current session stats
|
|
184
|
+
*/
|
|
185
|
+
getSessionStats() {
|
|
186
|
+
return {
|
|
187
|
+
commandCount: this.commandCount,
|
|
188
|
+
blockedCount: this.blockedCount,
|
|
189
|
+
avgRiskScore: this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Flush in-memory session stats to DB (watch mode only).
|
|
194
|
+
*/
|
|
195
|
+
flushSessionStats() {
|
|
196
|
+
if (!this.sessionId || this.hookMode || this.commandCount === 0) return;
|
|
197
|
+
const avgRiskScore = this.totalRiskScore / this.commandCount;
|
|
198
|
+
this.db.updateSession(this.sessionId, {
|
|
199
|
+
commandCount: this.commandCount,
|
|
200
|
+
blockedCount: this.blockedCount,
|
|
201
|
+
avgRiskScore
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Close database connection
|
|
206
|
+
*/
|
|
207
|
+
close() {
|
|
208
|
+
this.flushSessionStats();
|
|
209
|
+
this.db.close();
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get the underlying database instance (for advanced use)
|
|
213
|
+
*/
|
|
214
|
+
getDB() {
|
|
215
|
+
return this.db;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var writer_default = DashboardWriter;
|
|
219
|
+
|
|
220
|
+
export {
|
|
221
|
+
DashboardWriter,
|
|
222
|
+
writer_default
|
|
223
|
+
};
|
|
224
|
+
//# sourceMappingURL=chunk-KYDMPE4N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dashboard/writer.ts"],"sourcesContent":["/**\n * Dashboard Writer Module\n * Bridge for watch mode to write monitoring data to the dashboard database\n */\n\nimport { homedir } from 'os'\nimport { join } from 'path'\nimport { mkdirSync, existsSync } from 'fs'\nimport { DashboardDB, type InsertCommandInput, type InsertBroEventInput, type InsertBroStatusInput, type InsertToolUseInput } from './db.js'\nimport type { RiskScore } from '../policy/risk-scorer.js'\nimport type { PolicyViolation } from '../types.js'\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface BroEventInput {\n eventType: string\n inputContext: string\n outputSummary: string\n modelUsed: string\n latencyMs: number\n success: boolean\n}\n\nexport interface BroStatusInput {\n ollamaAvailable: boolean\n ollamaModel: string\n platform: string\n shell: string\n projectType?: string\n}\n\nexport interface ToolUseInput {\n toolName: string\n toolInput: string\n toolOutput: string\n exitCode?: number | null\n success?: boolean | null\n cwd: string\n repoName?: string | null\n repoPath?: string | null\n}\n\n// ─────────────────────────────────────────────────────────────\n// Default Database Path\n// ─────────────────────────────────────────────────────────────\n\nfunction getDefaultDbPath(): string {\n const bashbrosDir = join(homedir(), '.bashbros')\n\n // Ensure directory exists\n if (!existsSync(bashbrosDir)) {\n mkdirSync(bashbrosDir, { recursive: true })\n }\n\n return join(bashbrosDir, 'dashboard.db')\n}\n\n// ─────────────────────────────────────────────────────────────\n// Dashboard Writer Class\n// ─────────────────────────────────────────────────────────────\n\nexport class DashboardWriter {\n private db: DashboardDB\n private sessionId: string | null = null\n private commandCount: number = 0\n private blockedCount: number = 0\n private totalRiskScore: number = 0\n private hookMode: boolean = false\n\n constructor(dbPath?: string) {\n const path = dbPath ?? getDefaultDbPath()\n this.db = new DashboardDB(path)\n }\n\n /**\n * Start a new watch session\n */\n startSession(agent: string, workingDir: string): string {\n this.sessionId = this.db.insertSession({\n agent,\n pid: process.pid,\n workingDir\n })\n\n this.commandCount = 0\n this.blockedCount = 0\n this.totalRiskScore = 0\n\n return this.sessionId\n }\n\n /**\n * Ensure a hook-mode session exists for the given external session ID.\n * Idempotent - safe to call multiple times per hook invocation.\n * Uses atomic DB increments so concurrent hook processes don't clobber each other.\n */\n ensureHookSession(hookSessionId: string, workingDir: string, repoName?: string | null): void {\n // INSERT OR IGNORE handles the race where two processes try to create simultaneously\n this.db.insertSessionWithId(hookSessionId, {\n agent: 'claude-code',\n pid: process.pid,\n workingDir,\n repoName: repoName ?? null,\n mode: 'hook'\n })\n this.sessionId = hookSessionId\n this.hookMode = true\n }\n\n /**\n * End a hook-mode session by ID\n */\n endHookSession(hookSessionId: string): void {\n const session = this.db.getSession(hookSessionId)\n if (session && session.status === 'active') {\n this.db.updateSession(hookSessionId, {\n endTime: new Date(),\n status: 'completed'\n })\n }\n }\n\n /**\n * End the current session\n */\n endSession(): void {\n if (!this.sessionId) return\n\n const avgRiskScore = this.commandCount > 0\n ? this.totalRiskScore / this.commandCount\n : 0\n\n this.db.updateSession(this.sessionId, {\n endTime: new Date(),\n status: 'completed',\n commandCount: this.commandCount,\n blockedCount: this.blockedCount,\n avgRiskScore\n })\n\n this.sessionId = null\n }\n\n /**\n * Mark session as crashed (for unexpected exits)\n */\n crashSession(): void {\n if (!this.sessionId) return\n\n const avgRiskScore = this.commandCount > 0\n ? this.totalRiskScore / this.commandCount\n : 0\n\n this.db.updateSession(this.sessionId, {\n endTime: new Date(),\n status: 'crashed',\n commandCount: this.commandCount,\n blockedCount: this.blockedCount,\n avgRiskScore\n })\n\n this.sessionId = null\n }\n\n /**\n * Record a command execution\n */\n recordCommand(\n command: string,\n allowed: boolean,\n riskScore: RiskScore,\n violations: PolicyViolation[],\n durationMs: number\n ): string | null {\n const input: InsertCommandInput = {\n sessionId: this.sessionId ?? undefined,\n command,\n allowed,\n riskScore: riskScore.score,\n riskLevel: riskScore.level,\n riskFactors: riskScore.factors,\n durationMs,\n violations: violations.map(v => v.message)\n }\n\n const id = this.db.insertCommand(input)\n\n // Update session stats if we have an active session\n if (this.sessionId) {\n if (this.hookMode) {\n // Hook mode: atomic SQL increment, race-safe across concurrent processes\n this.db.incrementSessionCommand(this.sessionId, !allowed, riskScore.score)\n } else {\n // Watch mode: batch in memory, flush periodically or on close\n this.commandCount++\n this.totalRiskScore += riskScore.score\n if (!allowed) {\n this.blockedCount++\n }\n\n if (this.commandCount % 10 === 0) {\n this.flushSessionStats()\n }\n }\n }\n\n return id\n }\n\n /**\n * Record a Bash Bro AI event\n */\n recordBroEvent(input: BroEventInput): string {\n const dbInput: InsertBroEventInput = {\n sessionId: this.sessionId ?? undefined,\n eventType: input.eventType,\n inputContext: input.inputContext,\n outputSummary: input.outputSummary,\n modelUsed: input.modelUsed,\n latencyMs: input.latencyMs,\n success: input.success\n }\n\n return this.db.insertBroEvent(dbInput)\n }\n\n /**\n * Update Bash Bro status\n */\n updateBroStatus(status: BroStatusInput): string {\n const dbInput: InsertBroStatusInput = {\n ollamaAvailable: status.ollamaAvailable,\n ollamaModel: status.ollamaModel,\n platform: status.platform,\n shell: status.shell,\n projectType: status.projectType\n }\n\n return this.db.updateBroStatus(dbInput)\n }\n\n /**\n * Record a generic tool use (for all Claude Code tools)\n */\n recordToolUse(input: ToolUseInput): string {\n const dbInput: InsertToolUseInput = {\n toolName: input.toolName,\n toolInput: input.toolInput,\n toolOutput: input.toolOutput,\n exitCode: input.exitCode,\n success: input.success,\n cwd: input.cwd,\n repoName: input.repoName,\n repoPath: input.repoPath,\n sessionId: this.sessionId ?? undefined\n }\n\n return this.db.insertToolUse(dbInput)\n }\n\n /**\n * Get current session ID\n */\n getSessionId(): string | null {\n return this.sessionId\n }\n\n /**\n * Get current session stats\n */\n getSessionStats(): {\n commandCount: number\n blockedCount: number\n avgRiskScore: number\n } {\n return {\n commandCount: this.commandCount,\n blockedCount: this.blockedCount,\n avgRiskScore: this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0\n }\n }\n\n /**\n * Flush in-memory session stats to DB (watch mode only).\n */\n private flushSessionStats(): void {\n if (!this.sessionId || this.hookMode || this.commandCount === 0) return\n const avgRiskScore = this.totalRiskScore / this.commandCount\n this.db.updateSession(this.sessionId, {\n commandCount: this.commandCount,\n blockedCount: this.blockedCount,\n avgRiskScore\n })\n }\n\n /**\n * Close database connection\n */\n close(): void {\n this.flushSessionStats()\n this.db.close()\n }\n\n /**\n * Get the underlying database instance (for advanced use)\n */\n getDB(): DashboardDB {\n return this.db\n }\n}\n\nexport default DashboardWriter\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/bro/profiles.ts
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, unlinkSync, mkdirSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
var DEFAULT_PROFILES_DIR = join(homedir(), ".bashbros", "models", "profiles");
|
|
8
|
+
var ProfileManager = class {
|
|
9
|
+
profilesDir;
|
|
10
|
+
constructor(profilesDir) {
|
|
11
|
+
this.profilesDir = profilesDir || DEFAULT_PROFILES_DIR;
|
|
12
|
+
}
|
|
13
|
+
save(profile) {
|
|
14
|
+
if (!existsSync(this.profilesDir)) mkdirSync(this.profilesDir, { recursive: true });
|
|
15
|
+
writeFileSync(join(this.profilesDir, `${profile.name}.json`), JSON.stringify(profile, null, 2));
|
|
16
|
+
}
|
|
17
|
+
load(name) {
|
|
18
|
+
const filePath = join(this.profilesDir, `${name}.json`);
|
|
19
|
+
if (!existsSync(filePath)) return null;
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
list() {
|
|
27
|
+
if (!existsSync(this.profilesDir)) return [];
|
|
28
|
+
try {
|
|
29
|
+
return readdirSync(this.profilesDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(readFileSync(join(this.profilesDir, f), "utf-8"));
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}).filter((p) => p !== null);
|
|
36
|
+
} catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
delete(name) {
|
|
41
|
+
const filePath = join(this.profilesDir, `${name}.json`);
|
|
42
|
+
if (!existsSync(filePath)) return false;
|
|
43
|
+
unlinkSync(filePath);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
getModelForPurpose(profile, purpose) {
|
|
47
|
+
const adapterName = profile.adapters[purpose];
|
|
48
|
+
if (adapterName) return `bashbros/${adapterName}`;
|
|
49
|
+
return profile.baseModel;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
ProfileManager
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=chunk-LJE4EPIU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bro/profiles.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, readdirSync, unlinkSync, mkdirSync } from 'fs'\nimport { join } from 'path'\nimport { homedir } from 'os'\nimport type { AdapterPurpose } from './adapters.js'\n\nexport interface ModelProfile {\n name: string\n baseModel: string\n adapters: Partial<Record<AdapterPurpose, string>>\n}\n\nconst DEFAULT_PROFILES_DIR = join(homedir(), '.bashbros', 'models', 'profiles')\n\nexport class ProfileManager {\n private profilesDir: string\n\n constructor(profilesDir?: string) {\n this.profilesDir = profilesDir || DEFAULT_PROFILES_DIR\n }\n\n save(profile: ModelProfile): void {\n if (!existsSync(this.profilesDir)) mkdirSync(this.profilesDir, { recursive: true })\n writeFileSync(join(this.profilesDir, `${profile.name}.json`), JSON.stringify(profile, null, 2))\n }\n\n load(name: string): ModelProfile | null {\n const filePath = join(this.profilesDir, `${name}.json`)\n if (!existsSync(filePath)) return null\n try { return JSON.parse(readFileSync(filePath, 'utf-8')) as ModelProfile } catch { return null }\n }\n\n list(): ModelProfile[] {\n if (!existsSync(this.profilesDir)) return []\n try {\n return readdirSync(this.profilesDir)\n .filter(f => f.endsWith('.json'))\n .map(f => { try { return JSON.parse(readFileSync(join(this.profilesDir, f), 'utf-8')) as ModelProfile } catch { return null } })\n .filter((p): p is ModelProfile => p !== null)\n } catch { return [] }\n }\n\n delete(name: string): boolean {\n const filePath = join(this.profilesDir, `${name}.json`)\n if (!existsSync(filePath)) return false\n unlinkSync(filePath)\n return true\n }\n\n getModelForPurpose(profile: ModelProfile, purpose: AdapterPurpose): string {\n const adapterName = profile.adapters[purpose]\n if (adapterName) return `bashbros/${adapterName}`\n return profile.baseModel\n }\n}\n"],"mappings":";;;AAAA,SAAS,YAAY,cAAc,eAAe,aAAa,YAAY,iBAAiB;AAC5F,SAAS,YAAY;AACrB,SAAS,eAAe;AASxB,IAAM,uBAAuB,KAAK,QAAQ,GAAG,aAAa,UAAU,UAAU;AAEvE,IAAM,iBAAN,MAAqB;AAAA,EAClB;AAAA,EAER,YAAY,aAAsB;AAChC,SAAK,cAAc,eAAe;AAAA,EACpC;AAAA,EAEA,KAAK,SAA6B;AAChC,QAAI,CAAC,WAAW,KAAK,WAAW,EAAG,WAAU,KAAK,aAAa,EAAE,WAAW,KAAK,CAAC;AAClF,kBAAc,KAAK,KAAK,aAAa,GAAG,QAAQ,IAAI,OAAO,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,EAChG;AAAA,EAEA,KAAK,MAAmC;AACtC,UAAM,WAAW,KAAK,KAAK,aAAa,GAAG,IAAI,OAAO;AACtD,QAAI,CAAC,WAAW,QAAQ,EAAG,QAAO;AAClC,QAAI;AAAE,aAAO,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;AAAA,IAAkB,QAAQ;AAAE,aAAO;AAAA,IAAK;AAAA,EACjG;AAAA,EAEA,OAAuB;AACrB,QAAI,CAAC,WAAW,KAAK,WAAW,EAAG,QAAO,CAAC;AAC3C,QAAI;AACF,aAAO,YAAY,KAAK,WAAW,EAChC,OAAO,OAAK,EAAE,SAAS,OAAO,CAAC,EAC/B,IAAI,OAAK;AAAE,YAAI;AAAE,iBAAO,KAAK,MAAM,aAAa,KAAK,KAAK,aAAa,CAAC,GAAG,OAAO,CAAC;AAAA,QAAkB,QAAQ;AAAE,iBAAO;AAAA,QAAK;AAAA,MAAE,CAAC,EAC9H,OAAO,CAAC,MAAyB,MAAM,IAAI;AAAA,IAChD,QAAQ;AAAE,aAAO,CAAC;AAAA,IAAE;AAAA,EACtB;AAAA,EAEA,OAAO,MAAuB;AAC5B,UAAM,WAAW,KAAK,KAAK,aAAa,GAAG,IAAI,OAAO;AACtD,QAAI,CAAC,WAAW,QAAQ,EAAG,QAAO;AAClC,eAAW,QAAQ;AACnB,WAAO;AAAA,EACT;AAAA,EAEA,mBAAmB,SAAuB,SAAiC;AACzE,UAAM,cAAc,QAAQ,SAAS,OAAO;AAC5C,QAAI,YAAa,QAAO,YAAY,WAAW;AAC/C,WAAO,QAAQ;AAAA,EACjB;AACF;","names":[]}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/hooks/claude-code.ts
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
var CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|
|
8
|
+
var CLAUDE_DIR = join(homedir(), ".claude");
|
|
9
|
+
var BASHBROS_HOOK_MARKER = "# bashbros-managed";
|
|
10
|
+
var BASHBROS_ALL_TOOLS_MARKER = "--marker=bashbros-all-tools";
|
|
11
|
+
var ClaudeCodeHooks = class {
|
|
12
|
+
/**
|
|
13
|
+
* Check if Claude Code is installed
|
|
14
|
+
*/
|
|
15
|
+
static isClaudeInstalled() {
|
|
16
|
+
return existsSync(CLAUDE_DIR);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Load current Claude settings
|
|
20
|
+
*/
|
|
21
|
+
static loadSettings() {
|
|
22
|
+
if (!existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const content = readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
|
|
27
|
+
return JSON.parse(content);
|
|
28
|
+
} catch {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Save Claude settings
|
|
34
|
+
*/
|
|
35
|
+
static saveSettings(settings) {
|
|
36
|
+
if (!existsSync(CLAUDE_DIR)) {
|
|
37
|
+
mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
writeFileSync(
|
|
40
|
+
CLAUDE_SETTINGS_PATH,
|
|
41
|
+
JSON.stringify(settings, null, 2),
|
|
42
|
+
"utf-8"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Install BashBros hooks into Claude Code
|
|
47
|
+
*/
|
|
48
|
+
static install() {
|
|
49
|
+
if (!this.isClaudeInstalled()) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
message: "Claude Code not found. Install Claude Code first."
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const settings = this.loadSettings();
|
|
56
|
+
if (!settings.hooks) {
|
|
57
|
+
settings.hooks = {};
|
|
58
|
+
}
|
|
59
|
+
if (this.isInstalled(settings)) {
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
message: "BashBros hooks already installed."
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const preToolUseHook = {
|
|
66
|
+
matcher: "Bash",
|
|
67
|
+
hooks: [{
|
|
68
|
+
type: "command",
|
|
69
|
+
command: `bashbros gate "$TOOL_INPUT" ${BASHBROS_HOOK_MARKER}`
|
|
70
|
+
}]
|
|
71
|
+
};
|
|
72
|
+
const postToolUseHook = {
|
|
73
|
+
matcher: "Bash",
|
|
74
|
+
hooks: [{
|
|
75
|
+
type: "command",
|
|
76
|
+
command: `bashbros record "$TOOL_INPUT" "$TOOL_OUTPUT" ${BASHBROS_HOOK_MARKER}`
|
|
77
|
+
}]
|
|
78
|
+
};
|
|
79
|
+
const sessionEndHook = {
|
|
80
|
+
hooks: [{
|
|
81
|
+
type: "command",
|
|
82
|
+
command: `bashbros session-end ${BASHBROS_HOOK_MARKER}`
|
|
83
|
+
}]
|
|
84
|
+
};
|
|
85
|
+
settings.hooks.PreToolUse = [
|
|
86
|
+
...settings.hooks.PreToolUse || [],
|
|
87
|
+
preToolUseHook
|
|
88
|
+
];
|
|
89
|
+
settings.hooks.PostToolUse = [
|
|
90
|
+
...settings.hooks.PostToolUse || [],
|
|
91
|
+
postToolUseHook
|
|
92
|
+
];
|
|
93
|
+
settings.hooks.SessionEnd = [
|
|
94
|
+
...settings.hooks.SessionEnd || [],
|
|
95
|
+
sessionEndHook
|
|
96
|
+
];
|
|
97
|
+
this.saveSettings(settings);
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
message: "BashBros hooks installed successfully."
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Uninstall BashBros hooks from Claude Code
|
|
105
|
+
*/
|
|
106
|
+
static uninstall() {
|
|
107
|
+
if (!this.isClaudeInstalled()) {
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
message: "Claude Code not found."
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const settings = this.loadSettings();
|
|
114
|
+
if (!settings.hooks) {
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
message: "No hooks to uninstall."
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const filterHooks = (hooks) => {
|
|
121
|
+
if (!hooks) return [];
|
|
122
|
+
return hooks.filter(
|
|
123
|
+
(h) => !h.hooks.some((hook) => hook.command.includes(BASHBROS_HOOK_MARKER))
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
settings.hooks.PreToolUse = filterHooks(settings.hooks.PreToolUse);
|
|
127
|
+
settings.hooks.PostToolUse = filterHooks(settings.hooks.PostToolUse);
|
|
128
|
+
settings.hooks.SessionEnd = filterHooks(settings.hooks.SessionEnd);
|
|
129
|
+
if (settings.hooks.PreToolUse?.length === 0) delete settings.hooks.PreToolUse;
|
|
130
|
+
if (settings.hooks.PostToolUse?.length === 0) delete settings.hooks.PostToolUse;
|
|
131
|
+
if (settings.hooks.SessionEnd?.length === 0) delete settings.hooks.SessionEnd;
|
|
132
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
133
|
+
this.saveSettings(settings);
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
message: "BashBros hooks uninstalled successfully."
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if BashBros hooks are installed
|
|
141
|
+
*/
|
|
142
|
+
static isInstalled(settings) {
|
|
143
|
+
const s = settings || this.loadSettings();
|
|
144
|
+
if (!s.hooks) return false;
|
|
145
|
+
const hasMarker = (hooks) => {
|
|
146
|
+
if (!hooks) return false;
|
|
147
|
+
return hooks.some(
|
|
148
|
+
(h) => h.hooks.some((hook) => hook.command.includes(BASHBROS_HOOK_MARKER))
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
return hasMarker(s.hooks.PreToolUse) || hasMarker(s.hooks.PostToolUse) || hasMarker(s.hooks.SessionEnd);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get hook status
|
|
155
|
+
*/
|
|
156
|
+
static getStatus() {
|
|
157
|
+
const claudeInstalled = this.isClaudeInstalled();
|
|
158
|
+
const settings = claudeInstalled ? this.loadSettings() : {};
|
|
159
|
+
const hooksInstalled = this.isInstalled(settings);
|
|
160
|
+
const allToolsInstalled = this.isAllToolsInstalled(settings);
|
|
161
|
+
const hooks = [];
|
|
162
|
+
if (settings.hooks?.PreToolUse) hooks.push("PreToolUse (gate)");
|
|
163
|
+
if (settings.hooks?.PostToolUse) hooks.push("PostToolUse (record)");
|
|
164
|
+
if (settings.hooks?.SessionEnd) hooks.push("SessionEnd (report)");
|
|
165
|
+
if (allToolsInstalled) hooks.push("PostToolUse (all-tools)");
|
|
166
|
+
return {
|
|
167
|
+
claudeInstalled,
|
|
168
|
+
hooksInstalled,
|
|
169
|
+
allToolsInstalled,
|
|
170
|
+
hooks
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if all-tools recording is installed
|
|
175
|
+
*/
|
|
176
|
+
static isAllToolsInstalled(settings) {
|
|
177
|
+
const s = settings || this.loadSettings();
|
|
178
|
+
if (!s.hooks?.PostToolUse) return false;
|
|
179
|
+
return s.hooks.PostToolUse.some(
|
|
180
|
+
(h) => h.hooks.some(
|
|
181
|
+
(hook) => hook.command.includes(BASHBROS_ALL_TOOLS_MARKER) || hook.command.includes("bashbros-all-tools")
|
|
182
|
+
)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Install all-tools recording hook (records ALL Claude Code tools, not just Bash)
|
|
187
|
+
*/
|
|
188
|
+
static installAllTools() {
|
|
189
|
+
if (!this.isClaudeInstalled()) {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
message: "Claude Code not found. Install Claude Code first."
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const settings = this.loadSettings();
|
|
196
|
+
if (!settings.hooks) {
|
|
197
|
+
settings.hooks = {};
|
|
198
|
+
}
|
|
199
|
+
if (this.isAllToolsInstalled(settings)) {
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
message: "BashBros all-tools recording already installed."
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const allToolsHook = {
|
|
206
|
+
matcher: "",
|
|
207
|
+
// Empty matcher matches ALL tools
|
|
208
|
+
hooks: [{
|
|
209
|
+
type: "command",
|
|
210
|
+
command: `bashbros record-tool ${BASHBROS_ALL_TOOLS_MARKER}`
|
|
211
|
+
}]
|
|
212
|
+
};
|
|
213
|
+
settings.hooks.PostToolUse = [
|
|
214
|
+
allToolsHook,
|
|
215
|
+
...settings.hooks.PostToolUse || []
|
|
216
|
+
];
|
|
217
|
+
this.saveSettings(settings);
|
|
218
|
+
return {
|
|
219
|
+
success: true,
|
|
220
|
+
message: "BashBros all-tools recording installed. All Claude Code tools will now be recorded."
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Uninstall all-tools recording hook
|
|
225
|
+
*/
|
|
226
|
+
static uninstallAllTools() {
|
|
227
|
+
if (!this.isClaudeInstalled()) {
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
message: "Claude Code not found."
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const settings = this.loadSettings();
|
|
234
|
+
if (!settings.hooks?.PostToolUse) {
|
|
235
|
+
return {
|
|
236
|
+
success: true,
|
|
237
|
+
message: "No all-tools hook to uninstall."
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(
|
|
241
|
+
(h) => !h.hooks.some(
|
|
242
|
+
(hook) => hook.command.includes(BASHBROS_ALL_TOOLS_MARKER) || hook.command.includes("bashbros-all-tools")
|
|
243
|
+
)
|
|
244
|
+
);
|
|
245
|
+
if (settings.hooks.PostToolUse.length === 0) {
|
|
246
|
+
delete settings.hooks.PostToolUse;
|
|
247
|
+
}
|
|
248
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
249
|
+
delete settings.hooks;
|
|
250
|
+
}
|
|
251
|
+
this.saveSettings(settings);
|
|
252
|
+
return {
|
|
253
|
+
success: true,
|
|
254
|
+
message: "BashBros all-tools recording uninstalled."
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
async function gateCommand(command) {
|
|
259
|
+
const { PolicyEngine } = await import("./engine-EGPAS2EX.js");
|
|
260
|
+
const { RiskScorer } = await import("./risk-scorer-Y6KF2XCZ.js");
|
|
261
|
+
const { loadConfig } = await import("./config-I5NCK3RJ.js");
|
|
262
|
+
const config = loadConfig();
|
|
263
|
+
const engine = new PolicyEngine(config);
|
|
264
|
+
const scorer = new RiskScorer();
|
|
265
|
+
const violations = engine.validate(command);
|
|
266
|
+
const risk = scorer.score(command);
|
|
267
|
+
if (violations.length > 0) {
|
|
268
|
+
return {
|
|
269
|
+
allowed: false,
|
|
270
|
+
reason: violations[0].message,
|
|
271
|
+
riskScore: risk.score
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (config.riskScoring.enabled) {
|
|
275
|
+
if (risk.score >= config.riskScoring.blockThreshold) {
|
|
276
|
+
return {
|
|
277
|
+
allowed: false,
|
|
278
|
+
reason: `Risk score ${risk.score} >= block threshold ${config.riskScoring.blockThreshold}: ${risk.factors.join(", ")}`,
|
|
279
|
+
riskScore: risk.score
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
if (risk.score >= config.riskScoring.warnThreshold) {
|
|
283
|
+
process.stderr.write(`[BashBros] Warning: risk score ${risk.score} (${risk.factors.join(", ")})
|
|
284
|
+
`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
const { join: join2 } = await import("path");
|
|
289
|
+
const { homedir: homedir2 } = await import("os");
|
|
290
|
+
const { DashboardDB } = await import("./db-ETWTBXAE.js");
|
|
291
|
+
const { checkLoopDetection, checkAnomalyDetection, checkRateLimit } = await import("./db-checks-2YOVECD4.js");
|
|
292
|
+
const dbPath = join2(homedir2(), ".bashbros", "dashboard.db");
|
|
293
|
+
const db = new DashboardDB(dbPath);
|
|
294
|
+
try {
|
|
295
|
+
if (config.loopDetection.enabled) {
|
|
296
|
+
const loop = checkLoopDetection(command, config.loopDetection, db);
|
|
297
|
+
if (loop.violation) {
|
|
298
|
+
db.close();
|
|
299
|
+
return { allowed: false, reason: loop.violation.message, riskScore: risk.score };
|
|
300
|
+
}
|
|
301
|
+
if (loop.warning) {
|
|
302
|
+
process.stderr.write(`[BashBros] ${loop.warning}
|
|
303
|
+
`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (config.anomalyDetection.enabled) {
|
|
307
|
+
const anomaly = checkAnomalyDetection(command, config.anomalyDetection, db);
|
|
308
|
+
if (anomaly.violation) {
|
|
309
|
+
db.close();
|
|
310
|
+
return { allowed: false, reason: anomaly.violation.message, riskScore: risk.score };
|
|
311
|
+
}
|
|
312
|
+
if (anomaly.warning) {
|
|
313
|
+
process.stderr.write(`[BashBros] ${anomaly.warning}
|
|
314
|
+
`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (config.rateLimit.enabled) {
|
|
318
|
+
const rate = checkRateLimit(config.rateLimit, db);
|
|
319
|
+
if (rate.violation) {
|
|
320
|
+
db.close();
|
|
321
|
+
return { allowed: false, reason: rate.violation.message, riskScore: risk.score };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
} finally {
|
|
325
|
+
db.close();
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
allowed: true,
|
|
331
|
+
riskScore: risk.score
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export {
|
|
336
|
+
ClaudeCodeHooks,
|
|
337
|
+
gateCommand
|
|
338
|
+
};
|
|
339
|
+
//# sourceMappingURL=chunk-LZYW7XQO.js.map
|