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.
- package/README.md +727 -265
- package/dist/adapters-JAZGGNVP.js +9 -0
- package/dist/chunk-4XZ64P4V.js +47 -0
- package/dist/chunk-4XZ64P4V.js.map +1 -0
- package/dist/{chunk-2RPTM6EQ.js → chunk-7OEWYFN3.js} +745 -629
- package/dist/chunk-7OEWYFN3.js.map +1 -0
- package/dist/{chunk-WPJJZLT6.js → chunk-CG6VEHJM.js} +3 -2
- package/dist/chunk-CG6VEHJM.js.map +1 -0
- package/dist/{chunk-DLP2O6PN.js → chunk-EMLEJVJZ.js} +102 -1
- package/dist/chunk-EMLEJVJZ.js.map +1 -0
- package/dist/chunk-IUUBCPMV.js +166 -0
- package/dist/chunk-IUUBCPMV.js.map +1 -0
- package/dist/chunk-J6ONXY6N.js +146 -0
- package/dist/chunk-J6ONXY6N.js.map +1 -0
- package/dist/{chunk-EYO44OMN.js → chunk-KYDMPE4N.js} +60 -17
- package/dist/chunk-KYDMPE4N.js.map +1 -0
- package/dist/chunk-LJE4EPIU.js +56 -0
- package/dist/chunk-LJE4EPIU.js.map +1 -0
- package/dist/chunk-LZYW7XQO.js +339 -0
- package/dist/chunk-LZYW7XQO.js.map +1 -0
- package/dist/{chunk-JYWQT2B4.js → chunk-RDNSS3ME.js} +489 -14
- package/dist/chunk-RDNSS3ME.js.map +1 -0
- package/dist/{chunk-A535VV7N.js → chunk-RTZ4QWG2.js} +5 -4
- package/dist/chunk-RTZ4QWG2.js.map +1 -0
- package/dist/chunk-SDN6TAGD.js +157 -0
- package/dist/chunk-SDN6TAGD.js.map +1 -0
- package/dist/chunk-T5ONCUHZ.js +198 -0
- package/dist/chunk-T5ONCUHZ.js.map +1 -0
- package/dist/cli.js +1069 -88
- package/dist/cli.js.map +1 -1
- package/dist/{config-43SK6SFI.js → config-I5NCK3RJ.js} +2 -2
- package/dist/copilot-cli-5WJWK5YT.js +9 -0
- package/dist/{db-SWJUUSFX.js → db-ETWTBXAE.js} +2 -2
- package/dist/db-checks-2YOVECD4.js +133 -0
- package/dist/db-checks-2YOVECD4.js.map +1 -0
- package/dist/{display-HFIFXOOL.js → display-UH7KEHOW.js} +3 -3
- package/dist/gemini-cli-3563EELZ.js +9 -0
- package/dist/gemini-cli-3563EELZ.js.map +1 -0
- package/dist/index.d.ts +176 -72
- package/dist/index.js +119 -398
- package/dist/index.js.map +1 -1
- package/dist/{ollama-HY35OHW4.js → ollama-5JVKNFOV.js} +2 -2
- package/dist/ollama-5JVKNFOV.js.map +1 -0
- package/dist/opencode-DRCY275R.js +9 -0
- package/dist/opencode-DRCY275R.js.map +1 -0
- package/dist/profiles-7CLN6TAT.js +9 -0
- package/dist/profiles-7CLN6TAT.js.map +1 -0
- package/dist/setup-YS27MOPE.js +124 -0
- package/dist/setup-YS27MOPE.js.map +1 -0
- package/dist/static/index.html +4815 -2007
- package/dist/store-WJ5Y7MOE.js +9 -0
- package/dist/store-WJ5Y7MOE.js.map +1 -0
- package/dist/{writer-4ZEAKUFD.js → writer-3NAVABN6.js} +3 -3
- package/dist/writer-3NAVABN6.js.map +1 -0
- package/package.json +77 -68
- package/dist/chunk-2RPTM6EQ.js.map +0 -1
- package/dist/chunk-A535VV7N.js.map +0 -1
- package/dist/chunk-DLP2O6PN.js.map +0 -1
- package/dist/chunk-EYO44OMN.js.map +0 -1
- package/dist/chunk-JYWQT2B4.js.map +0 -1
- package/dist/chunk-WPJJZLT6.js.map +0 -1
- /package/dist/{config-43SK6SFI.js.map → adapters-JAZGGNVP.js.map} +0 -0
- /package/dist/{db-SWJUUSFX.js.map → config-I5NCK3RJ.js.map} +0 -0
- /package/dist/{display-HFIFXOOL.js.map → copilot-cli-5WJWK5YT.js.map} +0 -0
- /package/dist/{ollama-HY35OHW4.js.map → db-ETWTBXAE.js.map} +0 -0
- /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
|
|
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
|
|
542
|
-
|
|
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) =>
|
|
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-
|
|
1341
|
+
//# sourceMappingURL=chunk-RDNSS3ME.js.map
|