bashbros 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +45 -44
  2. package/dist/{chunk-LZYW7XQO.js → chunk-25TREQ6V.js} +131 -5
  3. package/dist/chunk-25TREQ6V.js.map +1 -0
  4. package/dist/{chunk-RTZ4QWG2.js → chunk-2CI2MRKI.js} +19 -3
  5. package/dist/chunk-2CI2MRKI.js.map +1 -0
  6. package/dist/chunk-5BBPRDWL.js +186 -0
  7. package/dist/chunk-5BBPRDWL.js.map +1 -0
  8. package/dist/{chunk-7OEWYFN3.js → chunk-6QVMBCSX.js} +7 -306
  9. package/dist/chunk-6QVMBCSX.js.map +1 -0
  10. package/dist/{chunk-RDNSS3ME.js → chunk-6SLR5WPD.js} +173 -5
  11. package/dist/chunk-6SLR5WPD.js.map +1 -0
  12. package/dist/{chunk-KYDMPE4N.js → chunk-AZVT6AZY.js} +20 -2
  13. package/dist/chunk-AZVT6AZY.js.map +1 -0
  14. package/dist/{chunk-CG6VEHJM.js → chunk-C4GZNBFF.js} +2 -2
  15. package/dist/{chunk-EMLEJVJZ.js → chunk-JOIAG54E.js} +1 -107
  16. package/dist/chunk-JOIAG54E.js.map +1 -0
  17. package/dist/{chunk-QWZGB4V3.js → chunk-PAZIDRXK.js} +42 -181
  18. package/dist/chunk-PAZIDRXK.js.map +1 -0
  19. package/dist/chunk-PLSHJHHR.js +293 -0
  20. package/dist/chunk-PLSHJHHR.js.map +1 -0
  21. package/dist/chunk-R5I5DEXE.js +228 -0
  22. package/dist/chunk-R5I5DEXE.js.map +1 -0
  23. package/dist/cli.js +157 -122
  24. package/dist/cli.js.map +1 -1
  25. package/dist/{config-I5NCK3RJ.js → config-IXBXMIUA.js} +2 -2
  26. package/dist/{db-ETWTBXAE.js → db-GJALN3R7.js} +2 -2
  27. package/dist/{display-UH7KEHOW.js → display-UDIACHTP.js} +3 -3
  28. package/dist/{engine-EGPAS2EX.js → engine-4WNPXVMS.js} +3 -2
  29. package/dist/index.d.ts +57 -57
  30. package/dist/index.js +17 -8
  31. package/dist/index.js.map +1 -1
  32. package/dist/{ollama-5JVKNFOV.js → ollama-TNMD5WHW.js} +2 -2
  33. package/dist/server-3CMTP4W4.js +13 -0
  34. package/dist/{setup-YS27MOPE.js → setup-U4R5QJMV.js} +2 -2
  35. package/dist/static/index.html +75 -28
  36. package/dist/{writer-3NAVABN6.js → writer-OMHUMJR5.js} +3 -3
  37. package/dist/writer-OMHUMJR5.js.map +1 -0
  38. package/package.json +2 -1
  39. package/dist/chunk-7OEWYFN3.js.map +0 -1
  40. package/dist/chunk-EMLEJVJZ.js.map +0 -1
  41. package/dist/chunk-KYDMPE4N.js.map +0 -1
  42. package/dist/chunk-LZYW7XQO.js.map +0 -1
  43. package/dist/chunk-QWZGB4V3.js.map +0 -1
  44. package/dist/chunk-RDNSS3ME.js.map +0 -1
  45. package/dist/chunk-RTZ4QWG2.js.map +0 -1
  46. /package/dist/{chunk-CG6VEHJM.js.map → chunk-C4GZNBFF.js.map} +0 -0
  47. /package/dist/{config-I5NCK3RJ.js.map → config-IXBXMIUA.js.map} +0 -0
  48. /package/dist/{db-ETWTBXAE.js.map → db-GJALN3R7.js.map} +0 -0
  49. /package/dist/{display-UH7KEHOW.js.map → display-UDIACHTP.js.map} +0 -0
  50. /package/dist/{engine-EGPAS2EX.js.map → engine-4WNPXVMS.js.map} +0 -0
  51. /package/dist/{ollama-5JVKNFOV.js.map → ollama-TNMD5WHW.js.map} +0 -0
  52. /package/dist/{writer-3NAVABN6.js.map → server-3CMTP4W4.js.map} +0 -0
  53. /package/dist/{setup-YS27MOPE.js.map → setup-U4R5QJMV.js.map} +0 -0
@@ -137,6 +137,18 @@ var DashboardDB = class _DashboardDB {
137
137
  repo_path TEXT
138
138
  )
139
139
  `);
140
+ this.db.exec(`
141
+ CREATE TABLE IF NOT EXISTS user_prompts (
142
+ id TEXT PRIMARY KEY,
143
+ session_id TEXT,
144
+ timestamp TEXT NOT NULL,
145
+ prompt_text TEXT NOT NULL,
146
+ prompt_length INTEGER NOT NULL,
147
+ word_count INTEGER NOT NULL,
148
+ cwd TEXT,
149
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
150
+ )
151
+ `);
140
152
  this.db.exec(`
