@harbinger-ai/harbinger 0.1.1 → 0.1.2

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.
@@ -202,6 +202,25 @@ export async function triggerUpgrade() {
202
202
  return { success: true };
203
203
  }
204
204
 
205
+ // ─────────────────────────────────────────────────────────────────────────────
206
+ // Agent profile actions
207
+ // ─────────────────────────────────────────────────────────────────────────────
208
+
209
+ /**
210
+ * Get all discovered agent profiles for Mission Control.
211
+ * @returns {Promise<object[]>}
212
+ */
213
+ export async function getAgentProfiles() {
214
+ await requireAuth();
215
+ try {
216
+ const { discoverAgents } = await import('../agents.js');
217
+ return discoverAgents();
218
+ } catch (err) {
219
+ console.error('Failed to discover agents:', err);
220
+ return [];
221
+ }
222
+ }
223
+
205
224
  // ─────────────────────────────────────────────────────────────────────────────
206
225
  // API Key actions
207
226
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from "react";
4
- import { CirclePlusIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ArrowUpCircleIcon, LifeBuoyIcon, CrosshairIcon, ShieldIcon, PackageIcon } from "./icons.js";
4
+ import { CirclePlusIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ArrowUpCircleIcon, LifeBuoyIcon, CrosshairIcon, ShieldIcon, PackageIcon, CommandIcon } from "./icons.js";
5
5
  import { getUnreadNotificationCount, getAppVersion } from "../actions.js";
6
6
  import { SidebarHistory } from "./sidebar-history.js";
7
7
  import { SidebarUserNav } from "./sidebar-user-nav.js";
@@ -112,6 +112,22 @@ function AppSidebar({ user }) {
112
112
  ) }),
113
113
  collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Swarm" })
114
114
  ] }) }),
115
+ /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
116
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
117
+ SidebarMenuButton,
118
+ {
119
+ className: collapsed ? "justify-center" : "",
120
+ onClick: () => {
121
+ window.location.href = "/mission-control";
122
+ },
123
+ children: [
124
+ /* @__PURE__ */ jsx(CommandIcon, { size: 16 }),
125
+ !collapsed && /* @__PURE__ */ jsx("span", { children: "Mission Control" })
126
+ ]
127
+ }
128
+ ) }),
129
+ collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Mission Control" })
130
+ ] }) }),
115
131
  /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
116
132
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
117
133
  SidebarMenuButton,
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
- import { CirclePlusIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ArrowUpCircleIcon, LifeBuoyIcon, CrosshairIcon, ShieldIcon, PackageIcon } from './icons.js';
4
+ import { CirclePlusIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ArrowUpCircleIcon, LifeBuoyIcon, CrosshairIcon, ShieldIcon, PackageIcon, CommandIcon } from './icons.js';
5
5
  import { getUnreadNotificationCount, getAppVersion } from '../actions.js';
6
6
  import { SidebarHistory } from './sidebar-history.js';
7
7
  import { SidebarUserNav } from './sidebar-user-nav.js';
