bashbros 0.1.2 → 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.
@@ -228,7 +228,7 @@ function validateRemotePath(value) {
228
228
  function getDefaultConfig() {
229
229
  return {
230
230
  agent: "claude-code",
231
- profile: "permissive",
231
+ profile: "balanced",
232
232
  commands: getDefaultCommands("balanced"),
233
233
  paths: getDefaultPaths("balanced"),
234
234
  secrets: {
@@ -498,10 +498,9 @@ function getDefaultCommands(profile) {
498
498
  "biome *",
499
499
  "ruff *",
500
500
  "black *",
501
- // AI coding assistants & security tools
501
+ // AI coding assistants
502
502
  "claude *",
503
503
  "aider *",
504
- "bashbros *",
505
504
  // Editors
506
505
  "code *",
507
506
  "cursor *",
@@ -603,4 +602,4 @@ export {
603
602
  loadConfig,
604
603
  getDefaultConfig
605
604
  };
606
- //# sourceMappingURL=chunk-BW6XCOJH.js.map
605
+ //# sourceMappingURL=chunk-A535VV7N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { readFileSync, existsSync, statSync } from 'fs'\nimport { parse } from 'yaml'\nimport { join } from 'path'\nimport { homedir } from 'os'\nimport type {\n BashBrosConfig,\n SecurityProfile,\n RiskScoringPolicy,\n LoopDetectionPolicy,\n AnomalyDetectionPolicy,\n OutputScanningPolicy,\n UndoPolicy,\n RiskPattern,\n WardPolicy,\n DashboardPolicy\n} from './types.js'\n\nconst CONFIG_FILENAME = '.bashbros.yml'\n\n// Configuration limits for validation\nconst CONFIG_LIMITS = {\n maxPerMinute: { min: 1, max: 10000 },\n maxPerHour: { min: 1, max: 100000 },\n maxPatterns: 100,\n maxPathLength: 1000\n}\n\nexport function findConfig(): string | null {\n // Check current directory\n if (existsSync(CONFIG_FILENAME)) {\n return CONFIG_FILENAME\n }\n\n // Check home directory\n const homeConfig = join(homedir(), CONFIG_FILENAME)\n if (existsSync(homeConfig)) {\n return homeConfig\n }\n\n // Check ~/.bashbros/config.yml\n const dotConfig = join(homedir(), '.bashbros', 'config.yml')\n if (existsSync(dotConfig)) {\n return dotConfig\n }\n\n return null\n}\n\n/**\n * SECURITY: Validate config file permissions\n */\nfunction validateConfigPermissions(configPath: string): void {\n try {\n const stats = statSync(configPath)\n\n // On Unix, check if file is world-writable (security risk)\n if (process.platform !== 'win32') {\n const mode = stats.mode\n const worldWritable = (mode & 0o002) !== 0\n const groupWritable = (mode & 0o020) !== 0\n\n if (worldWritable || groupWritable) {\n console.warn(`⚠️ Warning: Config file ${configPath} has insecure permissions`)\n console.warn(' Run: chmod 600 ' + configPath)\n }\n }\n } catch {\n // Ignore permission check errors\n }\n}\n\nexport function loadConfig(path?: string): BashBrosConfig {\n const configPath = path || findConfig()\n\n if (!configPath) {\n return getDefaultConfig()\n }\n\n // SECURITY: Check file permissions\n validateConfigPermissions(configPath)\n\n const content = readFileSync(configPath, 'utf-8')\n\n // SECURITY: Use safe YAML parsing (no custom tags)\n let parsed: unknown\n try {\n parsed = parse(content, { strict: true })\n } catch (error) {\n console.error('Failed to parse config file:', error)\n return getDefaultConfig()\n }\n\n // SECURITY: Validate parsed config\n const validated = validateConfig(parsed)\n\n return mergeWithDefaults(validated)\n}\n\n/**\n * SECURITY: Validate and sanitize config values\n */\nfunction validateConfig(parsed: unknown): Partial<BashBrosConfig> {\n if (!parsed || typeof parsed !== 'object') {\n return {}\n }\n\n const config = parsed as Record<string, unknown>\n const validated: Partial<BashBrosConfig> = {}\n\n // Validate agent type\n const validAgents = ['claude-code', 'clawdbot', 'gemini-cli', 'aider', 'opencode', 'custom']\n if (typeof config.agent === 'string' && validAgents.includes(config.agent)) {\n validated.agent = config.agent as BashBrosConfig['agent']\n }\n\n // Validate profile\n const validProfiles = ['balanced', 'strict', 'permissive', 'custom']\n if (typeof config.profile === 'string' && validProfiles.includes(config.profile)) {\n validated.profile = config.profile as SecurityProfile\n }\n\n // Validate commands\n if (config.commands && typeof config.commands === 'object') {\n const cmds = config.commands as Record<string, unknown>\n validated.commands = {\n allow: validateStringArray(cmds.allow, CONFIG_LIMITS.maxPatterns),\n block: validateStringArray(cmds.block, CONFIG_LIMITS.maxPatterns)\n }\n }\n\n // Validate paths\n if (config.paths && typeof config.paths === 'object') {\n const paths = config.paths as Record<string, unknown>\n validated.paths = {\n allow: validatePathArray(paths.allow),\n block: validatePathArray(paths.block)\n }\n }\n\n // Validate secrets\n if (config.secrets && typeof config.secrets === 'object') {\n const secrets = config.secrets as Record<string, unknown>\n validated.secrets = {\n enabled: typeof secrets.enabled === 'boolean' ? secrets.enabled : true,\n mode: secrets.mode === 'audit' ? 'audit' : 'block',\n patterns: validateStringArray(secrets.patterns, CONFIG_LIMITS.maxPatterns)\n }\n }\n\n // Validate audit\n if (config.audit && typeof config.audit === 'object') {\n const audit = config.audit as Record<string, unknown>\n validated.audit = {\n enabled: typeof audit.enabled === 'boolean' ? audit.enabled : true,\n destination: validateAuditDestination(audit.destination),\n remotePath: validateRemotePath(audit.remotePath)\n }\n }\n\n // Validate rate limit\n if (config.rateLimit && typeof config.rateLimit === 'object') {\n const rl = config.rateLimit as Record<string, unknown>\n const maxPerMinute = validateNumber(rl.maxPerMinute, CONFIG_LIMITS.maxPerMinute)\n const maxPerHour = validateNumber(rl.maxPerHour, CONFIG_LIMITS.maxPerHour)\n\n // SECURITY: Ensure hour limit >= minute limit\n validated.rateLimit = {\n enabled: typeof rl.enabled === 'boolean' ? rl.enabled : true,\n maxPerMinute,\n maxPerHour: Math.max(maxPerHour, maxPerMinute)\n }\n }\n\n // Validate risk scoring\n if (config.riskScoring && typeof config.riskScoring === 'object') {\n const rs = config.riskScoring as Record<string, unknown>\n validated.riskScoring = {\n enabled: typeof rs.enabled === 'boolean' ? rs.enabled : true,\n blockThreshold: validateNumber(rs.blockThreshold, { min: 1, max: 10 }),\n warnThreshold: validateNumber(rs.warnThreshold, { min: 1, max: 10 }),\n customPatterns: validateRiskPatterns(rs.customPatterns)\n }\n }\n\n // Validate loop detection\n if (config.loopDetection && typeof config.loopDetection === 'object') {\n const ld = config.loopDetection as Record<string, unknown>\n validated.loopDetection = {\n enabled: typeof ld.enabled === 'boolean' ? ld.enabled : true,\n maxRepeats: validateNumber(ld.maxRepeats, { min: 1, max: 100 }),\n maxTurns: validateNumber(ld.maxTurns, { min: 10, max: 10000 }),\n similarityThreshold: validateNumber(ld.similarityThreshold, { min: 0, max: 1 }) / 1, // Keep as float\n cooldownMs: validateNumber(ld.cooldownMs, { min: 0, max: 60000 }),\n windowSize: validateNumber(ld.windowSize, { min: 5, max: 100 }),\n action: ld.action === 'block' ? 'block' : 'warn'\n }\n }\n\n // Validate anomaly detection\n if (config.anomalyDetection && typeof config.anomalyDetection === 'object') {\n const ad = config.anomalyDetection as Record<string, unknown>\n validated.anomalyDetection = {\n enabled: typeof ad.enabled === 'boolean' ? ad.enabled : true,\n workingHours: validateWorkingHours(ad.workingHours),\n typicalCommandsPerMinute: validateNumber(ad.typicalCommandsPerMinute, { min: 1, max: 1000 }),\n learningCommands: validateNumber(ad.learningCommands, { min: 10, max: 500 }),\n suspiciousPatterns: validateStringArray(ad.suspiciousPatterns, 50),\n action: ad.action === 'block' ? 'block' : 'warn'\n }\n }\n\n // Validate output scanning\n if (config.outputScanning && typeof config.outputScanning === 'object') {\n const os = config.outputScanning as Record<string, unknown>\n validated.outputScanning = {\n enabled: typeof os.enabled === 'boolean' ? os.enabled : true,\n scanForSecrets: typeof os.scanForSecrets === 'boolean' ? os.scanForSecrets : true,\n scanForErrors: typeof os.scanForErrors === 'boolean' ? os.scanForErrors : true,\n maxOutputLength: validateNumber(os.maxOutputLength, { min: 1000, max: 10000000 }),\n redactPatterns: validateStringArray(os.redactPatterns, 50)\n }\n }\n\n // Validate undo\n if (config.undo && typeof config.undo === 'object') {\n const undo = config.undo as Record<string, unknown>\n validated.undo = {\n enabled: typeof undo.enabled === 'boolean' ? undo.enabled : true,\n maxStackSize: validateNumber(undo.maxStackSize, { min: 10, max: 1000 }),\n maxFileSize: validateNumber(undo.maxFileSize, { min: 1024, max: 100 * 1024 * 1024 }),\n ttlMinutes: validateNumber(undo.ttlMinutes, { min: 5, max: 1440 }),\n backupPath: typeof undo.backupPath === 'string' ? undo.backupPath.slice(0, 500) : '~/.bashbros/undo'\n }\n }\n\n return validated\n}\n\nfunction validateRiskPatterns(value: unknown): RiskPattern[] {\n if (!Array.isArray(value)) return []\n\n return value\n .filter((item): item is Record<string, unknown> =>\n item && typeof item === 'object' &&\n typeof item.pattern === 'string' &&\n typeof item.score === 'number' &&\n typeof item.factor === 'string'\n )\n .slice(0, 50)\n .map(item => ({\n pattern: String(item.pattern).slice(0, 500),\n score: Math.max(1, Math.min(10, Math.floor(Number(item.score)))),\n factor: String(item.factor).slice(0, 200)\n }))\n}\n\nfunction validateWorkingHours(value: unknown): [number, number] {\n if (!Array.isArray(value) || value.length !== 2) {\n return [6, 22]\n }\n\n const start = Math.max(0, Math.min(23, Math.floor(Number(value[0]) || 0)))\n const end = Math.max(0, Math.min(24, Math.floor(Number(value[1]) || 24)))\n\n return [start, end]\n}\n\nfunction validateStringArray(value: unknown, maxItems: number): string[] {\n if (!Array.isArray(value)) return []\n\n return value\n .filter((item): item is string => typeof item === 'string')\n .slice(0, maxItems)\n .map(s => s.slice(0, 500)) // Limit string length\n}\n\nfunction validatePathArray(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n\n return value\n .filter((item): item is string => typeof item === 'string')\n .slice(0, CONFIG_LIMITS.maxPatterns)\n .map(s => s.slice(0, CONFIG_LIMITS.maxPathLength))\n .filter(s => !s.includes('\\0')) // Block null bytes\n}\n\nfunction validateNumber(value: unknown, limits: { min: number; max: number }): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return limits.min\n }\n return Math.max(limits.min, Math.min(limits.max, Math.floor(value)))\n}\n\nfunction validateAuditDestination(value: unknown): 'local' | 'remote' | 'both' {\n if (value === 'remote' || value === 'both') {\n return value\n }\n return 'local'\n}\n\n/**\n * SECURITY: Validate remote audit path (must be HTTPS)\n */\nfunction validateRemotePath(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined\n }\n\n try {\n const url = new URL(value)\n\n // SECURITY: Only allow HTTPS\n if (url.protocol !== 'https:') {\n console.warn('⚠️ Warning: Remote audit path must use HTTPS. Ignoring:', value)\n return undefined\n }\n\n // Block localhost/private IPs for remote\n const hostname = url.hostname.toLowerCase()\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.')) {\n // Allow for testing but warn\n console.warn('⚠️ Warning: Remote audit path points to local address')\n }\n\n return value\n } catch {\n console.warn('⚠️ Warning: Invalid remote audit URL:', value)\n return undefined\n }\n}\n\nexport function getDefaultConfig(): BashBrosConfig {\n return {\n agent: 'claude-code',\n profile: 'balanced',\n commands: getDefaultCommands('balanced'),\n paths: getDefaultPaths('balanced'),\n secrets: {\n enabled: true,\n mode: 'block',\n patterns: [\n '.env*',\n '*.pem',\n '*.key',\n '*credentials*',\n '*secret*',\n '.aws/*',\n '.ssh/*'\n ]\n },\n audit: {\n enabled: true,\n destination: 'local'\n },\n rateLimit: {\n enabled: true,\n maxPerMinute: 100,\n maxPerHour: 1000\n },\n riskScoring: getDefaultRiskScoring('balanced'),\n loopDetection: getDefaultLoopDetection('balanced'),\n anomalyDetection: getDefaultAnomalyDetection('balanced'),\n outputScanning: getDefaultOutputScanning('balanced'),\n undo: getDefaultUndo(),\n ward: getDefaultWard(),\n dashboard: getDefaultDashboard()\n }\n}\n\nfunction getDefaultRiskScoring(profile: SecurityProfile): RiskScoringPolicy {\n const thresholds: Record<string, { block: number; warn: number }> = {\n strict: { block: 6, warn: 3 },\n balanced: { block: 9, warn: 6 },\n permissive: { block: 10, warn: 8 }\n }\n const t = thresholds[profile] || thresholds.balanced\n\n return {\n enabled: true,\n blockThreshold: t.block,\n warnThreshold: t.warn,\n customPatterns: []\n }\n}\n\nfunction getDefaultLoopDetection(profile: SecurityProfile): LoopDetectionPolicy {\n const settings: Record<string, { maxRepeats: number; maxTurns: number; action: 'warn' | 'block' }> = {\n strict: { maxRepeats: 2, maxTurns: 50, action: 'block' },\n balanced: { maxRepeats: 3, maxTurns: 100, action: 'warn' },\n permissive: { maxRepeats: 5, maxTurns: 200, action: 'warn' }\n }\n const s = settings[profile] || settings.balanced\n\n return {\n enabled: true,\n maxRepeats: s.maxRepeats,\n maxTurns: s.maxTurns,\n similarityThreshold: 0.85,\n cooldownMs: 1000,\n windowSize: 20,\n action: s.action\n }\n}\n\nfunction getDefaultAnomalyDetection(profile: SecurityProfile): AnomalyDetectionPolicy {\n return {\n enabled: profile !== 'permissive',\n workingHours: [6, 22],\n typicalCommandsPerMinute: 30,\n learningCommands: 50,\n suspiciousPatterns: [],\n action: profile === 'strict' ? 'block' : 'warn'\n }\n}\n\nfunction getDefaultOutputScanning(profile: SecurityProfile): OutputScanningPolicy {\n return {\n enabled: true,\n scanForSecrets: true,\n scanForErrors: true,\n maxOutputLength: 100000,\n redactPatterns: [\n 'password\\\\s*[=:]\\\\s*\\\\S+',\n 'api[_-]?key\\\\s*[=:]\\\\s*\\\\S+',\n 'secret\\\\s*[=:]\\\\s*\\\\S+',\n 'token\\\\s*[=:]\\\\s*\\\\S+',\n 'Bearer\\\\s+[A-Za-z0-9\\\\-._~+/]+=*',\n 'sk-[A-Za-z0-9]{20,}',\n 'ghp_[A-Za-z0-9]{36}',\n 'glpat-[A-Za-z0-9\\\\-]{20,}'\n ]\n }\n}\n\nfunction getDefaultUndo(): UndoPolicy {\n return {\n enabled: true,\n maxStackSize: 100,\n maxFileSize: 10 * 1024 * 1024, // 10MB\n ttlMinutes: 60, // 1 hour\n backupPath: '~/.bashbros/undo'\n }\n}\n\nfunction getDefaultWard(): WardPolicy {\n return {\n enabled: true,\n exposure: {\n scanInterval: 30000, // 30 seconds\n externalProbe: false,\n severityActions: {\n low: 'alert',\n medium: 'alert',\n high: 'block',\n critical: 'block_and_kill'\n }\n },\n connectors: {\n proxyAllMcp: false,\n telemetryRetention: '7d'\n },\n egress: {\n defaultAction: 'block'\n }\n }\n}\n\nfunction getDefaultDashboard(): DashboardPolicy {\n return {\n enabled: true,\n port: 7890,\n bind: '127.0.0.1'\n }\n}\n\nfunction getDefaultCommands(profile: SecurityProfile) {\n const dangerousCommands = [\n // Destructive rm patterns (various flag orders)\n 'rm * /',\n 'rm * ~',\n 'rm * /*',\n 'rm * /home*',\n 'rm * /etc*',\n 'rm * /usr*',\n 'rm * /var*',\n 'rm * /bin*',\n 'rm * /sbin*',\n 'rm * /lib*',\n 'rm * /boot*',\n 'rm * /opt*',\n 'rm * /root*',\n 'rm * /srv*',\n 'rm * /mnt*',\n 'rm * /media*',\n // Windows destructive patterns\n 'rm * C:\\\\*',\n 'rm * C:/*',\n 'Remove-Item * C:\\\\*',\n 'Remove-Item * C:/*',\n 'rd /s *',\n 'rmdir /s *',\n // Fork bomb\n ':(){:|:&};:',\n // Disk destruction\n 'mkfs*',\n 'dd if=/dev/zero*',\n 'dd of=/dev/*',\n '> /dev/sda*',\n '> /dev/nvme*',\n '> /dev/hd*',\n // Dangerous permission changes\n 'chmod -R 777 /*',\n 'chmod -R 777 /',\n 'chmod * 777 /*',\n 'chown -R * /*',\n // Pipe to shell (code execution)\n 'curl * | bash*',\n 'curl * | sh*',\n 'wget * | bash*',\n 'wget * | sh*',\n 'curl * | sudo*',\n 'wget * | sudo*',\n // History/log destruction\n 'history -c*',\n 'shred *',\n // Network attacks\n ':(){ :|:& };:',\n // Dangerous redirects\n '> /etc/passwd*',\n '> /etc/shadow*'\n ]\n\n const commonAllowed = [\n // File operations\n 'ls *', 'dir *', 'cat *', 'head *', 'tail *', 'less *', 'more *',\n 'grep *', 'find *', 'rg *', 'fd *',\n 'mkdir *', 'touch *', 'cp *', 'mv *', 'rm *',\n 'cd *', 'pwd', 'echo *', 'printf *', 'which *', 'where *', 'type *',\n 'tar *', 'zip *', 'unzip *', 'gzip *', 'gunzip *',\n\n // Text processing\n 'sed *', 'awk *', 'sort *', 'uniq *', 'wc *', 'diff *', 'tr *',\n\n // Version control\n 'git *', 'gh *',\n\n // Package managers & runtimes\n 'npm *', 'npx *', 'pnpm *', 'yarn *', 'bun *',\n 'node *', 'deno *', 'tsx *', 'ts-node *',\n 'python *', 'python3 *', 'pip *', 'pip3 *', 'uv *', 'pipx *',\n 'cargo *', 'rustc *', 'rustup *',\n 'go *',\n\n // Build tools\n 'tsc *', 'esbuild *', 'vite *', 'webpack *', 'rollup *', 'tsup *',\n 'make *', 'cmake *',\n\n // Testing & linting\n 'jest *', 'vitest *', 'pytest *', 'mocha *',\n 'eslint *', 'prettier *', 'biome *', 'ruff *', 'black *',\n\n // AI coding assistants\n 'claude *', 'aider *',\n\n // Editors\n 'code *', 'cursor *', 'vim *', 'nvim *', 'nano *', 'emacs *',\n\n // Docker & containers\n 'docker *', 'docker-compose *', 'podman *',\n\n // Network (safe operations)\n 'curl *', 'wget *', 'ping *', 'ssh *',\n\n // System info\n 'env', 'env *', 'printenv *', 'whoami', 'hostname', 'uname *', 'date', 'uptime',\n 'ps *', 'top', 'htop', 'btop',\n\n // Shell basics\n 'clear', 'cls', 'history', 'alias *', 'export *', 'source *', 'exit',\n 'true', 'false', 'test *', 'man *', 'help *',\n\n // PowerShell (Windows)\n 'Get-*', 'Set-*', 'New-*', 'Remove-*', 'Select-*', 'Where-*', 'ForEach-*'\n ]\n\n if (profile === 'strict') {\n return { allow: [], block: dangerousCommands }\n }\n\n if (profile === 'permissive') {\n return { allow: ['*'], block: dangerousCommands }\n }\n\n // balanced\n return { allow: commonAllowed, block: dangerousCommands }\n}\n\nfunction getDefaultPaths(profile: SecurityProfile) {\n const dangerousPaths = [\n '~/.ssh',\n '~/.aws',\n '~/.gnupg',\n '~/.config/gh',\n '/etc/passwd',\n '/etc/shadow'\n ]\n\n if (profile === 'strict') {\n return { allow: ['.'], block: dangerousPaths }\n }\n\n if (profile === 'permissive') {\n return { allow: ['*'], block: dangerousPaths }\n }\n\n // balanced\n return { allow: ['.', '~'], block: dangerousPaths }\n}\n\nfunction mergeWithDefaults(parsed: Partial<BashBrosConfig>): BashBrosConfig {\n const defaults = getDefaultConfig()\n return {\n ...defaults,\n ...parsed,\n commands: { ...defaults.commands, ...parsed.commands },\n paths: { ...defaults.paths, ...parsed.paths },\n secrets: { ...defaults.secrets, ...parsed.secrets },\n audit: { ...defaults.audit, ...parsed.audit },\n rateLimit: { ...defaults.rateLimit, ...parsed.rateLimit },\n riskScoring: { ...defaults.riskScoring, ...parsed.riskScoring },\n loopDetection: { ...defaults.loopDetection, ...parsed.loopDetection },\n anomalyDetection: { ...defaults.anomalyDetection, ...parsed.anomalyDetection },\n outputScanning: { ...defaults.outputScanning, ...parsed.outputScanning },\n undo: { ...defaults.undo, ...parsed.undo },\n ward: { ...defaults.ward, ...parsed.ward },\n dashboard: { ...defaults.dashboard, ...parsed.dashboard }\n }\n}\n\nexport { BashBrosConfig }\n"],"mappings":"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","names":[]}
@@ -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":[]}
@@ -122,6 +122,20 @@ var DashboardDB = class {
122
122
  project_type TEXT
123
123
  )
124
124
  `);
125
+ this.db.exec(`
126
+ CREATE TABLE IF NOT EXISTS tool_uses (
127
+ id TEXT PRIMARY KEY,
128
+ timestamp TEXT NOT NULL,
129
+ tool_name TEXT NOT NULL,
130
+ tool_input TEXT NOT NULL,
131
+ tool_output TEXT NOT NULL,
132
+ exit_code INTEGER,
133
+ success INTEGER,
134
+ cwd TEXT NOT NULL,
135
+ repo_name TEXT,
136
+ repo_path TEXT
137
+ )
138
+ `);
125
139
  this.db.exec(`
126
140
  CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
127
141
  CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time);
@@ -131,6 +145,8 @@ var DashboardDB = class {
131
145
  CREATE INDEX IF NOT EXISTS idx_bro_events_session_id ON bro_events(session_id);
132
146
  CREATE INDEX IF NOT EXISTS idx_bro_events_timestamp ON bro_events(timestamp);
133
147
  CREATE INDEX IF NOT EXISTS idx_bro_status_timestamp ON bro_status(timestamp);
148
+ CREATE INDEX IF NOT EXISTS idx_tool_uses_timestamp ON tool_uses(timestamp);
149
+ CREATE INDEX IF NOT EXISTS idx_tool_uses_tool_name ON tool_uses(tool_name);
134
150
  `);
135
151
  }
136
152
  // ─────────────────────────────────────────────────────────────
@@ -637,6 +653,89 @@ var DashboardDB = class {
637
653
  };
638
654
  }
639
655
  // ─────────────────────────────────────────────────────────────
656
+ // Tool Uses
657
+ // ─────────────────────────────────────────────────────────────
658
+ insertToolUse(input) {
659
+ const id = randomUUID();
660
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
661
+ const stmt = this.db.prepare(`
662
+ INSERT INTO tool_uses (id, timestamp, tool_name, tool_input, tool_output, exit_code, success, cwd, repo_name, repo_path)
663
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
664
+ `);
665
+ stmt.run(
666
+ id,
667
+ timestamp,
668
+ input.toolName,
669
+ input.toolInput.substring(0, 5e4),
670
+ // Truncate very long inputs
671
+ input.toolOutput.substring(0, 5e4),
672
+ // Truncate very long outputs
673
+ input.exitCode ?? null,
674
+ input.success === void 0 ? null : input.success ? 1 : 0,
675
+ input.cwd,
676
+ input.repoName ?? null,
677
+ input.repoPath ?? null
678
+ );
679
+ return id;
680
+ }
681
+ getToolUses(filter = {}) {
682
+ const conditions = [];
683
+ const params = [];
684
+ if (filter.toolName) {
685
+ conditions.push("tool_name = ?");
686
+ params.push(filter.toolName);
687
+ }
688
+ if (filter.since) {
689
+ conditions.push("timestamp >= ?");
690
+ params.push(filter.since.toISOString());
691
+ }
692
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
693
+ const limit = filter.limit ?? 100;
694
+ const offset = filter.offset ?? 0;
695
+ const stmt = this.db.prepare(`
696
+ SELECT * FROM tool_uses
697
+ ${whereClause}
698
+ ORDER BY timestamp DESC
699
+ LIMIT ? OFFSET ?
700
+ `);
701
+ params.push(limit, offset);
702
+ const rows = stmt.all(...params);
703
+ return rows.map((row) => ({
704
+ id: row.id,
705
+ timestamp: new Date(row.timestamp),
706
+ toolName: row.tool_name,
707
+ toolInput: row.tool_input,
708
+ toolOutput: row.tool_output,
709
+ exitCode: row.exit_code,
710
+ success: row.success === null ? null : row.success === 1,
711
+ cwd: row.cwd,
712
+ repoName: row.repo_name,
713
+ repoPath: row.repo_path
714
+ }));
715
+ }
716
+ getLiveToolUses(limit = 50) {
717
+ return this.getToolUses({ limit });
718
+ }
719
+ getToolUseStats() {
720
+ const totalRow = this.db.prepare("SELECT COUNT(*) as count FROM tool_uses").get();
721
+ const toolRows = this.db.prepare(`
722
+ SELECT tool_name, COUNT(*) as count FROM tool_uses GROUP BY tool_name ORDER BY count DESC
723
+ `).all();
724
+ const byTool = {};
725
+ for (const row of toolRows) {
726
+ byTool[row.tool_name] = row.count;
727
+ }
728
+ const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
729
+ const last24hRow = this.db.prepare(`
730
+ SELECT COUNT(*) as count FROM tool_uses WHERE timestamp >= ?
731
+ `).get(oneDayAgo);
732
+ return {
733
+ totalUses: totalRow.count,
734
+ byTool,
735
+ last24h: last24hRow.count
736
+ };
737
+ }
738
+ // ─────────────────────────────────────────────────────────────
640
739
  // Session Metrics
641
740
  // ─────────────────────────────────────────────────────────────
642
741
  getSessionMetrics(sessionId) {
@@ -751,7 +850,8 @@ var DashboardDB = class {
751
850
  `).run(cutoff).changes;
752
851
  const broEventsDeleted = this.db.prepare("DELETE FROM bro_events WHERE timestamp < ?").run(cutoff).changes;
753
852
  const broStatusDeleted = this.db.prepare("DELETE FROM bro_status WHERE timestamp < ?").run(cutoff).changes;
754
- return eventsDeleted + connectorDeleted + blocksDeleted + exposuresDeleted + commandsDeleted + sessionsDeleted + broEventsDeleted + broStatusDeleted;
853
+ const toolUsesDeleted = this.db.prepare("DELETE FROM tool_uses WHERE timestamp < ?").run(cutoff).changes;
854
+ return eventsDeleted + connectorDeleted + blocksDeleted + exposuresDeleted + commandsDeleted + sessionsDeleted + broEventsDeleted + broStatusDeleted + toolUsesDeleted;
755
855
  }
756
856
  close() {
757
857
  this.db.close();
@@ -763,4 +863,4 @@ export {
763
863
  DashboardDB,
764
864
  db_default
765
865
  };
766
- //# sourceMappingURL=chunk-YUMNBQAY.js.map
866
+ //# sourceMappingURL=chunk-JYWQT2B4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dashboard/db.ts"],"sourcesContent":["/**\r\n * Dashboard Database Module\r\n * SQLite-based storage for security events, connector activity, and egress blocks\r\n */\r\n\r\nimport Database from 'better-sqlite3'\r\nimport { randomUUID } from 'crypto'\r\nimport type {\r\n EventSource,\r\n EventLevel,\r\n UnifiedEvent,\r\n ConnectorEvent,\r\n EgressMatch,\r\n EgressPattern,\r\n RedactedPayload,\r\n ExposureResult\r\n} from '../policy/ward/types.js'\r\n\r\n// ─────────────────────────────────────────────────────────────\r\n// Types\r\n// ─────────────────────────────────────────────────────────────\r\n\r\nexport interface EventFilter {\r\n source?: EventSource\r\n level?: EventLevel\r\n category?: string\r\n since?: Date\r\n limit?: number\r\n offset?: number\r\n}\r\n\r\nexport interface InsertEventInput {\r\n source: EventSource\r\n level: EventLevel\r\n category: string\r\n message: string\r\n data?: Record<string, unknown>\r\n}\r\n\r\nexport interface InsertConnectorEventInput {\r\n connector: string\r\n method: string\r\n direction: 'inbound' | 'outbound'\r\n payload: RedactedPayload\r\n resourcesAccessed: string[]\r\n}\r\n\r\nexport interface InsertEgressBlockInput {\r\n pattern: EgressPattern\r\n matchedText: string\r\n redactedText: string\r\n connector?: string\r\n destination?: string\r\n}\r\n\r\nexport interface DashboardStats {\r\n totalEvents: number\r\n eventsBySource: Record<string, number>\r\n eventsByLevel: Record<string, number>\r\n pendingBlocks: number\r\n connectorCount: number\r\n recentExposures: number\r\n // Enhanced stats\r\n activeSessions: number\r\n todayCommands: number\r\n todayViolations: number\r\n avgRiskScore24h: number\r\n ollamaStatus: 'connected' | 'disconnected' | 'unknown'\r\n}\r\n\r\n// ─────────────────────────────────────────────────────────────\r\n// Session & Command Types\r\n// ─────────────────────────────────────────────────────────────\r\n\r\nexport interface SessionRecord {\r\n id: string\r\n agent: string\r\n pid: number\r\n startTime: Date\r\n endTime?: Date\r\n status: 'active' | 'completed' | 'crashed'\r\n commandCount: number\r\n blockedCount: number\r\n avgRiskScore: number\r\n workingDir: string\r\n}\r\n\r\nexport interface CommandRecord {\r\n id: string\r\n sessionId: string\r\n timestamp: Date\r\n command: string\r\n allowed: boolean\r\n riskScore: number\r\n riskLevel: 'safe' | 'caution' | 'dangerous' | 'critical'\r\n riskFactors: string[]\r\n durationMs: number\r\n violations: string[]\r\n}\r\n\r\nexport interface BroEventRecord {\r\n id: string\r\n sessionId: string | null\r\n timestamp: Date\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 BroStatusRecord {\r\n id: string\r\n timestamp: Date\r\n ollamaAvailable: boolean\r\n ollamaModel: string\r\n platform: string\r\n shell: string\r\n projectType: string | null\r\n}\r\n\r\nexport interface ToolUseRecord {\r\n id: string\r\n timestamp: Date\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\nexport interface InsertSessionInput {\r\n agent: string\r\n pid: number\r\n workingDir: string\r\n}\r\n\r\nexport interface InsertCommandInput {\r\n sessionId: string\r\n command: string\r\n allowed: boolean\r\n riskScore: number\r\n riskLevel: 'safe' | 'caution' | 'dangerous' | 'critical'\r\n riskFactors: string[]\r\n durationMs: number\r\n violations: string[]\r\n}\r\n\r\nexport interface InsertBroEventInput {\r\n sessionId?: string\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 InsertBroStatusInput {\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 InsertToolUseInput {\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\nexport interface ToolUseFilter {\r\n toolName?: string\r\n since?: Date\r\n limit?: number\r\n offset?: number\r\n}\r\n\r\nexport interface SessionFilter {\r\n status?: 'active' | 'completed' | 'crashed'\r\n since?: Date\r\n until?: Date\r\n agent?: string\r\n limit?: number\r\n offset?: number\r\n}\r\n\r\nexport interface CommandFilter {\r\n sessionId?: string\r\n allowed?: boolean\r\n riskLevel?: string\r\n since?: Date\r\n afterId?: string\r\n limit?: number\r\n offset?: number\r\n}\r\n\r\n// ─────────────────────────────────────────────────────────────\r\n// Database Class\r\n// ─────────────────────────────────────────────────────────────\r\n\r\nexport class DashboardDB {\r\n private db: Database.Database\r\n\r\n constructor(dbPath: string = '.bashbros.db') {\r\n this.db = new Database(dbPath)\r\n this.db.pragma('journal_mode = WAL')\r\n this.initTables()\r\n }\r\n\r\n private initTables(): void {\r\n // Events table - unified security events\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS events (\r\n id TEXT PRIMARY KEY,\r\n timestamp TEXT NOT NULL,\r\n source TEXT NOT NULL,\r\n level TEXT NOT NULL,\r\n category TEXT NOT NULL,\r\n message TEXT NOT NULL,\r\n data TEXT\r\n )\r\n `)\r\n\r\n // Connector events table - MCP connector activity\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS connector_events (\r\n id TEXT PRIMARY KEY,\r\n timestamp TEXT NOT NULL,\r\n connector TEXT NOT NULL,\r\n method TEXT NOT NULL,\r\n direction TEXT NOT NULL,\r\n payload TEXT NOT NULL,\r\n resources_accessed TEXT NOT NULL\r\n )\r\n `)\r\n\r\n // Egress blocks table - blocked sensitive data\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS egress_blocks (\r\n id TEXT PRIMARY KEY,\r\n timestamp TEXT NOT NULL,\r\n pattern TEXT NOT NULL,\r\n matched_text TEXT NOT NULL,\r\n redacted_text TEXT NOT NULL,\r\n connector TEXT,\r\n destination TEXT,\r\n status TEXT NOT NULL DEFAULT 'pending',\r\n approved_by TEXT,\r\n approved_at TEXT\r\n )\r\n `)\r\n\r\n // Exposure scans table - network exposure scan results\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS exposure_scans (\r\n id TEXT PRIMARY KEY,\r\n timestamp TEXT NOT NULL,\r\n agent TEXT NOT NULL,\r\n pid INTEGER,\r\n port INTEGER NOT NULL,\r\n bind_address TEXT NOT NULL,\r\n has_auth TEXT NOT NULL,\r\n severity TEXT NOT NULL,\r\n action TEXT NOT NULL,\r\n message TEXT NOT NULL\r\n )\r\n `)\r\n\r\n // Create indexes for common queries\r\n this.db.exec(`\r\n CREATE INDEX IF NOT EXISTS idx_events_source ON events(source);\r\n CREATE INDEX IF NOT EXISTS idx_events_level ON events(level);\r\n CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);\r\n CREATE INDEX IF NOT EXISTS idx_connector_events_connector ON connector_events(connector);\r\n CREATE INDEX IF NOT EXISTS idx_egress_blocks_status ON egress_blocks(status);\r\n `)\r\n\r\n // Sessions table - track watch sessions\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS sessions (\r\n id TEXT PRIMARY KEY,\r\n agent TEXT NOT NULL,\r\n pid INTEGER NOT NULL,\r\n start_time TEXT NOT NULL,\r\n end_time TEXT,\r\n status TEXT NOT NULL DEFAULT 'active',\r\n command_count INTEGER NOT NULL DEFAULT 0,\r\n blocked_count INTEGER NOT NULL DEFAULT 0,\r\n avg_risk_score REAL NOT NULL DEFAULT 0,\r\n working_dir TEXT NOT NULL\r\n )\r\n `)\r\n\r\n // Commands table - detailed command history\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS commands (\r\n id TEXT PRIMARY KEY,\r\n session_id TEXT NOT NULL,\r\n timestamp TEXT NOT NULL,\r\n command TEXT NOT NULL,\r\n allowed INTEGER NOT NULL,\r\n risk_score INTEGER NOT NULL,\r\n risk_level TEXT NOT NULL,\r\n risk_factors TEXT NOT NULL,\r\n duration_ms INTEGER NOT NULL,\r\n violations TEXT NOT NULL,\r\n FOREIGN KEY (session_id) REFERENCES sessions(id)\r\n )\r\n `)\r\n\r\n // Bash Bro events table - AI activity log\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS bro_events (\r\n id TEXT PRIMARY KEY,\r\n session_id TEXT,\r\n timestamp TEXT NOT NULL,\r\n event_type TEXT NOT NULL,\r\n input_context TEXT NOT NULL,\r\n output_summary TEXT NOT NULL,\r\n model_used TEXT NOT NULL,\r\n latency_ms INTEGER NOT NULL,\r\n success INTEGER NOT NULL,\r\n FOREIGN KEY (session_id) REFERENCES sessions(id)\r\n )\r\n `)\r\n\r\n // Bash Bro status snapshots table\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS bro_status (\r\n id TEXT PRIMARY KEY,\r\n timestamp TEXT NOT NULL,\r\n ollama_available INTEGER NOT NULL,\r\n ollama_model TEXT NOT NULL,\r\n platform TEXT NOT NULL,\r\n shell TEXT NOT NULL,\r\n project_type TEXT\r\n )\r\n `)\r\n\r\n // Tool uses table - generic Claude Code tool tracking\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS tool_uses (\r\n id TEXT PRIMARY KEY,\r\n timestamp TEXT NOT NULL,\r\n tool_name TEXT NOT NULL,\r\n tool_input TEXT NOT NULL,\r\n tool_output TEXT NOT NULL,\r\n exit_code INTEGER,\r\n success INTEGER,\r\n cwd TEXT NOT NULL,\r\n repo_name TEXT,\r\n repo_path TEXT\r\n )\r\n `)\r\n\r\n // Create indexes for new tables\r\n this.db.exec(`\r\n CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);\r\n CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time);\r\n CREATE INDEX IF NOT EXISTS idx_commands_session_id ON commands(session_id);\r\n CREATE INDEX IF NOT EXISTS idx_commands_timestamp ON commands(timestamp);\r\n CREATE INDEX IF NOT EXISTS idx_commands_allowed ON commands(allowed);\r\n CREATE INDEX IF NOT EXISTS idx_bro_events_session_id ON bro_events(session_id);\r\n CREATE INDEX IF NOT EXISTS idx_bro_events_timestamp ON bro_events(timestamp);\r\n CREATE INDEX IF NOT EXISTS idx_bro_status_timestamp ON bro_status(timestamp);\r\n CREATE INDEX IF NOT EXISTS idx_tool_uses_timestamp ON tool_uses(timestamp);\r\n CREATE INDEX IF NOT EXISTS idx_tool_uses_tool_name ON tool_uses(tool_name);\r\n `)\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Events\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertEvent(input: InsertEventInput): string {\r\n const id = randomUUID()\r\n const timestamp = new Date().toISOString()\r\n const data = input.data ? JSON.stringify(input.data) : null\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO events (id, timestamp, source, level, category, message, data)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)\r\n `)\r\n\r\n stmt.run(id, timestamp, input.source, input.level, input.category, input.message, data)\r\n return id\r\n }\r\n\r\n getEvents(filter: EventFilter = {}): UnifiedEvent[] {\r\n const conditions: string[] = []\r\n const params: unknown[] = []\r\n\r\n if (filter.source) {\r\n conditions.push('source = ?')\r\n params.push(filter.source)\r\n }\r\n\r\n if (filter.level) {\r\n conditions.push('level = ?')\r\n params.push(filter.level)\r\n }\r\n\r\n if (filter.category) {\r\n conditions.push('category = ?')\r\n params.push(filter.category)\r\n }\r\n\r\n if (filter.since) {\r\n conditions.push('timestamp >= ?')\r\n params.push(filter.since.toISOString())\r\n }\r\n\r\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\r\n const limit = filter.limit ?? 100\r\n const offset = filter.offset ?? 0\r\n\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM events\r\n ${whereClause}\r\n ORDER BY timestamp DESC\r\n LIMIT ? OFFSET ?\r\n `)\r\n\r\n params.push(limit, offset)\r\n const rows = stmt.all(...params) as Array<{\r\n id: string\r\n timestamp: string\r\n source: EventSource\r\n level: EventLevel\r\n category: string\r\n message: string\r\n data: string | null\r\n }>\r\n\r\n return rows.map(row => ({\r\n id: row.id,\r\n timestamp: new Date(row.timestamp),\r\n source: row.source,\r\n level: row.level,\r\n category: row.category,\r\n message: row.message,\r\n data: row.data ? JSON.parse(row.data) : undefined\r\n }))\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Connector Events\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertConnectorEvent(input: InsertConnectorEventInput): string {\r\n const id = randomUUID()\r\n const timestamp = new Date().toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO connector_events (id, timestamp, connector, method, direction, payload, resources_accessed)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)\r\n `)\r\n\r\n stmt.run(\r\n id,\r\n timestamp,\r\n input.connector,\r\n input.method,\r\n input.direction,\r\n JSON.stringify(input.payload),\r\n JSON.stringify(input.resourcesAccessed)\r\n )\r\n\r\n return id\r\n }\r\n\r\n getConnectorEvents(connector: string, limit: number = 100): ConnectorEvent[] {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM connector_events\r\n WHERE connector = ?\r\n ORDER BY timestamp DESC\r\n LIMIT ?\r\n `)\r\n\r\n const rows = stmt.all(connector, limit) as Array<{\r\n id: string\r\n timestamp: string\r\n connector: string\r\n method: string\r\n direction: 'inbound' | 'outbound'\r\n payload: string\r\n resources_accessed: string\r\n }>\r\n\r\n return rows.map(row => ({\r\n id: row.id,\r\n timestamp: new Date(row.timestamp),\r\n connector: row.connector,\r\n method: row.method,\r\n direction: row.direction,\r\n payload: JSON.parse(row.payload),\r\n resourcesAccessed: JSON.parse(row.resources_accessed)\r\n }))\r\n }\r\n\r\n getAllConnectorEvents(limit: number = 100): ConnectorEvent[] {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM connector_events\r\n ORDER BY timestamp DESC\r\n LIMIT ?\r\n `)\r\n\r\n const rows = stmt.all(limit) as Array<{\r\n id: string\r\n timestamp: string\r\n connector: string\r\n method: string\r\n direction: 'inbound' | 'outbound'\r\n payload: string\r\n resources_accessed: string\r\n }>\r\n\r\n return rows.map(row => ({\r\n id: row.id,\r\n timestamp: new Date(row.timestamp),\r\n connector: row.connector,\r\n method: row.method,\r\n direction: row.direction,\r\n payload: JSON.parse(row.payload),\r\n resourcesAccessed: JSON.parse(row.resources_accessed)\r\n }))\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Egress Blocks\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertEgressBlock(input: InsertEgressBlockInput): string {\r\n const id = randomUUID()\r\n const timestamp = new Date().toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO egress_blocks (id, timestamp, pattern, matched_text, redacted_text, connector, destination, status)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')\r\n `)\r\n\r\n stmt.run(\r\n id,\r\n timestamp,\r\n JSON.stringify(input.pattern),\r\n input.matchedText,\r\n input.redactedText,\r\n input.connector ?? null,\r\n input.destination ?? null\r\n )\r\n\r\n return id\r\n }\r\n\r\n getPendingBlocks(): EgressMatch[] {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM egress_blocks\r\n WHERE status = 'pending'\r\n ORDER BY timestamp DESC\r\n `)\r\n\r\n const rows = stmt.all() as Array<{\r\n id: string\r\n timestamp: string\r\n pattern: string\r\n matched_text: string\r\n redacted_text: string\r\n connector: string | null\r\n destination: string | null\r\n status: 'pending' | 'approved' | 'denied'\r\n approved_by: string | null\r\n approved_at: string | null\r\n }>\r\n\r\n return rows.map(row => this.rowToEgressMatch(row))\r\n }\r\n\r\n getBlock(id: string): EgressMatch | null {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM egress_blocks WHERE id = ?\r\n `)\r\n\r\n const row = stmt.get(id) as {\r\n id: string\r\n timestamp: string\r\n pattern: string\r\n matched_text: string\r\n redacted_text: string\r\n connector: string | null\r\n destination: string | null\r\n status: 'pending' | 'approved' | 'denied'\r\n approved_by: string | null\r\n approved_at: string | null\r\n } | undefined\r\n\r\n if (!row) return null\r\n return this.rowToEgressMatch(row)\r\n }\r\n\r\n approveBlock(id: string, approvedBy: string): void {\r\n const stmt = this.db.prepare(`\r\n UPDATE egress_blocks\r\n SET status = 'approved', approved_by = ?, approved_at = ?\r\n WHERE id = ?\r\n `)\r\n\r\n stmt.run(approvedBy, new Date().toISOString(), id)\r\n }\r\n\r\n denyBlock(id: string, deniedBy: string): void {\r\n const stmt = this.db.prepare(`\r\n UPDATE egress_blocks\r\n SET status = 'denied', approved_by = ?, approved_at = ?\r\n WHERE id = ?\r\n `)\r\n\r\n stmt.run(deniedBy, new Date().toISOString(), id)\r\n }\r\n\r\n private rowToEgressMatch(row: {\r\n id: string\r\n timestamp: string\r\n pattern: string\r\n matched_text: string\r\n redacted_text: string\r\n connector: string | null\r\n destination: string | null\r\n status: 'pending' | 'approved' | 'denied'\r\n approved_by: string | null\r\n approved_at: string | null\r\n }): EgressMatch {\r\n return {\r\n id: row.id,\r\n timestamp: new Date(row.timestamp),\r\n pattern: JSON.parse(row.pattern),\r\n matchedText: row.matched_text,\r\n redactedText: row.redacted_text,\r\n connector: row.connector ?? undefined,\r\n destination: row.destination ?? undefined,\r\n status: row.status,\r\n approvedBy: row.approved_by ?? undefined,\r\n approvedAt: row.approved_at ? new Date(row.approved_at) : undefined\r\n }\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Exposure Scans\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertExposureScan(result: ExposureResult): string {\r\n const id = randomUUID()\r\n const timestamp = result.timestamp.toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO exposure_scans (id, timestamp, agent, pid, port, bind_address, has_auth, severity, action, message)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\r\n `)\r\n\r\n stmt.run(\r\n id,\r\n timestamp,\r\n result.agent,\r\n result.pid ?? null,\r\n result.port,\r\n result.bindAddress,\r\n String(result.hasAuth),\r\n result.severity,\r\n result.action,\r\n result.message\r\n )\r\n\r\n return id\r\n }\r\n\r\n getRecentExposures(limit: number = 100): ExposureResult[] {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM exposure_scans\r\n ORDER BY timestamp DESC\r\n LIMIT ?\r\n `)\r\n\r\n const rows = stmt.all(limit) as Array<{\r\n id: string\r\n timestamp: string\r\n agent: string\r\n pid: number | null\r\n port: number\r\n bind_address: string\r\n has_auth: string\r\n severity: string\r\n action: string\r\n message: string\r\n }>\r\n\r\n return rows.map(row => ({\r\n agent: row.agent,\r\n pid: row.pid ?? undefined,\r\n port: row.port,\r\n bindAddress: row.bind_address,\r\n hasAuth: row.has_auth === 'true' ? true : row.has_auth === 'false' ? false : 'unknown' as const,\r\n severity: row.severity as ExposureResult['severity'],\r\n action: row.action as ExposureResult['action'],\r\n message: row.message,\r\n timestamp: new Date(row.timestamp)\r\n }))\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Sessions\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertSession(input: InsertSessionInput): string {\r\n const id = randomUUID()\r\n const startTime = new Date().toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO sessions (id, agent, pid, start_time, status, command_count, blocked_count, avg_risk_score, working_dir)\r\n VALUES (?, ?, ?, ?, 'active', 0, 0, 0, ?)\r\n `)\r\n\r\n stmt.run(id, input.agent, input.pid, startTime, input.workingDir)\r\n return id\r\n }\r\n\r\n updateSession(id: string, updates: {\r\n endTime?: Date\r\n status?: 'active' | 'completed' | 'crashed'\r\n commandCount?: number\r\n blockedCount?: number\r\n avgRiskScore?: number\r\n }): void {\r\n const setClauses: string[] = []\r\n const params: unknown[] = []\r\n\r\n if (updates.endTime !== undefined) {\r\n setClauses.push('end_time = ?')\r\n params.push(updates.endTime.toISOString())\r\n }\r\n if (updates.status !== undefined) {\r\n setClauses.push('status = ?')\r\n params.push(updates.status)\r\n }\r\n if (updates.commandCount !== undefined) {\r\n setClauses.push('command_count = ?')\r\n params.push(updates.commandCount)\r\n }\r\n if (updates.blockedCount !== undefined) {\r\n setClauses.push('blocked_count = ?')\r\n params.push(updates.blockedCount)\r\n }\r\n if (updates.avgRiskScore !== undefined) {\r\n setClauses.push('avg_risk_score = ?')\r\n params.push(updates.avgRiskScore)\r\n }\r\n\r\n if (setClauses.length === 0) return\r\n\r\n params.push(id)\r\n const stmt = this.db.prepare(`\r\n UPDATE sessions SET ${setClauses.join(', ')} WHERE id = ?\r\n `)\r\n stmt.run(...params)\r\n }\r\n\r\n getSession(id: string): SessionRecord | null {\r\n const stmt = this.db.prepare('SELECT * FROM sessions WHERE id = ?')\r\n const row = stmt.get(id) as {\r\n id: string\r\n agent: string\r\n pid: number\r\n start_time: string\r\n end_time: string | null\r\n status: 'active' | 'completed' | 'crashed'\r\n command_count: number\r\n blocked_count: number\r\n avg_risk_score: number\r\n working_dir: string\r\n } | undefined\r\n\r\n if (!row) return null\r\n return this.rowToSession(row)\r\n }\r\n\r\n getSessions(filter: SessionFilter = {}): SessionRecord[] {\r\n const conditions: string[] = []\r\n const params: unknown[] = []\r\n\r\n if (filter.status) {\r\n conditions.push('status = ?')\r\n params.push(filter.status)\r\n }\r\n if (filter.since) {\r\n conditions.push('start_time >= ?')\r\n params.push(filter.since.toISOString())\r\n }\r\n if (filter.until) {\r\n conditions.push('start_time <= ?')\r\n params.push(filter.until.toISOString())\r\n }\r\n if (filter.agent) {\r\n conditions.push('agent = ?')\r\n params.push(filter.agent)\r\n }\r\n\r\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\r\n const limit = filter.limit ?? 100\r\n const offset = filter.offset ?? 0\r\n\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM sessions\r\n ${whereClause}\r\n ORDER BY start_time DESC\r\n LIMIT ? OFFSET ?\r\n `)\r\n\r\n params.push(limit, offset)\r\n const rows = stmt.all(...params) as Array<{\r\n id: string\r\n agent: string\r\n pid: number\r\n start_time: string\r\n end_time: string | null\r\n status: 'active' | 'completed' | 'crashed'\r\n command_count: number\r\n blocked_count: number\r\n avg_risk_score: number\r\n working_dir: string\r\n }>\r\n\r\n return rows.map(row => this.rowToSession(row))\r\n }\r\n\r\n getActiveSession(): SessionRecord | null {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM sessions WHERE status = 'active' ORDER BY start_time DESC LIMIT 1\r\n `)\r\n const row = stmt.get() as {\r\n id: string\r\n agent: string\r\n pid: number\r\n start_time: string\r\n end_time: string | null\r\n status: 'active' | 'completed' | 'crashed'\r\n command_count: number\r\n blocked_count: number\r\n avg_risk_score: number\r\n working_dir: string\r\n } | undefined\r\n\r\n if (!row) return null\r\n return this.rowToSession(row)\r\n }\r\n\r\n private rowToSession(row: {\r\n id: string\r\n agent: string\r\n pid: number\r\n start_time: string\r\n end_time: string | null\r\n status: 'active' | 'completed' | 'crashed'\r\n command_count: number\r\n blocked_count: number\r\n avg_risk_score: number\r\n working_dir: string\r\n }): SessionRecord {\r\n return {\r\n id: row.id,\r\n agent: row.agent,\r\n pid: row.pid,\r\n startTime: new Date(row.start_time),\r\n endTime: row.end_time ? new Date(row.end_time) : undefined,\r\n status: row.status,\r\n commandCount: row.command_count,\r\n blockedCount: row.blocked_count,\r\n avgRiskScore: row.avg_risk_score,\r\n workingDir: row.working_dir\r\n }\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Commands\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertCommand(input: InsertCommandInput): string {\r\n const id = randomUUID()\r\n const timestamp = new Date().toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO commands (id, session_id, timestamp, command, allowed, risk_score, risk_level, risk_factors, duration_ms, violations)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\r\n `)\r\n\r\n stmt.run(\r\n id,\r\n input.sessionId,\r\n timestamp,\r\n input.command,\r\n input.allowed ? 1 : 0,\r\n input.riskScore,\r\n input.riskLevel,\r\n JSON.stringify(input.riskFactors),\r\n input.durationMs,\r\n JSON.stringify(input.violations)\r\n )\r\n\r\n return id\r\n }\r\n\r\n getCommands(filter: CommandFilter = {}): CommandRecord[] {\r\n const conditions: string[] = []\r\n const params: unknown[] = []\r\n\r\n if (filter.sessionId) {\r\n conditions.push('session_id = ?')\r\n params.push(filter.sessionId)\r\n }\r\n if (filter.allowed !== undefined) {\r\n conditions.push('allowed = ?')\r\n params.push(filter.allowed ? 1 : 0)\r\n }\r\n if (filter.riskLevel) {\r\n conditions.push('risk_level = ?')\r\n params.push(filter.riskLevel)\r\n }\r\n if (filter.since) {\r\n conditions.push('timestamp >= ?')\r\n params.push(filter.since.toISOString())\r\n }\r\n if (filter.afterId) {\r\n conditions.push('id > ?')\r\n params.push(filter.afterId)\r\n }\r\n\r\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\r\n const limit = filter.limit ?? 100\r\n const offset = filter.offset ?? 0\r\n\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM commands\r\n ${whereClause}\r\n ORDER BY timestamp DESC\r\n LIMIT ? OFFSET ?\r\n `)\r\n\r\n params.push(limit, offset)\r\n const rows = stmt.all(...params) as Array<{\r\n id: string\r\n session_id: string\r\n timestamp: string\r\n command: string\r\n allowed: number\r\n risk_score: number\r\n risk_level: 'safe' | 'caution' | 'dangerous' | 'critical'\r\n risk_factors: string\r\n duration_ms: number\r\n violations: string\r\n }>\r\n\r\n return rows.map(row => this.rowToCommand(row))\r\n }\r\n\r\n getCommandsBySession(sessionId: string, limit: number = 100): CommandRecord[] {\r\n return this.getCommands({ sessionId, limit })\r\n }\r\n\r\n getLiveCommands(limit: number = 20): CommandRecord[] {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM commands\r\n ORDER BY timestamp DESC\r\n LIMIT ?\r\n `)\r\n\r\n const rows = stmt.all(limit) as Array<{\r\n id: string\r\n session_id: string\r\n timestamp: string\r\n command: string\r\n allowed: number\r\n risk_score: number\r\n risk_level: 'safe' | 'caution' | 'dangerous' | 'critical'\r\n risk_factors: string\r\n duration_ms: number\r\n violations: string\r\n }>\r\n\r\n return rows.map(row => this.rowToCommand(row))\r\n }\r\n\r\n private rowToCommand(row: {\r\n id: string\r\n session_id: string\r\n timestamp: string\r\n command: string\r\n allowed: number\r\n risk_score: number\r\n risk_level: 'safe' | 'caution' | 'dangerous' | 'critical'\r\n risk_factors: string\r\n duration_ms: number\r\n violations: string\r\n }): CommandRecord {\r\n return {\r\n id: row.id,\r\n sessionId: row.session_id,\r\n timestamp: new Date(row.timestamp),\r\n command: row.command,\r\n allowed: row.allowed === 1,\r\n riskScore: row.risk_score,\r\n riskLevel: row.risk_level,\r\n riskFactors: JSON.parse(row.risk_factors),\r\n durationMs: row.duration_ms,\r\n violations: JSON.parse(row.violations)\r\n }\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Bro Events\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertBroEvent(input: InsertBroEventInput): string {\r\n const id = randomUUID()\r\n const timestamp = new Date().toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO bro_events (id, session_id, timestamp, event_type, input_context, output_summary, model_used, latency_ms, success)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\r\n `)\r\n\r\n stmt.run(\r\n id,\r\n input.sessionId ?? null,\r\n timestamp,\r\n input.eventType,\r\n input.inputContext,\r\n input.outputSummary,\r\n input.modelUsed,\r\n input.latencyMs,\r\n input.success ? 1 : 0\r\n )\r\n\r\n return id\r\n }\r\n\r\n getBroEvents(limit: number = 100, sessionId?: string): BroEventRecord[] {\r\n let stmt\r\n let rows\r\n\r\n if (sessionId) {\r\n stmt = this.db.prepare(`\r\n SELECT * FROM bro_events\r\n WHERE session_id = ?\r\n ORDER BY timestamp DESC\r\n LIMIT ?\r\n `)\r\n rows = stmt.all(sessionId, limit)\r\n } else {\r\n stmt = this.db.prepare(`\r\n SELECT * FROM bro_events\r\n ORDER BY timestamp DESC\r\n LIMIT ?\r\n `)\r\n rows = stmt.all(limit)\r\n }\r\n\r\n return (rows as Array<{\r\n id: string\r\n session_id: string | null\r\n timestamp: string\r\n event_type: string\r\n input_context: string\r\n output_summary: string\r\n model_used: string\r\n latency_ms: number\r\n success: number\r\n }>).map(row => ({\r\n id: row.id,\r\n sessionId: row.session_id,\r\n timestamp: new Date(row.timestamp),\r\n eventType: row.event_type,\r\n inputContext: row.input_context,\r\n outputSummary: row.output_summary,\r\n modelUsed: row.model_used,\r\n latencyMs: row.latency_ms,\r\n success: row.success === 1\r\n }))\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Bro Status\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n updateBroStatus(input: InsertBroStatusInput): string {\r\n const id = randomUUID()\r\n const timestamp = new Date().toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO bro_status (id, timestamp, ollama_available, ollama_model, platform, shell, project_type)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)\r\n `)\r\n\r\n stmt.run(\r\n id,\r\n timestamp,\r\n input.ollamaAvailable ? 1 : 0,\r\n input.ollamaModel,\r\n input.platform,\r\n input.shell,\r\n input.projectType ?? null\r\n )\r\n\r\n return id\r\n }\r\n\r\n getLatestBroStatus(): BroStatusRecord | null {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM bro_status\r\n ORDER BY timestamp DESC\r\n LIMIT 1\r\n `)\r\n\r\n const row = stmt.get() as {\r\n id: string\r\n timestamp: string\r\n ollama_available: number\r\n ollama_model: string\r\n platform: string\r\n shell: string\r\n project_type: string | null\r\n } | undefined\r\n\r\n if (!row) return null\r\n\r\n return {\r\n id: row.id,\r\n timestamp: new Date(row.timestamp),\r\n ollamaAvailable: row.ollama_available === 1,\r\n ollamaModel: row.ollama_model,\r\n platform: row.platform,\r\n shell: row.shell,\r\n projectType: row.project_type\r\n }\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Tool Uses\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertToolUse(input: InsertToolUseInput): string {\r\n const id = randomUUID()\r\n const timestamp = new Date().toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO tool_uses (id, timestamp, tool_name, tool_input, tool_output, exit_code, success, cwd, repo_name, repo_path)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\r\n `)\r\n\r\n stmt.run(\r\n id,\r\n timestamp,\r\n input.toolName,\r\n input.toolInput.substring(0, 50000), // Truncate very long inputs\r\n input.toolOutput.substring(0, 50000), // Truncate very long outputs\r\n input.exitCode ?? null,\r\n input.success === undefined ? null : (input.success ? 1 : 0),\r\n input.cwd,\r\n input.repoName ?? null,\r\n input.repoPath ?? null\r\n )\r\n\r\n return id\r\n }\r\n\r\n getToolUses(filter: ToolUseFilter = {}): ToolUseRecord[] {\r\n const conditions: string[] = []\r\n const params: unknown[] = []\r\n\r\n if (filter.toolName) {\r\n conditions.push('tool_name = ?')\r\n params.push(filter.toolName)\r\n }\r\n if (filter.since) {\r\n conditions.push('timestamp >= ?')\r\n params.push(filter.since.toISOString())\r\n }\r\n\r\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\r\n const limit = filter.limit ?? 100\r\n const offset = filter.offset ?? 0\r\n\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM tool_uses\r\n ${whereClause}\r\n ORDER BY timestamp DESC\r\n LIMIT ? OFFSET ?\r\n `)\r\n\r\n params.push(limit, offset)\r\n const rows = stmt.all(...params) as Array<{\r\n id: string\r\n timestamp: string\r\n tool_name: string\r\n tool_input: string\r\n tool_output: string\r\n exit_code: number | null\r\n success: number | null\r\n cwd: string\r\n repo_name: string | null\r\n repo_path: string | null\r\n }>\r\n\r\n return rows.map(row => ({\r\n id: row.id,\r\n timestamp: new Date(row.timestamp),\r\n toolName: row.tool_name,\r\n toolInput: row.tool_input,\r\n toolOutput: row.tool_output,\r\n exitCode: row.exit_code,\r\n success: row.success === null ? null : row.success === 1,\r\n cwd: row.cwd,\r\n repoName: row.repo_name,\r\n repoPath: row.repo_path\r\n }))\r\n }\r\n\r\n getLiveToolUses(limit: number = 50): ToolUseRecord[] {\r\n return this.getToolUses({ limit })\r\n }\r\n\r\n getToolUseStats(): {\r\n totalUses: number\r\n byTool: Record<string, number>\r\n last24h: number\r\n } {\r\n const totalRow = this.db.prepare('SELECT COUNT(*) as count FROM tool_uses').get() as { count: number }\r\n\r\n const toolRows = this.db.prepare(`\r\n SELECT tool_name, COUNT(*) as count FROM tool_uses GROUP BY tool_name ORDER BY count DESC\r\n `).all() as Array<{ tool_name: string; count: number }>\r\n\r\n const byTool: Record<string, number> = {}\r\n for (const row of toolRows) {\r\n byTool[row.tool_name] = row.count\r\n }\r\n\r\n const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()\r\n const last24hRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM tool_uses WHERE timestamp >= ?\r\n `).get(oneDayAgo) as { count: number }\r\n\r\n return {\r\n totalUses: totalRow.count,\r\n byTool,\r\n last24h: last24hRow.count\r\n }\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Session Metrics\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n getSessionMetrics(sessionId: string): {\r\n totalCommands: number\r\n allowedCommands: number\r\n blockedCommands: number\r\n avgRiskScore: number\r\n riskDistribution: Record<string, number>\r\n topCommands: Array<{ command: string; count: number }>\r\n } {\r\n // Total commands\r\n const totalRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM commands WHERE session_id = ?\r\n `).get(sessionId) as { count: number }\r\n\r\n // Allowed/blocked counts\r\n const allowedRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM commands WHERE session_id = ? AND allowed = 1\r\n `).get(sessionId) as { count: number }\r\n\r\n const blockedRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM commands WHERE session_id = ? AND allowed = 0\r\n `).get(sessionId) as { count: number }\r\n\r\n // Average risk score\r\n const avgRow = this.db.prepare(`\r\n SELECT AVG(risk_score) as avg FROM commands WHERE session_id = ?\r\n `).get(sessionId) as { avg: number | null }\r\n\r\n // Risk distribution\r\n const riskRows = this.db.prepare(`\r\n SELECT risk_level, COUNT(*) as count FROM commands WHERE session_id = ? GROUP BY risk_level\r\n `).all(sessionId) as Array<{ risk_level: string; count: number }>\r\n\r\n const riskDistribution: Record<string, number> = {}\r\n for (const row of riskRows) {\r\n riskDistribution[row.risk_level] = row.count\r\n }\r\n\r\n // Top commands (by base command, first word)\r\n const cmdRows = this.db.prepare(`\r\n SELECT command, COUNT(*) as count FROM commands WHERE session_id = ?\r\n GROUP BY command ORDER BY count DESC LIMIT 10\r\n `).all(sessionId) as Array<{ command: string; count: number }>\r\n\r\n return {\r\n totalCommands: totalRow.count,\r\n allowedCommands: allowedRow.count,\r\n blockedCommands: blockedRow.count,\r\n avgRiskScore: avgRow.avg ?? 0,\r\n riskDistribution,\r\n topCommands: cmdRows\r\n }\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Stats\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n getStats(): DashboardStats {\r\n // Total events\r\n const totalEventsRow = this.db.prepare('SELECT COUNT(*) as count FROM events').get() as { count: number }\r\n\r\n // Events by source\r\n const sourceRows = this.db.prepare(`\r\n SELECT source, COUNT(*) as count FROM events GROUP BY source\r\n `).all() as Array<{ source: string; count: number }>\r\n\r\n const eventsBySource: Record<string, number> = {}\r\n for (const row of sourceRows) {\r\n eventsBySource[row.source] = row.count\r\n }\r\n\r\n // Events by level\r\n const levelRows = this.db.prepare(`\r\n SELECT level, COUNT(*) as count FROM events GROUP BY level\r\n `).all() as Array<{ level: string; count: number }>\r\n\r\n const eventsByLevel: Record<string, number> = {}\r\n for (const row of levelRows) {\r\n eventsByLevel[row.level] = row.count\r\n }\r\n\r\n // Pending blocks\r\n const pendingBlocksRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM egress_blocks WHERE status = 'pending'\r\n `).get() as { count: number }\r\n\r\n // Unique connectors\r\n const connectorCountRow = this.db.prepare(`\r\n SELECT COUNT(DISTINCT connector) as count FROM connector_events\r\n `).get() as { count: number }\r\n\r\n // Recent exposures (last 24 hours)\r\n const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()\r\n const recentExposuresRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM exposure_scans WHERE timestamp >= ?\r\n `).get(oneDayAgo) as { count: number }\r\n\r\n // Active sessions\r\n const activeSessionsRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM sessions WHERE status = 'active'\r\n `).get() as { count: number }\r\n\r\n // Today's commands\r\n const todayStart = new Date()\r\n todayStart.setHours(0, 0, 0, 0)\r\n const todayCommandsRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM commands WHERE timestamp >= ?\r\n `).get(todayStart.toISOString()) as { count: number }\r\n\r\n // Today's violations (blocked commands)\r\n const todayViolationsRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM commands WHERE timestamp >= ? AND allowed = 0\r\n `).get(todayStart.toISOString()) as { count: number }\r\n\r\n // Average risk score in last 24 hours\r\n const avgRiskRow = this.db.prepare(`\r\n SELECT AVG(risk_score) as avg FROM commands WHERE timestamp >= ?\r\n `).get(oneDayAgo) as { avg: number | null }\r\n\r\n // Ollama status from latest bro_status\r\n const latestStatus = this.getLatestBroStatus()\r\n let ollamaStatus: 'connected' | 'disconnected' | 'unknown' = 'unknown'\r\n if (latestStatus) {\r\n ollamaStatus = latestStatus.ollamaAvailable ? 'connected' : 'disconnected'\r\n }\r\n\r\n return {\r\n totalEvents: totalEventsRow.count,\r\n eventsBySource,\r\n eventsByLevel,\r\n pendingBlocks: pendingBlocksRow.count,\r\n connectorCount: connectorCountRow.count,\r\n recentExposures: recentExposuresRow.count,\r\n activeSessions: activeSessionsRow.count,\r\n todayCommands: todayCommandsRow.count,\r\n todayViolations: todayViolationsRow.count,\r\n avgRiskScore24h: avgRiskRow.avg ?? 0,\r\n ollamaStatus\r\n }\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Maintenance\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n cleanup(olderThanDays: number = 30): number {\r\n const cutoff = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000).toISOString()\r\n\r\n const eventsDeleted = this.db.prepare('DELETE FROM events WHERE timestamp < ?').run(cutoff).changes\r\n const connectorDeleted = this.db.prepare('DELETE FROM connector_events WHERE timestamp < ?').run(cutoff).changes\r\n const blocksDeleted = this.db.prepare(`\r\n DELETE FROM egress_blocks WHERE timestamp < ? AND status != 'pending'\r\n `).run(cutoff).changes\r\n const exposuresDeleted = this.db.prepare('DELETE FROM exposure_scans WHERE timestamp < ?').run(cutoff).changes\r\n\r\n // Cleanup new tables - commands first (due to foreign key), then sessions\r\n const commandsDeleted = this.db.prepare('DELETE FROM commands WHERE timestamp < ?').run(cutoff).changes\r\n const sessionsDeleted = this.db.prepare(`\r\n DELETE FROM sessions WHERE start_time < ? AND status != 'active'\r\n `).run(cutoff).changes\r\n const broEventsDeleted = this.db.prepare('DELETE FROM bro_events WHERE timestamp < ?').run(cutoff).changes\r\n const broStatusDeleted = this.db.prepare('DELETE FROM bro_status WHERE timestamp < ?').run(cutoff).changes\r\n const toolUsesDeleted = this.db.prepare('DELETE FROM tool_uses WHERE timestamp < ?').run(cutoff).changes\r\n\r\n return eventsDeleted + connectorDeleted + blocksDeleted + exposuresDeleted +\r\n commandsDeleted + sessionsDeleted + broEventsDeleted + broStatusDeleted + toolUsesDeleted\r\n }\r\n\r\n close(): void {\r\n this.db.close()\r\n }\r\n}\r\n\r\nexport default DashboardDB\r\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadConfig
4
- } from "./chunk-BW6XCOJH.js";
4
+ } from "./chunk-A535VV7N.js";
5
5
 
6
6
  // src/transparency/display.ts
7
7
  import chalk from "chalk";
@@ -210,4 +210,4 @@ export {
210
210
  formatPermissionsTable,
211
211
  formatAgentSummary
212
212
  };
213
- //# sourceMappingURL=chunk-SQCP6IYB.js.map
213
+ //# sourceMappingURL=chunk-WPJJZLT6.js.map