141
153
  CREATE TABLE IF NOT EXISTS adapter_events (
142
154
  id TEXT PRIMARY KEY,
@@ -151,6 +163,7 @@ var DashboardDB = class _DashboardDB {
151
163
  this.migrateToolUsesAddSessionId();
152
164
  this.migrateSessionsAddMode();
153
165
  this.migrateSessionsAddRepoName();
166
+ this.migrateSessionsAddMetadata();
154
167
  this.db.exec(`
155
168
  CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
156
169
  CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time);
@@ -164,6 +177,8 @@ var DashboardDB = class _DashboardDB {
164
177
  CREATE INDEX IF NOT EXISTS idx_tool_uses_tool_name ON tool_uses(tool_name);
165
178
  CREATE INDEX IF NOT EXISTS idx_tool_uses_session_id ON tool_uses(session_id);
166
179
  CREATE INDEX IF NOT EXISTS idx_adapter_events_timestamp ON adapter_events(timestamp);
180
+ CREATE INDEX IF NOT EXISTS idx_user_prompts_timestamp ON user_prompts(timestamp);
181
+ CREATE INDEX IF NOT EXISTS idx_user_prompts_session_id ON user_prompts(session_id);
167
182
  `);
168
183
  }
169
184
  // ─────────────────────────────────────────────────────────────
@@ -500,6 +515,10 @@ var DashboardDB = class _DashboardDB {
500
515
  const rows = stmt.all();
501
516
  return rows.map((row) => this.rowToSession(row));
502
517
  }
518
+ updateSessionMetadata(id, metadata) {
519
+ const stmt = this.db.prepare("UPDATE sessions SET metadata = ? WHERE id = ?");
520
+ stmt.run(JSON.stringify(metadata), id);
521
+ }
503
522
  /**
504
523
  * Atomically increment session counters in a single UPDATE.
505
524
  * Race-safe for concurrent hook processes (SQLite serializes writes).
@@ -526,7 +545,8 @@ var DashboardDB = class _DashboardDB {
526
545
  blockedCount: row.blocked_count,
527
546
  avgRiskScore: row.avg_risk_score,
528
547
  workingDir: row.working_dir,
529
- repoName: row.repo_name ?? null
548
+ repoName: row.repo_name ?? null,
549
+ metadata: JSON.parse(row.metadata || "{}")
530
550
  };
531
551
  }
532
552
  // ─────────────────────────────────────────────────────────────
@@ -592,6 +612,16 @@ var DashboardDB = class _DashboardDB {
592
612
  getCommandsBySession(sessionId, limit = 100) {
593
613
  return this.getCommands({ sessionId, limit });
594
614
  }
615
+ searchCommands(query, limit = 50) {
616
+ const stmt = this.db.prepare(`
617
+ SELECT * FROM commands
618
+ WHERE command LIKE ?
619
+ ORDER BY timestamp DESC
620
+ LIMIT ?
621
+ `);
622
+ const rows = stmt.all(`%${query}%`, limit);
623
+ return rows.map((row) => this.rowToCommand(row));
624
+ }
595
625
  getLiveCommands(limit = 20) {
596
626
  const stmt = this.db.prepare(`
597
627
  SELECT c.*, s.repo_name, s.working_dir
@@ -840,6 +870,93 @@ var DashboardDB = class _DashboardDB {
840
870
  };
841
871
  }
842
872
  // ─────────────────────────────────────────────────────────────
873
+ // User Prompts
874
+ // ─────────────────────────────────────────────────────────────
875
+ insertUserPrompt(input) {
876
+ const id = randomUUID();
877
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
878
+ const originalLength = input.promptText.length;
879
+ const promptText = input.promptText.substring(0, 5e4);
880
+ const wordCount = promptText.trim() === "" ? 0 : promptText.trim().split(/\s+/).length;
881
+ const stmt = this.db.prepare(`
882
+ INSERT INTO user_prompts (id, session_id, timestamp, prompt_text, prompt_length, word_count, cwd)
883
+ VALUES (?, ?, ?, ?, ?, ?, ?)
884
+ `);
885
+ stmt.run(
886
+ id,
887
+ input.sessionId ?? null,
888
+ timestamp,
889
+ promptText,
890
+ originalLength,
891
+ wordCount,
892
+ input.cwd ?? null
893
+ );
894
+ return id;
895
+ }
896
+ getUserPrompts(filter = {}) {
897
+ const conditions = [];
898
+ const params = [];
899
+ if (filter.sessionId) {
900
+ conditions.push("session_id = ?");
901
+ params.push(filter.sessionId);
902
+ }
903
+ if (filter.since) {
904
+ conditions.push("timestamp >= ?");
905
+ params.push(filter.since.toISOString());
906
+ }
907
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
908
+ const limit = filter.limit ?? 100;
909
+ const offset = filter.offset ?? 0;
910
+ const stmt = this.db.prepare(`
911
+ SELECT * FROM user_prompts
912
+ ${whereClause}
913
+ ORDER BY timestamp DESC
914
+ LIMIT ? OFFSET ?
915
+ `);
916
+ params.push(limit, offset);
917
+ const rows = stmt.all(...params);
918
+ return rows.map((row) => ({
919
+ id: row.id,
920
+ sessionId: row.session_id,
921
+ timestamp: new Date(row.timestamp),
922
+ promptText: row.prompt_text,
923
+ promptLength: row.prompt_length,
924
+ wordCount: row.word_count,
925
+ cwd: row.cwd
926
+ }));
927
+ }
928
+ getUserPromptStats() {
929
+ const totalRow = this.db.prepare("SELECT COUNT(*) as count FROM user_prompts").get();
930
+ const sumRow = this.db.prepare(`
931
+ SELECT
932
+ COALESCE(SUM(word_count), 0) as total_words,
933
+ COALESCE(SUM(prompt_length), 0) as total_chars,
934
+ COALESCE(AVG(prompt_length), 0) as avg_length,
935
+ COALESCE(AVG(word_count), 0) as avg_words,
936
+ COALESCE(MAX(prompt_length), 0) as longest
937
+ FROM user_prompts
938
+ `).get();
939
+ const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
940
+ const last24hRow = this.db.prepare(`
941
+ SELECT COUNT(*) as count FROM user_prompts WHERE timestamp >= ?
942
+ `).get(oneDayAgo);
943
+ const perSessionRow = this.db.prepare(`
944
+ SELECT COALESCE(AVG(cnt), 0) as avg_per_session FROM (
945
+ SELECT COUNT(*) as cnt FROM user_prompts WHERE session_id IS NOT NULL GROUP BY session_id
946
+ )
947
+ `).get();
948
+ return {
949
+ totalPrompts: totalRow.count,
950
+ totalWords: sumRow.total_words,
951
+ totalChars: sumRow.total_chars,
952
+ avgPromptLength: Math.round(sumRow.avg_length),
953
+ avgWordCount: Math.round(sumRow.avg_words),
954
+ longestPrompt: sumRow.longest,
955
+ last24h: last24hRow.count,
956
+ promptsPerSession: Math.round(perSessionRow.avg_per_session)
957
+ };
958
+ }
959
+ // ─────────────────────────────────────────────────────────────
843
960
  // Session Metrics
844
961
  // ─────────────────────────────────────────────────────────────
845
962
  getSessionMetrics(sessionId) {
@@ -1031,7 +1148,8 @@ var DashboardDB = class _DashboardDB {
1031
1148
  const broEventsDeleted = this.db.prepare("DELETE FROM bro_events WHERE timestamp < ?").run(cutoff).changes;
1032
1149
  const broStatusDeleted = this.db.prepare("DELETE FROM bro_status WHERE timestamp < ?").run(cutoff).changes;
1033
1150
  const toolUsesDeleted = this.db.prepare("DELETE FROM tool_uses WHERE timestamp < ?").run(cutoff).changes;
1034
- return eventsDeleted + connectorDeleted + blocksDeleted + exposuresDeleted + commandsDeleted + sessionsDeleted + broEventsDeleted + broStatusDeleted + toolUsesDeleted;
1151
+ const promptsDeleted = this.db.prepare("DELETE FROM user_prompts WHERE timestamp < ?").run(cutoff).changes;
1152
+ return eventsDeleted + connectorDeleted + blocksDeleted + exposuresDeleted + commandsDeleted + sessionsDeleted + broEventsDeleted + broStatusDeleted + toolUsesDeleted + promptsDeleted;
1035
1153
  }
1036
1154
  /**
1037
1155
  * Migration: add session_id column to tool_uses table for session tracking
@@ -1072,6 +1190,19 @@ var DashboardDB = class _DashboardDB {
1072
1190
  } catch {
1073
1191
  }
1074
1192
  }
1193
+ /**
1194
+ * Migration: add metadata column to sessions table
1195
+ */
1196
+ migrateSessionsAddMetadata() {
1197
+ try {
1198
+ const tableInfo = this.db.pragma("table_info(sessions)");
1199
+ const hasMetadata = tableInfo.some((col) => col.name === "metadata");
1200
+ if (!hasMetadata) {
1201
+ this.db.exec("ALTER TABLE sessions ADD COLUMN metadata TEXT DEFAULT '{}'");
1202
+ }
1203
+ } catch {
1204
+ }
1205
+ }
1075
1206
  /**
1076
1207
  * Migration: allow NULL session_id in commands table for hook-mode recording
1077
1208
  * (each hook invocation is a separate process with no long-running session)
@@ -1131,7 +1262,12 @@ var DashboardDB = class _DashboardDB {
1131
1262
  { id: "explorer", name: "Explorer", description: "Unique commands used", category: "behavioral", icon: "\u{1F9ED}", stat: "uniqueCommands", tiers: [25, 50, 100, 250, 500] },
1132
1263
  // Repo
1133
1264
  { id: "home_base", name: "Home Base", description: "Protect a repo", category: "repo", icon: "\u{1F3E0}", stat: "uniqueRepos", tiers: [1, 1, 1, 1, 1] },
1134
- { id: "empire", name: "Empire", description: "Protect multiple repos", category: "repo", icon: "\u{1F3F0}", stat: "uniqueRepos", tiers: [3, 5, 10, 25, 50] }
1265
+ { id: "empire", name: "Empire", description: "Protect multiple repos", category: "repo", icon: "\u{1F3F0}", stat: "uniqueRepos", tiers: [3, 5, 10, 25, 50] },
1266
+ // Prompt
1267
+ { id: "conversationalist", name: "Conversationalist", description: "Submit prompts", category: "prompt", icon: "\u{1F4AC}", stat: "totalPrompts", tiers: [1, 50, 500, 5e3, 5e4] },
1268
+ { id: "wordsmith", name: "Wordsmith", description: "Total words prompted", category: "prompt", icon: "\u270D", stat: "totalPromptWords", tiers: [100, 1e3, 1e4, 1e5, 1e6] },
1269
+ { id: "novelist", name: "Novelist", description: "Longest single prompt", category: "prompt", icon: "\u{1F4D6}", stat: "longestPromptLength", tiers: [500, 1e3, 2e3, 5e3, 1e4] },
1270
+ { id: "chatty", name: "Chatty", description: "Prompts per session avg", category: "prompt", icon: "\u{1F5E3}", stat: "promptsPerSession", tiers: [5, 10, 25, 50, 100] }
1135
1271
  ];
1136
1272
  getAchievementStats() {
1137
1273
  const totalCommands = this.db.prepare("SELECT COUNT(*) as c FROM commands").get().c;
@@ -1223,6 +1359,31 @@ var DashboardDB = class _DashboardDB {
1223
1359
  const highRiskCount = this.db.prepare(
1224
1360
  "SELECT COUNT(*) as c FROM commands WHERE risk_score >= 8"
1225
1361
  ).get().c;
1362
+ let totalPrompts = 0;
1363
+ let totalPromptWords = 0;
1364
+ let totalPromptChars = 0;
1365
+ let longestPromptLength = 0;
1366
+ let promptsPerSession = 0;
1367
+ try {
1368
+ totalPrompts = this.db.prepare("SELECT COUNT(*) as c FROM user_prompts").get().c;
1369
+ const promptSums = this.db.prepare(`
1370
+ SELECT
1371
+ COALESCE(SUM(word_count), 0) as words,
1372
+ COALESCE(SUM(prompt_length), 0) as chars,
1373
+ COALESCE(MAX(prompt_length), 0) as longest
1374
+ FROM user_prompts
1375
+ `).get();
1376
+ totalPromptWords = promptSums.words;
1377
+ totalPromptChars = promptSums.chars;
1378
+ longestPromptLength = promptSums.longest;
1379
+ const perSessionRow = this.db.prepare(`
1380
+ SELECT COALESCE(AVG(cnt), 0) as avg FROM (
1381
+ SELECT COUNT(*) as cnt FROM user_prompts WHERE session_id IS NOT NULL GROUP BY session_id
1382
+ )
1383
+ `).get();
1384
+ promptsPerSession = Math.round(perSessionRow.avg);
1385
+ } catch {
1386
+ }
1226
1387
  return {
1227
1388
  totalCommands,
1228
1389
  totalBlocked,
@@ -1253,7 +1414,12 @@ var DashboardDB = class _DashboardDB {
1253
1414
  currentCleanStreak: currentStreak,
1254
1415
  highestRiskCommand,
1255
1416
  highestRiskScore,
1256
- highRiskCount
1417
+ highRiskCount,
1418
+ totalPrompts,
1419
+ totalPromptWords,
1420
+ totalPromptChars,
1421
+ longestPromptLength,
1422
+ promptsPerSession
1257
1423
  };
1258
1424
  }
1259
1425
  computeAchievements(stats) {
@@ -1296,6 +1462,8 @@ var DashboardDB = class _DashboardDB {
1296
1462
  totalXP += stats.totalSessions * 10;
1297
1463
  totalXP += stats.lateNightCount * 2;
1298
1464
  totalXP += Math.floor(stats.cleanestStreak / 100) * 25;
1465
+ totalXP += Math.floor(stats.totalPrompts / 2);
1466
+ totalXP += Math.floor(stats.totalPromptWords / 500);
1299
1467
  for (const badge of badges) {
1300
1468
  if (badge.tier > 0) {
1301
1469
  totalXP += TIER_XP[badge.tier];
@@ -1338,4 +1506,4 @@ export {
1338
1506
  DashboardDB,
1339
1507
  db_default
1340
1508
  };
1341
- //# sourceMappingURL=chunk-RDNSS3ME.js.map
1509
+ //# sourceMappingURL=chunk-6SLR5WPD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dashboard/db.ts"],"sourcesContent":["/**\n * Dashboard Database Module\n * SQLite-based storage for security events, connector activity, and egress blocks\n */\n\nimport Database from 'better-sqlite3'\nimport { randomUUID } from 'crypto'\nimport type {\n EventSource,\n EventLevel,\n UnifiedEvent,\n ConnectorEvent,\n EgressMatch,\n EgressPattern,\n RedactedPayload,\n ExposureResult\n} from '../policy/ward/types.js'\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface EventFilter {\n source?: EventSource\n level?: EventLevel\n category?: string\n since?: Date\n limit?: number\n offset?: number\n}\n\nexport interface InsertEventInput {\n source: EventSource\n level: EventLevel\n category: string\n message: string\n data?: Record<string, unknown>\n}\n\nexport interface InsertConnectorEventInput {\n connector: string\n method: string\n direction: 'inbound' | 'outbound'\n payload: RedactedPayload\n resourcesAccessed: string[]\n}\n\nexport interface InsertEgressBlockInput {\n pattern: EgressPattern\n matchedText: string\n redactedText: string\n connector?: string\n destination?: string\n}\n\nexport interface DashboardStats {\n totalEvents: number\n eventsBySource: Record<string, number>\n eventsByLevel: Record<string, number>\n pendingBlocks: number\n connectorCount: number\n recentExposures: number\n // Enhanced stats\n activeSessions: number\n todayCommands: number\n todayViolations: number\n avgRiskScore24h: number\n ollamaStatus: 'connected' | 'disconnected' | 'unknown'\n}\n\n// ─────────────────────────────────────────────────────────────\n// Session & Command Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface SessionRecord {\n id: string\n agent: string\n pid: number\n startTime: Date\n endTime?: Date\n status: 'active' | 'completed' | 'crashed'\n commandCount: number\n blockedCount: number\n avgRiskScore: number\n workingDir: string\n repoName: string | null\n metadata: Record<string, unknown>\n}\n\nexport interface CommandRecord {\n id: string\n sessionId: string\n timestamp: Date\n command: string\n allowed: boolean\n riskScore: number\n riskLevel: 'safe' | 'caution' | 'dangerous' | 'critical'\n riskFactors: string[]\n durationMs: number\n violations: string[]\n repoName?: string | null\n}\n\nexport interface BroEventRecord {\n id: string\n sessionId: string | null\n timestamp: Date\n eventType: string\n inputContext: string\n outputSummary: string\n modelUsed: string\n latencyMs: number\n success: boolean\n}\n\nexport interface BroStatusRecord {\n id: string\n timestamp: Date\n ollamaAvailable: boolean\n ollamaModel: string\n platform: string\n shell: string\n projectType: string | null\n}\n\nexport interface ToolUseRecord {\n id: string\n timestamp: Date\n toolName: string\n toolInput: string\n toolOutput: string\n exitCode: number | null\n success: boolean | null\n cwd: string\n repoName: string | null\n repoPath: string | null\n}\n\nexport interface InsertSessionInput {\n agent: string\n pid: number\n workingDir: string\n repoName?: string | null\n}\n\nexport interface InsertCommandInput {\n sessionId?: string\n command: string\n allowed: boolean\n riskScore: number\n riskLevel: 'safe' | 'caution' | 'dangerous' | 'critical'\n riskFactors: string[]\n durationMs: number\n violations: string[]\n}\n\nexport interface InsertBroEventInput {\n sessionId?: string\n eventType: string\n inputContext: string\n outputSummary: string\n modelUsed: string\n latencyMs: number\n success: boolean\n}\n\nexport interface InsertBroStatusInput {\n ollamaAvailable: boolean\n ollamaModel: string\n platform: string\n shell: string\n projectType?: string\n}\n\nexport interface InsertAdapterEventInput {\n adapterName: string\n baseModel: string\n purpose: string\n action: 'activated' | 'deactivated' | 'created' | 'deleted'\n success: boolean\n}\n\nexport interface AdapterEventRecord {\n id: string\n timestamp: Date\n adapterName: string\n baseModel: string\n purpose: string\n action: string\n success: boolean\n}\n\nexport interface InsertToolUseInput {\n toolName: string\n toolInput: string\n toolOutput: string\n exitCode?: number | null\n success?: boolean | null\n cwd: string\n repoName?: string | null\n repoPath?: string | null\n sessionId?: string\n}\n\nexport interface ToolUseFilter {\n toolName?: string\n sessionId?: string\n since?: Date\n limit?: number\n offset?: number\n}\n\n// ─────────────────────────────────────────────────────────────\n// User Prompt Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface UserPromptRecord {\n id: string\n sessionId: string | null\n timestamp: Date\n promptText: string\n promptLength: number\n wordCount: number\n cwd: string | null\n}\n\nexport interface InsertUserPromptInput {\n sessionId?: string\n promptText: string\n cwd?: string\n}\n\nexport interface UserPromptFilter {\n sessionId?: string\n since?: Date\n limit?: number\n offset?: number\n}\n\nexport interface UserPromptStats {\n totalPrompts: number\n totalWords: number\n totalChars: number\n avgPromptLength: number\n avgWordCount: number\n longestPrompt: number\n last24h: number\n promptsPerSession: number\n}\n\nexport interface SessionFilter {\n status?: 'active' | 'completed' | 'crashed'\n since?: Date\n until?: Date\n agent?: string\n limit?: number\n offset?: number\n}\n\nexport interface CommandFilter {\n sessionId?: string\n allowed?: boolean\n riskLevel?: string\n since?: Date\n afterId?: string\n limit?: number\n offset?: number\n}\n\n// ─────────────────────────────────────────────────────────────\n// Database Class\n// ─────────────────────────────────────────────────────────────\n\nexport class DashboardDB {\n private db: Database.Database\n\n constructor(dbPath: string = '.bashbros.db') {\n this.db = new Database(dbPath)\n this.db.pragma('journal_mode = WAL')\n this.initTables()\n }\n\n private initTables(): void {\n // Events table - unified security events\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n source TEXT NOT NULL,\n level TEXT NOT NULL,\n category TEXT NOT NULL,\n message TEXT NOT NULL,\n data TEXT\n )\n `)\n\n // Connector events table - MCP connector activity\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS connector_events (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n connector TEXT NOT NULL,\n method TEXT NOT NULL,\n direction TEXT NOT NULL,\n payload TEXT NOT NULL,\n resources_accessed TEXT NOT NULL\n )\n `)\n\n // Egress blocks table - blocked sensitive data\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS egress_blocks (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n pattern TEXT NOT NULL,\n matched_text TEXT NOT NULL,\n redacted_text TEXT NOT NULL,\n connector TEXT,\n destination TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n approved_by TEXT,\n approved_at TEXT\n )\n `)\n\n // Exposure scans table - network exposure scan results\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS exposure_scans (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n agent TEXT NOT NULL,\n pid INTEGER,\n port INTEGER NOT NULL,\n bind_address TEXT NOT NULL,\n has_auth TEXT NOT NULL,\n severity TEXT NOT NULL,\n action TEXT NOT NULL,\n message TEXT NOT NULL\n )\n `)\n\n // Create indexes for common queries\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_events_source ON events(source);\n CREATE INDEX IF NOT EXISTS idx_events_level ON events(level);\n CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);\n CREATE INDEX IF NOT EXISTS idx_connector_events_connector ON connector_events(connector);\n CREATE INDEX IF NOT EXISTS idx_egress_blocks_status ON egress_blocks(status);\n `)\n\n // Sessions table - track watch sessions\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n agent TEXT NOT NULL,\n pid INTEGER NOT NULL,\n start_time TEXT NOT NULL,\n end_time TEXT,\n status TEXT NOT NULL DEFAULT 'active',\n command_count INTEGER NOT NULL DEFAULT 0,\n blocked_count INTEGER NOT NULL DEFAULT 0,\n avg_risk_score REAL NOT NULL DEFAULT 0,\n working_dir TEXT NOT NULL\n )\n `)\n\n // Commands table - detailed command history\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS commands (\n id TEXT PRIMARY KEY,\n session_id TEXT,\n timestamp TEXT NOT NULL,\n command TEXT NOT NULL,\n allowed INTEGER NOT NULL,\n risk_score INTEGER NOT NULL,\n risk_level TEXT NOT NULL,\n risk_factors TEXT NOT NULL,\n duration_ms INTEGER NOT NULL,\n violations TEXT NOT NULL,\n FOREIGN KEY (session_id) REFERENCES sessions(id)\n )\n `)\n\n // Migration: relax session_id NOT NULL for hook-mode recording\n this.migrateCommandsNullableSessionId()\n\n // Bash Bro events table - AI activity log\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS bro_events (\n id TEXT PRIMARY KEY,\n session_id TEXT,\n timestamp TEXT NOT NULL,\n event_type TEXT NOT NULL,\n input_context TEXT NOT NULL,\n output_summary TEXT NOT NULL,\n model_used TEXT NOT NULL,\n latency_ms INTEGER NOT NULL,\n success INTEGER NOT NULL,\n FOREIGN KEY (session_id) REFERENCES sessions(id)\n )\n `)\n\n // Bash Bro status snapshots table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS bro_status (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n ollama_available INTEGER NOT NULL,\n ollama_model TEXT NOT NULL,\n platform TEXT NOT NULL,\n shell TEXT NOT NULL,\n project_type TEXT\n )\n `)\n\n // Tool uses table - generic Claude Code tool tracking\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS tool_uses (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n tool_name TEXT NOT NULL,\n tool_input TEXT NOT NULL,\n tool_output TEXT NOT NULL,\n exit_code INTEGER,\n success INTEGER,\n cwd TEXT NOT NULL,\n repo_name TEXT,\n repo_path TEXT\n )\n `)\n\n // User prompts table - track user prompt submissions\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS user_prompts (\n id TEXT PRIMARY KEY,\n session_id TEXT,\n timestamp TEXT NOT NULL,\n prompt_text TEXT NOT NULL,\n prompt_length INTEGER NOT NULL,\n word_count INTEGER NOT NULL,\n cwd TEXT,\n FOREIGN KEY (session_id) REFERENCES sessions(id)\n )\n `)\n\n // Adapter events table - track adapter activations\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS adapter_events (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n adapter_name TEXT NOT NULL,\n base_model TEXT NOT NULL,\n purpose TEXT NOT NULL,\n action TEXT NOT NULL,\n success INTEGER NOT NULL\n )\n `)\n\n // Migrations for multi-session support\n this.migrateToolUsesAddSessionId()\n this.migrateSessionsAddMode()\n this.migrateSessionsAddRepoName()\n this.migrateSessionsAddMetadata()\n\n // Create indexes for new tables\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);\n CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time);\n CREATE INDEX IF NOT EXISTS idx_commands_session_id ON commands(session_id);\n CREATE INDEX IF NOT EXISTS idx_commands_timestamp ON commands(timestamp);\n CREATE INDEX IF NOT EXISTS idx_commands_allowed ON commands(allowed);\n CREATE INDEX IF NOT EXISTS idx_bro_events_session_id ON bro_events(session_id);\n CREATE INDEX IF NOT EXISTS idx_bro_events_timestamp ON bro_events(timestamp);\n CREATE INDEX IF NOT EXISTS idx_bro_status_timestamp ON bro_status(timestamp);\n CREATE INDEX IF NOT EXISTS idx_tool_uses_timestamp ON tool_uses(timestamp);\n CREATE INDEX IF NOT EXISTS idx_tool_uses_tool_name ON tool_uses(tool_name);\n CREATE INDEX IF NOT EXISTS idx_tool_uses_session_id ON tool_uses(session_id);\n CREATE INDEX IF NOT EXISTS idx_adapter_events_timestamp ON adapter_events(timestamp);\n CREATE INDEX IF NOT EXISTS idx_user_prompts_timestamp ON user_prompts(timestamp);\n CREATE INDEX IF NOT EXISTS idx_user_prompts_session_id ON user_prompts(session_id);\n `)\n }\n\n // ─────────────────────────────────────────────────────────────\n // Events\n // ─────────────────────────────────────────────────────────────\n\n insertEvent(input: InsertEventInput): string {\n const id = randomUUID()\n const timestamp = new Date().toISOString()\n const data = input.data ? JSON.stringify(input.data) : null\n\n const stmt = this.db.prepare(`\n INSERT INTO events (id, timestamp, source, level, category, message, data)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `)\n\n stmt.run(id, timestamp, input.source, input.level, input.category, input.message, data)\n return id\n }\n\n getEvents(filter: EventFilter = {}): UnifiedEvent[] {\n const conditions: string[] = []\n const params: unknown[] = []\n\n if (filter.source) {\n conditions.push('source = ?')\n params.push(filter.source)\n }\n\n if (filter.level) {\n conditions.push('level = ?')\n params.push(filter.level)\n }\n\n if (filter.category) {\n conditions.push('category = ?')\n params.push(filter.category)\n }\n\n if (filter.since) {\n conditions.push('timestamp >= ?')\n params.push(filter.since.toISOString())\n }\n\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n const limit = filter.limit ?? 100\n const offset = filter.offset ?? 0\n\n const stmt = this.db.prepare(`\n SELECT * FROM events\n ${whereClause}\n ORDER BY timestamp DESC\n LIMIT ? OFFSET ?\n `)\n\n params.push(limit, offset)\n const rows = stmt.all(...params) as Array<{\n id: string\n timestamp: string\n source: EventSource\n level: EventLevel\n category: string\n message: string\n data: string | null\n }>\n\n return rows.map(row => ({\n id: row.id,\n timestamp: new Date(row.timestamp),\n source: row.source,\n level: row.level,\n category: row.category,\n message: row.message,\n data: row.data ? JSON.parse(row.data) : undefined\n }))\n }\n\n // ─────────────────────────────────────────────────────────────\n // Connector Events\n // ─────────────────────────────────────────────────────────────\n\n insertConnectorEvent(input: InsertConnectorEventInput): string {\n const id = randomUUID()\n const timestamp = new Date().toISOString()\n\n const stmt = this.db.prepare(`\n INSERT INTO connector_events (id, timestamp, connector, method, direction, payload, resources_accessed)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `)\n\n stmt.run(\n id,\n timestamp,\n input.connector,\n input.method,\n input.direction,\n JSON.stringify(input.payload),\n JSON.stringify(input.resourcesAccessed)\n )\n\n return id\n }\n\n getConnectorEvents(connector: string, limit: number = 100): ConnectorEvent[] {\n const stmt = this.db.prepare(`\n SELECT * FROM connector_events\n WHERE connector = ?\n ORDER BY timestamp DESC\n LIMIT ?\n `)\n\n const rows = stmt.all(connector, limit) as Array<{\n id: string\n timestamp: string\n connector: string\n method: string\n direction: 'inbound' | 'outbound'\n payload: string\n resources_accessed: string\n }>\n\n return rows.map(row => ({\n id: row.id,\n timestamp: new Date(row.timestamp),\n connector: row.connector,\n method: row.method,\n direction: row.direction,\n payload: JSON.parse(row.payload),\n resourcesAccessed: JSON.parse(row.resources_accessed)\n }))\n }\n\n getAllConnectorEvents(limit: number = 100): ConnectorEvent[] {\n const stmt = this.db.prepare(`\n SELECT * FROM connector_events\n ORDER BY timestamp DESC\n LIMIT ?\n `)\n\n const rows = stmt.all(limit) as Array<{\n id: string\n timestamp: string\n connector: string\n method: string\n direction: 'inbound' | 'outbound'\n payload: string\n resources_accessed: string\n }>\n\n return rows.map(row => ({\n id: row.id,\n timestamp: new Date(row.timestamp),\n connector: row.connector,\n method: row.method,\n direction: row.direction,\n payload: JSON.parse(row.payload),\n resourcesAccessed: JSON.parse(row.resources_accessed)\n }))\n }\n\n // ─────────────────────────────────────────────────────────────\n // Egress Blocks\n // ─────────────────────────────────────────────────────────────\n\n insertEgressBlock(input: InsertEgressBlockInput): string {\n const id = randomUUID()\n const timestamp = new Date().toISOString()\n\n const stmt = this.db.prepare(`\n INSERT INTO egress_blocks (id, timestamp, pattern, matched_text, redacted_text, connector, destination, status)\n VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')\n `)\n\n stmt.run(\n id,\n timestamp,\n JSON.stringify(input.pattern),\n input.matchedText,\n input.redactedText,\n input.connector ?? null,\n input.destination ?? null\n )\n\n return id\n }\n\n getPendingBlocks(): EgressMatch[] {\n const stmt = this.db.prepare(`\n SELECT * FROM egress_blocks\n WHERE status = 'pending'\n ORDER BY timestamp DESC\n `)\n\n const rows = stmt.all() as Array<{\n id: string\n timestamp: string\n pattern: string\n matched_text: string\n redacted_text: string\n connector: string | null\n destination: string | null\n status: 'pending' | 'approved' | 'denied'\n approved_by: string | null\n approved_at: string | null\n }>\n\n return rows.map(row => this.rowToEgressMatch(row))\n }\n\n getBlock(id: string): EgressMatch | null {\n const stmt = this.db.prepare(`\n SELECT * FROM egress_blocks WHERE id = ?\n `)\n\n const row = stmt.get(id) as {\n id: string\n timestamp: string\n pattern: string\n matched_text: string\n redacted_text: string\n connector: string | null\n destination: string | null\n status: 'pending' | 'approved' | 'denied'\n approved_by: string | null\n approved_at: string | null\n } | undefined\n\n if (!row) return null\n return this.rowToEgressMatch(row)\n }\n\n approveBlock(id: string, approvedBy: string): void {\n const stmt = this.db.prepare(`\n UPDATE egress_blocks\n SET status = 'approved', approved_by = ?, approved_at = ?\n WHERE id = ?\n `)\n\n stmt.run(approvedBy, new Date().toISOString(), id)\n }\n\n denyBlock(id: string, deniedBy: string): void {\n const stmt = this.db.prepare(`\n UPDATE egress_blocks\n SET status = 'denied', approved_by = ?, approved_at = ?\n WHERE id = ?\n `)\n\n stmt.run(deniedBy, new Date().toISOString(), id)\n }\n\n private rowToEgressMatch(row: {\n id: string\n timestamp: string\n pattern: string\n matched_text: string\n redacted_text: string\n connector: string | null\n destination: string | null\n status: 'pending' | 'approved' | 'denied'\n approved_by: string | null\n approved_at: string | null\n }): EgressMatch {\n return {\n id: row.id,\n timestamp: new Date(row.timestamp),\n pattern: JSON.parse(row.pattern),\n matchedText: row.matched_text,\n redactedText: row.redacted_text,\n connector: row.connector ?? undefined,\n destination: row.destination ?? undefined,\n status: row.status,\n approvedBy: row.approved_by ?? undefined,\n approvedAt: row.approved_at ? new Date(row.approved_at) : undefined\n }\n }\n\n // ─────────────────────────────────────────────────────────────\n // Exposure Scans\n // ─────────────────────────────────────────────────────────────\n\n insertExposureScan(result: ExposureResult): string {\n const id = randomUUID()\n const timestamp = result.timestamp.toISOString()\n\n const stmt = this.db.prepare(`\n INSERT INTO exposure_scans (id, timestamp, agent, pid, port, bind_address, has_auth, severity, action, message)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `)\n\n stmt.run(\n id,\n timestamp,\n result.agent,\n result.pid ?? null,\n result.port,\n result.bindAddress,\n String(result.hasAuth),\n result.severity,\n result.action,\n result.message\n )\n\n return id\n }\n\n getRecentExposures(limit: number = 100): ExposureResult[] {\n const stmt = this.db.prepare(`\n SELECT * FROM exposure_scans\n ORDER BY timestamp DESC\n LIMIT ?\n `)\n\n const rows = stmt.all(limit) as Array<{\n id: string\n timestamp: string\n agent: string\n pid: number | null\n port: number\n bind_address: string\n has_auth: string\n severity: string\n action: string\n message: string\n }>\n\n return rows.map(row => ({\n agent: row.agent,\n pid: row.pid ?? undefined,\n port: row.port,\n bindAddress: row.bind_address,\n hasAuth: row.has_auth === 'true' ? true : row.has_auth === 'false' ? false : 'unknown' as const,\n severity: row.severity as ExposureResult['severity'],\n action: row.action as ExposureResult['action'],\n message: row.message,\n timestamp: new Date(row.timestamp)\n }))\n }\n\n // ─────────────────────────────────────────────────────────────\n // Sessions\n // ─────────────────────────────────────────────────────────────\n\n insertSession(input: InsertSessionInput): string {\n const id = randomUUID()\n const startTime = new Date().toISOString()\n\n const stmt = this.db.prepare(`\n INSERT INTO sessions (id, agent, pid, start_time, status, command_count, blocked_count, avg_risk_score, working_dir, repo_name)\n VALUES (?, ?, ?, ?, 'active', 0, 0, 0, ?, ?)\n `)\n\n stmt.run(id, input.agent, input.pid, startTime, input.workingDir, input.repoName ?? null)\n return id\n }\n\n updateSession(id: string, updates: {\n endTime?: Date\n status?: 'active' | 'completed' | 'crashed'\n commandCount?: number\n blockedCount?: number\n avgRiskScore?: number\n }): void {\n const setClauses: string[] = []\n const params: unknown[] = []\n\n if (updates.endTime !== undefined) {\n setClauses.push('end_time = ?')\n params.push(updates.endTime.toISOString())\n }\n if (updates.status !== undefined) {\n setClauses.push('status = ?')\n params.push(updates.status)\n }\n if (updates.commandCount !== undefined) {\n setClauses.push('command_count = ?')\n params.push(updates.commandCount)\n }\n if (updates.blockedCount !== undefined) {\n setClauses.push('blocked_count = ?')\n params.push(updates.blockedCount)\n }\n if (updates.avgRiskScore !== undefined) {\n setClauses.push('avg_risk_score = ?')\n params.push(updates.avgRiskScore)\n }\n\n if (setClauses.length === 0) return\n\n params.push(id)\n const stmt = this.db.prepare(`\n UPDATE sessions SET ${setClauses.join(', ')} WHERE id = ?\n `)\n stmt.run(...params)\n }\n\n getSession(id: string): SessionRecord | null {\n const stmt = this.db.prepare('SELECT * FROM sessions WHERE id = ?')\n const row = stmt.get(id) as {\n id: string\n agent: string\n pid: number\n start_time: string\n end_time: string | null\n status: 'active' | 'completed' | 'crashed'\n command_count: number\n blocked_count: number\n avg_risk_score: number\n working_dir: string\n metadata?: string\n } | undefined\n\n if (!row) return null\n return this.rowToSession(row)\n }\n\n getSessions(filter: SessionFilter = {}): SessionRecord[] {\n const conditions: string[] = []\n const params: unknown[] = []\n\n if (filter.status) {\n conditions.push('status = ?')\n params.push(filter.status)\n }\n if (filter.since) {\n conditions.push('start_time >= ?')\n params.push(filter.since.toISOString())\n }\n if (filter.until) {\n conditions.push('start_time <= ?')\n params.push(filter.until.toISOString())\n }\n if (filter.agent) {\n conditions.push('agent = ?')\n params.push(filter.agent)\n }\n\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n const limit = filter.limit ?? 100\n const offset = filter.offset ?? 0\n\n const stmt = this.db.prepare(`\n SELECT * FROM sessions\n ${whereClause}\n ORDER BY start_time DESC\n LIMIT ? OFFSET ?\n `)\n\n params.push(limit, offset)\n const rows = stmt.all(...params) as Array<{\n id: string\n agent: string\n pid: number\n start_time: string\n end_time: string | null\n status: 'active' | 'completed' | 'crashed'\n command_count: number\n blocked_count: number\n avg_risk_score: number\n working_dir: string\n }>\n\n return rows.map(row => this.rowToSession(row))\n }\n\n getActiveSession(): SessionRecord | null {\n const stmt = this.db.prepare(`\n SELECT * FROM sessions WHERE status = 'active' ORDER BY start_time DESC LIMIT 1\n `)\n const row = stmt.get() as {\n id: string\n agent: string\n pid: number\n start_time: string\n end_time: string | null\n status: 'active' | 'completed' | 'crashed'\n command_count: number\n blocked_count: number\n avg_risk_score: number\n working_dir: string\n } | undefined\n\n if (!row) return null\n return this.rowToSession(row)\n }\n\n /**\n * Insert a session with an externally-provided ID (for hook-mode sessions).\n * Uses INSERT OR IGNORE for race-safe concurrent hook calls.\n */\n insertSessionWithId(id: string, input: InsertSessionInput & { mode?: string }): string {\n const startTime = new Date().toISOString()\n\n const stmt = this.db.prepare(`\n INSERT OR IGNORE INTO sessions (id, agent, pid, start_time, status, command_count, blocked_count, avg_risk_score, working_dir, mode, repo_name)\n VALUES (?, ?, ?, ?, 'active', 0, 0, 0, ?, ?, ?)\n `)\n\n stmt.run(id, input.agent, input.pid, startTime, input.workingDir, input.mode ?? 'hook', input.repoName ?? null)\n return id\n }\n\n /**\n * Get ALL active sessions, ordered by start_time DESC.\n * Used by the multi-session dashboard.\n */\n getActiveSessions(): SessionRecord[] {\n const stmt = this.db.prepare(`\n SELECT * FROM sessions WHERE status = 'active' ORDER BY start_time DESC\n `)\n const rows = stmt.all() as Array<{\n id: string\n agent: string\n pid: number\n start_time: string\n end_time: string | null\n status: 'active' | 'completed' | 'crashed'\n command_count: number\n blocked_count: number\n avg_risk_score: number\n working_dir: string\n }>\n\n return rows.map(row => this.rowToSession(row))\n }\n\n updateSessionMetadata(id: string, metadata: Record<string, unknown>): void {\n const stmt = this.db.prepare('UPDATE sessions SET metadata = ? WHERE id = ?')\n stmt.run(JSON.stringify(metadata), id)\n }\n\n /**\n * Atomically increment session counters in a single UPDATE.\n * Race-safe for concurrent hook processes (SQLite serializes writes).\n */\n incrementSessionCommand(id: string, blocked: boolean, riskScore: number): void {\n const stmt = this.db.prepare(`\n UPDATE sessions SET\n command_count = command_count + 1,\n blocked_count = blocked_count + ?,\n avg_risk_score = (avg_risk_score * command_count + ?) / (command_count + 1)\n WHERE id = ?\n `)\n stmt.run(blocked ? 1 : 0, riskScore, id)\n }\n\n private rowToSession(row: {\n id: string\n agent: string\n pid: number\n start_time: string\n end_time: string | null\n status: 'active' | 'completed' | 'crashed'\n command_count: number\n blocked_count: number\n avg_risk_score: number\n working_dir: string\n repo_name?: string | null\n metadata?: string\n }): SessionRecord {\n return {\n id: row.id,\n agent: row.agent,\n pid: row.pid,\n startTime: new Date(row.start_time),\n endTime: row.end_time ? new Date(row.end_time) : undefined,\n status: row.status,\n commandCount: row.command_count,\n blockedCount: row.blocked_count,\n avgRiskScore: row.avg_risk_score,\n workingDir: row.working_dir,\n repoName: row.repo_name ?? null,\n metadata: JSON.parse(row.metadata || '{}')\n }\n }\n\n // ─────────────────────────────────────────────────────────────\n // Commands\n // ─────────────────────────────────────────────────────────────\n\n insertCommand(input: InsertCommandInput): string {\n const id = randomUUID()\n const timestamp = new Date().toISOString()\n\n const stmt = this.db.prepare(`\n INSERT INTO commands (id, session_id, timestamp, command, allowed, risk_score, risk_level, risk_factors, duration_ms, violations)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `)\n\n stmt.run(\n id,\n input.sessionId ?? null,\n timestamp,\n input.command,\n input.allowed ? 1 : 0,\n input.riskScore,\n input.riskLevel,\n JSON.stringify(input.riskFactors),\n input.durationMs,\n JSON.stringify(input.violations)\n )\n\n return id\n }\n\n getCommands(filter: CommandFilter = {}): CommandRecord[] {\n const conditions: string[] = []\n const params: unknown[] = []\n\n if (filter.sessionId) {\n conditions.push('session_id = ?')\n params.push(filter.sessionId)\n }\n if (filter.allowed !== undefined) {\n conditions.push('allowed = ?')\n params.push(filter.allowed ? 1 : 0)\n }\n if (filter.riskLevel) {\n conditions.push('risk_level = ?')\n params.push(filter.riskLevel)\n }\n if (filter.since) {\n conditions.push('timestamp >= ?')\n params.push(filter.since.toISOString())\n }\n if (filter.afterId) {\n conditions.push('id > ?')\n params.push(filter.afterId)\n }\n\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n const limit = filter.limit ?? 100\n const offset = filter.offset ?? 0\n\n const stmt = this.db.prepare(`\n SELECT * FROM commands\n ${whereClause}\n ORDER BY timestamp DESC\n LIMIT ? OFFSET ?\n `)\n\n params.push(limit, offset)\n const rows = stmt.all(...params) as Array<{\n id: string\n session_id: string\n timestamp: string\n command: string\n allowed: number\n risk_score: number\n risk_level: 'safe' | 'caution' | 'dangerous' | 'critical'\n risk_factors: string\n duration_ms: number\n violations: string\n }>\n\n return rows.map(row => this.rowToCommand(row))\n }\n\n getCommandsBySession(sessionId: string, limit: number = 100): CommandRecord[] {\n return this.getCommands({ sessionId, limit })\n }\n\n searchCommands(query: string, limit: number = 50): CommandRecord[] {\n const stmt = this.db.prepare(`\n SELECT * FROM commands\n WHERE command LIKE ?\n ORDER BY timestamp DESC\n LIMIT ?\n `)\n\n const rows = stmt.all(`%${query}%`, limit) as Array<{\n id: string\n session_id: string\n timestamp: string\n command: string\n allowed: number\n risk_score: number\n risk_level: 'safe' | 'caution' | 'dangerous' | 'critical'\n risk_factors: string\n duration_ms: number\n violations: string\n }>\n\n return rows.map(row => this.rowToCommand(row))\n }\n\n getLiveCommands(limit: number = 20): CommandRecord[] {\n const stmt = this.db.prepare(`\n SELECT c.*, s.repo_name, s.working_dir\n FROM commands c\n LEFT JOIN sessions s ON c.session_id = s.id\n ORDER BY c.timestamp DESC\n LIMIT ?\n `)\n\n const rows = stmt.all(limit) as Array<{\n id: string\n session_id: string\n timestamp: string\n command: string\n allowed: number\n risk_score: number\n risk_level: 'safe' | 'caution' | 'dangerous' | 'critical'\n risk_factors: string\n duration_ms: number\n violations: string\n repo_name: string | null\n working_dir: string | null\n }>\n\n return rows.map(row => ({\n ...this.rowToCommand(row),\n repoName: row.repo_name ?? (row.working_dir ? row.working_dir.split(/[/\\\\]/).pop() ?? null : null)\n }))\n }\n\n private rowToCommand(row: {\n id: string\n session_id: string\n timestamp: string\n command: string\n allowed: number\n risk_score: number\n risk_level: 'safe' | 'caution' | 'dangerous' | 'critical'\n risk_factors: string\n duration_ms: number\n violations: string\n }): CommandRecord {\n return {\n id: row.id,\n sessionId: row.session_id,\n timestamp: new Date(row.timestamp),\n command: row.command,\n allowed: row.allowed === 1,\n riskScore: row.risk_score,\n riskLevel: row.risk_level,\n riskFactors: JSON.parse(row.risk_factors),\n durationMs: row.duration_ms,\n violations: JSON.parse(row.violations)\n }\n }\n\n // ─────────────────────────────────────────────────────────────\n // Bro Events\n // ─────────────────────────────────────────────────────────────\n\n insertBroEvent(input: InsertBroEventInput): string {\n const id = randomUUID()\n const timestamp = new Date().toISOString()\n\n const stmt = this.db.prepare(`\n INSERT INTO bro_events (id, session_id, timestamp, event_type, input_context, output_summary, model_used, latency_ms, success)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `)\n\n stmt.run(\n id,\n input.sessionId ?? null,\n timestamp,\n input.eventType,\n input.inputContext,\n input.outputSummary,\n input.modelUsed,\n input.latencyMs,\n input.success ? 1 : 0\n )\n\n return id\n }\n\n getBroEvents(limit: number = 100, sessionId?: string): BroEventRecord[] {\n let stmt\n let rows\n\n if (sessionId) {\n stmt = this.db.prepare(`\n SELECT * FROM bro_events\n WHERE session_id = ?\n ORDER BY timestamp DESC\n LIMIT ?\n `)\n rows = stmt.all(sessionId, limit)\n } else {\n stmt = this.db.prepare(`\n SELECT * FROM bro_events\n ORDER BY timestamp DESC\n LIMIT ?\n `)\n rows = stmt.all(limit)\n }\n\n return (rows as Array<{\n id: string\n session_id: string | null\n timestamp: string\n event_type: string\n input_context: string\n output_summary: string\n model_used: string\n latency_ms: number\n success: number\n }>).map(row => ({\n id: row.id,\n sessionId: row.session_id,\n timestamp: new Date(row.timestamp),\n eventType: row.event_type,\n inputContext: row.input_context,\n outputSummary: row.output_summary,\n modelUsed: row.model_used,\n latencyMs: row.latency_ms,\n success: row.success === 1\n }))\n }\n\n // ─────────────────────────────────────────────────────────────\n // Bro Status\n // ─────────────────────────────────────────────────────────────\n\n updateBroStatus(input: InsertBroStatusInput): string {\n const id = randomUUID()\n const timestamp = new Date().toISOString()\n\n const stmt = this.db.prepare(`\n INSERT INTO bro_status (id, timestamp, ollama_available, ollama_model, platform, shell, project_type)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `)\n\n stmt.run(\n id,\n timestamp,\n input.ollamaAvailable ? 1 : 0,\n input.ollamaModel,\n input.platform,\n input.shell,\n input.projectType ?? null\n )\n\n return id\n }\n\n getLatestBroStatus(): BroStatusRecord | null {\n const stmt = this.db.prepare(`\n SELECT * FROM bro_status\n ORDER BY timestamp DESC\n LIMIT 1\n `)\n\n const row = stmt.get() as {\n id: string\n timestamp: string\n ollama_available: number\n ollama_model: string\n platform: string\n shell: string\n project_type: string | null\n } | undefined\n\n if (!row) return null\n\n return {\n id: row.id,\n timestamp: new Date(row.timestamp),\n ollamaAvailable: row.ollama_available === 1,\n ollamaModel: row.ollama_model,\n platform: row.platform,\n shell: row.shell,\n projectType: row.project_type\n }\n }\n\n // ─────────────────────────────────────────────────────────────\n // Adapter Events\n // ─────────────────────────────────────────────────────────────\n\n insertAdapterEvent(input: InsertAdapterEventInput): string {\n const id = randomUUID()\n const timestamp = new Date().toISOString()\n\n const stmt = this.db.prepare(`\n INSERT INTO adapter_events (id, timestamp, adapter_name, base_model, purpose, action, success)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `)\n\n stmt.run(\n id,\n timestamp,\n input.adapterName,\n input.baseModel,\n input.purpose,\n input.action,\n input.success ? 1 : 0\n )\n\n return id\n }\n\n getAdapterEvents(limit: number = 50): AdapterEventRecord[] {\n const stmt = this.db.prepare(`\n SELECT * FROM adapter_events\n ORDER BY timestamp DESC\n LIMIT ?\n `)\n\n const rows = stmt.all(limit) as any[]\n\n return rows.map(row => ({\n id: row.id,\n timestamp: new Date(row.timestamp),\n adapterName: row.adapter_name,\n baseModel: row.base_model,\n purpose: row.purpose,\n action: row.action,\n success: row.success === 1\n }))\n }\n\n // ─────────────────────────────────────────────────────────────\n // Tool Uses\n // ─────────────────────────────────────────────────────────────\n\n insertToolUse(input: InsertToolUseInput): string {\n const id = randomUUID()\n const timestamp = new Date().toISOString()\n\n const stmt = this.db.prepare(`\n INSERT INTO tool_uses (id, timestamp, tool_name, tool_input, tool_output, exit_code, success, cwd, repo_name, repo_path, session_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `)\n\n stmt.run(\n id,\n timestamp,\n input.toolName,\n input.toolInput.substring(0, 50000), // Truncate very long inputs\n input.toolOutput.substring(0, 50000), // Truncate very long outputs\n input.exitCode ?? null,\n input.success === undefined ? null : (input.success ? 1 : 0),\n input.cwd,\n input.repoName ?? null,\n input.repoPath ?? null,\n input.sessionId ?? null\n )\n\n return id\n }\n\n getToolUses(filter: ToolUseFilter = {}): ToolUseRecord[] {\n const conditions: string[] = []\n const params: unknown[] = []\n\n if (filter.toolName) {\n conditions.push('tool_name = ?')\n params.push(filter.toolName)\n }\n if (filter.sessionId) {\n conditions.push('session_id = ?')\n params.push(filter.sessionId)\n }\n if (filter.since) {\n conditions.push('timestamp >= ?')\n params.push(filter.since.toISOString())\n }\n\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n const limit = filter.limit ?? 100\n const offset = filter.offset ?? 0\n\n const stmt = this.db.prepare(`\n SELECT * FROM tool_uses\n ${whereClause}\n ORDER BY timestamp DESC\n LIMIT ? OFFSET ?\n `)\n\n params.push(limit, offset)\n const rows = stmt.all(...params) as Array<{\n id: string\n timestamp: string\n tool_name: string\n tool_input: string\n tool_output: string\n exit_code: number | null\n success: number | null\n cwd: string\n repo_name: string | null\n repo_path: string | null\n }>\n\n return rows.map(row => ({\n id: row.id,\n timestamp: new Date(row.timestamp),\n toolName: row.tool_name,\n toolInput: row.tool_input,\n toolOutput: row.tool_output,\n exitCode: row.exit_code,\n success: row.success === null ? null : row.success === 1,\n cwd: row.cwd,\n repoName: row.repo_name,\n repoPath: row.repo_path\n }))\n }\n\n getLiveToolUses(limit: number = 50): ToolUseRecord[] {\n return this.getToolUses({ limit })\n }\n\n getToolUseStats(): {\n totalUses: number\n byTool: Record<string, number>\n last24h: number\n } {\n const totalRow = this.db.prepare('SELECT COUNT(*) as count FROM tool_uses').get() as { count: number }\n\n const toolRows = this.db.prepare(`\n SELECT tool_name, COUNT(*) as count FROM tool_uses GROUP BY tool_name ORDER BY count DESC\n `).all() as Array<{ tool_name: string; count: number }>\n\n const byTool: Record<string, number> = {}\n for (const row of toolRows) {\n byTool[row.tool_name] = row.count\n }\n\n const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()\n const last24hRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM tool_uses WHERE timestamp >= ?\n `).get(oneDayAgo) as { count: number }\n\n return {\n totalUses: totalRow.count,\n byTool,\n last24h: last24hRow.count\n }\n }\n\n // ─────────────────────────────────────────────────────────────\n // User Prompts\n // ─────────────────────────────────────────────────────────────\n\n insertUserPrompt(input: InsertUserPromptInput): string {\n const id = randomUUID()\n const timestamp = new Date().toISOString()\n const originalLength = input.promptText.length\n // Truncate text to 50KB but preserve original length for stats\n const promptText = input.promptText.substring(0, 50000)\n const wordCount = promptText.trim() === '' ? 0 : promptText.trim().split(/\\s+/).length\n\n const stmt = this.db.prepare(`\n INSERT INTO user_prompts (id, session_id, timestamp, prompt_text, prompt_length, word_count, cwd)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `)\n\n stmt.run(\n id,\n input.sessionId ?? null,\n timestamp,\n promptText,\n originalLength,\n wordCount,\n input.cwd ?? null\n )\n\n return id\n }\n\n getUserPrompts(filter: UserPromptFilter = {}): UserPromptRecord[] {\n const conditions: string[] = []\n const params: unknown[] = []\n\n if (filter.sessionId) {\n conditions.push('session_id = ?')\n params.push(filter.sessionId)\n }\n if (filter.since) {\n conditions.push('timestamp >= ?')\n params.push(filter.since.toISOString())\n }\n\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n const limit = filter.limit ?? 100\n const offset = filter.offset ?? 0\n\n const stmt = this.db.prepare(`\n SELECT * FROM user_prompts\n ${whereClause}\n ORDER BY timestamp DESC\n LIMIT ? OFFSET ?\n `)\n\n params.push(limit, offset)\n const rows = stmt.all(...params) as Array<{\n id: string\n session_id: string | null\n timestamp: string\n prompt_text: string\n prompt_length: number\n word_count: number\n cwd: string | null\n }>\n\n return rows.map(row => ({\n id: row.id,\n sessionId: row.session_id,\n timestamp: new Date(row.timestamp),\n promptText: row.prompt_text,\n promptLength: row.prompt_length,\n wordCount: row.word_count,\n cwd: row.cwd\n }))\n }\n\n getUserPromptStats(): UserPromptStats {\n const totalRow = this.db.prepare('SELECT COUNT(*) as count FROM user_prompts').get() as { count: number }\n\n const sumRow = this.db.prepare(`\n SELECT\n COALESCE(SUM(word_count), 0) as total_words,\n COALESCE(SUM(prompt_length), 0) as total_chars,\n COALESCE(AVG(prompt_length), 0) as avg_length,\n COALESCE(AVG(word_count), 0) as avg_words,\n COALESCE(MAX(prompt_length), 0) as longest\n FROM user_prompts\n `).get() as { total_words: number; total_chars: number; avg_length: number; avg_words: number; longest: number }\n\n const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()\n const last24hRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM user_prompts WHERE timestamp >= ?\n `).get(oneDayAgo) as { count: number }\n\n // Prompts per session (average across sessions that have prompts)\n const perSessionRow = this.db.prepare(`\n SELECT COALESCE(AVG(cnt), 0) as avg_per_session FROM (\n SELECT COUNT(*) as cnt FROM user_prompts WHERE session_id IS NOT NULL GROUP BY session_id\n )\n `).get() as { avg_per_session: number }\n\n return {\n totalPrompts: totalRow.count,\n totalWords: sumRow.total_words,\n totalChars: sumRow.total_chars,\n avgPromptLength: Math.round(sumRow.avg_length),\n avgWordCount: Math.round(sumRow.avg_words),\n longestPrompt: sumRow.longest,\n last24h: last24hRow.count,\n promptsPerSession: Math.round(perSessionRow.avg_per_session)\n }\n }\n\n // ─────────────────────────────────────────────────────────────\n // Session Metrics\n // ─────────────────────────────────────────────────────────────\n\n getSessionMetrics(sessionId: string): {\n totalCommands: number\n allowedCommands: number\n blockedCommands: number\n avgRiskScore: number\n riskDistribution: Record<string, number>\n topCommands: Array<{ command: string; count: number }>\n } {\n // Total commands\n const totalRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM commands WHERE session_id = ?\n `).get(sessionId) as { count: number }\n\n // Allowed/blocked counts\n const allowedRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM commands WHERE session_id = ? AND allowed = 1\n `).get(sessionId) as { count: number }\n\n const blockedRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM commands WHERE session_id = ? AND allowed = 0\n `).get(sessionId) as { count: number }\n\n // Average risk score\n const avgRow = this.db.prepare(`\n SELECT AVG(risk_score) as avg FROM commands WHERE session_id = ?\n `).get(sessionId) as { avg: number | null }\n\n // Risk distribution\n const riskRows = this.db.prepare(`\n SELECT risk_level, COUNT(*) as count FROM commands WHERE session_id = ? GROUP BY risk_level\n `).all(sessionId) as Array<{ risk_level: string; count: number }>\n\n const riskDistribution: Record<string, number> = {}\n for (const row of riskRows) {\n riskDistribution[row.risk_level] = row.count\n }\n\n // Top commands (by base command, first word)\n const cmdRows = this.db.prepare(`\n SELECT command, COUNT(*) as count FROM commands WHERE session_id = ?\n GROUP BY command ORDER BY count DESC LIMIT 10\n `).all(sessionId) as Array<{ command: string; count: number }>\n\n return {\n totalCommands: totalRow.count,\n allowedCommands: allowedRow.count,\n blockedCommands: blockedRow.count,\n avgRiskScore: avgRow.avg ?? 0,\n riskDistribution,\n topCommands: cmdRows\n }\n }\n\n // ─────────────────────────────────────────────────────────────\n // Security Summary\n // ─────────────────────────────────────────────────────────────\n\n getSecuritySummary(): {\n totalCommands24h: number\n blockedCount24h: number\n avgRiskScore24h: number\n riskDistribution: Record<string, number>\n violationTypes: Array<{ type: string; count: number }>\n highRiskCount24h: number\n } {\n const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()\n\n // Total commands in last 24h\n const totalRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM commands WHERE timestamp >= ?\n `).get(oneDayAgo) as { count: number }\n\n // Blocked count in last 24h\n const blockedRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM commands WHERE timestamp >= ? AND allowed = 0\n `).get(oneDayAgo) as { count: number }\n\n // Average risk score in last 24h\n const avgRow = this.db.prepare(`\n SELECT AVG(risk_score) as avg FROM commands WHERE timestamp >= ?\n `).get(oneDayAgo) as { avg: number | null }\n\n // Risk distribution in last 24h\n const riskRows = this.db.prepare(`\n SELECT risk_level, COUNT(*) as count FROM commands WHERE timestamp >= ? GROUP BY risk_level\n `).all(oneDayAgo) as Array<{ risk_level: string; count: number }>\n\n const riskDistribution: Record<string, number> = {}\n for (const row of riskRows) {\n riskDistribution[row.risk_level] = row.count\n }\n\n // High risk count (dangerous + critical)\n const highRiskRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM commands WHERE timestamp >= ? AND risk_level IN ('dangerous', 'critical')\n `).get(oneDayAgo) as { count: number }\n\n // Violation types from blocked commands\n const violationRows = this.db.prepare(`\n SELECT violations FROM commands WHERE timestamp >= ? AND allowed = 0 AND violations != '[]'\n `).all(oneDayAgo) as Array<{ violations: string }>\n\n const typeCounts: Record<string, number> = {}\n for (const row of violationRows) {\n try {\n const violations = JSON.parse(row.violations) as string[]\n for (const v of violations) {\n const type = v.includes(':') ? v.split(':')[0] : v\n typeCounts[type] = (typeCounts[type] || 0) + 1\n }\n } catch {\n // skip malformed JSON\n }\n }\n\n const violationTypes = Object.entries(typeCounts)\n .map(([type, count]) => ({ type, count }))\n .sort((a, b) => b.count - a.count)\n\n return {\n totalCommands24h: totalRow.count,\n blockedCount24h: blockedRow.count,\n avgRiskScore24h: avgRow.avg ?? 0,\n riskDistribution,\n violationTypes,\n highRiskCount24h: highRiskRow.count\n }\n }\n\n getBlockedCommandsRecent(limit: number = 25): CommandRecord[] {\n const stmt = this.db.prepare(`\n SELECT * FROM commands WHERE allowed = 0 ORDER BY timestamp DESC LIMIT ?\n `)\n\n const rows = stmt.all(limit) as Array<{\n id: string\n session_id: string\n timestamp: string\n command: string\n allowed: number\n risk_score: number\n risk_level: 'safe' | 'caution' | 'dangerous' | 'critical'\n risk_factors: string\n duration_ms: number\n violations: string\n }>\n\n return rows.map(row => this.rowToCommand(row))\n }\n\n // ─────────────────────────────────────────────────────────────\n // Stats\n // ─────────────────────────────────────────────────────────────\n\n getStats(): DashboardStats {\n // Total events\n const totalEventsRow = this.db.prepare('SELECT COUNT(*) as count FROM events').get() as { count: number }\n\n // Events by source\n const sourceRows = this.db.prepare(`\n SELECT source, COUNT(*) as count FROM events GROUP BY source\n `).all() as Array<{ source: string; count: number }>\n\n const eventsBySource: Record<string, number> = {}\n for (const row of sourceRows) {\n eventsBySource[row.source] = row.count\n }\n\n // Events by level\n const levelRows = this.db.prepare(`\n SELECT level, COUNT(*) as count FROM events GROUP BY level\n `).all() as Array<{ level: string; count: number }>\n\n const eventsByLevel: Record<string, number> = {}\n for (const row of levelRows) {\n eventsByLevel[row.level] = row.count\n }\n\n // Pending blocks\n const pendingBlocksRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM egress_blocks WHERE status = 'pending'\n `).get() as { count: number }\n\n // Unique connectors\n const connectorCountRow = this.db.prepare(`\n SELECT COUNT(DISTINCT connector) as count FROM connector_events\n `).get() as { count: number }\n\n // Recent exposures (last 24 hours)\n const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()\n const recentExposuresRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM exposure_scans WHERE timestamp >= ?\n `).get(oneDayAgo) as { count: number }\n\n // Active sessions\n const activeSessionsRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM sessions WHERE status = 'active'\n `).get() as { count: number }\n\n // Today's commands\n const todayStart = new Date()\n todayStart.setHours(0, 0, 0, 0)\n const todayCommandsRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM commands WHERE timestamp >= ?\n `).get(todayStart.toISOString()) as { count: number }\n\n // Today's violations (blocked commands)\n const todayViolationsRow = this.db.prepare(`\n SELECT COUNT(*) as count FROM commands WHERE timestamp >= ? AND allowed = 0\n `).get(todayStart.toISOString()) as { count: number }\n\n // Average risk score in last 24 hours\n const avgRiskRow = this.db.prepare(`\n SELECT AVG(risk_score) as avg FROM commands WHERE timestamp >= ?\n `).get(oneDayAgo) as { avg: number | null }\n\n // Ollama status from latest bro_status\n const latestStatus = this.getLatestBroStatus()\n let ollamaStatus: 'connected' | 'disconnected' | 'unknown' = 'unknown'\n if (latestStatus) {\n ollamaStatus = latestStatus.ollamaAvailable ? 'connected' : 'disconnected'\n }\n\n return {\n totalEvents: totalEventsRow.count,\n eventsBySource,\n eventsByLevel,\n pendingBlocks: pendingBlocksRow.count,\n connectorCount: connectorCountRow.count,\n recentExposures: recentExposuresRow.count,\n activeSessions: activeSessionsRow.count,\n todayCommands: todayCommandsRow.count,\n todayViolations: todayViolationsRow.count,\n avgRiskScore24h: avgRiskRow.avg ?? 0,\n ollamaStatus\n }\n }\n\n // ─────────────────────────────────────────────────────────────\n // Cross-process Query Helpers (for db-checks)\n // ─────────────────────────────────────────────────────────────\n\n getRecentCommandTexts(windowSize: number): { command: string; timestamp: string }[] {\n const stmt = this.db.prepare(`\n SELECT command, timestamp FROM commands\n ORDER BY timestamp DESC\n LIMIT ?\n `)\n return stmt.all(windowSize) as { command: string; timestamp: string }[]\n }\n\n getCommandCountSince(sinceISO: string): number {\n const row = this.db.prepare(`\n SELECT COUNT(*) as count FROM commands WHERE timestamp >= ?\n `).get(sinceISO) as { count: number }\n return row.count\n }\n\n getTotalCommandCount(): number {\n const row = this.db.prepare('SELECT COUNT(*) as count FROM commands').get() as { count: number }\n return row.count\n }\n\n // ─────────────────────────────────────────────────────────────\n // Maintenance\n // ─────────────────────────────────────────────────────────────\n\n cleanup(olderThanDays: number = 30): number {\n const cutoff = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000).toISOString()\n\n const eventsDeleted = this.db.prepare('DELETE FROM events WHERE timestamp < ?').run(cutoff).changes\n const connectorDeleted = this.db.prepare('DELETE FROM connector_events WHERE timestamp < ?').run(cutoff).changes\n const blocksDeleted = this.db.prepare(`\n DELETE FROM egress_blocks WHERE timestamp < ? AND status != 'pending'\n `).run(cutoff).changes\n const exposuresDeleted = this.db.prepare('DELETE FROM exposure_scans WHERE timestamp < ?').run(cutoff).changes\n\n // Cleanup new tables - commands first (due to foreign key), then sessions\n const commandsDeleted = this.db.prepare('DELETE FROM commands WHERE timestamp < ?').run(cutoff).changes\n const sessionsDeleted = this.db.prepare(`\n DELETE FROM sessions WHERE start_time < ? AND status != 'active'\n `).run(cutoff).changes\n const broEventsDeleted = this.db.prepare('DELETE FROM bro_events WHERE timestamp < ?').run(cutoff).changes\n const broStatusDeleted = this.db.prepare('DELETE FROM bro_status WHERE timestamp < ?').run(cutoff).changes\n const toolUsesDeleted = this.db.prepare('DELETE FROM tool_uses WHERE timestamp < ?').run(cutoff).changes\n const promptsDeleted = this.db.prepare('DELETE FROM user_prompts WHERE timestamp < ?').run(cutoff).changes\n\n return eventsDeleted + connectorDeleted + blocksDeleted + exposuresDeleted +\n commandsDeleted + sessionsDeleted + broEventsDeleted + broStatusDeleted + toolUsesDeleted + promptsDeleted\n }\n\n /**\n * Migration: add session_id column to tool_uses table for session tracking\n */\n private migrateToolUsesAddSessionId(): void {\n try {\n const tableInfo = this.db.pragma('table_info(tool_uses)') as Array<{ name: string }>\n const hasSessionId = tableInfo.some(col => col.name === 'session_id')\n if (!hasSessionId) {\n this.db.exec('ALTER TABLE tool_uses ADD COLUMN session_id TEXT')\n }\n } catch {\n // Table doesn't exist yet or already migrated\n }\n }\n\n /**\n * Migration: add mode column to sessions table to distinguish watch vs hook sessions\n */\n private migrateSessionsAddMode(): void {\n try {\n const tableInfo = this.db.pragma('table_info(sessions)') as Array<{ name: string }>\n const hasMode = tableInfo.some(col => col.name === 'mode')\n if (!hasMode) {\n this.db.exec(\"ALTER TABLE sessions ADD COLUMN mode TEXT DEFAULT 'watch'\")\n }\n } catch {\n // Table doesn't exist yet or already migrated\n }\n }\n\n /**\n * Migration: add repo_name column to sessions table\n */\n private migrateSessionsAddRepoName(): void {\n try {\n const tableInfo = this.db.pragma('table_info(sessions)') as Array<{ name: string }>\n const hasRepoName = tableInfo.some(col => col.name === 'repo_name')\n if (!hasRepoName) {\n this.db.exec('ALTER TABLE sessions ADD COLUMN repo_name TEXT')\n }\n } catch {\n // Table doesn't exist yet or already migrated\n }\n }\n\n /**\n * Migration: add metadata column to sessions table\n */\n private migrateSessionsAddMetadata(): void {\n try {\n const tableInfo = this.db.pragma('table_info(sessions)') as Array<{ name: string }>\n const hasMetadata = tableInfo.some(col => col.name === 'metadata')\n if (!hasMetadata) {\n this.db.exec(\"ALTER TABLE sessions ADD COLUMN metadata TEXT DEFAULT '{}'\")\n }\n } catch {\n // Table doesn't exist yet or already migrated\n }\n }\n\n /**\n * Migration: allow NULL session_id in commands table for hook-mode recording\n * (each hook invocation is a separate process with no long-running session)\n */\n private migrateCommandsNullableSessionId(): void {\n try {\n const tableInfo = this.db.pragma('table_info(commands)') as Array<{ name: string; notnull: number }>\n const sessionCol = tableInfo.find(col => col.name === 'session_id')\n if (sessionCol && sessionCol.notnull === 1) {\n this.db.exec(`\n CREATE TABLE commands_mig (\n id TEXT PRIMARY KEY,\n session_id TEXT,\n timestamp TEXT NOT NULL,\n command TEXT NOT NULL,\n allowed INTEGER NOT NULL,\n risk_score INTEGER NOT NULL,\n risk_level TEXT NOT NULL,\n risk_factors TEXT NOT NULL,\n duration_ms INTEGER NOT NULL,\n violations TEXT NOT NULL,\n FOREIGN KEY (session_id) REFERENCES sessions(id)\n );\n INSERT INTO commands_mig SELECT * FROM commands;\n DROP TABLE commands;\n ALTER TABLE commands_mig RENAME TO commands;\n CREATE INDEX IF NOT EXISTS idx_commands_session_id ON commands(session_id);\n CREATE INDEX IF NOT EXISTS idx_commands_timestamp ON commands(timestamp);\n CREATE INDEX IF NOT EXISTS idx_commands_allowed ON commands(allowed);\n `)\n }\n } catch {\n // Table doesn't exist yet or already migrated — no action needed\n }\n }\n\n // ─────────────────────────────────────────────────────────────\n // Achievement System\n // ─────────────────────────────────────────────────────────────\n\n static readonly BADGE_DEFINITIONS: BadgeDefinition[] = [\n // Volume\n { id: 'first_blood', name: 'First Blood', description: 'Execute commands', category: 'volume', icon: '\\u2694', stat: 'totalCommands', tiers: [1, 100, 1000, 10000, 100000] },\n { id: 'marathon_runner', name: 'Marathon Runner', description: 'Complete sessions', category: 'volume', icon: '\\ud83c\\udfc3', stat: 'totalSessions', tiers: [1, 10, 50, 200, 1000] },\n { id: 'watchdog', name: 'Watchdog', description: 'Time under watch', category: 'volume', icon: '\\u23f1', stat: 'totalWatchTimeMinutes', tiers: [60, 1440, 10080, 43200, 525600] },\n // Security\n { id: 'shield_bearer', name: 'Shield Bearer', description: 'Threats blocked', category: 'security', icon: '\\ud83d\\udee1', stat: 'totalBlocked', tiers: [1, 25, 100, 500, 2000] },\n { id: 'clean_hands', name: 'Clean Hands', description: 'Consecutive clean commands', category: 'security', icon: '\\u2728', stat: 'cleanestStreak', tiers: [10, 50, 200, 1000, 5000] },\n { id: 'risk_taker', name: 'Risk Taker', description: 'High risk commands executed', category: 'security', icon: '\\ud83c\\udfb2', stat: 'highRiskCount', tiers: [1, 5, 10, 25, 50] },\n // Agents\n { id: 'buddy_system', name: 'Buddy System', description: 'Use 2+ agents', category: 'agents', icon: '\\ud83e\\udd1d', stat: 'uniqueAgents', tiers: [2, 2, 2, 2, 2] },\n { id: 'squad_up', name: 'Squad Up', description: 'Use multiple agents', category: 'agents', icon: '\\ud83d\\udc6b', stat: 'uniqueAgents', tiers: [2, 3, 4, 5, 5] },\n { id: 'loyal', name: 'Loyal', description: '1000 commands from one agent', category: 'agents', icon: '\\ud83d\\udc51', stat: 'maxAgentCommands', tiers: [100, 250, 500, 1000, 5000] },\n { id: 'polyglot', name: 'Polyglot', description: '100+ commands from multiple agents', category: 'agents', icon: '\\ud83c\\udf0d', stat: 'agentsWith100', tiers: [1, 2, 3, 4, 5] },\n // Behavioral\n { id: 'night_owl', name: 'Night Owl', description: 'Commands after midnight', category: 'behavioral', icon: '\\ud83e\\udd89', stat: 'lateNightCount', tiers: [10, 100, 500, 2000, 10000] },\n { id: 'speed_demon', name: 'Speed Demon', description: 'Commands in a single hour', category: 'behavioral', icon: '\\u26a1', stat: 'peakHourCount', tiers: [60, 100, 150, 200, 300] },\n { id: 'one_liner', name: 'One-Liner', description: 'Longest command (chars)', category: 'behavioral', icon: '\\ud83d\\udcdd', stat: 'longestCommandLength', tiers: [500, 1000, 2000, 5000, 10000] },\n { id: 'creature_of_habit', name: 'Creature of Habit', description: 'Same command repeated', category: 'behavioral', icon: '\\ud83d\\udd01', stat: 'mostUsedCommandCount', tiers: [50, 200, 500, 1000, 5000] },\n { id: 'explorer', name: 'Explorer', description: 'Unique commands used', category: 'behavioral', icon: '\\ud83e\\udded', stat: 'uniqueCommands', tiers: [25, 50, 100, 250, 500] },\n // Repo\n { id: 'home_base', name: 'Home Base', description: 'Protect a repo', category: 'repo', icon: '\\ud83c\\udfe0', stat: 'uniqueRepos', tiers: [1, 1, 1, 1, 1] },\n { id: 'empire', name: 'Empire', description: 'Protect multiple repos', category: 'repo', icon: '\\ud83c\\udff0', stat: 'uniqueRepos', tiers: [3, 5, 10, 25, 50] },\n // Prompt\n { id: 'conversationalist', name: 'Conversationalist', description: 'Submit prompts', category: 'prompt', icon: '\\ud83d\\udcac', stat: 'totalPrompts', tiers: [1, 50, 500, 5000, 50000] },\n { id: 'wordsmith', name: 'Wordsmith', description: 'Total words prompted', category: 'prompt', icon: '\\u270d', stat: 'totalPromptWords', tiers: [100, 1000, 10000, 100000, 1000000] },\n { id: 'novelist', name: 'Novelist', description: 'Longest single prompt', category: 'prompt', icon: '\\ud83d\\udcd6', stat: 'longestPromptLength', tiers: [500, 1000, 2000, 5000, 10000] },\n { id: 'chatty', name: 'Chatty', description: 'Prompts per session avg', category: 'prompt', icon: '\\ud83d\\udde3', stat: 'promptsPerSession', tiers: [5, 10, 25, 50, 100] },\n ]\n\n getAchievementStats(): AchievementStats {\n // Core totals\n const totalCommands = (this.db.prepare('SELECT COUNT(*) as c FROM commands').get() as any).c\n const totalBlocked = (this.db.prepare('SELECT COUNT(*) as c FROM commands WHERE allowed = 0').get() as any).c\n const totalSessions = (this.db.prepare('SELECT COUNT(*) as c FROM sessions').get() as any).c\n const totalCharacters = (this.db.prepare('SELECT COALESCE(SUM(LENGTH(command)), 0) as c FROM commands').get() as any).c\n\n // Watch time in minutes (sum of session durations)\n const watchTimeRow = this.db.prepare(`\n SELECT COALESCE(SUM(\n (julianday(COALESCE(end_time, datetime('now'))) - julianday(start_time)) * 1440\n ), 0) as minutes FROM sessions\n `).get() as any\n const totalWatchTimeMinutes = Math.round(watchTimeRow.minutes)\n\n // Unique repos\n const uniqueRepos = (this.db.prepare(`\n SELECT COUNT(DISTINCT repo_name) as c FROM sessions WHERE repo_name IS NOT NULL AND repo_name != ''\n `).get() as any).c\n\n // First command timestamp\n const firstCommandRow = this.db.prepare('SELECT MIN(timestamp) as t FROM commands').get() as any\n const memberSince = firstCommandRow.t || null\n\n // Agent breakdown\n const agentRows = this.db.prepare(`\n SELECT s.agent, COUNT(*) as count\n FROM commands c\n JOIN sessions s ON c.session_id = s.id\n GROUP BY s.agent\n ORDER BY count DESC\n `).all() as Array<{ agent: string; count: number }>\n const agentBreakdown: Record<string, number> = {}\n for (const row of agentRows) {\n agentBreakdown[row.agent] = row.count\n }\n const uniqueAgents = Object.keys(agentBreakdown).length\n const maxAgentCommands = agentRows.length > 0 ? agentRows[0].count : 0\n const favoriteAgent = agentRows.length > 0 ? agentRows[0].agent : null\n const agentsWith100 = agentRows.filter(r => r.count >= 100).length\n\n // Behavioral stats\n const mostUsedRow = this.db.prepare(`\n SELECT command, COUNT(*) as count FROM commands\n GROUP BY command ORDER BY count DESC LIMIT 1\n `).get() as { command: string; count: number } | undefined\n const mostUsedCommand = mostUsedRow?.command || null\n const mostUsedCommandCount = mostUsedRow?.count || 0\n\n const uniqueCommands = (this.db.prepare('SELECT COUNT(DISTINCT command) as c FROM commands').get() as any).c\n\n const longestCommandLength = (this.db.prepare('SELECT COALESCE(MAX(LENGTH(command)), 0) as c FROM commands').get() as any).c\n\n // Peak hour\n const peakHourRow = this.db.prepare(`\n SELECT CAST(strftime('%H', timestamp) AS INTEGER) as hour, COUNT(*) as count\n FROM commands GROUP BY hour ORDER BY count DESC LIMIT 1\n `).get() as { hour: number; count: number } | undefined\n const peakHour = peakHourRow?.hour ?? null\n const peakHourCount = peakHourRow?.count ?? 0\n\n // Peak day of week (0=Sunday)\n const peakDayRow = this.db.prepare(`\n SELECT CAST(strftime('%w', timestamp) AS INTEGER) as day, COUNT(*) as count\n FROM commands GROUP BY day ORDER BY count DESC LIMIT 1\n `).get() as { day: number; count: number } | undefined\n const peakDay = peakDayRow?.day ?? null\n\n // Busiest single day\n const busiestDayRow = this.db.prepare(`\n SELECT DATE(timestamp) as day, COUNT(*) as count\n FROM commands GROUP BY day ORDER BY count DESC LIMIT 1\n `).get() as { day: string; count: number } | undefined\n const busiestDay = busiestDayRow?.day || null\n const busiestDayCount = busiestDayRow?.count || 0\n\n // Avg commands per session\n const avgCommandsPerSession = totalSessions > 0 ? Math.round(totalCommands / totalSessions) : 0\n\n // Longest session (minutes)\n const longestSessionRow = this.db.prepare(`\n SELECT MAX(\n (julianday(COALESCE(end_time, datetime('now'))) - julianday(start_time)) * 1440\n ) as minutes FROM sessions\n `).get() as any\n const longestSessionMinutes = Math.round(longestSessionRow.minutes || 0)\n\n // Late night commands (midnight to 5 AM)\n const lateNightCount = (this.db.prepare(`\n SELECT COUNT(*) as c FROM commands\n WHERE CAST(strftime('%H', timestamp) AS INTEGER) < 5\n `).get() as any).c\n\n // Lifetime average risk\n const avgRiskRow = this.db.prepare('SELECT AVG(risk_score) as avg FROM commands').get() as any\n const lifetimeAvgRisk = avgRiskRow.avg ?? 0\n\n // Cleanest streak (longest run of allowed commands)\n const allAllowed = this.db.prepare(\n 'SELECT allowed FROM commands ORDER BY timestamp ASC'\n ).all() as Array<{ allowed: number }>\n let cleanestStreak = 0\n let currentStreak = 0\n for (const row of allAllowed) {\n if (row.allowed === 1) {\n currentStreak++\n if (currentStreak > cleanestStreak) cleanestStreak = currentStreak\n } else {\n currentStreak = 0\n }\n }\n\n // Highest risk command\n const highestRiskRow = this.db.prepare(\n 'SELECT command, risk_score FROM commands ORDER BY risk_score DESC LIMIT 1'\n ).get() as { command: string; risk_score: number } | undefined\n const highestRiskCommand = highestRiskRow?.command || null\n const highestRiskScore = highestRiskRow?.risk_score || 0\n\n // High risk count (risk_score >= 8)\n const highRiskCount = (this.db.prepare(\n 'SELECT COUNT(*) as c FROM commands WHERE risk_score >= 8'\n ).get() as any).c\n\n // Prompt stats (backward compat: try/catch for DBs without user_prompts table)\n let totalPrompts = 0\n let totalPromptWords = 0\n let totalPromptChars = 0\n let longestPromptLength = 0\n let promptsPerSession = 0\n try {\n totalPrompts = (this.db.prepare('SELECT COUNT(*) as c FROM user_prompts').get() as any).c\n const promptSums = this.db.prepare(`\n SELECT\n COALESCE(SUM(word_count), 0) as words,\n COALESCE(SUM(prompt_length), 0) as chars,\n COALESCE(MAX(prompt_length), 0) as longest\n FROM user_prompts\n `).get() as any\n totalPromptWords = promptSums.words\n totalPromptChars = promptSums.chars\n longestPromptLength = promptSums.longest\n const perSessionRow = this.db.prepare(`\n SELECT COALESCE(AVG(cnt), 0) as avg FROM (\n SELECT COUNT(*) as cnt FROM user_prompts WHERE session_id IS NOT NULL GROUP BY session_id\n )\n `).get() as any\n promptsPerSession = Math.round(perSessionRow.avg)\n } catch {\n // Table may not exist in older DBs\n }\n\n return {\n totalCommands, totalBlocked, totalSessions, totalCharacters,\n totalWatchTimeMinutes, uniqueRepos, memberSince,\n agentBreakdown, uniqueAgents, maxAgentCommands, favoriteAgent, agentsWith100,\n mostUsedCommand, mostUsedCommandCount, uniqueCommands, longestCommandLength,\n peakHour, peakHourCount, peakDay, busiestDay, busiestDayCount,\n avgCommandsPerSession, longestSessionMinutes, lateNightCount,\n lifetimeAvgRisk, cleanestStreak, currentCleanStreak: currentStreak,\n highestRiskCommand, highestRiskScore, highRiskCount,\n totalPrompts, totalPromptWords, totalPromptChars, longestPromptLength, promptsPerSession,\n }\n }\n\n computeAchievements(stats: AchievementStats): BadgeResult[] {\n return DashboardDB.BADGE_DEFINITIONS.map(badge => {\n const value = (stats as any)[badge.stat] as number ?? 0\n let currentTier = 0\n for (let i = 0; i < badge.tiers.length; i++) {\n if (value >= badge.tiers[i]) currentTier = i + 1\n }\n const nextThreshold = currentTier < badge.tiers.length ? badge.tiers[currentTier] : badge.tiers[badge.tiers.length - 1]\n const prevThreshold = currentTier > 0 ? badge.tiers[currentTier - 1] : 0\n const progress = currentTier >= badge.tiers.length\n ? 1\n : (value - prevThreshold) / Math.max(1, nextThreshold - prevThreshold)\n\n return {\n id: badge.id,\n name: badge.name,\n description: badge.description,\n category: badge.category,\n icon: badge.icon,\n tier: currentTier as BadgeTier,\n tierName: TIER_NAMES[currentTier as BadgeTier],\n value,\n nextThreshold,\n progress: Math.min(1, Math.max(0, progress)),\n maxed: currentTier >= 5,\n }\n })\n }\n\n computeXP(stats: AchievementStats, badges: BadgeResult[]): XPResult {\n const TIER_XP = [0, 50, 100, 200, 500, 1000]\n const RANK_THRESHOLDS: Array<{ rank: string; xp: number }> = [\n { rank: 'Obsidian', xp: 100000 },\n { rank: 'Diamond', xp: 25000 },\n { rank: 'Gold', xp: 5000 },\n { rank: 'Silver', xp: 1000 },\n { rank: 'Bronze', xp: 0 },\n ]\n\n let totalXP = 0\n\n // +1 per command\n totalXP += stats.totalCommands\n // +3 per block\n totalXP += stats.totalBlocked * 3\n // +10 per session\n totalXP += stats.totalSessions * 10\n // +2 per late night command\n totalXP += stats.lateNightCount * 2\n // +25 per 100-clean-streak segments\n totalXP += Math.floor(stats.cleanestStreak / 100) * 25\n // +1 per 2 prompts\n totalXP += Math.floor(stats.totalPrompts / 2)\n // +1 per 500 prompt words\n totalXP += Math.floor(stats.totalPromptWords / 500)\n\n // Badge tier XP\n for (const badge of badges) {\n if (badge.tier > 0) {\n totalXP += TIER_XP[badge.tier]\n }\n }\n\n // Determine rank\n let rank = 'Bronze'\n let nextRankXP = 1000\n for (const t of RANK_THRESHOLDS) {\n if (totalXP >= t.xp) {\n rank = t.rank\n // next rank is the one above, if any\n const idx = RANK_THRESHOLDS.indexOf(t)\n nextRankXP = idx > 0 ? RANK_THRESHOLDS[idx - 1].xp : totalXP\n break\n }\n }\n\n const currentRankXP = RANK_THRESHOLDS.find(t => t.rank === rank)!.xp\n const progress = rank === 'Obsidian'\n ? 1\n : (totalXP - currentRankXP) / Math.max(1, nextRankXP - currentRankXP)\n\n return {\n totalXP,\n rank,\n nextRankXP,\n progress: Math.min(1, Math.max(0, progress)),\n }\n }\n\n close(): void {\n this.db.close()\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Achievement Types\n// ─────────────────────────────────────────────────────────────\n\nexport type BadgeTier = 0 | 1 | 2 | 3 | 4 | 5\n\nconst TIER_NAMES: Record<BadgeTier, string> = {\n 0: 'Locked',\n 1: 'Bronze',\n 2: 'Silver',\n 3: 'Gold',\n 4: 'Diamond',\n 5: 'Obsidian',\n}\n\nexport interface BadgeDefinition {\n id: string\n name: string\n description: string\n category: string\n icon: string\n stat: string\n tiers: [number, number, number, number, number]\n}\n\nexport interface BadgeResult {\n id: string\n name: string\n description: string\n category: string\n icon: string\n tier: BadgeTier\n tierName: string\n value: number\n nextThreshold: number\n progress: number\n maxed: boolean\n}\n\nexport interface AchievementStats {\n totalCommands: number\n totalBlocked: number\n totalSessions: number\n totalCharacters: number\n totalWatchTimeMinutes: number\n uniqueRepos: number\n memberSince: string | null\n agentBreakdown: Record<string, number>\n uniqueAgents: number\n maxAgentCommands: number\n favoriteAgent: string | null\n agentsWith100: number\n mostUsedCommand: string | null\n mostUsedCommandCount: number\n uniqueCommands: number\n longestCommandLength: number\n peakHour: number | null\n peakHourCount: number\n peakDay: number | null\n busiestDay: string | null\n busiestDayCount: number\n avgCommandsPerSession: number\n longestSessionMinutes: number\n lateNightCount: number\n lifetimeAvgRisk: number\n cleanestStreak: number\n currentCleanStreak: number\n highestRiskCommand: string | null\n highestRiskScore: number\n highRiskCount: number\n // Prompt stats\n totalPrompts: number\n totalPromptWords: number\n totalPromptChars: number\n longestPromptLength: number\n promptsPerSession: number\n}\n\nexport interface XPResult {\n totalXP: number\n rank: string\n nextRankXP: number\n progress: number\n}\n\nexport default DashboardDB\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DashboardDB
4
- } from "./chunk-RDNSS3ME.js";
4
+ } from "./chunk-6SLR5WPD.js";
5
5
 
