bashbros 0.1.1 → 0.1.3

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.
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DashboardDB
4
+ } from "./chunk-JYWQT2B4.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
+ constructor(dbPath) {
24
+ const path = dbPath ?? getDefaultDbPath();
25
+ this.db = new DashboardDB(path);
26
+ }
27
+ /**
28
+ * Start a new watch session
29
+ */
30
+ startSession(agent, workingDir) {
31
+ this.sessionId = this.db.insertSession({
32
+ agent,
33
+ pid: process.pid,
34
+ workingDir
35
+ });
36
+ this.commandCount = 0;
37
+ this.blockedCount = 0;
38
+ this.totalRiskScore = 0;
39
+ return this.sessionId;
40
+ }
41
+ /**
42
+ * End the current session
43
+ */
44
+ endSession() {
45
+ if (!this.sessionId) return;
46
+ const avgRiskScore = this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0;
47
+ this.db.updateSession(this.sessionId, {
48
+ endTime: /* @__PURE__ */ new Date(),
49
+ status: "completed",
50
+ commandCount: this.commandCount,
51
+ blockedCount: this.blockedCount,
52
+ avgRiskScore
53
+ });
54
+ this.sessionId = null;
55
+ }
56
+ /**
57
+ * Mark session as crashed (for unexpected exits)
58
+ */
59
+ crashSession() {
60
+ if (!this.sessionId) return;
61
+ const avgRiskScore = this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0;
62
+ this.db.updateSession(this.sessionId, {
63
+ endTime: /* @__PURE__ */ new Date(),
64
+ status: "crashed",
65
+ commandCount: this.commandCount,
66
+ blockedCount: this.blockedCount,
67
+ avgRiskScore
68
+ });
69
+ this.sessionId = null;
70
+ }
71
+ /**
72
+ * Record a command execution
73
+ */
74
+ recordCommand(command, allowed, riskScore, violations, durationMs) {
75
+ if (!this.sessionId) return null;
76
+ const input = {
77
+ sessionId: this.sessionId,
78
+ command,
79
+ allowed,
80
+ riskScore: riskScore.score,
81
+ riskLevel: riskScore.level,
82
+ riskFactors: riskScore.factors,
83
+ durationMs,
84
+ violations: violations.map((v) => v.message)
85
+ };
86
+ const id = this.db.insertCommand(input);
87
+ this.commandCount++;
88
+ this.totalRiskScore += riskScore.score;
89
+ if (!allowed) {
90
+ this.blockedCount++;
91
+ }
92
+ if (this.commandCount % 10 === 0) {
93
+ const avgRiskScore = this.totalRiskScore / this.commandCount;
94
+ this.db.updateSession(this.sessionId, {
95
+ commandCount: this.commandCount,
96
+ blockedCount: this.blockedCount,
97
+ avgRiskScore
98
+ });
99
+ }
100
+ return id;
101
+ }
102
+ /**
103
+ * Record a Bash Bro AI event
104
+ */
105
+ recordBroEvent(input) {
106
+ const dbInput = {
107
+ sessionId: this.sessionId ?? void 0,
108
+ eventType: input.eventType,
109
+ inputContext: input.inputContext,
110
+ outputSummary: input.outputSummary,
111
+ modelUsed: input.modelUsed,
112
+ latencyMs: input.latencyMs,
113
+ success: input.success
114
+ };
115
+ return this.db.insertBroEvent(dbInput);
116
+ }
117
+ /**
118
+ * Update Bash Bro status
119
+ */
120
+ updateBroStatus(status) {
121
+ const dbInput = {
122
+ ollamaAvailable: status.ollamaAvailable,
123
+ ollamaModel: status.ollamaModel,
124
+ platform: status.platform,
125
+ shell: status.shell,
126
+ projectType: status.projectType
127
+ };
128
+ return this.db.updateBroStatus(dbInput);
129
+ }
130
+ /**
131
+ * Record a generic tool use (for all Claude Code tools)
132
+ */
133
+ recordToolUse(input) {
134
+ const dbInput = {
135
+ toolName: input.toolName,
136
+ toolInput: input.toolInput,
137
+ toolOutput: input.toolOutput,
138
+ exitCode: input.exitCode,
139
+ success: input.success,
140
+ cwd: input.cwd,
141
+ repoName: input.repoName,
142
+ repoPath: input.repoPath
143
+ };
144
+ return this.db.insertToolUse(dbInput);
145
+ }
146
+ /**
147
+ * Get current session ID
148
+ */
149
+ getSessionId() {
150
+ return this.sessionId;
151
+ }
152
+ /**
153
+ * Get current session stats
154
+ */
155
+ getSessionStats() {
156
+ return {
157
+ commandCount: this.commandCount,
158
+ blockedCount: this.blockedCount,
159
+ avgRiskScore: this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0
160
+ };
161
+ }
162
+ /**
163
+ * Close database connection
164
+ */
165
+ close() {
166
+ this.db.close();
167
+ }
168
+ /**
169
+ * Get the underlying database instance (for advanced use)
170
+ */
171
+ getDB() {
172
+ return this.db;
173
+ }
174
+ };
175
+ var writer_default = DashboardWriter;
176
+
177
+ export {
178
+ DashboardWriter,
179
+ writer_default
180
+ };
181
+ //# sourceMappingURL=chunk-EYO44OMN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dashboard/writer.ts"],"sourcesContent":["/**\r\n * Dashboard Writer Module\r\n * Bridge for watch mode to write monitoring data to the dashboard database\r\n */\r\n\r\nimport { homedir } from 'os'\r\nimport { join } from 'path'\r\nimport { mkdirSync, existsSync } from 'fs'\r\nimport { DashboardDB, type InsertCommandInput, type InsertBroEventInput, type InsertBroStatusInput, type InsertToolUseInput } from './db.js'\r\nimport type { RiskScore } from '../policy/risk-scorer.js'\r\nimport type { PolicyViolation } from '../types.js'\r\n\r\n// ─────────────────────────────────────────────────────────────\r\n// Types\r\n// ─────────────────────────────────────────────────────────────\r\n\r\nexport interface BroEventInput {\r\n eventType: string\r\n inputContext: string\r\n outputSummary: string\r\n modelUsed: string\r\n latencyMs: number\r\n success: boolean\r\n}\r\n\r\nexport interface BroStatusInput {\r\n ollamaAvailable: boolean\r\n ollamaModel: string\r\n platform: string\r\n shell: string\r\n projectType?: string\r\n}\r\n\r\nexport interface ToolUseInput {\r\n toolName: string\r\n toolInput: string\r\n toolOutput: string\r\n exitCode?: number | null\r\n success?: boolean | null\r\n cwd: string\r\n repoName?: string | null\r\n repoPath?: string | null\r\n}\r\n\r\n// ─────────────────────────────────────────────────────────────\r\n// Default Database Path\r\n// ─────────────────────────────────────────────────────────────\r\n\r\nfunction getDefaultDbPath(): string {\r\n const bashbrosDir = join(homedir(), '.bashbros')\r\n\r\n // Ensure directory exists\r\n if (!existsSync(bashbrosDir)) {\r\n mkdirSync(bashbrosDir, { recursive: true })\r\n }\r\n\r\n return join(bashbrosDir, 'dashboard.db')\r\n}\r\n\r\n// ─────────────────────────────────────────────────────────────\r\n// Dashboard Writer Class\r\n// ─────────────────────────────────────────────────────────────\r\n\r\nexport class DashboardWriter {\r\n private db: DashboardDB\r\n private sessionId: string | null = null\r\n private commandCount: number = 0\r\n private blockedCount: number = 0\r\n private totalRiskScore: number = 0\r\n\r\n constructor(dbPath?: string) {\r\n const path = dbPath ?? getDefaultDbPath()\r\n this.db = new DashboardDB(path)\r\n }\r\n\r\n /**\r\n * Start a new watch session\r\n */\r\n startSession(agent: string, workingDir: string): string {\r\n this.sessionId = this.db.insertSession({\r\n agent,\r\n pid: process.pid,\r\n workingDir\r\n })\r\n\r\n this.commandCount = 0\r\n this.blockedCount = 0\r\n this.totalRiskScore = 0\r\n\r\n return this.sessionId\r\n }\r\n\r\n /**\r\n * End the current session\r\n */\r\n endSession(): void {\r\n if (!this.sessionId) return\r\n\r\n const avgRiskScore = this.commandCount > 0\r\n ? this.totalRiskScore / this.commandCount\r\n : 0\r\n\r\n this.db.updateSession(this.sessionId, {\r\n endTime: new Date(),\r\n status: 'completed',\r\n commandCount: this.commandCount,\r\n blockedCount: this.blockedCount,\r\n avgRiskScore\r\n })\r\n\r\n this.sessionId = null\r\n }\r\n\r\n /**\r\n * Mark session as crashed (for unexpected exits)\r\n */\r\n crashSession(): void {\r\n if (!this.sessionId) return\r\n\r\n const avgRiskScore = this.commandCount > 0\r\n ? this.totalRiskScore / this.commandCount\r\n : 0\r\n\r\n this.db.updateSession(this.sessionId, {\r\n endTime: new Date(),\r\n status: 'crashed',\r\n commandCount: this.commandCount,\r\n blockedCount: this.blockedCount,\r\n avgRiskScore\r\n })\r\n\r\n this.sessionId = null\r\n }\r\n\r\n /**\r\n * Record a command execution\r\n */\r\n recordCommand(\r\n command: string,\r\n allowed: boolean,\r\n riskScore: RiskScore,\r\n violations: PolicyViolation[],\r\n durationMs: number\r\n ): string | null {\r\n if (!this.sessionId) return null\r\n\r\n const input: InsertCommandInput = {\r\n sessionId: this.sessionId,\r\n command,\r\n allowed,\r\n riskScore: riskScore.score,\r\n riskLevel: riskScore.level,\r\n riskFactors: riskScore.factors,\r\n durationMs,\r\n violations: violations.map(v => v.message)\r\n }\r\n\r\n const id = this.db.insertCommand(input)\r\n\r\n // Update session stats\r\n this.commandCount++\r\n this.totalRiskScore += riskScore.score\r\n if (!allowed) {\r\n this.blockedCount++\r\n }\r\n\r\n // Update session in DB periodically (every 10 commands)\r\n if (this.commandCount % 10 === 0) {\r\n const avgRiskScore = this.totalRiskScore / this.commandCount\r\n this.db.updateSession(this.sessionId, {\r\n commandCount: this.commandCount,\r\n blockedCount: this.blockedCount,\r\n avgRiskScore\r\n })\r\n }\r\n\r\n return id\r\n }\r\n\r\n /**\r\n * Record a Bash Bro AI event\r\n */\r\n recordBroEvent(input: BroEventInput): string {\r\n const dbInput: InsertBroEventInput = {\r\n sessionId: this.sessionId ?? undefined,\r\n eventType: input.eventType,\r\n inputContext: input.inputContext,\r\n outputSummary: input.outputSummary,\r\n modelUsed: input.modelUsed,\r\n latencyMs: input.latencyMs,\r\n success: input.success\r\n }\r\n\r\n return this.db.insertBroEvent(dbInput)\r\n }\r\n\r\n /**\r\n * Update Bash Bro status\r\n */\r\n updateBroStatus(status: BroStatusInput): string {\r\n const dbInput: InsertBroStatusInput = {\r\n ollamaAvailable: status.ollamaAvailable,\r\n ollamaModel: status.ollamaModel,\r\n platform: status.platform,\r\n shell: status.shell,\r\n projectType: status.projectType\r\n }\r\n\r\n return this.db.updateBroStatus(dbInput)\r\n }\r\n\r\n /**\r\n * Record a generic tool use (for all Claude Code tools)\r\n */\r\n recordToolUse(input: ToolUseInput): string {\r\n const dbInput: InsertToolUseInput = {\r\n toolName: input.toolName,\r\n toolInput: input.toolInput,\r\n toolOutput: input.toolOutput,\r\n exitCode: input.exitCode,\r\n success: input.success,\r\n cwd: input.cwd,\r\n repoName: input.repoName,\r\n repoPath: input.repoPath\r\n }\r\n\r\n return this.db.insertToolUse(dbInput)\r\n }\r\n\r\n /**\r\n * Get current session ID\r\n */\r\n getSessionId(): string | null {\r\n return this.sessionId\r\n }\r\n\r\n /**\r\n * Get current session stats\r\n */\r\n getSessionStats(): {\r\n commandCount: number\r\n blockedCount: number\r\n avgRiskScore: number\r\n } {\r\n return {\r\n commandCount: this.commandCount,\r\n blockedCount: this.blockedCount,\r\n avgRiskScore: this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0\r\n }\r\n }\r\n\r\n /**\r\n * Close database connection\r\n */\r\n close(): void {\r\n this.db.close()\r\n }\r\n\r\n /**\r\n * Get the underlying database instance (for advanced use)\r\n */\r\n getDB(): DashboardDB {\r\n return this.db\r\n }\r\n}\r\n\r\nexport default DashboardWriter\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/session.ts
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+ var SESSION_FILE = join(homedir(), ".bashbros", "session-allow.json");
8
+ function ensureDir() {
9
+ const dir = join(homedir(), ".bashbros");
10
+ if (!existsSync(dir)) {
11
+ mkdirSync(dir, { recursive: true, mode: 448 });
12
+ }
13
+ }
14
+ function loadSession() {
15
+ try {
16
+ if (!existsSync(SESSION_FILE)) {
17
+ return null;
18
+ }
19
+ const data = JSON.parse(readFileSync(SESSION_FILE, "utf-8"));
20
+ if (data.pid !== process.pid) {
21
+ const age = Date.now() - data.startTime;
22
+ if (age > 24 * 60 * 60 * 1e3) {
23
+ return null;
24
+ }
25
+ }
26
+ return data;
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ function saveSession(data) {
32
+ ensureDir();
33
+ writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2), { mode: 384 });
34
+ }
35
+ function getOrCreateSession() {
36
+ const existing = loadSession();
37
+ if (existing) {
38
+ return existing;
39
+ }
40
+ const newSession = {
41
+ pid: process.pid,
42
+ startTime: Date.now(),
43
+ allowedCommands: []
44
+ };
45
+ saveSession(newSession);
46
+ return newSession;
47
+ }
48
+ function allowForSession(command) {
49
+ const session = getOrCreateSession();
50
+ if (!session.allowedCommands.includes(command)) {
51
+ session.allowedCommands.push(command);
52
+ saveSession(session);
53
+ }
54
+ }
55
+ function isAllowedForSession(command) {
56
+ const session = loadSession();
57
+ if (!session) {
58
+ return false;
59
+ }
60
+ if (session.allowedCommands.includes(command)) {
61
+ return true;
62
+ }
63
+ for (const allowed of session.allowedCommands) {
64
+ if (allowed.endsWith("*")) {
65
+ const prefix = allowed.slice(0, -1);
66
+ if (command.startsWith(prefix)) {
67
+ return true;
68
+ }
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+ function getSessionAllowlist() {
74
+ const session = loadSession();
75
+ return session?.allowedCommands || [];
76
+ }
77
+ function clearSessionAllowlist() {
78
+ const session = getOrCreateSession();
79
+ session.allowedCommands = [];
80
+ saveSession(session);
81
+ }
82
+
83
+ export {
84
+ allowForSession,
85
+ isAllowedForSession,
86
+ getSessionAllowlist,
87
+ clearSessionAllowlist
88
+ };
89
+ //# sourceMappingURL=chunk-FRMAIRQ2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/session.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'\r\nimport { join } from 'path'\r\nimport { homedir } from 'os'\r\n\r\n/**\r\n * Session-based allowlist for temporary command permissions.\r\n * Stored in a temp file that gets cleared on restart.\r\n */\r\n\r\nconst SESSION_FILE = join(homedir(), '.bashbros', 'session-allow.json')\r\n\r\ninterface SessionData {\r\n pid: number\r\n startTime: number\r\n allowedCommands: string[]\r\n}\r\n\r\nfunction ensureDir(): void {\r\n const dir = join(homedir(), '.bashbros')\r\n if (!existsSync(dir)) {\r\n mkdirSync(dir, { recursive: true, mode: 0o700 })\r\n }\r\n}\r\n\r\nfunction loadSession(): SessionData | null {\r\n try {\r\n if (!existsSync(SESSION_FILE)) {\r\n return null\r\n }\r\n\r\n const data = JSON.parse(readFileSync(SESSION_FILE, 'utf-8'))\r\n\r\n // Check if session is from current process\r\n if (data.pid !== process.pid) {\r\n // Different process - check if it's stale (older than 24 hours)\r\n const age = Date.now() - data.startTime\r\n if (age > 24 * 60 * 60 * 1000) {\r\n return null\r\n }\r\n }\r\n\r\n return data\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\nfunction saveSession(data: SessionData): void {\r\n ensureDir()\r\n writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2), { mode: 0o600 })\r\n}\r\n\r\nfunction getOrCreateSession(): SessionData {\r\n const existing = loadSession()\r\n\r\n if (existing) {\r\n return existing\r\n }\r\n\r\n const newSession: SessionData = {\r\n pid: process.pid,\r\n startTime: Date.now(),\r\n allowedCommands: []\r\n }\r\n\r\n saveSession(newSession)\r\n return newSession\r\n}\r\n\r\n/**\r\n * Add a command to the session allowlist\r\n */\r\nexport function allowForSession(command: string): void {\r\n const session = getOrCreateSession()\r\n\r\n if (!session.allowedCommands.includes(command)) {\r\n session.allowedCommands.push(command)\r\n saveSession(session)\r\n }\r\n}\r\n\r\n/**\r\n * Check if a command is allowed for this session\r\n */\r\nexport function isAllowedForSession(command: string): boolean {\r\n const session = loadSession()\r\n\r\n if (!session) {\r\n return false\r\n }\r\n\r\n // Check exact match\r\n if (session.allowedCommands.includes(command)) {\r\n return true\r\n }\r\n\r\n // Check pattern match (command starts with allowed pattern)\r\n for (const allowed of session.allowedCommands) {\r\n if (allowed.endsWith('*')) {\r\n const prefix = allowed.slice(0, -1)\r\n if (command.startsWith(prefix)) {\r\n return true\r\n }\r\n }\r\n }\r\n\r\n return false\r\n}\r\n\r\n/**\r\n * Get all commands allowed for this session\r\n */\r\nexport function getSessionAllowlist(): string[] {\r\n const session = loadSession()\r\n return session?.allowedCommands || []\r\n}\r\n\r\n/**\r\n * Clear the session allowlist\r\n */\r\nexport function clearSessionAllowlist(): void {\r\n const session = getOrCreateSession()\r\n session.allowedCommands = []\r\n saveSession(session)\r\n}\r\n"],"mappings":";;;AAAA,SAAS,YAAY,cAAc,eAAe,iBAAiB;AACnE,SAAS,YAAY;AACrB,SAAS,eAAe;AAOxB,IAAM,eAAe,KAAK,QAAQ,GAAG,aAAa,oBAAoB;AAQtE,SAAS,YAAkB;AACzB,QAAM,MAAM,KAAK,QAAQ,GAAG,WAAW;AACvC,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,cAAU,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAAA,EACjD;AACF;AAEA,SAAS,cAAkC;AACzC,MAAI;AACF,QAAI,CAAC,WAAW,YAAY,GAAG;AAC7B,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAG3D,QAAI,KAAK,QAAQ,QAAQ,KAAK;AAE5B,YAAM,MAAM,KAAK,IAAI,IAAI,KAAK;AAC9B,UAAI,MAAM,KAAK,KAAK,KAAK,KAAM;AAC7B,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,YAAY,MAAyB;AAC5C,YAAU;AACV,gBAAc,cAAc,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAC5E;AAEA,SAAS,qBAAkC;AACzC,QAAM,WAAW,YAAY;AAE7B,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AAEA,QAAM,aAA0B;AAAA,IAC9B,KAAK,QAAQ;AAAA,IACb,WAAW,KAAK,IAAI;AAAA,IACpB,iBAAiB,CAAC;AAAA,EACpB;AAEA,cAAY,UAAU;AACtB,SAAO;AACT;AAKO,SAAS,gBAAgB,SAAuB;AACrD,QAAM,UAAU,mBAAmB;AAEnC,MAAI,CAAC,QAAQ,gBAAgB,SAAS,OAAO,GAAG;AAC9C,YAAQ,gBAAgB,KAAK,OAAO;AACpC,gBAAY,OAAO;AAAA,EACrB;AACF;AAKO,SAAS,oBAAoB,SAA0B;AAC5D,QAAM,UAAU,YAAY;AAE5B,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,gBAAgB,SAAS,OAAO,GAAG;AAC7C,WAAO;AAAA,EACT;AAGA,aAAW,WAAW,QAAQ,iBAAiB;AAC7C,QAAI,QAAQ,SAAS,GAAG,GAAG;AACzB,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,UAAI,QAAQ,WAAW,MAAM,GAAG;AAC9B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,sBAAgC;AAC9C,QAAM,UAAU,YAAY;AAC5B,SAAO,SAAS,mBAAmB,CAAC;AACtC;AAKO,SAAS,wBAA8B;AAC5C,QAAM,UAAU,mBAAmB;AACnC,UAAQ,kBAAkB,CAAC;AAC3B,cAAY,OAAO;AACrB;","names":[]}