@@ -129,6 +129,24 @@ export function AppSidebar({ user }) {
129
129
  </Tooltip>
130
130
  </SidebarMenuItem>
131
131
 
132
+ {/* Mission Control */}
133
+ <SidebarMenuItem>
134
+ <Tooltip>
135
+ <TooltipTrigger asChild>
136
+ <SidebarMenuButton
137
+ className={collapsed ? 'justify-center' : ''}
138
+ onClick={() => { window.location.href = '/mission-control'; }}
139
+ >
140
+ <CommandIcon size={16} />
141
+ {!collapsed && <span>Mission Control</span>}
142
+ </SidebarMenuButton>
143
+ </TooltipTrigger>
144
+ {collapsed && (
145
+ <TooltipContent side="right">Mission Control</TooltipContent>
146
+ )}
147
+ </Tooltip>
148
+ </SidebarMenuItem>
149
+
132
150
  {/* Targets */}
133
151
  <SidebarMenuItem>
134
152
  <Tooltip>
@@ -1,24 +1,25 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from "react";
4
+ import { motion, AnimatePresence } from "framer-motion";
4
5
  import { ShieldIcon, PlusIcon, ChevronDownIcon, SpinnerIcon } from "./icons.js";
5
6
  import { getFindings, createFinding, updateFinding, deleteFinding, getFindingCounts } from "../../bounty/actions.js";
6
7
  const SEVERITY_CONFIG = {
7
- critical: { color: "bg-red-600/15 text-red-500 border-red-500/20", dot: "bg-red-500", label: "Critical" },
8
- high: { color: "bg-orange-500/15 text-orange-500 border-orange-500/20", dot: "bg-orange-500", label: "High" },
9
- medium: { color: "bg-yellow-500/15 text-yellow-500 border-yellow-500/20", dot: "bg-yellow-500", label: "Medium" },
10
- low: { color: "bg-blue-500/15 text-blue-500 border-blue-500/20", dot: "bg-blue-500", label: "Low" },
11
- info: { color: "bg-muted text-muted-foreground border-border", dot: "bg-muted-foreground", label: "Info" }
8
+ critical: { bg: "bg-red-600/15", text: "text-red-500", border: "border-red-500/20", dot: "bg-red-500", glow: "shadow-[0_0_20px_oklch(0.6_0.22_25/20%)]", label: "Critical" },
9
+ high: { bg: "bg-orange-500/15", text: "text-orange-500", border: "border-orange-500/20", dot: "bg-orange-500", glow: "shadow-[0_0_20px_oklch(0.7_0.17_55/20%)]", label: "High" },
10
+ medium: { bg: "bg-yellow-500/15", text: "text-yellow-500", border: "border-yellow-500/20", dot: "bg-yellow-500", glow: "", label: "Medium" },
11
+ low: { bg: "bg-blue-500/15", text: "text-blue-500", border: "border-blue-500/20", dot: "bg-blue-500", glow: "", label: "Low" },
12
+ info: { bg: "bg-white/5", text: "text-muted-foreground", border: "border-white/10", dot: "bg-muted-foreground", glow: "", label: "Info" }
12
13
  };
13
14
  const STATUS_FLOW = ["new", "triaging", "confirmed", "reported", "duplicate", "resolved", "bounty_paid"];
14
15
  const STATUS_COLORS = {
15
- new: "bg-blue-500/10 text-blue-500",
16
- triaging: "bg-yellow-500/10 text-yellow-500",
17
- confirmed: "bg-green-500/10 text-green-500",
18
- reported: "bg-purple-500/10 text-purple-500",
19
- duplicate: "bg-muted text-muted-foreground",
20
- resolved: "bg-emerald-500/10 text-emerald-500",
21
- bounty_paid: "bg-green-600/10 text-green-600"
16
+ new: { bg: "bg-blue-500/10", text: "text-blue-500", border: "border-blue-500/20" },
17
+ triaging: { bg: "bg-yellow-500/10", text: "text-yellow-500", border: "border-yellow-500/20" },
18
+ confirmed: { bg: "bg-green-500/10", text: "text-green-500", border: "border-green-500/20" },
19
+ reported: { bg: "bg-purple-500/10", text: "text-purple-500", border: "border-purple-500/20" },
20
+ duplicate: { bg: "bg-white/5", text: "text-muted-foreground", border: "border-white/10" },
21
+ resolved: { bg: "bg-emerald-500/10", text: "text-emerald-500", border: "border-emerald-500/20" },
22
+ bounty_paid: { bg: "bg-green-600/10", text: "text-green-600", border: "border-green-600/20" }
22
23
  };
23
24
  const FINDING_TYPES = ["xss", "sqli", "ssrf", "idor", "rce", "lfi", "open_redirect", "subdomain_takeover", "info_disclosure", "misconfig", "auth_bypass", "rate_limit", "cors", "xxe", "csrf", "other"];
24
25
  function timeAgo(ts) {
@@ -32,78 +33,109 @@ function timeAgo(ts) {
32
33
  }
33
34
  function StatsBar({ counts }) {
34
35
  if (!counts) return null;
35
- return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-6 gap-3 mb-6", children: [
36
- { label: "Total", value: counts.total, color: "text-foreground" },
37
- { label: "Critical", value: counts.critical, color: "text-red-500" },
38
- { label: "High", value: counts.high, color: "text-orange-500" },
39
- { label: "Confirmed", value: counts.confirmed, color: "text-green-500" },
40
- { label: "Reported", value: counts.reported, color: "text-purple-500" },
41
- { label: "Bounty", value: counts.totalBounty > 0 ? `$${counts.totalBounty.toLocaleString()}` : "$0", color: "text-emerald-500" }
42
- ].map((s) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-3", children: [
43
- /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground uppercase tracking-wide", children: s.label }),
44
- /* @__PURE__ */ jsx("p", { className: `text-xl font-semibold mt-0.5 ${s.color}`, children: s.value })
45
- ] }, s.label)) });
36
+ const stats = [
37
+ { label: "Total", value: counts.total, text: "text-[--cyan]", glow: "glow-cyan" },
38
+ { label: "Critical", value: counts.critical, text: "text-red-500", glow: counts.critical > 0 ? "shadow-[0_0_20px_oklch(0.6_0.22_25/15%)]" : "" },
39
+ { label: "High", value: counts.high, text: "text-orange-500", glow: counts.high > 0 ? "shadow-[0_0_20px_oklch(0.7_0.17_55/15%)]" : "" },
40
+ { label: "Confirmed", value: counts.confirmed, text: "text-green-500", glow: "" },
41
+ { label: "Reported", value: counts.reported, text: "text-purple-500", glow: "" },
42
+ { label: "Bounty", value: counts.totalBounty > 0 ? `$${counts.totalBounty.toLocaleString()}` : "$0", text: "text-emerald-500", glow: counts.totalBounty > 0 ? "shadow-[0_0_20px_oklch(0.7_0.17_160/15%)]" : "" }
43
+ ];
44
+ return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3 mb-6", children: stats.map((s, i) => /* @__PURE__ */ jsxs(
45
+ motion.div,
46
+ {
47
+ initial: { opacity: 0, y: 8 },
48
+ animate: { opacity: 1, y: 0 },
49
+ transition: { duration: 0.25, delay: i * 0.05 },
50
+ className: `rounded-lg border border-white/[0.06] bg-[--card] p-3 ${s.glow}`,
51
+ children: [
52
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground uppercase tracking-wider font-mono", children: s.label }),
53
+ /* @__PURE__ */ jsx("p", { className: `text-xl font-mono font-semibold mt-0.5 ${s.text}`, children: s.value })
54
+ ]
55
+ },
56
+ s.label
57
+ )) });
46
58
  }