6
6
  // src/dashboard/writer.ts
7
7
  import { homedir } from "os";
@@ -55,6 +55,13 @@ var DashboardWriter = class {
55
55
  this.sessionId = hookSessionId;
56
56
  this.hookMode = true;
57
57
  }
58
+ /**
59
+ * Update session metadata (e.g., git branch, config profile)
60
+ */
61
+ updateSessionMetadata(metadata) {
62
+ if (!this.sessionId) return;
63
+ this.db.updateSessionMetadata(this.sessionId, metadata);
64
+ }
58
65
  /**
59
66
  * End a hook-mode session by ID
60
67
  */
@@ -173,6 +180,17 @@ var DashboardWriter = class {
173
180
  };
174
181
  return this.db.insertToolUse(dbInput);
175
182
  }
183
+ /**
184
+ * Record a user prompt submission
185
+ */
186
+ recordUserPrompt(input) {
187
+ const dbInput = {
188
+ sessionId: this.sessionId ?? void 0,
189
+ promptText: input.promptText,
190
+ cwd: input.cwd
191
+ };
192
+ return this.db.insertUserPrompt(dbInput);
193
+ }
176
194
  /**
177
195
  * Get current session ID
178
196
  */
@@ -221,4 +239,4 @@ export {
221
239
  DashboardWriter,
222
240
  writer_default
223
241
  };
