@harbinger-ai/harbinger 0.1.0 → 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.
Files changed (65) hide show
  1. package/bin/cli.js +40 -38
  2. package/bin/local.sh +2 -2
  3. package/bin/postinstall.js +8 -8
  4. package/config/index.js +6 -3
  5. package/config/instrumentation.js +11 -11
  6. package/lib/chat/actions.js +19 -0
  7. package/lib/chat/components/app-sidebar.js +17 -1
  8. package/lib/chat/components/app-sidebar.jsx +19 -1
  9. package/lib/chat/components/findings-page.js +164 -103
  10. package/lib/chat/components/findings-page.jsx +156 -101
  11. package/lib/chat/components/icons.js +22 -0
  12. package/lib/chat/components/icons.jsx +20 -0
  13. package/lib/chat/components/index.js +1 -0
  14. package/lib/chat/components/mission-control.js +490 -0
  15. package/lib/chat/components/mission-control.jsx +618 -0
  16. package/lib/chat/components/registry-page.js +267 -133
  17. package/lib/chat/components/registry-page.jsx +299 -138
  18. package/lib/chat/components/sidebar-user-nav.js +1 -1
  19. package/lib/chat/components/sidebar-user-nav.jsx +1 -1
  20. package/lib/chat/components/targets-page.js +269 -200
  21. package/lib/chat/components/targets-page.jsx +181 -111
  22. package/lib/chat/components/upgrade-dialog.js +2 -2
  23. package/lib/chat/components/upgrade-dialog.jsx +2 -2
  24. package/lib/cron.js +11 -7
  25. package/lib/db/index.js +6 -1
  26. package/lib/mcp/actions.js +1 -1
  27. package/lib/mcp/handler.js +2 -2
  28. package/lib/mcp/server.js +1 -1
  29. package/lib/paths.js +1 -1
  30. package/package.json +1 -1
  31. package/templates/.env.example +4 -4
  32. package/templates/.github/workflows/rebuild-event-handler.yml +20 -20
  33. package/templates/.github/workflows/run-job.yml +6 -6
  34. package/templates/.github/workflows/upgrade-event-handler.yml +12 -12
  35. package/templates/CLAUDE.md +3 -3
  36. package/templates/CLAUDE.md.template +9 -9
  37. package/templates/app/api/[...thepopebot]/route.js +1 -1
  38. package/templates/app/api/auth/[...nextauth]/route.js +1 -1
  39. package/templates/app/chat/[chatId]/page.js +2 -2
  40. package/templates/app/chats/page.js +2 -2
  41. package/templates/app/components/setup-form.jsx +1 -1
  42. package/templates/app/findings/page.js +2 -2
  43. package/templates/app/globals.css +1 -1
  44. package/templates/app/layout.js +1 -1
  45. package/templates/app/login/page.js +1 -1
  46. package/templates/app/notifications/page.js +2 -2
  47. package/templates/app/page.js +2 -2
  48. package/templates/app/settings/crons/page.js +1 -1
  49. package/templates/app/settings/layout.js +2 -2
  50. package/templates/app/settings/mcp/page.js +1 -1
  51. package/templates/app/settings/secrets/page.js +1 -1
  52. package/templates/app/settings/triggers/page.js +1 -1
  53. package/templates/app/stream/chat/route.js +1 -1
  54. package/templates/app/swarm/page.js +2 -2
  55. package/templates/app/targets/page.js +2 -2
  56. package/templates/app/toolbox/page.js +2 -2
  57. package/templates/config/AGENT.md +2 -2
  58. package/templates/config/EVENT_HANDLER.md +3 -3
  59. package/templates/config/SKILL_BUILDING_GUIDE.md +1 -1
  60. package/templates/config/SOUL.md +1 -1
  61. package/templates/docker/event-handler/Dockerfile +1 -1
  62. package/templates/docker-compose.yml +2 -2
  63. package/templates/instrumentation.js +1 -1
  64. package/templates/middleware.js +1 -1
  65. package/templates/next.config.mjs +2 -2
