bashbros 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +727 -265
  2. package/dist/adapters-JAZGGNVP.js +9 -0
  3. package/dist/chunk-4XZ64P4V.js +47 -0
  4. package/dist/chunk-4XZ64P4V.js.map +1 -0
  5. package/dist/{chunk-2RPTM6EQ.js → chunk-7OEWYFN3.js} +745 -629
  6. package/dist/chunk-7OEWYFN3.js.map +1 -0
  7. package/dist/{chunk-WPJJZLT6.js → chunk-CG6VEHJM.js} +3 -2
  8. package/dist/chunk-CG6VEHJM.js.map +1 -0
  9. package/dist/{chunk-DLP2O6PN.js → chunk-EMLEJVJZ.js} +102 -1
  10. package/dist/chunk-EMLEJVJZ.js.map +1 -0
  11. package/dist/chunk-IUUBCPMV.js +166 -0
  12. package/dist/chunk-IUUBCPMV.js.map +1 -0
  13. package/dist/chunk-J6ONXY6N.js +146 -0
  14. package/dist/chunk-J6ONXY6N.js.map +1 -0
  15. package/dist/{chunk-EYO44OMN.js → chunk-KYDMPE4N.js} +60 -17
  16. package/dist/chunk-KYDMPE4N.js.map +1 -0
  17. package/dist/chunk-LJE4EPIU.js +56 -0
  18. package/dist/chunk-LJE4EPIU.js.map +1 -0
  19. package/dist/chunk-LZYW7XQO.js +339 -0
  20. package/dist/chunk-LZYW7XQO.js.map +1 -0
  21. package/dist/{chunk-JYWQT2B4.js → chunk-RDNSS3ME.js} +489 -14
  22. package/dist/chunk-RDNSS3ME.js.map +1 -0
  23. package/dist/{chunk-A535VV7N.js → chunk-RTZ4QWG2.js} +5 -4
  24. package/dist/chunk-RTZ4QWG2.js.map +1 -0
  25. package/dist/chunk-SDN6TAGD.js +157 -0
  26. package/dist/chunk-SDN6TAGD.js.map +1 -0
  27. package/dist/chunk-T5ONCUHZ.js +198 -0
  28. package/dist/chunk-T5ONCUHZ.js.map +1 -0
  29. package/dist/cli.js +1069 -88
  30. package/dist/cli.js.map +1 -1
  31. package/dist/{config-43SK6SFI.js → config-I5NCK3RJ.js} +2 -2
  32. package/dist/copilot-cli-5WJWK5YT.js +9 -0
  33. package/dist/{db-SWJUUSFX.js → db-ETWTBXAE.js} +2 -2
  34. package/dist/db-checks-2YOVECD4.js +133 -0
  35. package/dist/db-checks-2YOVECD4.js.map +1 -0
  36. package/dist/{display-HFIFXOOL.js → display-UH7KEHOW.js} +3 -3
  37. package/dist/gemini-cli-3563EELZ.js +9 -0
  38. package/dist/gemini-cli-3563EELZ.js.map +1 -0
  39. package/dist/index.d.ts +176 -72
  40. package/dist/index.js +119 -398
  41. package/dist/index.js.map +1 -1
  42. package/dist/{ollama-HY35OHW4.js → ollama-5JVKNFOV.js} +2 -2
  43. package/dist/ollama-5JVKNFOV.js.map +1 -0
  44. package/dist/opencode-DRCY275R.js +9 -0
  45. package/dist/opencode-DRCY275R.js.map +1 -0
  46. package/dist/profiles-7CLN6TAT.js +9 -0
  47. package/dist/profiles-7CLN6TAT.js.map +1 -0
  48. package/dist/setup-YS27MOPE.js +124 -0
  49. package/dist/setup-YS27MOPE.js.map +1 -0
  50. package/dist/static/index.html +4815 -2007
  51. package/dist/store-WJ5Y7MOE.js +9 -0
  52. package/dist/store-WJ5Y7MOE.js.map +1 -0
  53. package/dist/{writer-4ZEAKUFD.js → writer-3NAVABN6.js} +3 -3
  54. package/dist/writer-3NAVABN6.js.map +1 -0
  55. package/package.json +77 -68
  56. package/dist/chunk-2RPTM6EQ.js.map +0 -1
  57. package/dist/chunk-A535VV7N.js.map +0 -1
  58. package/dist/chunk-DLP2O6PN.js.map +0 -1
  59. package/dist/chunk-EYO44OMN.js.map +0 -1
  60. package/dist/chunk-JYWQT2B4.js.map +0 -1
  61. package/dist/chunk-WPJJZLT6.js.map +0 -1
  62. /package/dist/{config-43SK6SFI.js.map → adapters-JAZGGNVP.js.map} +0 -0
  63. /package/dist/{db-SWJUUSFX.js.map → config-I5NCK3RJ.js.map} +0 -0
  64. /package/dist/{display-HFIFXOOL.js.map → copilot-cli-5WJWK5YT.js.map} +0 -0
  65. /package/dist/{ollama-HY35OHW4.js.map → db-ETWTBXAE.js.map} +0 -0
  66. /package/dist/{writer-4ZEAKUFD.js.map → display-UH7KEHOW.js.map} +0 -0