47
- function FindingCard({ finding, onUpdate, onDelete }) {
59
+ function FindingCard({ finding, onUpdate, onDelete, index }) {
48
60
  const [expanded, setExpanded] = useState(false);
49
61
  const sev = SEVERITY_CONFIG[finding.severity] || SEVERITY_CONFIG.info;
50
- return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border bg-card ${sev.color.split(" ")[0].replace("/15", "/5").replace("/10", "/5")}`, children: [
51
- /* @__PURE__ */ jsxs("button", { onClick: () => setExpanded(!expanded), className: "flex items-center gap-3 w-full text-left p-4 hover:bg-accent/30 rounded-lg", children: [
52
- /* @__PURE__ */ jsx("div", { className: `shrink-0 w-2 h-2 rounded-full ${sev.dot}` }),
53
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
54
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: finding.title }),
55
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5 flex-wrap", children: [
56
- /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium border ${sev.color}`, children: sev.label }),
57
- /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground", children: finding.type }),
58
- finding.agentId && /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-purple-500/10 px-2 py-0.5 text-[10px] font-medium text-purple-500", children: finding.agentId }),
59
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: timeAgo(finding.createdAt) })
60
- ] })
61
- ] }),
62
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
63
- /* @__PURE__ */ jsx(
64
- "select",
62
+ const st = STATUS_COLORS[finding.status] || STATUS_COLORS.new;
63
+ return /* @__PURE__ */ jsxs(
64
+ motion.div,
65
+ {
66
+ initial: { opacity: 0, y: 8 },
67
+ animate: { opacity: 1, y: 0 },
68
+ transition: { duration: 0.25, delay: index * 0.03 },
69
+ className: `rounded-lg border border-white/[0.06] bg-[--card] hover:border-[--cyan]/20 transition-all ${sev.glow}`,
70
+ children: [
71
+ /* @__PURE__ */ jsxs("button", { onClick: () => setExpanded(!expanded), className: "flex items-center gap-3 w-full text-left p-4 hover:bg-white/[0.02] rounded-lg transition-colors", children: [
72
+ /* @__PURE__ */ jsx("div", { className: `shrink-0 w-2.5 h-2.5 rounded-full ${sev.dot}` }),
73
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
74
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium truncate", children: finding.title }),
75
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5 flex-wrap", children: [
76
+ /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[10px] font-mono font-medium border ${sev.bg} ${sev.text} ${sev.border}`, children: sev.label }),
77
+ /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-white/5 border border-white/10 px-2 py-0.5 text-[10px] font-mono font-medium text-muted-foreground", children: finding.type }),
78
+ finding.agentId && /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-purple-500/10 border border-purple-500/20 px-2 py-0.5 text-[10px] font-mono font-medium text-purple-500", children: finding.agentId }),
79
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground", children: timeAgo(finding.createdAt) })
80
+ ] })
81
+ ] }),
82
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
83
+ /* @__PURE__ */ jsx(
84
+ "select",
85
+ {
86
+ value: finding.status,
87
+ onClick: (e) => e.stopPropagation(),
88
+ onChange: (e) => {
89
+ e.stopPropagation();
90
+ onUpdate(finding.id, { status: e.target.value });
91
+ },
92
+ className: `text-[10px] font-mono font-medium rounded-full px-2 py-0.5 border cursor-pointer bg-transparent ${st.bg} ${st.text} ${st.border} focus:outline-none`,
93
+ children: STATUS_FLOW.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: s.replace("_", " ") }, s))
94
+ }
95
+ ),
96
+ /* @__PURE__ */ jsx("span", { className: `transition-transform ${expanded ? "rotate-180" : ""}`, children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) })
97
+ ] })
98
+ ] }),
99
+ /* @__PURE__ */ jsx(AnimatePresence, { children: expanded && /* @__PURE__ */ jsx(
100
+ motion.div,
65
101
  {
66
- value: finding.status,
67
- onClick: (e) => e.stopPropagation(),
68
- onChange: (e) => {
69
- e.stopPropagation();
70
- onUpdate(finding.id, { status: e.target.value });
71
- },
72
- className: `text-[10px] font-medium rounded-full px-2 py-0.5 border-0 cursor-pointer ${STATUS_COLORS[finding.status] || ""}`,
73
- children: STATUS_FLOW.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: s.replace("_", " ") }, s))
102
+ initial: { height: 0, opacity: 0 },
103
+ animate: { height: "auto", opacity: 1 },
104
+ exit: { height: 0, opacity: 0 },
105
+ transition: { duration: 0.2 },
106
+ className: "overflow-hidden",
107
+ children: /* @__PURE__ */ jsxs("div", { className: "border-t border-white/[0.06] px-4 py-3 flex flex-col gap-3", children: [
108
+ finding.description && /* @__PURE__ */ jsxs("div", { children: [
109
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Description" }),
110
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-mono whitespace-pre-wrap text-foreground/80", children: finding.description })
111
+ ] }),
112
+ finding.stepsToReproduce && /* @__PURE__ */ jsxs("div", { children: [
113
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Steps to Reproduce" }),
114
+ /* @__PURE__ */ jsx("pre", { className: "text-[11px] bg-black/30 border border-white/[0.04] rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48 text-foreground/80 scrollbar-thin", children: finding.stepsToReproduce })
115
+ ] }),
116
+ finding.impact && /* @__PURE__ */ jsxs("div", { children: [
117
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Impact" }),
118
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-mono text-foreground/80", children: finding.impact })
119
+ ] }),
120
+ finding.rawOutput && /* @__PURE__ */ jsxs("div", { children: [
121
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Raw Output" }),
122
+ /* @__PURE__ */ jsx("pre", { className: "text-[11px] bg-black/30 border border-white/[0.04] rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-32 text-foreground/80 scrollbar-thin", children: finding.rawOutput })
123
+ ] }),
124
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-1", children: [
125
+ finding.bountyAmount > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex rounded-full bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 px-2 py-0.5 text-[10px] font-mono font-medium", children: [
126
+ "$",
127
+ finding.bountyAmount
128
+ ] }),
129
+ finding.reportUrl && /* @__PURE__ */ jsx("a", { href: finding.reportUrl, target: "_blank", rel: "noopener", className: "text-[10px] font-mono text-[--cyan] hover:underline", children: "View Report" }),
130
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
131
+ /* @__PURE__ */ jsx("button", { onClick: () => onDelete(finding.id), className: "text-[10px] font-mono text-muted-foreground hover:text-[--destructive] transition-colors", children: "Delete" })
132
+ ] })
133
+ ] })
74
134
  }
75
- ),
76
- /* @__PURE__ */ jsx("span", { className: `transition-transform ${expanded ? "rotate-180" : ""}`, children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) })
77
- ] })
78
- ] }),
79
- expanded && /* @__PURE__ */ jsxs("div", { className: "border-t px-4 py-3 flex flex-col gap-3", children: [
80
- finding.description && /* @__PURE__ */ jsxs("div", { children: [
81
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Description" }),
82
- /* @__PURE__ */ jsx("p", { className: "text-xs whitespace-pre-wrap", children: finding.description })
83
- ] }),
84
- finding.stepsToReproduce && /* @__PURE__ */ jsxs("div", { children: [
85
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Steps to Reproduce" }),
86
- /* @__PURE__ */ jsx("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48", children: finding.stepsToReproduce })
87
- ] }),
88
- finding.impact && /* @__PURE__ */ jsxs("div", { children: [
89
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Impact" }),
90
- /* @__PURE__ */ jsx("p", { className: "text-xs", children: finding.impact })
91
- ] }),
92
- finding.rawOutput && /* @__PURE__ */ jsxs("div", { children: [
93
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Raw Output" }),
94
- /* @__PURE__ */ jsx("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-32", children: finding.rawOutput })
95
- ] }),
96
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
97
- finding.bountyAmount > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex rounded-full bg-emerald-500/10 text-emerald-500 px-2 py-0.5 text-[10px] font-medium", children: [
98
- "$",
99
- finding.bountyAmount
100
- ] }),
101
- finding.reportUrl && /* @__PURE__ */ jsx("a", { href: finding.reportUrl, target: "_blank", rel: "noopener", className: "text-[10px] text-blue-500 hover:underline", children: "View Report" }),
102
- /* @__PURE__ */ jsx("div", { className: "flex-1" }),
103
- /* @__PURE__ */ jsx("button", { onClick: () => onDelete(finding.id), className: "text-[10px] text-muted-foreground hover:text-destructive", children: "Delete" })
104
- ] })
105
- ] })
106
- ] });
135
+ ) })
136
+ ]
137
+ }
138
+ );
107
139
  }
108
140
  function FindingsPage() {
109
141
  const [findings_, setFindings] = useState([]);
@@ -137,37 +169,66 @@ function FindingsPage() {
137
169
  load();
138
170
  }
139
171
  const filtered = filter === "all" ? findings_ : findings_.filter((f) => f.severity === filter);
140
- if (loading) return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-16 animate-pulse rounded-lg bg-border/50" }, i)) });
172
+ if (loading) return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-16 animate-pulse rounded-lg bg-white/[0.04] border border-white/[0.06]" }, i)) });
141
173
  return /* @__PURE__ */ jsxs(Fragment, { children: [
142
174
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
143
175
  /* @__PURE__ */ jsxs("div", { children: [
144
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: "Findings" }),
145
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Vulnerability discoveries across all agents and tools" })
176
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-mono font-semibold text-[--cyan] text-glow-cyan", children: "Findings" }),
177
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-1 font-mono", children: "Vulnerability discoveries across all agents and tools" })
146
178
  ] }),
147
- /* @__PURE__ */ jsxs("button", { onClick: () => setShowAdd(!showAdd), className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium bg-foreground text-background hover:opacity-90", children: [
148
- /* @__PURE__ */ jsx(PlusIcon, { size: 12 }),
149
- " New Finding"
150
- ] })
179
+ /* @__PURE__ */ jsxs(
180
+ "button",
181
+ {
182
+ onClick: () => setShowAdd(!showAdd),
183
+ className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-mono font-medium bg-[--cyan] text-[--primary-foreground] hover:opacity-90 transition-opacity",
184
+ children: [
185
+ /* @__PURE__ */ jsx(PlusIcon, { size: 12 }),
186
+ " New Finding"
187
+ ]
188
+ }
189
+ )
151
190
  ] }),
152
191
  /* @__PURE__ */ jsx(StatsBar, { counts }),
153
- showAdd && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 mb-4 flex flex-col gap-3", children: [
154
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-3", children: [
155
- /* @__PURE__ */ jsx("input", { placeholder: "Title", value: newFinding.title, onChange: (e) => setNewFinding({ ...newFinding, title: e.target.value }), className: "text-sm border rounded-md px-3 py-2 bg-background col-span-1 sm:col-span-3" }),
156
- /* @__PURE__ */ jsx("select", { value: newFinding.severity, onChange: (e) => setNewFinding({ ...newFinding, severity: e.target.value }), className: "text-sm border rounded-md px-3 py-2 bg-background", children: Object.keys(SEVERITY_CONFIG).map((s) => /* @__PURE__ */ jsx("option", { value: s, children: s }, s)) }),
157
- /* @__PURE__ */ jsx("select", { value: newFinding.type, onChange: (e) => setNewFinding({ ...newFinding, type: e.target.value }), className: "text-sm border rounded-md px-3 py-2 bg-background", children: FINDING_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: t }, t)) })
158
- ] }),
159
- /* @__PURE__ */ jsx("textarea", { placeholder: "Description...", value: newFinding.description, onChange: (e) => setNewFinding({ ...newFinding, description: e.target.value }), className: "text-sm border rounded-md px-3 py-2 bg-background min-h-[80px]" }),
160
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
161
- /* @__PURE__ */ jsx("button", { onClick: handleCreate, className: "inline-flex items-center gap-1 rounded-md px-3 py-1.5 text-xs font-medium bg-foreground text-background hover:opacity-90", children: "Save" }),
162
- /* @__PURE__ */ jsx("button", { onClick: () => setShowAdd(false), className: "text-xs text-muted-foreground hover:text-foreground px-3 py-1.5", children: "Cancel" })
163
- ] })
164
- ] }),
165
- /* @__PURE__ */ jsx("div", { className: "flex gap-1 mb-4 overflow-x-auto", children: ["all", ...Object.keys(SEVERITY_CONFIG)].map((f) => /* @__PURE__ */ jsx("button", { onClick: () => setFilter(f), className: `px-3 py-1 rounded-full text-xs font-medium transition-colors ${filter === f ? "bg-foreground text-background" : "bg-muted text-muted-foreground hover:text-foreground"}`, children: f === "all" ? `All (${findings_.length})` : `${SEVERITY_CONFIG[f].label} (${findings_.filter((x) => x.severity === f).length})` }, f)) }),
192
+ /* @__PURE__ */ jsx(AnimatePresence, { children: showAdd && /* @__PURE__ */ jsx(
193
+ motion.div,
194
+ {
195
+ initial: { opacity: 0, height: 0 },
196
+ animate: { opacity: 1, height: "auto" },
197
+ exit: { opacity: 0, height: 0 },
198
+ transition: { duration: 0.2 },
199
+ className: "overflow-hidden",
200
+ children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-white/[0.06] bg-[--card] p-4 mb-4 flex flex-col gap-3", children: [
201
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-3", children: [
202
+ /* @__PURE__ */ jsx("input", { placeholder: "Title", value: newFinding.title, onChange: (e) => setNewFinding({ ...newFinding, title: e.target.value }), className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 col-span-1 sm:col-span-3 font-mono placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors" }),
203
+ /* @__PURE__ */ jsx("select", { value: newFinding.severity, onChange: (e) => setNewFinding({ ...newFinding, severity: e.target.value }), className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono focus:outline-none focus:border-[--cyan]/40 transition-colors", children: Object.keys(SEVERITY_CONFIG).map((s) => /* @__PURE__ */ jsx("option", { value: s, children: s }, s)) }),
204
+ /* @__PURE__ */ jsx("select", { value: newFinding.type, onChange: (e) => setNewFinding({ ...newFinding, type: e.target.value }), className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono focus:outline-none focus:border-[--cyan]/40 transition-colors", children: FINDING_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: t }, t)) })
205
+ ] }),
206
+ /* @__PURE__ */ jsx("textarea", { placeholder: "Description...", value: newFinding.description, onChange: (e) => setNewFinding({ ...newFinding, description: e.target.value }), className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 min-h-[80px] font-mono placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors" }),
207
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
208
+ /* @__PURE__ */ jsx("button", { onClick: handleCreate, className: "inline-flex items-center gap-1 rounded-md px-3 py-1.5 text-xs font-mono font-medium bg-[--cyan] text-[--primary-foreground] hover:opacity-90 transition-opacity", children: "Save" }),
209
+ /* @__PURE__ */ jsx("button", { onClick: () => setShowAdd(false), className: "text-xs font-mono text-muted-foreground hover:text-foreground px-3 py-1.5 transition-colors", children: "Cancel" })
210
+ ] })
211
+ ] })
212
+ }
213
+ ) }),
214
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1.5 mb-4 overflow-x-auto scrollbar-thin", children: ["all", ...Object.keys(SEVERITY_CONFIG)].map((f) => {
215
+ const isActive = filter === f;
216
+ const sev = SEVERITY_CONFIG[f];
217
+ return /* @__PURE__ */ jsx(
218
+ "button",
219
+ {
220
+ onClick: () => setFilter(f),
221
+ className: `shrink-0 px-3 py-1 rounded-full text-[10px] font-mono font-medium border transition-colors ${isActive ? f === "all" ? "bg-[--cyan]/10 text-[--cyan] border-[--cyan]/20" : `${sev.bg} ${sev.text} ${sev.border}` : "border-white/[0.06] text-muted-foreground hover:text-foreground hover:border-white/[0.12]"}`,
222
+ children: f === "all" ? `All (${findings_.length})` : `${sev.label} (${findings_.filter((x) => x.severity === f).length})`
223
+ },
224
+ f
225
+ );
226
+ }) }),
166
227
  filtered.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
167
- /* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-4 mb-4", children: /* @__PURE__ */ jsx(ShieldIcon, { size: 24 }) }),
168
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "No findings yet" }),
169
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground max-w-sm", children: "Findings will appear here as your agents discover vulnerabilities, or add them manually." })
170
- ] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: filtered.map((f) => /* @__PURE__ */ jsx(FindingCard, { finding: f, onUpdate: handleUpdate, onDelete: handleDelete }, f.id)) })
228
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-white/[0.04] border border-white/[0.06] p-4 mb-4", children: /* @__PURE__ */ jsx(ShieldIcon, { size: 24 }) }),
229
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium mb-1", children: "No findings yet" }),
230
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-mono text-muted-foreground max-w-sm", children: "Findings will appear here as your agents discover vulnerabilities, or add them manually." })
231
+ ] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: filtered.map((f, i) => /* @__PURE__ */ jsx(FindingCard, { finding: f, onUpdate: handleUpdate, onDelete: handleDelete, index: i }, f.id)) })
171
232
  ] });
172
233
  }
173
234
  export {