@@ -1,80 +1,122 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from "react";
4
- import { PackageIcon, PlusIcon, SearchIcon, SpinnerIcon, CheckIcon, TrashIcon, ChevronDownIcon, DownloadIcon, GlobeIcon } from "./icons.js";
5
- import { getCatalog, getInstalledTools, installTool, installCustomTool, uninstallTool, toggleTool, installFromGithub, getContainers, spawnContainer, stopContainer } from "../../registry/actions.js";
4
+ import { motion, AnimatePresence } from "framer-motion";
5
+ import { PackageIcon, SearchIcon, SpinnerIcon, CheckIcon, ChevronDownIcon, DownloadIcon, GlobeIcon } from "./icons.js";
6
+ import { getCatalog, getInstalledTools, installTool, uninstallTool, toggleTool, installFromGithub, getContainers, spawnContainer, stopContainer } from "../../registry/actions.js";
6
7
  const CATEGORY_COLORS = {
7
- recon: "bg-blue-500/10 text-blue-500",
8
- scanning: "bg-cyan-500/10 text-cyan-500",
9
- web: "bg-orange-500/10 text-orange-500",
10
- osint: "bg-green-500/10 text-green-500",
11
- cloud: "bg-purple-500/10 text-purple-500",
12
- credential: "bg-red-500/10 text-red-500",
13
- exploitation: "bg-red-600/10 text-red-600",
14
- binary: "bg-gray-500/10 text-gray-500",
15
- forensics: "bg-yellow-500/10 text-yellow-500",
16
- automation: "bg-indigo-500/10 text-indigo-500",
17
- custom: "bg-muted text-muted-foreground"
8
+ recon: { bg: "bg-blue-500/10", text: "text-blue-500", border: "border-blue-500/20" },
9
+ scanning: { bg: "bg-cyan-500/10", text: "text-cyan-500", border: "border-cyan-500/20" },
10
+ web: { bg: "bg-orange-500/10", text: "text-orange-500", border: "border-orange-500/20" },
11
+ osint: { bg: "bg-green-500/10", text: "text-green-500", border: "border-green-500/20" },
12
+ cloud: { bg: "bg-purple-500/10", text: "text-purple-500", border: "border-purple-500/20" },
13
+ credential: { bg: "bg-red-500/10", text: "text-red-500", border: "border-red-500/20" },
14
+ exploitation: { bg: "bg-red-600/10", text: "text-red-600", border: "border-red-600/20" },
15
+ binary: { bg: "bg-gray-500/10", text: "text-gray-500", border: "border-gray-500/20" },
16
+ forensics: { bg: "bg-yellow-500/10", text: "text-yellow-500", border: "border-yellow-500/20" },
17
+ automation: { bg: "bg-indigo-500/10", text: "text-indigo-500", border: "border-indigo-500/20" },
18
+ custom: { bg: "bg-white/5", text: "text-muted-foreground", border: "border-white/10" }
18
19
  };
19
- function CatalogToolCard({ tool, installed, onInstall, installing }) {
20
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3 rounded-lg border bg-card hover:bg-accent/30 transition-colors", children: [
21
- /* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-muted p-2", children: /* @__PURE__ */ jsx(PackageIcon, { size: 14 }) }),
22
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
23
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: tool.name }),
24
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-0.5 line-clamp-1", children: tool.description })
25
- ] }),
26
- /* @__PURE__ */ jsx("span", { className: `shrink-0 inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium ${CATEGORY_COLORS[tool.category] || CATEGORY_COLORS.custom}`, children: tool.category }),
27
- installed ? /* @__PURE__ */ jsxs("span", { className: "shrink-0 inline-flex items-center gap-1 rounded-full bg-green-500/10 text-green-500 px-2 py-0.5 text-[10px] font-medium", children: [
28
- /* @__PURE__ */ jsx(CheckIcon, { size: 10 }),
29
- " installed"
30
- ] }) : /* @__PURE__ */ jsxs(
31
- "button",
32
- {
33
- onClick: () => onInstall(tool.id),
34
- disabled: installing === tool.id,
35
- className: "shrink-0 inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium border hover:bg-accent/50 transition-colors disabled:opacity-50",
36
- children: [
37
- installing === tool.id ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(DownloadIcon, { size: 12 }),
38
- "Install"
39
- ]
40
- }
41
- )
42
- ] });
20
+ function getCatStyle(cat) {
21
+ return CATEGORY_COLORS[cat] || CATEGORY_COLORS.custom;
43
22
  }