@@ -3,7 +3,7 @@
3
3
  // src/dashboard/db.ts
4
4
  import Database from "better-sqlite3";
5
5
  import { randomUUID } from "crypto";
6
- var DashboardDB = class {
6
+ var DashboardDB = class _DashboardDB {
7
7
  db;
8
8
  constructor(dbPath = ".bashbros.db") {
9
9
  this.db = new Database(dbPath);
@@ -85,7 +85,7 @@ var DashboardDB = class {
85
85
  this.db.exec(`
86
86
  CREATE TABLE IF NOT EXISTS commands (
87
87
  id TEXT PRIMARY KEY,
88
- session_id TEXT NOT NULL,
88
+ session_id TEXT,
89
89
  timestamp TEXT NOT NULL,
90
90
  command TEXT NOT NULL,
91
91
  allowed INTEGER NOT NULL,
@@ -97,6 +97,7 @@ var DashboardDB = class {
97
97
  FOREIGN KEY (session_id) REFERENCES sessions(id)
98
98
  )
99
99
  `);
100
+ this.migrateCommandsNullableSessionId();
100
101
  this.db.exec(`
101
102
  CREATE TABLE IF NOT EXISTS bro_events (
102
103
  id TEXT PRIMARY KEY,
@@ -136,6 +137,20 @@ var DashboardDB = class {
136
137
  repo_path TEXT
137
138
  )
138
139
  `);
140
+ this.db.exec(`
141
+ CREATE TABLE IF NOT EXISTS adapter_events (
142
+ id TEXT PRIMARY KEY,
143
+ timestamp TEXT NOT NULL,
144
+ adapter_name TEXT NOT NULL,
145
+ base_model TEXT NOT NULL,
146
+ purpose TEXT NOT NULL,
147
+ action TEXT NOT NULL,
148
+ success INTEGER NOT NULL
149
+ )
150
+ `);
151
+ this.migrateToolUsesAddSessionId();
152
+ this.migrateSessionsAddMode();
153
+ this.migrateSessionsAddRepoName();
139
154
  this.db.exec(`
140
155
  CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
141
156
  CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time);
@@ -147,6 +162,8 @@ var DashboardDB = class {
147
162
  CREATE INDEX IF NOT EXISTS idx_bro_status_timestamp ON bro_status(timestamp);
148
163
  CREATE INDEX IF NOT EXISTS idx_tool_uses_timestamp ON tool_uses(timestamp);
149
164
  CREATE INDEX IF NOT EXISTS idx_tool_uses_tool_name ON tool_uses(tool_name);
165
+ CREATE INDEX IF NOT EXISTS idx_tool_uses_session_id ON tool_uses(session_id);
166
+ CREATE INDEX IF NOT EXISTS idx_adapter_events_timestamp ON adapter_events(timestamp);
150
167
  `);
151
168
  }
152
169
  // ─────────────────────────────────────────────────────────────
@@ -377,10 +394,10 @@ var DashboardDB = class {
377
394
  const id = randomUUID();
378
395
  const startTime = (/* @__PURE__ */ new Date()).toISOString();
379
396
  const stmt = this.db.prepare(`
380
- INSERT INTO sessions (id, agent, pid, start_time, status, command_count, blocked_count, avg_risk_score, working_dir)
381
- VALUES (?, ?, ?, ?, 'active', 0, 0, 0, ?)
397
+ INSERT INTO sessions (id, agent, pid, start_time, status, command_count, blocked_count, avg_risk_score, working_dir, repo_name)
398
+ VALUES (?, ?, ?, ?, 'active', 0, 0, 0, ?, ?)
382
399
  `);
383
- stmt.run(id, input.agent, input.pid, startTime, input.workingDir);
400
+ stmt.run(id, input.agent, input.pid, startTime, input.workingDir, input.repoName ?? null);
384
401
  return id;
385
402
  }
386
403
  updateSession(id, updates) {
@@ -459,6 +476,44 @@ var DashboardDB = class {
459
476
  if (!row) return null;
460
477
  return this.rowToSession(row);
461
478
  }
479
+ /**
480
+ * Insert a session with an externally-provided ID (for hook-mode sessions).
481
+ * Uses INSERT OR IGNORE for race-safe concurrent hook calls.
482
+ */
483
+ insertSessionWithId(id, input) {
484
+ const startTime = (/* @__PURE__ */ new Date()).toISOString();
485
+ const stmt = this.db.prepare(`
486
+ INSERT OR IGNORE INTO sessions (id, agent, pid, start_time, status, command_count, blocked_count, avg_risk_score, working_dir, mode, repo_name)
487
+ VALUES (?, ?, ?, ?, 'active', 0, 0, 0, ?, ?, ?)
488
+ `);
489
+ stmt.run(id, input.agent, input.pid, startTime, input.workingDir, input.mode ?? "hook", input.repoName ?? null);
490
+ return id;
491
+ }
492
+ /**
493
+ * Get ALL active sessions, ordered by start_time DESC.
494
+ * Used by the multi-session dashboard.
495
+ */
496
+ getActiveSessions() {
497
+ const stmt = this.db.prepare(`
498
+ SELECT * FROM sessions WHERE status = 'active' ORDER BY start_time DESC
499
+ `);
500
+ const rows = stmt.all();
501
+ return rows.map((row) => this.rowToSession(row));
502
+ }
503
+ /**
504
+ * Atomically increment session counters in a single UPDATE.
505
+ * Race-safe for concurrent hook processes (SQLite serializes writes).
506
+ */
507
+ incrementSessionCommand(id, blocked, riskScore) {
508
+ const stmt = this.db.prepare(`
509
+ UPDATE sessions SET
510
+ command_count = command_count + 1,
511
+ blocked_count = blocked_count + ?,
512
+ avg_risk_score = (avg_risk_score * command_count + ?) / (command_count + 1)
513
+ WHERE id = ?
514
+ `);
515
+ stmt.run(blocked ? 1 : 0, riskScore, id);
516
+ }
462
517
  rowToSession(row) {
463
518
  return {
464
519
  id: row.id,
@@ -470,7 +525,8 @@ var DashboardDB = class {
470
525
  commandCount: row.command_count,
471
526
  blockedCount: row.blocked_count,
472
527
  avgRiskScore: row.avg_risk_score,
473
- workingDir: row.working_dir
528
+ workingDir: row.working_dir,
529
+ repoName: row.repo_name ?? null
474
530
  };
475
531
  }
476
532
  // ─────────────────────────────────────────────────────────────
@@ -485,7 +541,7 @@ var DashboardDB = class {
485
541
  `);
486
542
  stmt.run(
487
543
  id,
488
- input.sessionId,
544
+ input.sessionId ?? null,
489
545
  timestamp,
490
546
  input.command,
491
547
  input.allowed ? 1 : 0,
@@ -538,12 +594,17 @@ var DashboardDB = class {
538
594
  }
539
595
  getLiveCommands(limit = 20) {
540
596
  const stmt = this.db.prepare(`
541
- SELECT * FROM commands
542
- ORDER BY timestamp DESC
597
+ SELECT c.*, s.repo_name, s.working_dir
598
+ FROM commands c
599
+ LEFT JOIN sessions s ON c.session_id = s.id
600
+ ORDER BY c.timestamp DESC
543
601
  LIMIT ?
544
602
  `);
545
603
  const rows = stmt.all(limit);
546
- return rows.map((row) => this.rowToCommand(row));
604
+ return rows.map((row) => ({
605
+ ...this.rowToCommand(row),
606
+ repoName: row.repo_name ?? (row.working_dir ? row.working_dir.split(/[/\\]/).pop() ?? null : null)
607
+ }));
547
608
  }
548
609
  rowToCommand(row) {
549
610
  return {
@@ -653,14 +714,52 @@ var DashboardDB = class {
653
714
  };
654
715
  }
655
716
  // ─────────────────────────────────────────────────────────────
717
+ // Adapter Events
718
+ // ─────────────────────────────────────────────────────────────
719
+ insertAdapterEvent(input) {
720
+ const id = randomUUID();
721
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
722
+ const stmt = this.db.prepare(`
723
+ INSERT INTO adapter_events (id, timestamp, adapter_name, base_model, purpose, action, success)
724
+ VALUES (?, ?, ?, ?, ?, ?, ?)
725
+ `);
726
+ stmt.run(
727
+ id,
728
+ timestamp,
729
+ input.adapterName,
730
+ input.baseModel,
731
+ input.purpose,
732
+ input.action,
733
+ input.success ? 1 : 0
734
+ );
735
+ return id;
736
+ }
737
+ getAdapterEvents(limit = 50) {
738
+ const stmt = this.db.prepare(`
739
+ SELECT * FROM adapter_events
740
+ ORDER BY timestamp DESC
741
+ LIMIT ?
742
+ `);
743
+ const rows = stmt.all(limit);
744
+ return rows.map((row) => ({
745
+ id: row.id,
746
+ timestamp: new Date(row.timestamp),
747
+ adapterName: row.adapter_name,
748
+ baseModel: row.base_model,
749
+ purpose: row.purpose,
750
+ action: row.action,
751
+ success: row.success === 1
752
+ }));
753
+ }
754
+ // ─────────────────────────────────────────────────────────────
656
755
  // Tool Uses
657
756
  // ─────────────────────────────────────────────────────────────
658
757
  insertToolUse(input) {
659
758
  const id = randomUUID();
660
759
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
661
760
  const stmt = this.db.prepare(`
662
- INSERT INTO tool_uses (id, timestamp, tool_name, tool_input, tool_output, exit_code, success, cwd, repo_name, repo_path)
663
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
761
+ INSERT INTO tool_uses (id, timestamp, tool_name, tool_input, tool_output, exit_code, success, cwd, repo_name, repo_path, session_id)
762
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
664
763
  `);
665
764
  stmt.run(
666
765
  id,
@@ -674,7 +773,8 @@ var DashboardDB = class {
674
773
  input.success === void 0 ? null : input.success ? 1 : 0,
675
774
  input.cwd,
676
775
  input.repoName ?? null,
677
- input.repoPath ?? null
776
+ input.repoPath ?? null,
777
+ input.sessionId ?? null
678
778
  );
679
779
  return id;
680
780
  }
@@ -685,6 +785,10 @@ var DashboardDB = class {
685
785
  conditions.push("tool_name = ?");
686
786
  params.push(filter.toolName);
687
787
  }
788
+ if (filter.sessionId) {
789
+ conditions.push("session_id = ?");
790
+ params.push(filter.sessionId);
791
+ }
688
792
  if (filter.since) {
689
793
  conditions.push("timestamp >= ?");
690
794
  params.push(filter.since.toISOString());
@@ -772,6 +876,61 @@ var DashboardDB = class {
772
876
  };
773
877
  }
774
878
  // ─────────────────────────────────────────────────────────────
879
+ // Security Summary
880
+ // ─────────────────────────────────────────────────────────────
881
+ getSecuritySummary() {
882
+ const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
883
+ const totalRow = this.db.prepare(`
884
+ SELECT COUNT(*) as count FROM commands WHERE timestamp >= ?
885
+ `).get(oneDayAgo);
886
+ const blockedRow = this.db.prepare(`
887
+ SELECT COUNT(*) as count FROM commands WHERE timestamp >= ? AND allowed = 0
888
+ `).get(oneDayAgo);
889
+ const avgRow = this.db.prepare(`
890
+ SELECT AVG(risk_score) as avg FROM commands WHERE timestamp >= ?
891
+ `).get(oneDayAgo);
892
+ const riskRows = this.db.prepare(`
893
+ SELECT risk_level, COUNT(*) as count FROM commands WHERE timestamp >= ? GROUP BY risk_level
894
+ `).all(oneDayAgo);
895
+ const riskDistribution = {};
896
+ for (const row of riskRows) {
897
+ riskDistribution[row.risk_level] = row.count;
898
+ }
899
+ const highRiskRow = this.db.prepare(`
900
+ SELECT COUNT(*) as count FROM commands WHERE timestamp >= ? AND risk_level IN ('dangerous', 'critical')
901
+ `).get(oneDayAgo);
902
+ const violationRows = this.db.prepare(`
903
+ SELECT violations FROM commands WHERE timestamp >= ? AND allowed = 0 AND violations != '[]'
904
+ `).all(oneDayAgo);
905
+ const typeCounts = {};
906
+ for (const row of violationRows) {
907
+ try {
908
+ const violations = JSON.parse(row.violations);
909
+ for (const v of violations) {
910
+ const type = v.includes(":") ? v.split(":")[0] : v;
911
+ typeCounts[type] = (typeCounts[type] || 0) + 1;
912
+ }
913
+ } catch {
914
+ }
915
+ }
916
+ const violationTypes = Object.entries(typeCounts).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count);
917
+ return {
918
+ totalCommands24h: totalRow.count,
919
+ blockedCount24h: blockedRow.count,
920
+ avgRiskScore24h: avgRow.avg ?? 0,
921
+ riskDistribution,
922
+ violationTypes,
923
+ highRiskCount24h: highRiskRow.count
924
+ };
925
+ }
926
+ getBlockedCommandsRecent(limit = 25) {
927
+ const stmt = this.db.prepare(`
928
+ SELECT * FROM commands WHERE allowed = 0 ORDER BY timestamp DESC LIMIT ?
929
+ `);
930
+ const rows = stmt.all(limit);
931
+ return rows.map((row) => this.rowToCommand(row));
932
+ }
933
+ // ─────────────────────────────────────────────────────────────
775
934
  // Stats
776
935
  // ─────────────────────────────────────────────────────────────
777
936
  getStats() {
@@ -834,6 +993,27 @@ var DashboardDB = class {
834
993
  };
835
994
  }
836
995
  // ─────────────────────────────────────────────────────────────
996
+ // Cross-process Query Helpers (for db-checks)
997
+ // ─────────────────────────────────────────────────────────────
998
+ getRecentCommandTexts(windowSize) {
999
+ const stmt = this.db.prepare(`
1000
+ SELECT command, timestamp FROM commands
1001
+ ORDER BY timestamp DESC
1002
+ LIMIT ?
1003
+ `);
1004
+ return stmt.all(windowSize);
1005
+ }
1006
+ getCommandCountSince(sinceISO) {
1007
+ const row = this.db.prepare(`
1008
+ SELECT COUNT(*) as count FROM commands WHERE timestamp >= ?
1009
+ `).get(sinceISO);
1010
+ return row.count;
1011
+ }
1012
+ getTotalCommandCount() {
1013
+ const row = this.db.prepare("SELECT COUNT(*) as count FROM commands").get();
1014
+ return row.count;
1015
+ }
1016
+ // ─────────────────────────────────────────────────────────────
837
1017
  // Maintenance
838
1018
  // ─────────────────────────────────────────────────────────────
839
1019
  cleanup(olderThanDays = 30) {
@@ -853,14 +1033,309 @@ var DashboardDB = class {
853
1033
  const toolUsesDeleted = this.db.prepare("DELETE FROM tool_uses WHERE timestamp < ?").run(cutoff).changes;
854
1034
  return eventsDeleted + connectorDeleted + blocksDeleted + exposuresDeleted + commandsDeleted + sessionsDeleted + broEventsDeleted + broStatusDeleted + toolUsesDeleted;
855
1035
  }
1036
+ /**
1037
+ * Migration: add session_id column to tool_uses table for session tracking
1038
+ */
1039
+ migrateToolUsesAddSessionId() {
1040
+ try {
1041
+ const tableInfo = this.db.pragma("table_info(tool_uses)");
1042
+ const hasSessionId = tableInfo.some((col) => col.name === "session_id");
1043
+ if (!hasSessionId) {
1044
+ this.db.exec("ALTER TABLE tool_uses ADD COLUMN session_id TEXT");
1045
+ }
1046
+ } catch {
1047
+ }
1048
+ }
1049
+ /**
1050
+ * Migration: add mode column to sessions table to distinguish watch vs hook sessions
1051
+ */
1052
+ migrateSessionsAddMode() {
1053
+ try {
1054
+ const tableInfo = this.db.pragma("table_info(sessions)");
1055
+ const hasMode = tableInfo.some((col) => col.name === "mode");
1056
+ if (!hasMode) {
1057
+ this.db.exec("ALTER TABLE sessions ADD COLUMN mode TEXT DEFAULT 'watch'");
1058
+ }
1059
+ } catch {
1060
+ }
1061
+ }
1062
+ /**
1063
+ * Migration: add repo_name column to sessions table
1064
+ */
1065
+ migrateSessionsAddRepoName() {
1066
+ try {
1067
+ const tableInfo = this.db.pragma("table_info(sessions)");
1068
+ const hasRepoName = tableInfo.some((col) => col.name === "repo_name");
1069
+ if (!hasRepoName) {
1070
+ this.db.exec("ALTER TABLE sessions ADD COLUMN repo_name TEXT");
1071
+ }
1072
+ } catch {
1073
+ }
1074
+ }
1075
+ /**
1076
+ * Migration: allow NULL session_id in commands table for hook-mode recording
1077
+ * (each hook invocation is a separate process with no long-running session)
1078
+ */
1079
+ migrateCommandsNullableSessionId() {
1080
+ try {
1081
+ const tableInfo = this.db.pragma("table_info(commands)");
1082
+ const sessionCol = tableInfo.find((col) => col.name === "session_id");
1083
+ if (sessionCol && sessionCol.notnull === 1) {
1084
+ this.db.exec(`
1085
+ CREATE TABLE commands_mig (
1086
+ id TEXT PRIMARY KEY,
1087
+ session_id TEXT,
1088
+ timestamp TEXT NOT NULL,
1089
+ command TEXT NOT NULL,
1090
+ allowed INTEGER NOT NULL,
1091
+ risk_score INTEGER NOT NULL,
1092
+ risk_level TEXT NOT NULL,
1093
+ risk_factors TEXT NOT NULL,
1094
+ duration_ms INTEGER NOT NULL,
1095
+ violations TEXT NOT NULL,
1096
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
1097
+ );
1098
+ INSERT INTO commands_mig SELECT * FROM commands;
1099
+ DROP TABLE commands;
1100
+ ALTER TABLE commands_mig RENAME TO commands;
1101
+ CREATE INDEX IF NOT EXISTS idx_commands_session_id ON commands(session_id);
1102
+ CREATE INDEX IF NOT EXISTS idx_commands_timestamp ON commands(timestamp);
1103
+ CREATE INDEX IF NOT EXISTS idx_commands_allowed ON commands(allowed);
1104
+ `);
1105
+ }
1106
+ } catch {
1107
+ }
1108
+ }
1109
+ // ─────────────────────────────────────────────────────────────
1110
+ // Achievement System
1111
+ // ─────────────────────────────────────────────────────────────
1112
+ static BADGE_DEFINITIONS = [
1113
+ // Volume
1114
+ { id: "first_blood", name: "First Blood", description: "Execute commands", category: "volume", icon: "\u2694", stat: "totalCommands", tiers: [1, 100, 1e3, 1e4, 1e5] },
1115
+ { id: "marathon_runner", name: "Marathon Runner", description: "Complete sessions", category: "volume", icon: "\u{1F3C3}", stat: "totalSessions", tiers: [1, 10, 50, 200, 1e3] },
1116
+ { id: "watchdog", name: "Watchdog", description: "Time under watch", category: "volume", icon: "\u23F1", stat: "totalWatchTimeMinutes", tiers: [60, 1440, 10080, 43200, 525600] },
1117
+ // Security
1118
+ { id: "shield_bearer", name: "Shield Bearer", description: "Threats blocked", category: "security", icon: "\u{1F6E1}", stat: "totalBlocked", tiers: [1, 25, 100, 500, 2e3] },
1119
+ { id: "clean_hands", name: "Clean Hands", description: "Consecutive clean commands", category: "security", icon: "\u2728", stat: "cleanestStreak", tiers: [10, 50, 200, 1e3, 5e3] },
1120
+ { id: "risk_taker", name: "Risk Taker", description: "High risk commands executed", category: "security", icon: "\u{1F3B2}", stat: "highRiskCount", tiers: [1, 5, 10, 25, 50] },
1121
+ // Agents
1122
+ { id: "buddy_system", name: "Buddy System", description: "Use 2+ agents", category: "agents", icon: "\u{1F91D}", stat: "uniqueAgents", tiers: [2, 2, 2, 2, 2] },
1123
+ { id: "squad_up", name: "Squad Up", description: "Use multiple agents", category: "agents", icon: "\u{1F46B}", stat: "uniqueAgents", tiers: [2, 3, 4, 5, 5] },
1124
+ { id: "loyal", name: "Loyal", description: "1000 commands from one agent", category: "agents", icon: "\u{1F451}", stat: "maxAgentCommands", tiers: [100, 250, 500, 1e3, 5e3] },
1125
+ { id: "polyglot", name: "Polyglot", description: "100+ commands from multiple agents", category: "agents", icon: "\u{1F30D}", stat: "agentsWith100", tiers: [1, 2, 3, 4, 5] },
1126
+ // Behavioral
1127
+ { id: "night_owl", name: "Night Owl", description: "Commands after midnight", category: "behavioral", icon: "\u{1F989}", stat: "lateNightCount", tiers: [10, 100, 500, 2e3, 1e4] },
1128
+ { id: "speed_demon", name: "Speed Demon", description: "Commands in a single hour", category: "behavioral", icon: "\u26A1", stat: "peakHourCount", tiers: [60, 100, 150, 200, 300] },
1129
+ { id: "one_liner", name: "One-Liner", description: "Longest command (chars)", category: "behavioral", icon: "\u{1F4DD}", stat: "longestCommandLength", tiers: [500, 1e3, 2e3, 5e3, 1e4] },
1130
+ { id: "creature_of_habit", name: "Creature of Habit", description: "Same command repeated", category: "behavioral", icon: "\u{1F501}", stat: "mostUsedCommandCount", tiers: [50, 200, 500, 1e3, 5e3] },
1131
+ { id: "explorer", name: "Explorer", description: "Unique commands used", category: "behavioral", icon: "\u{1F9ED}", stat: "uniqueCommands", tiers: [25, 50, 100, 250, 500] },
1132
+ // Repo
1133
+ { 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] }
1135
+ ];
1136
+ getAchievementStats() {
1137
+ const totalCommands = this.db.prepare("SELECT COUNT(*) as c FROM commands").get().c;
1138
+ const totalBlocked = this.db.prepare("SELECT COUNT(*) as c FROM commands WHERE allowed = 0").get().c;
1139
+ const totalSessions = this.db.prepare("SELECT COUNT(*) as c FROM sessions").get().c;
1140
+ const totalCharacters = this.db.prepare("SELECT COALESCE(SUM(LENGTH(command)), 0) as c FROM commands").get().c;
1141
+ const watchTimeRow = this.db.prepare(`
1142
+ SELECT COALESCE(SUM(
1143
+ (julianday(COALESCE(end_time, datetime('now'))) - julianday(start_time)) * 1440
1144
+ ), 0) as minutes FROM sessions
1145
+ `).get();
1146
+ const totalWatchTimeMinutes = Math.round(watchTimeRow.minutes);
1147
+ const uniqueRepos = this.db.prepare(`
1148
+ SELECT COUNT(DISTINCT repo_name) as c FROM sessions WHERE repo_name IS NOT NULL AND repo_name != ''
1149
+ `).get().c;
1150
+ const firstCommandRow = this.db.prepare("SELECT MIN(timestamp) as t FROM commands").get();
1151
+ const memberSince = firstCommandRow.t || null;
1152
+ const agentRows = this.db.prepare(`
1153
+ SELECT s.agent, COUNT(*) as count
1154
+ FROM commands c
1155
+ JOIN sessions s ON c.session_id = s.id
1156
+ GROUP BY s.agent
1157
+ ORDER BY count DESC
1158
+ `).all();
1159
+ const agentBreakdown = {};
1160
+ for (const row of agentRows) {
1161
+ agentBreakdown[row.agent] = row.count;
1162
+ }
1163
+ const uniqueAgents = Object.keys(agentBreakdown).length;
1164
+ const maxAgentCommands = agentRows.length > 0 ? agentRows[0].count : 0;
1165
+ const favoriteAgent = agentRows.length > 0 ? agentRows[0].agent : null;
1166
+ const agentsWith100 = agentRows.filter((r) => r.count >= 100).length;
1167
+ const mostUsedRow = this.db.prepare(`
1168
+ SELECT command, COUNT(*) as count FROM commands
1169
+ GROUP BY command ORDER BY count DESC LIMIT 1
1170
+ `).get();
1171
+ const mostUsedCommand = mostUsedRow?.command || null;
1172
+ const mostUsedCommandCount = mostUsedRow?.count || 0;
1173
+ const uniqueCommands = this.db.prepare("SELECT COUNT(DISTINCT command) as c FROM commands").get().c;
1174
+ const longestCommandLength = this.db.prepare("SELECT COALESCE(MAX(LENGTH(command)), 0) as c FROM commands").get().c;
1175
+ const peakHourRow = this.db.prepare(`
1176
+ SELECT CAST(strftime('%H', timestamp) AS INTEGER) as hour, COUNT(*) as count
1177
+ FROM commands GROUP BY hour ORDER BY count DESC LIMIT 1
1178
+ `).get();
1179
+ const peakHour = peakHourRow?.hour ?? null;
1180
+ const peakHourCount = peakHourRow?.count ?? 0;
1181
+ const peakDayRow = this.db.prepare(`
1182
+ SELECT CAST(strftime('%w', timestamp) AS INTEGER) as day, COUNT(*) as count
1183
+ FROM commands GROUP BY day ORDER BY count DESC LIMIT 1
1184
+ `).get();
1185
+ const peakDay = peakDayRow?.day ?? null;
1186
+ const busiestDayRow = this.db.prepare(`
1187
+ SELECT DATE(timestamp) as day, COUNT(*) as count
1188
+ FROM commands GROUP BY day ORDER BY count DESC LIMIT 1
1189
+ `).get();
1190
+ const busiestDay = busiestDayRow?.day || null;
1191
+ const busiestDayCount = busiestDayRow?.count || 0;
1192
+ const avgCommandsPerSession = totalSessions > 0 ? Math.round(totalCommands / totalSessions) : 0;
1193
+ const longestSessionRow = this.db.prepare(`
1194
+ SELECT MAX(
1195
+ (julianday(COALESCE(end_time, datetime('now'))) - julianday(start_time)) * 1440
1196
+ ) as minutes FROM sessions
1197
+ `).get();
1198
+ const longestSessionMinutes = Math.round(longestSessionRow.minutes || 0);
1199
+ const lateNightCount = this.db.prepare(`
1200
+ SELECT COUNT(*) as c FROM commands
1201
+ WHERE CAST(strftime('%H', timestamp) AS INTEGER) < 5
1202
+ `).get().c;
1203
+ const avgRiskRow = this.db.prepare("SELECT AVG(risk_score) as avg FROM commands").get();
1204
+ const lifetimeAvgRisk = avgRiskRow.avg ?? 0;
1205
+ const allAllowed = this.db.prepare(
1206
+ "SELECT allowed FROM commands ORDER BY timestamp ASC"
1207
+ ).all();
1208
+ let cleanestStreak = 0;
1209
+ let currentStreak = 0;
1210
+ for (const row of allAllowed) {
1211
+ if (row.allowed === 1) {
1212
+ currentStreak++;
1213
+ if (currentStreak > cleanestStreak) cleanestStreak = currentStreak;
1214
+ } else {
1215
+ currentStreak = 0;
1216
+ }
1217
+ }
1218
+ const highestRiskRow = this.db.prepare(
1219
+ "SELECT command, risk_score FROM commands ORDER BY risk_score DESC LIMIT 1"
1220
+ ).get();
1221
+ const highestRiskCommand = highestRiskRow?.command || null;
1222
+ const highestRiskScore = highestRiskRow?.risk_score || 0;
1223
+ const highRiskCount = this.db.prepare(
1224
+ "SELECT COUNT(*) as c FROM commands WHERE risk_score >= 8"
1225
+ ).get().c;
1226
+ return {
1227
+ totalCommands,
1228
+ totalBlocked,
1229
+ totalSessions,
1230
+ totalCharacters,
1231
+ totalWatchTimeMinutes,
1232
+ uniqueRepos,
1233
+ memberSince,
1234
+ agentBreakdown,
1235
+ uniqueAgents,
1236
+ maxAgentCommands,
1237
+ favoriteAgent,
1238
+ agentsWith100,
1239
+ mostUsedCommand,
1240
+ mostUsedCommandCount,
1241
+ uniqueCommands,
1242
+ longestCommandLength,
1243
+ peakHour,
1244
+ peakHourCount,
1245
+ peakDay,
1246
+ busiestDay,
1247
+ busiestDayCount,
1248
+ avgCommandsPerSession,
1249
+ longestSessionMinutes,
1250
+ lateNightCount,
1251
+ lifetimeAvgRisk,
1252
+ cleanestStreak,
1253
+ currentCleanStreak: currentStreak,
1254
+ highestRiskCommand,
1255
+ highestRiskScore,
1256
+ highRiskCount
1257
+ };
1258
+ }
1259
+ computeAchievements(stats) {
1260
+ return _DashboardDB.BADGE_DEFINITIONS.map((badge) => {
1261
+ const value = stats[badge.stat] ?? 0;
1262
+ let currentTier = 0;
1263
+ for (let i = 0; i < badge.tiers.length; i++) {
1264
+ if (value >= badge.tiers[i]) currentTier = i + 1;
1265
+ }
1266
+ const nextThreshold = currentTier < badge.tiers.length ? badge.tiers[currentTier] : badge.tiers[badge.tiers.length - 1];
1267
+ const prevThreshold = currentTier > 0 ? badge.tiers[currentTier - 1] : 0;
1268
+ const progress = currentTier >= badge.tiers.length ? 1 : (value - prevThreshold) / Math.max(1, nextThreshold - prevThreshold);
1269
+ return {
1270
+ id: badge.id,
1271
+ name: badge.name,
1272
+ description: badge.description,
1273
+ category: badge.category,
1274
+ icon: badge.icon,
1275
+ tier: currentTier,
1276
+ tierName: TIER_NAMES[currentTier],
1277
+ value,
1278
+ nextThreshold,
1279
+ progress: Math.min(1, Math.max(0, progress)),
1280
+ maxed: currentTier >= 5
1281
+ };
1282
+ });
1283
+ }
1284
+ computeXP(stats, badges) {
1285
+ const TIER_XP = [0, 50, 100, 200, 500, 1e3];
1286
+ const RANK_THRESHOLDS = [
1287
+ { rank: "Obsidian", xp: 1e5 },
1288
+ { rank: "Diamond", xp: 25e3 },
1289
+ { rank: "Gold", xp: 5e3 },
1290
+ { rank: "Silver", xp: 1e3 },
1291
+ { rank: "Bronze", xp: 0 }
1292
+ ];
1293
+ let totalXP = 0;
1294
+ totalXP += stats.totalCommands;
1295
+ totalXP += stats.totalBlocked * 3;
1296
+ totalXP += stats.totalSessions * 10;
1297
+ totalXP += stats.lateNightCount * 2;
1298
+ totalXP += Math.floor(stats.cleanestStreak / 100) * 25;
1299
+ for (const badge of badges) {
1300
+ if (badge.tier > 0) {
1301
+ totalXP += TIER_XP[badge.tier];
1302
+ }
1303
+ }
1304
+ let rank = "Bronze";
1305
+ let nextRankXP = 1e3;
1306
+ for (const t of RANK_THRESHOLDS) {
1307
+ if (totalXP >= t.xp) {
1308
+ rank = t.rank;
1309
+ const idx = RANK_THRESHOLDS.indexOf(t);
1310
+ nextRankXP = idx > 0 ? RANK_THRESHOLDS[idx - 1].xp : totalXP;
1311
+ break;
1312
+ }
1313
+ }
1314
+ const currentRankXP = RANK_THRESHOLDS.find((t) => t.rank === rank).xp;
1315
+ const progress = rank === "Obsidian" ? 1 : (totalXP - currentRankXP) / Math.max(1, nextRankXP - currentRankXP);
1316
+ return {
1317
+ totalXP,
1318
+ rank,
1319
+ nextRankXP,
1320
+ progress: Math.min(1, Math.max(0, progress))
1321
+ };
1322
+ }
856
1323
  close() {
857
1324
  this.db.close();
858
1325
  }
859
1326
  };
1327
+ var TIER_NAMES = {
1328
+ 0: "Locked",
1329
+ 1: "Bronze",
1330
+ 2: "Silver",
1331
+ 3: "Gold",
1332
+ 4: "Diamond",
1333
+ 5: "Obsidian"
1334
+ };
860
1335
  var db_default = DashboardDB;
861
1336
 
862
1337
  export {
863
1338
  DashboardDB,
864
1339
  db_default
865
1340
  };
866
- //# sourceMappingURL=chunk-JYWQT2B4.js.map
1341
+ //# sourceMappingURL=chunk-RDNSS3ME.js.map