224
- //# sourceMappingURL=chunk-KYDMPE4N.js.map
242
+ //# sourceMappingURL=chunk-AZVT6AZY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dashboard/writer.ts"],"sourcesContent":["/**\n * Dashboard Writer Module\n * Bridge for watch mode to write monitoring data to the dashboard database\n */\n\nimport { homedir } from 'os'\nimport { join } from 'path'\nimport { mkdirSync, existsSync } from 'fs'\nimport { DashboardDB, type InsertCommandInput, type InsertBroEventInput, type InsertBroStatusInput, type InsertToolUseInput, type InsertUserPromptInput } from './db.js'\nimport type { RiskScore } from '../policy/risk-scorer.js'\nimport type { PolicyViolation } from '../types.js'\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface BroEventInput {\n eventType: string\n inputContext: string\n outputSummary: string\n modelUsed: string\n latencyMs: number\n success: boolean\n}\n\nexport interface BroStatusInput {\n ollamaAvailable: boolean\n ollamaModel: string\n platform: string\n shell: string\n projectType?: string\n}\n\nexport interface ToolUseInput {\n toolName: string\n toolInput: string\n toolOutput: string\n exitCode?: number | null\n success?: boolean | null\n cwd: string\n repoName?: string | null\n repoPath?: string | null\n}\n\n// ─────────────────────────────────────────────────────────────\n// Default Database Path\n// ─────────────────────────────────────────────────────────────\n\nfunction getDefaultDbPath(): string {\n const bashbrosDir = join(homedir(), '.bashbros')\n\n // Ensure directory exists\n if (!existsSync(bashbrosDir)) {\n mkdirSync(bashbrosDir, { recursive: true })\n }\n\n return join(bashbrosDir, 'dashboard.db')\n}\n\n// ─────────────────────────────────────────────────────────────\n// Dashboard Writer Class\n// ─────────────────────────────────────────────────────────────\n\nexport class DashboardWriter {\n private db: DashboardDB\n private sessionId: string | null = null\n private commandCount: number = 0\n private blockedCount: number = 0\n private totalRiskScore: number = 0\n private hookMode: boolean = false\n\n constructor(dbPath?: string) {\n const path = dbPath ?? getDefaultDbPath()\n this.db = new DashboardDB(path)\n }\n\n /**\n * Start a new watch session\n */\n startSession(agent: string, workingDir: string): string {\n this.sessionId = this.db.insertSession({\n agent,\n pid: process.pid,\n workingDir\n })\n\n this.commandCount = 0\n this.blockedCount = 0\n this.totalRiskScore = 0\n\n return this.sessionId\n }\n\n /**\n * Ensure a hook-mode session exists for the given external session ID.\n * Idempotent - safe to call multiple times per hook invocation.\n * Uses atomic DB increments so concurrent hook processes don't clobber each other.\n */\n ensureHookSession(hookSessionId: string, workingDir: string, repoName?: string | null): void {\n // INSERT OR IGNORE handles the race where two processes try to create simultaneously\n this.db.insertSessionWithId(hookSessionId, {\n agent: 'claude-code',\n pid: process.pid,\n workingDir,\n repoName: repoName ?? null,\n mode: 'hook'\n })\n this.sessionId = hookSessionId\n this.hookMode = true\n }\n\n /**\n * Update session metadata (e.g., git branch, config profile)\n */\n updateSessionMetadata(metadata: Record<string, unknown>): void {\n if (!this.sessionId) return\n this.db.updateSessionMetadata(this.sessionId, metadata)\n }\n\n /**\n * End a hook-mode session by ID\n */\n endHookSession(hookSessionId: string): void {\n const session = this.db.getSession(hookSessionId)\n if (session && session.status === 'active') {\n this.db.updateSession(hookSessionId, {\n endTime: new Date(),\n status: 'completed'\n })\n }\n }\n\n /**\n * End the current session\n */\n endSession(): void {\n if (!this.sessionId) return\n\n const avgRiskScore = this.commandCount > 0\n ? this.totalRiskScore / this.commandCount\n : 0\n\n this.db.updateSession(this.sessionId, {\n endTime: new Date(),\n status: 'completed',\n commandCount: this.commandCount,\n blockedCount: this.blockedCount,\n avgRiskScore\n })\n\n this.sessionId = null\n }\n\n /**\n * Mark session as crashed (for unexpected exits)\n */\n crashSession(): void {\n if (!this.sessionId) return\n\n const avgRiskScore = this.commandCount > 0\n ? this.totalRiskScore / this.commandCount\n : 0\n\n this.db.updateSession(this.sessionId, {\n endTime: new Date(),\n status: 'crashed',\n commandCount: this.commandCount,\n blockedCount: this.blockedCount,\n avgRiskScore\n })\n\n this.sessionId = null\n }\n\n /**\n * Record a command execution\n */\n recordCommand(\n command: string,\n allowed: boolean,\n riskScore: RiskScore,\n violations: PolicyViolation[],\n durationMs: number\n ): string | null {\n const input: InsertCommandInput = {\n sessionId: this.sessionId ?? undefined,\n command,\n allowed,\n riskScore: riskScore.score,\n riskLevel: riskScore.level,\n riskFactors: riskScore.factors,\n durationMs,\n violations: violations.map(v => v.message)\n }\n\n const id = this.db.insertCommand(input)\n\n // Update session stats if we have an active session\n if (this.sessionId) {\n if (this.hookMode) {\n // Hook mode: atomic SQL increment, race-safe across concurrent processes\n this.db.incrementSessionCommand(this.sessionId, !allowed, riskScore.score)\n } else {\n // Watch mode: batch in memory, flush periodically or on close\n this.commandCount++\n this.totalRiskScore += riskScore.score\n if (!allowed) {\n this.blockedCount++\n }\n\n if (this.commandCount % 10 === 0) {\n this.flushSessionStats()\n }\n }\n }\n\n return id\n }\n\n /**\n * Record a Bash Bro AI event\n */\n recordBroEvent(input: BroEventInput): string {\n const dbInput: InsertBroEventInput = {\n sessionId: this.sessionId ?? undefined,\n eventType: input.eventType,\n inputContext: input.inputContext,\n outputSummary: input.outputSummary,\n modelUsed: input.modelUsed,\n latencyMs: input.latencyMs,\n success: input.success\n }\n\n return this.db.insertBroEvent(dbInput)\n }\n\n /**\n * Update Bash Bro status\n */\n updateBroStatus(status: BroStatusInput): string {\n const dbInput: InsertBroStatusInput = {\n ollamaAvailable: status.ollamaAvailable,\n ollamaModel: status.ollamaModel,\n platform: status.platform,\n shell: status.shell,\n projectType: status.projectType\n }\n\n return this.db.updateBroStatus(dbInput)\n }\n\n /**\n * Record a generic tool use (for all Claude Code tools)\n */\n recordToolUse(input: ToolUseInput): string {\n const dbInput: InsertToolUseInput = {\n toolName: input.toolName,\n toolInput: input.toolInput,\n toolOutput: input.toolOutput,\n exitCode: input.exitCode,\n success: input.success,\n cwd: input.cwd,\n repoName: input.repoName,\n repoPath: input.repoPath,\n sessionId: this.sessionId ?? undefined\n }\n\n return this.db.insertToolUse(dbInput)\n }\n\n /**\n * Record a user prompt submission\n */\n recordUserPrompt(input: { promptText: string; cwd?: string }): string {\n const dbInput: InsertUserPromptInput = {\n sessionId: this.sessionId ?? undefined,\n promptText: input.promptText,\n cwd: input.cwd\n }\n\n return this.db.insertUserPrompt(dbInput)\n }\n\n /**\n * Get current session ID\n */\n getSessionId(): string | null {\n return this.sessionId\n }\n\n /**\n * Get current session stats\n */\n getSessionStats(): {\n commandCount: number\n blockedCount: number\n avgRiskScore: number\n } {\n return {\n commandCount: this.commandCount,\n blockedCount: this.blockedCount,\n avgRiskScore: this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0\n }\n }\n\n /**\n * Flush in-memory session stats to DB (watch mode only).\n */\n private flushSessionStats(): void {\n if (!this.sessionId || this.hookMode || this.commandCount === 0) return\n const avgRiskScore = this.totalRiskScore / this.commandCount\n this.db.updateSession(this.sessionId, {\n commandCount: this.commandCount,\n blockedCount: this.blockedCount,\n avgRiskScore\n })\n }\n\n /**\n * Close database connection\n */\n close(): void {\n this.flushSessionStats()\n this.db.close()\n }\n\n /**\n * Get the underlying database instance (for advanced use)\n */\n getDB(): DashboardDB {\n return this.db\n }\n}\n\nexport default DashboardWriter\n"],"mappings":";;;;;;AAKA,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,WAAW,kBAAkB;AAyCtC,SAAS,mBAA2B;AAClC,QAAM,cAAc,KAAK,QAAQ,GAAG,WAAW;AAG/C,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,cAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAAA,EAC5C;AAEA,SAAO,KAAK,aAAa,cAAc;AACzC;AAMO,IAAM,kBAAN,MAAsB;AAAA,EACnB;AAAA,EACA,YAA2B;AAAA,EAC3B,eAAuB;AAAA,EACvB,eAAuB;AAAA,EACvB,iBAAyB;AAAA,EACzB,WAAoB;AAAA,EAE5B,YAAY,QAAiB;AAC3B,UAAM,OAAO,UAAU,iBAAiB;AACxC,SAAK,KAAK,IAAI,YAAY,IAAI;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,OAAe,YAA4B;AACtD,SAAK,YAAY,KAAK,GAAG,cAAc;AAAA,MACrC;AAAA,MACA,KAAK,QAAQ;AAAA,MACb;AAAA,IACF,CAAC;AAED,SAAK,eAAe;AACpB,SAAK,eAAe;AACpB,SAAK,iBAAiB;AAEtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,eAAuB,YAAoB,UAAgC;AAE3F,SAAK,GAAG,oBAAoB,eAAe;AAAA,MACzC,OAAO;AAAA,MACP,KAAK,QAAQ;AAAA,MACb;AAAA,MACA,UAAU,YAAY;AAAA,MACtB,MAAM;AAAA,IACR,CAAC;AACD,SAAK,YAAY;AACjB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAsB,UAAyC;AAC7D,QAAI,CAAC,KAAK,UAAW;AACrB,SAAK,GAAG,sBAAsB,KAAK,WAAW,QAAQ;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,eAA6B;AAC1C,UAAM,UAAU,KAAK,GAAG,WAAW,aAAa;AAChD,QAAI,WAAW,QAAQ,WAAW,UAAU;AAC1C,WAAK,GAAG,cAAc,eAAe;AAAA,QACnC,SAAS,oBAAI,KAAK;AAAA,QAClB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,QAAI,CAAC,KAAK,UAAW;AAErB,UAAM,eAAe,KAAK,eAAe,IACrC,KAAK,iBAAiB,KAAK,eAC3B;AAEJ,SAAK,GAAG,cAAc,KAAK,WAAW;AAAA,MACpC,SAAS,oBAAI,KAAK;AAAA,MAClB,QAAQ;AAAA,MACR,cAAc,KAAK;AAAA,MACnB,cAAc,KAAK;AAAA,MACnB;AAAA,IACF,CAAC;AAED,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,QAAI,CAAC,KAAK,UAAW;AAErB,UAAM,eAAe,KAAK,eAAe,IACrC,KAAK,iBAAiB,KAAK,eAC3B;AAEJ,SAAK,GAAG,cAAc,KAAK,WAAW;AAAA,MACpC,SAAS,oBAAI,KAAK;AAAA,MAClB,QAAQ;AAAA,MACR,cAAc,KAAK;AAAA,MACnB,cAAc,KAAK;AAAA,MACnB;AAAA,IACF,CAAC;AAED,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,cACE,SACA,SACA,WACA,YACA,YACe;AACf,UAAM,QAA4B;AAAA,MAChC,WAAW,KAAK,aAAa;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,WAAW,UAAU;AAAA,MACrB,WAAW,UAAU;AAAA,MACrB,aAAa,UAAU;AAAA,MACvB;AAAA,MACA,YAAY,WAAW,IAAI,OAAK,EAAE,OAAO;AAAA,IAC3C;AAEA,UAAM,KAAK,KAAK,GAAG,cAAc,KAAK;AAGtC,QAAI,KAAK,WAAW;AAClB,UAAI,KAAK,UAAU;AAEjB,aAAK,GAAG,wBAAwB,KAAK,WAAW,CAAC,SAAS,UAAU,KAAK;AAAA,MAC3E,OAAO;AAEL,aAAK;AACL,aAAK,kBAAkB,UAAU;AACjC,YAAI,CAAC,SAAS;AACZ,eAAK;AAAA,QACP;AAEA,YAAI,KAAK,eAAe,OAAO,GAAG;AAChC,eAAK,kBAAkB;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,OAA8B;AAC3C,UAAM,UAA+B;AAAA,MACnC,WAAW,KAAK,aAAa;AAAA,MAC7B,WAAW,MAAM;AAAA,MACjB,cAAc,MAAM;AAAA,MACpB,eAAe,MAAM;AAAA,MACrB,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,IACjB;AAEA,WAAO,KAAK,GAAG,eAAe,OAAO;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,QAAgC;AAC9C,UAAM,UAAgC;AAAA,MACpC,iBAAiB,OAAO;AAAA,MACxB,aAAa,OAAO;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,IACtB;AAEA,WAAO,KAAK,GAAG,gBAAgB,OAAO;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,OAA6B;AACzC,UAAM,UAA8B;AAAA,MAClC,UAAU,MAAM;AAAA,MAChB,WAAW,MAAM;AAAA,MACjB,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM;AAAA,MACf,KAAK,MAAM;AAAA,MACX,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,WAAW,KAAK,aAAa;AAAA,IAC/B;AAEA,WAAO,KAAK,GAAG,cAAc,OAAO;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,OAAqD;AACpE,UAAM,UAAiC;AAAA,MACrC,WAAW,KAAK,aAAa;AAAA,MAC7B,YAAY,MAAM;AAAA,MAClB,KAAK,MAAM;AAAA,IACb;AAEA,WAAO,KAAK,GAAG,iBAAiB,OAAO;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,eAA8B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,kBAIE;AACA,WAAO;AAAA,MACL,cAAc,KAAK;AAAA,MACnB,cAAc,KAAK;AAAA,MACnB,cAAc,KAAK,eAAe,IAAI,KAAK,iBAAiB,KAAK,eAAe;AAAA,IAClF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAA0B;AAChC,QAAI,CAAC,KAAK,aAAa,KAAK,YAAY,KAAK,iBAAiB,EAAG;AACjE,UAAM,eAAe,KAAK,iBAAiB,KAAK;AAChD,SAAK,GAAG,cAAc,KAAK,WAAW;AAAA,MACpC,cAAc,KAAK;AAAA,MACnB,cAAc,KAAK;AAAA,MACnB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,kBAAkB;AACvB,SAAK,GAAG,MAAM;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AACF;AAEA,IAAO,iBAAQ;","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadConfig
4
- } from "./chunk-RTZ4QWG2.js";
4
+ } from "./chunk-2CI2MRKI.js";
5
5
 
6
6
  // src/transparency/display.ts
7
7
  import chalk from "chalk";
@@ -211,4 +211,4 @@ export {
211
211
  formatPermissionsTable,
212
212
  formatAgentSummary
213
213
  };
214
- //# sourceMappingURL=chunk-CG6VEHJM.js.map
214
+ //# sourceMappingURL=chunk-C4GZNBFF.js.map