44
- function InstalledToolCard({ tool, onUninstall, onToggle, onSpawn }) {
23
+ function CatalogToolCard({ tool, installed, onInstall, installing, index }) {
24
+ const cat = getCatStyle(tool.category);
25
+ return /* @__PURE__ */ jsx(
26
+ motion.div,
27
+ {
28
+ initial: { opacity: 0, y: 8 },
29
+ animate: { opacity: 1, y: 0 },
30
+ transition: { duration: 0.25, delay: index * 0.02 },
31
+ className: "group rounded-lg border border-white/[0.06] bg-[--card] hover:border-[--cyan]/20 transition-colors overflow-hidden",
32
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3", children: [
33
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
34
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
35
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
36
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
37
+ ] }),
38
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
39
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium truncate", children: tool.name }),
40
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-0.5 line-clamp-1 font-mono", children: tool.description })
41
+ ] }),
42
+ /* @__PURE__ */ jsx("span", { className: `shrink-0 inline-flex rounded-full px-2 py-0.5 text-[10px] font-mono font-medium border ${cat.bg} ${cat.text} ${cat.border}`, children: tool.category }),
43
+ installed ? /* @__PURE__ */ jsxs("span", { className: "shrink-0 inline-flex items-center gap-1 rounded-full bg-green-500/10 text-green-500 border border-green-500/20 px-2 py-0.5 text-[10px] font-mono font-medium", children: [
44
+ /* @__PURE__ */ jsx(CheckIcon, { size: 10 }),
45
+ " installed"
46
+ ] }) : /* @__PURE__ */ jsxs(
47
+ "button",
48
+ {
49
+ onClick: () => onInstall(tool.id),
50
+ disabled: installing === tool.id,
51
+ className: "shrink-0 inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20 hover:bg-[--cyan] hover:text-[--primary-foreground] transition-colors disabled:opacity-50",
52
+ children: [
53
+ installing === tool.id ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(DownloadIcon, { size: 12 }),
54
+ "Install"
55
+ ]
56
+ }
57
+ )
58
+ ] })
59
+ }
60
+ );
61
+ }
62
+ function InstalledToolCard({ tool, onUninstall, onToggle, onSpawn, index }) {
45
63
  const [expanded, setExpanded] = useState(false);
46
64
  const disabled = !tool.enabled;
47
- return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border bg-card transition-opacity ${disabled ? "opacity-60" : ""}`, children: [
48
- /* @__PURE__ */ jsxs("button", { onClick: () => setExpanded(!expanded), className: "flex items-center gap-3 w-full text-left p-3 hover:bg-accent/30 rounded-lg", children: [
49
- /* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-muted p-2", children: /* @__PURE__ */ jsx(PackageIcon, { size: 14 }) }),
50
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
51
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: tool.name }),
52
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-0.5 truncate", children: tool.description })
53
- ] }),
54
- /* @__PURE__ */ jsx("span", { className: `shrink-0 inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium ${CATEGORY_COLORS[tool.category] || CATEGORY_COLORS.custom}`, children: tool.category }),
55
- /* @__PURE__ */ jsx("span", { className: `shrink-0 transition-transform ${expanded ? "rotate-180" : ""}`, children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) })
56
- ] }),
57
- expanded && /* @__PURE__ */ jsxs("div", { className: "border-t px-4 py-3 flex flex-col gap-2", children: [
58
- tool.dockerImage && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs", children: [
59
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Docker:" }),
60
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: tool.dockerImage })
61
- ] }),
62
- tool.installCmd && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs", children: [
63
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Install:" }),
64
- /* @__PURE__ */ jsx("code", { className: "font-mono bg-muted px-1.5 py-0.5 rounded text-[11px] break-all", children: tool.installCmd })
65
- ] }),
66
- tool.sourceUrl && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs", children: [
67
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Source:" }),
68
- /* @__PURE__ */ jsx("a", { href: tool.sourceUrl, target: "_blank", rel: "noopener", className: "text-blue-500 hover:underline truncate", children: tool.sourceUrl })
69
- ] }),
70
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1", children: [
71
- tool.dockerImage && /* @__PURE__ */ jsx("button", { onClick: () => onSpawn(tool.id), className: "inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium border hover:bg-accent/50", children: "Spawn Container" }),
72
- /* @__PURE__ */ jsx("button", { onClick: () => onToggle(tool.id), className: "inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium border hover:bg-accent/50", children: tool.enabled ? "Disable" : "Enable" }),
73
- /* @__PURE__ */ jsx("div", { className: "flex-1" }),
74
- /* @__PURE__ */ jsx("button", { onClick: () => onUninstall(tool.id), className: "text-xs text-muted-foreground hover:text-destructive", children: "Uninstall" })
75
- ] })
76
- ] })
77
- ] });
65
+ const cat = getCatStyle(tool.category);
66
+ return /* @__PURE__ */ jsxs(
67
+ motion.div,
68
+ {
69
+ initial: { opacity: 0, y: 8 },
70
+ animate: { opacity: 1, y: 0 },
71
+ transition: { duration: 0.25, delay: index * 0.03 },
72
+ className: `rounded-lg border border-white/[0.06] bg-[--card] transition-all ${disabled ? "opacity-50" : "hover:border-[--cyan]/20"}`,
73
+ children: [
74
+ /* @__PURE__ */ jsxs("button", { onClick: () => setExpanded(!expanded), className: "flex items-center gap-3 w-full text-left p-3 hover:bg-white/[0.02] rounded-lg transition-colors", children: [
75
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
76
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
77
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
78
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
79
+ ] }),
80
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
81
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium truncate", children: tool.name }),
82
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-0.5 truncate font-mono", children: tool.description })
83
+ ] }),
84
+ /* @__PURE__ */ jsx("span", { className: `shrink-0 inline-flex rounded-full px-2 py-0.5 text-[10px] font-mono font-medium border ${cat.bg} ${cat.text} ${cat.border}`, children: tool.category }),
85
+ /* @__PURE__ */ jsx("span", { className: `shrink-0 transition-transform ${expanded ? "rotate-180" : ""}`, children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) })
86
+ ] }),
87
+ /* @__PURE__ */ jsx(AnimatePresence, { children: expanded && /* @__PURE__ */ jsx(
88
+ motion.div,
89
+ {
90
+ initial: { height: 0, opacity: 0 },
91
+ animate: { height: "auto", opacity: 1 },
92
+ exit: { height: 0, opacity: 0 },
93
+ transition: { duration: 0.2 },
94
+ className: "overflow-hidden",
95
+ children: /* @__PURE__ */ jsxs("div", { className: "border-t border-white/[0.06] px-4 py-3 flex flex-col gap-2", children: [
96
+ tool.dockerImage && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs items-baseline", children: [
97
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-mono text-[10px] uppercase tracking-wider w-14 shrink-0", children: "Docker" }),
98
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-foreground/80", children: tool.dockerImage })
99
+ ] }),
100
+ tool.installCmd && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs items-baseline", children: [
101
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-mono text-[10px] uppercase tracking-wider w-14 shrink-0", children: "Install" }),
102
+ /* @__PURE__ */ jsx("code", { className: "font-mono bg-black/30 px-2 py-0.5 rounded text-[11px] text-foreground/80 border border-white/[0.04] break-all", children: tool.installCmd })
103
+ ] }),
104
+ tool.sourceUrl && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs items-baseline", children: [
105
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-mono text-[10px] uppercase tracking-wider w-14 shrink-0", children: "Source" }),
106
+ /* @__PURE__ */ jsx("a", { href: tool.sourceUrl, target: "_blank", rel: "noopener", className: "text-[--cyan] hover:underline truncate font-mono", children: tool.sourceUrl })
107
+ ] }),
108
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
109
+ tool.dockerImage && /* @__PURE__ */ jsx("button", { onClick: () => onSpawn(tool.id), className: "inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20 hover:bg-[--cyan] hover:text-[--primary-foreground] transition-colors", children: "Spawn" }),
110
+ /* @__PURE__ */ jsx("button", { onClick: () => onToggle(tool.id), className: "inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] transition-colors", children: tool.enabled ? "Disable" : "Enable" }),
111
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
112
+ /* @__PURE__ */ jsx("button", { onClick: () => onUninstall(tool.id), className: "text-xs font-mono text-muted-foreground hover:text-[--destructive] transition-colors", children: "Uninstall" })
113
+ ] })
114
+ ] })
115
+ }
116
+ ) })
117
+ ]
118
+ }
119
+ );
78
120
  }
79
121
  function GithubInstaller({ onInstall }) {
80
122
  const [url, setUrl] = useState("");
@@ -89,17 +131,36 @@ function GithubInstaller({ onInstall }) {
89
131
  setLoading(false);
90
132
  if (!res.error) setUrl("");
91
133
  }
92
- return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 mb-4", children: [
93
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-2", children: "Install from GitHub" }),
94
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-3", children: "Paste a GitHub repository URL to add any tool to your registry." }),
134
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-white/[0.06] bg-[--card] p-4 mb-4", children: [
135
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
136
+ /* @__PURE__ */ jsx(GlobeIcon, { size: 14 }),
137
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium text-[--cyan]", children: "Install from GitHub" })
138
+ ] }),
139
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mb-3 font-mono", children: "Paste a GitHub repository URL to add any tool to your registry." }),
95
140
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
96
- /* @__PURE__ */ jsx("input", { placeholder: "https://github.com/owner/repo", value: url, onChange: (e) => setUrl(e.target.value), className: "flex-1 text-sm border rounded-md px-3 py-2 bg-background font-mono" }),
97
- /* @__PURE__ */ jsxs("button", { onClick: handleInstall, disabled: loading || !url, className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-medium bg-foreground text-background hover:opacity-90 disabled:opacity-50", children: [
98
- loading ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(DownloadIcon, { size: 12 }),
99
- " Install"
100
- ] })
141
+ /* @__PURE__ */ jsx(
142
+ "input",
143
+ {
144
+ placeholder: "https://github.com/owner/repo",
145
+ value: url,
146
+ onChange: (e) => setUrl(e.target.value),
147
+ className: "flex-1 text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors"
148
+ }
149
+ ),
150
+ /* @__PURE__ */ jsxs(
151
+ "button",
152
+ {
153
+ onClick: handleInstall,
154
+ disabled: loading || !url,
155
+ className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium bg-[--cyan] text-[--primary-foreground] hover:opacity-90 disabled:opacity-50 transition-opacity",
156
+ children: [
157
+ loading ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(DownloadIcon, { size: 12 }),
158
+ " Install"
159
+ ]
160
+ }
161
+ )
101
162
  ] }),
102
- result && /* @__PURE__ */ jsx("p", { className: `text-xs mt-2 ${result.error ? "text-destructive" : "text-green-500"}`, children: result.error || `Installed ${result.name}` })
163
+ result && /* @__PURE__ */ jsx("p", { className: `text-xs font-mono mt-2 ${result.error ? "text-[--destructive]" : "text-green-500"}`, children: result.error || `Installed ${result.name}` })
103
164
  ] });
104
165
  }
105
166
  function RegistryPage() {
@@ -122,6 +183,7 @@ function RegistryPage() {
122
183
  load();
123
184
  }, []);
124
185
  const installedSlugs = new Set(installed.map((t) => t.slug));
186
+ const runningContainers = containers.filter((c) => c.status === "running").length;
125
187
  async function handleInstall(catalogId) {
126
188
  setInstalling(catalogId);
127
189
  await installTool(catalogId);
@@ -154,67 +216,139 @@ function RegistryPage() {
154
216
  if (search && !t.name.toLowerCase().includes(search.toLowerCase()) && !t.description.toLowerCase().includes(search.toLowerCase())) return false;
155
217
  return true;
156
218
  });
157
- if (loading) return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(5)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-14 animate-pulse rounded-lg bg-border/50" }, i)) });
219
+ if (loading) {
220
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(5)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-14 animate-pulse rounded-lg bg-white/[0.04] border border-white/[0.06]" }, i)) });
221
+ }
222
+ const tabs = [
223
+ { id: "catalog", label: "CATALOG", count: catalog.tools.length },
224
+ { id: "installed", label: "INSTALLED", count: installed.length },
225
+ { id: "containers", label: "CONTAINERS", count: runningContainers }
226
+ ];
158
227
  return /* @__PURE__ */ jsxs(Fragment, { children: [
159
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between mb-4", children: /* @__PURE__ */ jsxs("div", { children: [
160
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: "Toolbox" }),
161
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mt-1", children: [
162
- installed.length,
163
- " installed, ",
164
- catalog.tools.length,
165
- " available in catalog"
166
- ] })
167
- ] }) }),
168
- /* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b border-border mb-4", children: [
169
- { id: "catalog", label: `Catalog (${catalog.tools.length})` },
170
- { id: "installed", label: `Installed (${installed.length})` },
171
- { id: "containers", label: `Containers (${containers.filter((c) => c.status === "running").length})` }
172
- ].map((t) => /* @__PURE__ */ jsx("button", { onClick: () => setTab(t.id), className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${tab === t.id ? "border-foreground text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"}`, children: t.label }, t.id)) }),
173
- tab === "catalog" && /* @__PURE__ */ jsxs(Fragment, { children: [
174
- /* @__PURE__ */ jsx("div", { className: "flex flex-col sm:flex-row gap-3 mb-4", children: /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
175
- /* @__PURE__ */ jsx(SearchIcon, { size: 14 }),
176
- /* @__PURE__ */ jsx("input", { placeholder: "Search tools...", value: search, onChange: (e) => setSearch(e.target.value), className: "w-full text-sm border rounded-md pl-3 pr-3 py-2 bg-background" })
177
- ] }) }),
178
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1 mb-4 overflow-x-auto pb-1", children: [
179
- /* @__PURE__ */ jsxs("button", { onClick: () => setActiveCategory("all"), className: `shrink-0 px-3 py-1 rounded-full text-xs font-medium transition-colors ${activeCategory === "all" ? "bg-foreground text-background" : "bg-muted text-muted-foreground hover:text-foreground"}`, children: [
180
- "All (",
228
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-6", children: [
229
+ /* @__PURE__ */ jsxs("div", { children: [
230
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-mono font-semibold text-[--cyan] text-glow-cyan", children: "Toolbox" }),
231
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-1 font-mono", children: "Security tool registry and container management" })
232
+ ] }),
233
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
234
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-[10px] font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20", children: [
181
235
  catalog.tools.length,
182
- ")"
236
+ " tools"
183
237
  ] }),
184
- catalog.categories.map((c) => /* @__PURE__ */ jsxs("button", { onClick: () => setActiveCategory(c.id), className: `shrink-0 px-3 py-1 rounded-full text-xs font-medium transition-colors ${activeCategory === c.id ? "bg-foreground text-background" : "bg-muted text-muted-foreground hover:text-foreground"}`, children: [
185
- c.name,
186
- " (",
187
- catalog.tools.filter((t) => t.category === c.id).length,
188
- ")"
189
- ] }, c.id))
190
- ] }),
191
- /* @__PURE__ */ jsx(GithubInstaller, { onInstall: handleGithubInstall }),
192
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
193
- filteredCatalog.map((t) => /* @__PURE__ */ jsx(CatalogToolCard, { tool: t, installed: installedSlugs.has(t.id), onInstall: handleInstall, installing }, t.id)),
194
- filteredCatalog.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-center text-sm text-muted-foreground py-8", children: "No tools match your search." })
238
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-[10px] font-mono font-medium bg-green-500/10 text-green-500 border border-green-500/20", children: [
239
+ installed.length,
240
+ " installed"
241
+ ] }),
242
+ runningContainers > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-[10px] font-mono font-medium bg-orange-500/10 text-orange-500 border border-orange-500/20", children: [
243
+ /* @__PURE__ */ jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-orange-500 animate-pulse" }),
244
+ runningContainers,
245
+ " running"
246
+ ] })
195
247
  ] })
196
248
  ] }),
197
- tab === "installed" && /* @__PURE__ */ jsx(Fragment, { children: installed.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
198
- /* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-4 mb-4", children: /* @__PURE__ */ jsx(PackageIcon, { size: 24 }) }),
199
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "No tools installed" }),
200
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Browse the catalog to install security tools for your agents." })
201
- ] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: installed.map((t) => /* @__PURE__ */ jsx(InstalledToolCard, { tool: t, onUninstall: handleUninstall, onToggle: handleToggle, onSpawn: handleSpawn }, t.id)) }) }),
202
- tab === "containers" && /* @__PURE__ */ jsx(Fragment, { children: containers.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
203
- /* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-4 mb-4", children: /* @__PURE__ */ jsx(PackageIcon, { size: 24 }) }),
204
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "No containers running" }),
205
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Spawn containers from installed tools to give agents terminal access." })
206
- ] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: containers.map((c) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3 rounded-lg border bg-card", children: [
207
- /* @__PURE__ */ jsx("div", { className: `shrink-0 w-2 h-2 rounded-full ${c.status === "running" ? "bg-green-500 animate-pulse" : c.status === "stopped" ? "bg-muted-foreground" : "bg-red-500"}` }),
208
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
209
- /* @__PURE__ */ jsx("p", { className: "text-sm font-mono truncate", children: c.imageName }),
210
- /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: [
211
- c.containerId?.slice(0, 12),
212
- " \u2014 ",
213
- c.status
249
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b border-white/[0.06] mb-4", children: tabs.map((t) => /* @__PURE__ */ jsxs(
250
+ "button",
251
+ {
252
+ onClick: () => setTab(t.id),
253
+ className: `px-4 py-2.5 text-[11px] font-mono font-medium uppercase tracking-wider border-b-2 transition-colors ${tab === t.id ? "border-[--cyan] text-[--cyan]" : "border-transparent text-muted-foreground hover:text-foreground"}`,
254
+ children: [
255
+ t.label,
256
+ " ",
257
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
258
+ "(",
259
+ t.count,
260
+ ")"
261
+ ] })
262
+ ]
263
+ },
264
+ t.id
265
+ )) }),
266
+ /* @__PURE__ */ jsxs(AnimatePresence, { mode: "wait", children: [
267
+ tab === "catalog" && /* @__PURE__ */ jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, children: [
268
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col sm:flex-row gap-3 mb-4", children: /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
269
+ /* @__PURE__ */ jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground", children: /* @__PURE__ */ jsx(SearchIcon, { size: 14 }) }),
270
+ /* @__PURE__ */ jsx(
271
+ "input",
272
+ {
273
+ placeholder: "Search tools...",
274
+ value: search,
275
+ onChange: (e) => setSearch(e.target.value),
276
+ className: "w-full text-sm border border-white/[0.06] rounded-md pl-9 pr-3 py-2 bg-black/20 font-mono placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors"
277
+ }
278
+ )
279
+ ] }) }),
280
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5 mb-4 overflow-x-auto pb-1 scrollbar-thin", children: [
281
+ /* @__PURE__ */ jsxs(
282
+ "button",
283
+ {
284
+ onClick: () => setActiveCategory("all"),
285
+ className: `shrink-0 px-3 py-1 rounded-full text-[10px] font-mono font-medium border transition-colors ${activeCategory === "all" ? "bg-[--cyan]/10 text-[--cyan] border-[--cyan]/20" : "border-white/[0.06] text-muted-foreground hover:text-foreground hover:border-white/[0.12]"}`,
286
+ children: [
287
+ "All (",
288
+ catalog.tools.length,
289
+ ")"
290
+ ]
291
+ }
292
+ ),
293
+ catalog.categories.map((c) => {
294
+ const cs = getCatStyle(c.id);
295
+ const isActive = activeCategory === c.id;
296
+ return /* @__PURE__ */ jsxs(
297
+ "button",
298
+ {
299
+ onClick: () => setActiveCategory(c.id),
300
+ className: `shrink-0 px-3 py-1 rounded-full text-[10px] font-mono font-medium border transition-colors ${isActive ? `${cs.bg} ${cs.text} ${cs.border}` : "border-white/[0.06] text-muted-foreground hover:text-foreground hover:border-white/[0.12]"}`,
301
+ children: [
302
+ c.name,
303
+ " (",
304
+ catalog.tools.filter((t) => t.category === c.id).length,
305
+ ")"
306
+ ]
307
+ },
308
+ c.id
309
+ );
310
+ })
311
+ ] }),
312
+ /* @__PURE__ */ jsx(GithubInstaller, { onInstall: handleGithubInstall }),
313
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
314
+ filteredCatalog.map((t, i) => /* @__PURE__ */ jsx(CatalogToolCard, { tool: t, installed: installedSlugs.has(t.id), onInstall: handleInstall, installing, index: i }, t.id)),
315
+ filteredCatalog.length === 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center py-12 text-center", children: [
316
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-white/[0.04] border border-white/[0.06] p-4 mb-4", children: /* @__PURE__ */ jsx(SearchIcon, { size: 24 }) }),
317
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono text-muted-foreground", children: "No tools match your search." })
318
+ ] })
214
319
  ] })
215
- ] }),
216
- c.status === "running" && /* @__PURE__ */ jsx("button", { onClick: () => handleStop(c.id), className: "text-xs text-muted-foreground hover:text-destructive", children: "Stop" })
217
- ] }, c.id)) }) })
320
+ ] }, "catalog"),
321
+ tab === "installed" && /* @__PURE__ */ jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, children: installed.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
322
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-white/[0.04] border border-white/[0.06] p-4 mb-4", children: /* @__PURE__ */ jsx(PackageIcon, { size: 24 }) }),
323
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium mb-1", children: "No tools installed" }),
324
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground font-mono", children: "Browse the catalog to install security tools for your agents." })
325
+ ] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: installed.map((t, i) => /* @__PURE__ */ jsx(InstalledToolCard, { tool: t, onUninstall: handleUninstall, onToggle: handleToggle, onSpawn: handleSpawn, index: i }, t.id)) }) }, "installed"),
326
+ tab === "containers" && /* @__PURE__ */ jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, children: containers.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
327
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-white/[0.04] border border-white/[0.06] p-4 mb-4", children: /* @__PURE__ */ jsx(PackageIcon, { size: 24 }) }),
328
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium mb-1", children: "No containers running" }),
329
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground font-mono", children: "Spawn containers from installed tools to give agents terminal access." })
330
+ ] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: containers.map((c, i) => /* @__PURE__ */ jsxs(
331
+ motion.div,
332
+ {
333
+ initial: { opacity: 0, y: 8 },
334
+ animate: { opacity: 1, y: 0 },
335
+ transition: { duration: 0.25, delay: i * 0.03 },
336
+ className: "flex items-center gap-3 p-3 rounded-lg border border-white/[0.06] bg-[--card] hover:border-[--cyan]/20 transition-colors",
337
+ children: [
338
+ /* @__PURE__ */ jsx("div", { className: `shrink-0 w-2.5 h-2.5 rounded-full ${c.status === "running" ? "bg-green-500 animate-pulse" : c.status === "stopped" ? "bg-muted-foreground" : "bg-red-500"}` }),
339
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
340
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium truncate", children: c.imageName }),
341
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5", children: [
342
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground", children: c.containerId?.slice(0, 12) }),
343
+ /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[9px] font-mono font-medium ${c.status === "running" ? "bg-green-500/10 text-green-500 border border-green-500/20" : "bg-white/5 text-muted-foreground border border-white/10"}`, children: c.status })
344
+ ] })
345
+ ] }),
346
+ c.status === "running" && /* @__PURE__ */ jsx("button", { onClick: () => handleStop(c.id), className: "text-xs font-mono text-muted-foreground hover:text-[--destructive] transition-colors", children: "Stop" })
347
+ ]
348
+ },
349
+ c.id
350
+ )) }) }, "containers")
351
+ ] })
218
352
  ] });
219
353
  }
220
354
  export {