@harbinger-ai/harbinger 0.1.0
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/LICENSE +21 -0
- package/README.md +406 -0
- package/agents/README.md +76 -0
- package/agents/_template/CONFIG.yaml +7 -0
- package/agents/_template/HEARTBEAT.md +59 -0
- package/agents/_template/IDENTITY.md +4 -0
- package/agents/_template/SKILLS.md +1 -0
- package/agents/_template/SOUL.md +25 -0
- package/agents/_template/TOOLS.md +3 -0
- package/agents/binary-reverser/CONFIG.yaml +21 -0
- package/agents/binary-reverser/HEARTBEAT.md +65 -0
- package/agents/binary-reverser/IDENTITY.md +1 -0
- package/agents/binary-reverser/SKILLS.md +1 -0
- package/agents/binary-reverser/SOUL.md +23 -0
- package/agents/binary-reverser/TOOLS.md +99 -0
- package/agents/browser-agent/CONFIG.yaml +20 -0
- package/agents/browser-agent/HEARTBEAT.md +79 -0
- package/agents/browser-agent/IDENTITY.md +5 -0
- package/agents/browser-agent/SKILLS.md +86 -0
- package/agents/browser-agent/SOUL.md +23 -0
- package/agents/browser-agent/TOOLS.md +186 -0
- package/agents/cloud-infiltrator/CONFIG.yaml +22 -0
- package/agents/cloud-infiltrator/HEARTBEAT.md +78 -0
- package/agents/cloud-infiltrator/IDENTITY.md +1 -0
- package/agents/cloud-infiltrator/SKILLS.md +1 -0
- package/agents/cloud-infiltrator/SOUL.md +23 -0
- package/agents/cloud-infiltrator/TOOLS.md +68 -0
- package/agents/coding-assistant/CONFIG.yaml +22 -0
- package/agents/coding-assistant/HEARTBEAT.md +57 -0
- package/agents/coding-assistant/IDENTITY.md +5 -0
- package/agents/coding-assistant/SKILLS.md +69 -0
- package/agents/coding-assistant/SOUL.md +60 -0
- package/agents/coding-assistant/TOOLS.md +168 -0
- package/agents/learning-agent/CONFIG.yaml +21 -0
- package/agents/learning-agent/HEARTBEAT.md +63 -0
- package/agents/learning-agent/IDENTITY.md +5 -0
- package/agents/learning-agent/SKILLS.md +86 -0
- package/agents/learning-agent/SOUL.md +77 -0
- package/agents/learning-agent/TOOLS.md +145 -0
- package/agents/maintainer/CONFIG.yaml +31 -0
- package/agents/maintainer/HEARTBEAT.md +28 -0
- package/agents/maintainer/IDENTITY.md +33 -0
- package/agents/maintainer/SKILLS.md +24 -0
- package/agents/maintainer/SOUL.md +61 -0
- package/agents/maintainer/TOOLS.md +29 -0
- package/agents/maintainer/lib/engine.js +279 -0
- package/agents/maintainer/lib/safe-fixer.js +183 -0
- package/agents/morning-brief/CONFIG.yaml +22 -0
- package/agents/morning-brief/HEARTBEAT.md +60 -0
- package/agents/morning-brief/IDENTITY.md +5 -0
- package/agents/morning-brief/SKILLS.md +56 -0
- package/agents/morning-brief/SOUL.md +64 -0
- package/agents/morning-brief/TOOLS.md +112 -0
- package/agents/osint-detective/CONFIG.yaml +24 -0
- package/agents/osint-detective/HEARTBEAT.md +66 -0
- package/agents/osint-detective/IDENTITY.md +1 -0
- package/agents/osint-detective/SKILLS.md +1 -0
- package/agents/osint-detective/SOUL.md +23 -0
- package/agents/osint-detective/TOOLS.md +81 -0
- package/agents/recon-scout/CONFIG.yaml +22 -0
- package/agents/recon-scout/HEARTBEAT.md +79 -0
- package/agents/recon-scout/IDENTITY.md +1 -0
- package/agents/recon-scout/SKILLS.md +1 -0
- package/agents/recon-scout/SOUL.md +23 -0
- package/agents/recon-scout/TOOLS.md +93 -0
- package/agents/report-writer/CONFIG.yaml +21 -0
- package/agents/report-writer/HEARTBEAT.md +63 -0
- package/agents/report-writer/IDENTITY.md +1 -0
- package/agents/report-writer/SKILLS.md +1 -0
- package/agents/report-writer/SOUL.md +23 -0
- package/agents/report-writer/TOOLS.md +69 -0
- package/agents/shared/README.md +13 -0
- package/agents/web-hacker/CONFIG.yaml +24 -0
- package/agents/web-hacker/HEARTBEAT.md +78 -0
- package/agents/web-hacker/IDENTITY.md +1 -0
- package/agents/web-hacker/SKILLS.md +1 -0
- package/agents/web-hacker/SOUL.md +23 -0
- package/agents/web-hacker/TOOLS.md +86 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +274 -0
- package/bin/cli.js +620 -0
- package/bin/local.sh +31 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +24 -0
- package/config/instrumentation.js +93 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_bounty_and_registry.sql +82 -0
- package/drizzle/0002_sync_columns.sql +7 -0
- package/drizzle/0003_graceful_bloodscream.sql +86 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0003_snapshot.json +878 -0
- package/drizzle/meta/_journal.json +34 -0
- package/drizzle/relations.ts +3 -0
- package/drizzle/schema.ts +145 -0
- package/lib/actions.js +47 -0
- package/lib/agents.js +166 -0
- package/lib/ai/agent.js +96 -0
- package/lib/ai/autonomous-engine.js +261 -0
- package/lib/ai/index.js +359 -0
- package/lib/ai/model-router.js +254 -0
- package/lib/ai/model.js +73 -0
- package/lib/ai/tools.js +84 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +27 -0
- package/lib/auth/edge-config.js +27 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +53 -0
- package/lib/bounty/actions.js +119 -0
- package/lib/bounty/findings.js +64 -0
- package/lib/bounty/programs.js +34 -0
- package/lib/bounty/sync-targets.js +267 -0
- package/lib/bounty/targets.js +33 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +148 -0
- package/lib/chat/actions.js +288 -0
- package/lib/chat/api.js +135 -0
- package/lib/chat/components/app-sidebar.js +237 -0
- package/lib/chat/components/app-sidebar.jsx +289 -0
- package/lib/chat/components/chat-header.js +27 -0
- package/lib/chat/components/chat-header.jsx +37 -0
- package/lib/chat/components/chat-input.js +230 -0
- package/lib/chat/components/chat-input.jsx +228 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +81 -0
- package/lib/chat/components/chat-page.jsx +100 -0
- package/lib/chat/components/chat.js +150 -0
- package/lib/chat/components/chat.jsx +182 -0
- package/lib/chat/components/chats-page.js +302 -0
- package/lib/chat/components/chats-page.jsx +330 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/enhanced-tool-call.js +103 -0
- package/lib/chat/components/enhanced-tool-call.jsx +139 -0
- package/lib/chat/components/findings-page.js +175 -0
- package/lib/chat/components/findings-page.jsx +214 -0
- package/lib/chat/components/greeting.js +22 -0
- package/lib/chat/components/greeting.jsx +26 -0
- package/lib/chat/components/icons.js +777 -0
- package/lib/chat/components/icons.jsx +741 -0
- package/lib/chat/components/index.js +26 -0
- package/lib/chat/components/mcp-page.js +260 -0
- package/lib/chat/components/mcp-page.jsx +355 -0
- package/lib/chat/components/message.js +289 -0
- package/lib/chat/components/message.jsx +315 -0
- package/lib/chat/components/messages.js +66 -0
- package/lib/chat/components/messages.jsx +77 -0
- package/lib/chat/components/notifications-page.js +56 -0
- package/lib/chat/components/notifications-page.jsx +87 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/registry-page.js +222 -0
- package/lib/chat/components/registry-page.jsx +255 -0
- package/lib/chat/components/settings-layout.js +40 -0
- package/lib/chat/components/settings-layout.jsx +54 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +132 -0
- package/lib/chat/components/sidebar-history-item.jsx +113 -0
- package/lib/chat/components/sidebar-history.js +115 -0
- package/lib/chat/components/sidebar-history.jsx +157 -0
- package/lib/chat/components/sidebar-user-nav.js +63 -0
- package/lib/chat/components/sidebar-user-nav.jsx +73 -0
- package/lib/chat/components/status-bar.js +39 -0
- package/lib/chat/components/status-bar.jsx +51 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/targets-page.js +376 -0
- package/lib/chat/components/targets-page.jsx +389 -0
- package/lib/chat/components/tool-call.js +86 -0
- package/lib/chat/components/tool-call.jsx +104 -0
- package/lib/chat/components/tool-panel.js +107 -0
- package/lib/chat/components/tool-panel.jsx +145 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/lib/chat/components/ui/dropdown-menu.js +98 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
- package/lib/chat/components/ui/rename-dialog.js +74 -0
- package/lib/chat/components/ui/rename-dialog.jsx +72 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +227 -0
- package/lib/chat/components/ui/sidebar.jsx +245 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/components/upgrade-dialog.js +151 -0
- package/lib/chat/components/upgrade-dialog.jsx +170 -0
- package/lib/chat/utils.js +11 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +145 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +145 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/mcp/actions.js +104 -0
- package/lib/mcp/client.js +79 -0
- package/lib/mcp/handler.js +57 -0
- package/lib/mcp/server.js +165 -0
- package/lib/paths.js +46 -0
- package/lib/registry/actions.js +164 -0
- package/lib/registry/catalog.js +137 -0
- package/lib/registry/tools.js +71 -0
- package/lib/tools/create-job.js +99 -0
- package/lib/tools/github.js +217 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +118 -0
- package/lib/utils/render-md.js +102 -0
- package/package.json +103 -0
- package/setup/lib/auth.mjs +81 -0
- package/setup/lib/env.mjs +21 -0
- package/setup/lib/fs-utils.mjs +20 -0
- package/setup/lib/github.mjs +149 -0
- package/setup/lib/prerequisites.mjs +155 -0
- package/setup/lib/prompts.mjs +267 -0
- package/setup/lib/providers.mjs +48 -0
- package/setup/lib/sync.mjs +125 -0
- package/setup/lib/targets.mjs +45 -0
- package/setup/lib/telegram-verify.mjs +63 -0
- package/setup/lib/telegram.mjs +76 -0
- package/setup/setup-telegram.mjs +264 -0
- package/setup/setup.mjs +842 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +63 -0
- package/templates/.github/workflows/auto-merge.yml +117 -0
- package/templates/.github/workflows/build-image.yml +36 -0
- package/templates/.github/workflows/notify-job-failed.yml +64 -0
- package/templates/.github/workflows/notify-pr-complete.yml +119 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
- package/templates/.github/workflows/run-job.yml +89 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
- package/templates/.gitignore.template +45 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
- package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
- package/templates/CLAUDE.md +29 -0
- package/templates/CLAUDE.md.template +307 -0
- package/templates/app/api/[...thepopebot]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +8 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/components/ascii-logo.jsx +10 -0
- package/templates/app/components/login-form.jsx +92 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +5 -0
- package/templates/app/findings/page.js +7 -0
- package/templates/app/globals.css +90 -0
- package/templates/app/layout.js +19 -0
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +7 -0
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/mcp/page.js +5 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/stream/chat/route.js +1 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/targets/page.js +7 -0
- package/templates/app/toolbox/page.js +7 -0
- package/templates/app/triggers/page.js +5 -0
- package/templates/config/AGENT.md +34 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/EVENT_HANDLER.md +224 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/MCP_SERVERS.json +1 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +90 -0
- package/templates/config/SOUL.md +17 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
- package/templates/docker/job-claude-code/Dockerfile +34 -0
- package/templates/docker/job-claude-code/entrypoint.sh +139 -0
- package/templates/docker/job-pi-coding-agent/Dockerfile +44 -0
- package/templates/docker/job-pi-coding-agent/entrypoint.sh +163 -0
- package/templates/docker-compose.yml +63 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +1 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/skills/LICENSE +21 -0
- package/templates/skills/README.md +119 -0
- package/templates/skills/brave-search/SKILL.md +79 -0
- package/templates/skills/brave-search/content.js +86 -0
- package/templates/skills/brave-search/package-lock.json +621 -0
- package/templates/skills/brave-search/package.json +14 -0
- package/templates/skills/brave-search/search.js +199 -0
- package/templates/skills/browser-tools/SKILL.md +196 -0
- package/templates/skills/browser-tools/browser-content.js +103 -0
- package/templates/skills/browser-tools/browser-cookies.js +35 -0
- package/templates/skills/browser-tools/browser-eval.js +53 -0
- package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/templates/skills/browser-tools/browser-nav.js +44 -0
- package/templates/skills/browser-tools/browser-pick.js +162 -0
- package/templates/skills/browser-tools/browser-screenshot.js +34 -0
- package/templates/skills/browser-tools/browser-start.js +87 -0
- package/templates/skills/browser-tools/package-lock.json +2556 -0
- package/templates/skills/browser-tools/package.json +19 -0
- package/templates/skills/llm-secrets/SKILL.md +34 -0
- package/templates/skills/llm-secrets/llm-secrets.js +33 -0
- package/templates/skills/modify-self/SKILL.md +12 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
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';
|
|
6
|
+
|
|
7
|
+
const CATEGORY_COLORS = {
|
|
8
|
+
recon: 'bg-blue-500/10 text-blue-500',
|
|
9
|
+
scanning: 'bg-cyan-500/10 text-cyan-500',
|
|
10
|
+
web: 'bg-orange-500/10 text-orange-500',
|
|
11
|
+
osint: 'bg-green-500/10 text-green-500',
|
|
12
|
+
cloud: 'bg-purple-500/10 text-purple-500',
|
|
13
|
+
credential: 'bg-red-500/10 text-red-500',
|
|
14
|
+
exploitation: 'bg-red-600/10 text-red-600',
|
|
15
|
+
binary: 'bg-gray-500/10 text-gray-500',
|
|
16
|
+
forensics: 'bg-yellow-500/10 text-yellow-500',
|
|
17
|
+
automation: 'bg-indigo-500/10 text-indigo-500',
|
|
18
|
+
custom: 'bg-muted text-muted-foreground',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function CatalogToolCard({ tool, installed, onInstall, installing }) {
|
|
22
|
+
return (
|
|
23
|
+
<div className="flex items-center gap-3 p-3 rounded-lg border bg-card hover:bg-accent/30 transition-colors">
|
|
24
|
+
<div className="shrink-0 rounded-md bg-muted p-2"><PackageIcon size={14} /></div>
|
|
25
|
+
<div className="flex-1 min-w-0">
|
|
26
|
+
<p className="text-sm font-medium truncate">{tool.name}</p>
|
|
27
|
+
<p className="text-[11px] text-muted-foreground mt-0.5 line-clamp-1">{tool.description}</p>
|
|
28
|
+
</div>
|
|
29
|
+
<span className={`shrink-0 inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium ${CATEGORY_COLORS[tool.category] || CATEGORY_COLORS.custom}`}>{tool.category}</span>
|
|
30
|
+
{installed ? (
|
|
31
|
+
<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"><CheckIcon size={10} /> installed</span>
|
|
32
|
+
) : (
|
|
33
|
+
<button
|
|
34
|
+
onClick={() => onInstall(tool.id)}
|
|
35
|
+
disabled={installing === tool.id}
|
|
36
|
+
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"
|
|
37
|
+
>
|
|
38
|
+
{installing === tool.id ? <SpinnerIcon size={12} /> : <DownloadIcon size={12} />}
|
|
39
|
+
Install
|
|
40
|
+
</button>
|
|
41
|
+
)}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function InstalledToolCard({ tool, onUninstall, onToggle, onSpawn }) {
|
|
47
|
+
const [expanded, setExpanded] = useState(false);
|
|
48
|
+
const disabled = !tool.enabled;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className={`rounded-lg border bg-card transition-opacity ${disabled ? 'opacity-60' : ''}`}>
|
|
52
|
+
<button onClick={() => setExpanded(!expanded)} className="flex items-center gap-3 w-full text-left p-3 hover:bg-accent/30 rounded-lg">
|
|
53
|
+
<div className="shrink-0 rounded-md bg-muted p-2"><PackageIcon size={14} /></div>
|
|
54
|
+
<div className="flex-1 min-w-0">
|
|
55
|
+
<p className="text-sm font-medium truncate">{tool.name}</p>
|
|
56
|
+
<p className="text-[11px] text-muted-foreground mt-0.5 truncate">{tool.description}</p>
|
|
57
|
+
</div>
|
|
58
|
+
<span className={`shrink-0 inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium ${CATEGORY_COLORS[tool.category] || CATEGORY_COLORS.custom}`}>{tool.category}</span>
|
|
59
|
+
<span className={`shrink-0 transition-transform ${expanded ? 'rotate-180' : ''}`}><ChevronDownIcon size={14} /></span>
|
|
60
|
+
</button>
|
|
61
|
+
{expanded && (
|
|
62
|
+
<div className="border-t px-4 py-3 flex flex-col gap-2">
|
|
63
|
+
{tool.dockerImage && <div className="flex gap-2 text-xs"><span className="text-muted-foreground">Docker:</span><span className="font-mono">{tool.dockerImage}</span></div>}
|
|
64
|
+
{tool.installCmd && <div className="flex gap-2 text-xs"><span className="text-muted-foreground">Install:</span><code className="font-mono bg-muted px-1.5 py-0.5 rounded text-[11px] break-all">{tool.installCmd}</code></div>}
|
|
65
|
+
{tool.sourceUrl && <div className="flex gap-2 text-xs"><span className="text-muted-foreground">Source:</span><a href={tool.sourceUrl} target="_blank" rel="noopener" className="text-blue-500 hover:underline truncate">{tool.sourceUrl}</a></div>}
|
|
66
|
+
<div className="flex items-center gap-2 mt-1">
|
|
67
|
+
{tool.dockerImage && <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">Spawn Container</button>}
|
|
68
|
+
<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">{tool.enabled ? 'Disable' : 'Enable'}</button>
|
|
69
|
+
<div className="flex-1" />
|
|
70
|
+
<button onClick={() => onUninstall(tool.id)} className="text-xs text-muted-foreground hover:text-destructive">Uninstall</button>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function GithubInstaller({ onInstall }) {
|
|
79
|
+
const [url, setUrl] = useState('');
|
|
80
|
+
const [loading, setLoading] = useState(false);
|
|
81
|
+
const [result, setResult] = useState(null);
|
|
82
|
+
|
|
83
|
+
async function handleInstall() {
|
|
84
|
+
if (!url) return;
|
|
85
|
+
setLoading(true);
|
|
86
|
+
setResult(null);
|
|
87
|
+
const res = await onInstall(url);
|
|
88
|
+
setResult(res);
|
|
89
|
+
setLoading(false);
|
|
90
|
+
if (!res.error) setUrl('');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div className="rounded-lg border bg-card p-4 mb-4">
|
|
95
|
+
<p className="text-sm font-medium mb-2">Install from GitHub</p>
|
|
96
|
+
<p className="text-xs text-muted-foreground mb-3">Paste a GitHub repository URL to add any tool to your registry.</p>
|
|
97
|
+
<div className="flex gap-2">
|
|
98
|
+
<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" />
|
|
99
|
+
<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">
|
|
100
|
+
{loading ? <SpinnerIcon size={12} /> : <DownloadIcon size={12} />} Install
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
{result && (
|
|
104
|
+
<p className={`text-xs mt-2 ${result.error ? 'text-destructive' : 'text-green-500'}`}>
|
|
105
|
+
{result.error || `Installed ${result.name}`}
|
|
106
|
+
</p>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function RegistryPage() {
|
|
113
|
+
const [catalog, setCatalog] = useState({ tools: [], categories: [] });
|
|
114
|
+
const [installed, setInstalled] = useState([]);
|
|
115
|
+
const [containers, setContainers] = useState([]);
|
|
116
|
+
const [loading, setLoading] = useState(true);
|
|
117
|
+
const [search, setSearch] = useState('');
|
|
118
|
+
const [activeCategory, setActiveCategory] = useState('all');
|
|
119
|
+
const [installing, setInstalling] = useState(null);
|
|
120
|
+
const [tab, setTab] = useState('catalog'); // catalog, installed, containers
|
|
121
|
+
|
|
122
|
+
async function load() {
|
|
123
|
+
const [c, i, cont] = await Promise.all([getCatalog(), getInstalledTools(), getContainers()]);
|
|
124
|
+
setCatalog(c);
|
|
125
|
+
setInstalled(i);
|
|
126
|
+
setContainers(cont);
|
|
127
|
+
setLoading(false);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
useEffect(() => { load(); }, []);
|
|
131
|
+
|
|
132
|
+
const installedSlugs = new Set(installed.map(t => t.slug));
|
|
133
|
+
|
|
134
|
+
async function handleInstall(catalogId) {
|
|
135
|
+
setInstalling(catalogId);
|
|
136
|
+
await installTool(catalogId);
|
|
137
|
+
await load();
|
|
138
|
+
setInstalling(null);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function handleGithubInstall(url) {
|
|
142
|
+
const result = await installFromGithub(url);
|
|
143
|
+
if (!result.error) await load();
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function handleUninstall(id) { await uninstallTool(id); load(); }
|
|
148
|
+
async function handleToggle(id) { await toggleTool(id); load(); }
|
|
149
|
+
async function handleSpawn(toolId) { await spawnContainer(toolId); load(); }
|
|
150
|
+
async function handleStop(id) { await stopContainer(id); load(); }
|
|
151
|
+
|
|
152
|
+
const filteredCatalog = catalog.tools.filter(t => {
|
|
153
|
+
if (activeCategory !== 'all' && t.category !== activeCategory) return false;
|
|
154
|
+
if (search && !t.name.toLowerCase().includes(search.toLowerCase()) && !t.description.toLowerCase().includes(search.toLowerCase())) return false;
|
|
155
|
+
return true;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (loading) return <div className="flex flex-col gap-3">{[...Array(5)].map((_, i) => <div key={i} className="h-14 animate-pulse rounded-lg bg-border/50" />)}</div>;
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
<div className="flex items-center justify-between mb-4">
|
|
163
|
+
<div>
|
|
164
|
+
<h1 className="text-2xl font-semibold">Toolbox</h1>
|
|
165
|
+
<p className="text-sm text-muted-foreground mt-1">{installed.length} installed, {catalog.tools.length} available in catalog</p>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
{/* Tabs */}
|
|
170
|
+
<div className="flex gap-1 border-b border-border mb-4">
|
|
171
|
+
{[
|
|
172
|
+
{ id: 'catalog', label: `Catalog (${catalog.tools.length})` },
|
|
173
|
+
{ id: 'installed', label: `Installed (${installed.length})` },
|
|
174
|
+
{ id: 'containers', label: `Containers (${containers.filter(c => c.status === 'running').length})` },
|
|
175
|
+
].map(t => (
|
|
176
|
+
<button key={t.id} 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'}`}>{t.label}</button>
|
|
177
|
+
))}
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{tab === 'catalog' && (
|
|
181
|
+
<>
|
|
182
|
+
{/* Search + category filter */}
|
|
183
|
+
<div className="flex flex-col sm:flex-row gap-3 mb-4">
|
|
184
|
+
<div className="relative flex-1">
|
|
185
|
+
<SearchIcon size={14} />
|
|
186
|
+
<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" />
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
<div className="flex gap-1 mb-4 overflow-x-auto pb-1">
|
|
190
|
+
<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'}`}>All ({catalog.tools.length})</button>
|
|
191
|
+
{catalog.categories.map(c => (
|
|
192
|
+
<button key={c.id} 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'}`}>
|
|
193
|
+
{c.name} ({catalog.tools.filter(t => t.category === c.id).length})
|
|
194
|
+
</button>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<GithubInstaller onInstall={handleGithubInstall} />
|
|
199
|
+
|
|
200
|
+
<div className="flex flex-col gap-2">
|
|
201
|
+
{filteredCatalog.map(t => (
|
|
202
|
+
<CatalogToolCard key={t.id} tool={t} installed={installedSlugs.has(t.id)} onInstall={handleInstall} installing={installing} />
|
|
203
|
+
))}
|
|
204
|
+
{filteredCatalog.length === 0 && (
|
|
205
|
+
<p className="text-center text-sm text-muted-foreground py-8">No tools match your search.</p>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
</>
|
|
209
|
+
)}
|
|
210
|
+
|
|
211
|
+
{tab === 'installed' && (
|
|
212
|
+
<>
|
|
213
|
+
{installed.length === 0 ? (
|
|
214
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
215
|
+
<div className="rounded-full bg-muted p-4 mb-4"><PackageIcon size={24} /></div>
|
|
216
|
+
<p className="text-sm font-medium mb-1">No tools installed</p>
|
|
217
|
+
<p className="text-xs text-muted-foreground">Browse the catalog to install security tools for your agents.</p>
|
|
218
|
+
</div>
|
|
219
|
+
) : (
|
|
220
|
+
<div className="flex flex-col gap-2">
|
|
221
|
+
{installed.map(t => <InstalledToolCard key={t.id} tool={t} onUninstall={handleUninstall} onToggle={handleToggle} onSpawn={handleSpawn} />)}
|
|
222
|
+
</div>
|
|
223
|
+
)}
|
|
224
|
+
</>
|
|
225
|
+
)}
|
|
226
|
+
|
|
227
|
+
{tab === 'containers' && (
|
|
228
|
+
<>
|
|
229
|
+
{containers.length === 0 ? (
|
|
230
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
231
|
+
<div className="rounded-full bg-muted p-4 mb-4"><PackageIcon size={24} /></div>
|
|
232
|
+
<p className="text-sm font-medium mb-1">No containers running</p>
|
|
233
|
+
<p className="text-xs text-muted-foreground">Spawn containers from installed tools to give agents terminal access.</p>
|
|
234
|
+
</div>
|
|
235
|
+
) : (
|
|
236
|
+
<div className="flex flex-col gap-2">
|
|
237
|
+
{containers.map(c => (
|
|
238
|
+
<div key={c.id} className="flex items-center gap-3 p-3 rounded-lg border bg-card">
|
|
239
|
+
<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'}`} />
|
|
240
|
+
<div className="flex-1 min-w-0">
|
|
241
|
+
<p className="text-sm font-mono truncate">{c.imageName}</p>
|
|
242
|
+
<p className="text-[10px] text-muted-foreground mt-0.5">{c.containerId?.slice(0, 12)} — {c.status}</p>
|
|
243
|
+
</div>
|
|
244
|
+
{c.status === 'running' && (
|
|
245
|
+
<button onClick={() => handleStop(c.id)} className="text-xs text-muted-foreground hover:text-destructive">Stop</button>
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
))}
|
|
249
|
+
</div>
|
|
250
|
+
)}
|
|
251
|
+
</>
|
|
252
|
+
)}
|
|
253
|
+
</>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { PageLayout } from "./page-layout.js";
|
|
5
|
+
import { ClockIcon, ZapIcon, KeyIcon, PlugIcon } from "./icons.js";
|
|
6
|
+
const TABS = [
|
|
7
|
+
{ id: "crons", label: "Crons", href: "/settings/crons", icon: ClockIcon },
|
|
8
|
+
{ id: "triggers", label: "Triggers", href: "/settings/triggers", icon: ZapIcon },
|
|
9
|
+
{ id: "secrets", label: "Secrets", href: "/settings/secrets", icon: KeyIcon },
|
|
10
|
+
{ id: "mcp", label: "MCP", href: "/settings/mcp", icon: PlugIcon }
|
|
11
|
+
];
|
|
12
|
+
function SettingsLayout({ session, children }) {
|
|
13
|
+
const [activePath, setActivePath] = useState("");
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
setActivePath(window.location.pathname);
|
|
16
|
+
}, []);
|
|
17
|
+
return /* @__PURE__ */ jsxs(PageLayout, { session, children: [
|
|
18
|
+
/* @__PURE__ */ jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", style: { fontFamily: "var(--font-display)" }, children: "Settings" }) }),
|
|
19
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-2 mb-6", children: TABS.map((tab) => {
|
|
20
|
+
const isActive = activePath === tab.href || activePath.startsWith(tab.href + "/");
|
|
21
|
+
const Icon = tab.icon;
|
|
22
|
+
return /* @__PURE__ */ jsxs(
|
|
23
|
+
"a",
|
|
24
|
+
{
|
|
25
|
+
href: tab.href,
|
|
26
|
+
className: `inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-xs font-mono font-medium transition-colors border ${isActive ? "bg-[--primary]/10 text-[--cyan] border-[--primary]/20" : "border-white/[0.06] text-muted-foreground hover:text-foreground hover:border-white/[0.12]"}`,
|
|
27
|
+
children: [
|
|
28
|
+
/* @__PURE__ */ jsx(Icon, { size: 12 }),
|
|
29
|
+
tab.label
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
tab.id
|
|
33
|
+
);
|
|
34
|
+
}) }),
|
|
35
|
+
children
|
|
36
|
+
] });
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
SettingsLayout
|
|
40
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { PageLayout } from './page-layout.js';
|
|
5
|
+
import { ClockIcon, ZapIcon, KeyIcon, PlugIcon } from './icons.js';
|
|
6
|
+
|
|
7
|
+
const TABS = [
|
|
8
|
+
{ id: 'crons', label: 'Crons', href: '/settings/crons', icon: ClockIcon },
|
|
9
|
+
{ id: 'triggers', label: 'Triggers', href: '/settings/triggers', icon: ZapIcon },
|
|
10
|
+
{ id: 'secrets', label: 'Secrets', href: '/settings/secrets', icon: KeyIcon },
|
|
11
|
+
{ id: 'mcp', label: 'MCP', href: '/settings/mcp', icon: PlugIcon },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export function SettingsLayout({ session, children }) {
|
|
15
|
+
const [activePath, setActivePath] = useState('');
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setActivePath(window.location.pathname);
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<PageLayout session={session}>
|
|
23
|
+
{/* Header */}
|
|
24
|
+
<div className="mb-6">
|
|
25
|
+
<h1 className="text-2xl font-semibold" style={{ fontFamily: 'var(--font-display)' }}>Settings</h1>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
{/* Tab navigation */}
|
|
29
|
+
<div className="flex gap-2 mb-6">
|
|
30
|
+
{TABS.map((tab) => {
|
|
31
|
+
const isActive = activePath === tab.href || activePath.startsWith(tab.href + '/');
|
|
32
|
+
const Icon = tab.icon;
|
|
33
|
+
return (
|
|
34
|
+
<a
|
|
35
|
+
key={tab.id}
|
|
36
|
+
href={tab.href}
|
|
37
|
+
className={`inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-xs font-mono font-medium transition-colors border ${
|
|
38
|
+
isActive
|
|
39
|
+
? 'bg-[--primary]/10 text-[--cyan] border-[--primary]/20'
|
|
40
|
+
: 'border-white/[0.06] text-muted-foreground hover:text-foreground hover:border-white/[0.12]'
|
|
41
|
+
}`}
|
|
42
|
+
>
|
|
43
|
+
<Icon size={12} />
|
|
44
|
+
{tab.label}
|
|
45
|
+
</a>
|
|
46
|
+
);
|
|
47
|
+
})}
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
{/* Tab content */}
|
|
51
|
+
{children}
|
|
52
|
+
</PageLayout>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, RefreshIcon } from "./icons.js";
|
|
5
|
+
import { createNewApiKey, getApiKeys, deleteApiKey } from "../actions.js";
|
|
6
|
+
function timeAgo(ts) {
|
|
7
|
+
if (!ts) return "Never";
|
|
8
|
+
const seconds = Math.floor((Date.now() - ts) / 1e3);
|
|
9
|
+
if (seconds < 60) return "just now";
|
|
10
|
+
const minutes = Math.floor(seconds / 60);
|
|
11
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
12
|
+
const hours = Math.floor(minutes / 60);
|
|
13
|
+
if (hours < 24) return `${hours}h ago`;
|
|
14
|
+
const days = Math.floor(hours / 24);
|
|
15
|
+
if (days < 30) return `${days}d ago`;
|
|
16
|
+
const months = Math.floor(days / 30);
|
|
17
|
+
return `${months}mo ago`;
|
|
18
|
+
}
|
|
19
|
+
function formatDate(ts) {
|
|
20
|
+
if (!ts) return "\u2014";
|
|
21
|
+
return new Date(ts).toLocaleDateString(void 0, {
|
|
22
|
+
year: "numeric",
|
|
23
|
+
month: "short",
|
|
24
|
+
day: "numeric"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function CopyButton({ text }) {
|
|
28
|
+
const [copied, setCopied] = useState(false);
|
|
29
|
+
const handleCopy = async () => {
|
|
30
|
+
try {
|
|
31
|
+
await navigator.clipboard.writeText(text);
|
|
32
|
+
setCopied(true);
|
|
33
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
34
|
+
} catch {
|
|
35
|
+
const textarea = document.createElement("textarea");
|
|
36
|
+
textarea.value = text;
|
|
37
|
+
document.body.appendChild(textarea);
|
|
38
|
+
textarea.select();
|
|
39
|
+
document.execCommand("copy");
|
|
40
|
+
document.body.removeChild(textarea);
|
|
41
|
+
setCopied(true);
|
|
42
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return /* @__PURE__ */ jsxs(
|
|
46
|
+
"button",
|
|
47
|
+
{
|
|
48
|
+
onClick: handleCopy,
|
|
49
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
50
|
+
children: [
|
|
51
|
+
copied ? /* @__PURE__ */ jsx(CheckIcon, { size: 14 }) : /* @__PURE__ */ jsx(CopyIcon, { size: 14 }),
|
|
52
|
+
copied ? "Copied" : "Copy"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function Section({ title, description, children }) {
|
|
58
|
+
return /* @__PURE__ */ jsxs("div", { className: "pb-8 mb-8 border-b border-border last:border-b-0 last:pb-0 last:mb-0", children: [
|
|
59
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-medium mb-1", children: title }),
|
|
60
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: description }),
|
|
61
|
+
children
|
|
62
|
+
] });
|
|
63
|
+
}
|
|
64
|
+
function ApiKeySection() {
|
|
65
|
+
const [currentKey, setCurrentKey] = useState(null);
|
|
66
|
+
const [loading, setLoading] = useState(true);
|
|
67
|
+
const [creating, setCreating] = useState(false);
|
|
68
|
+
const [newKey, setNewKey] = useState(null);
|
|
69
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
70
|
+
const [confirmRegenerate, setConfirmRegenerate] = useState(false);
|
|
71
|
+
const [error, setError] = useState(null);
|
|
72
|
+
const loadKey = async () => {
|
|
73
|
+
try {
|
|
74
|
+
const result = await getApiKeys();
|
|
75
|
+
setCurrentKey(result);
|
|
76
|
+
} catch {
|
|
77
|
+
} finally {
|
|
78
|
+
setLoading(false);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
loadKey();
|
|
83
|
+
}, []);
|
|
84
|
+
const handleCreate = async () => {
|
|
85
|
+
if (creating) return;
|
|
86
|
+
setCreating(true);
|
|
87
|
+
setError(null);
|
|
88
|
+
setConfirmRegenerate(false);
|
|
89
|
+
try {
|
|
90
|
+
const result = await createNewApiKey();
|
|
91
|
+
if (result.error) {
|
|
92
|
+
setError(result.error);
|
|
93
|
+
} else {
|
|
94
|
+
setNewKey(result.key);
|
|
95
|
+
await loadKey();
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
setError("Failed to create API key");
|
|
99
|
+
} finally {
|
|
100
|
+
setCreating(false);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const handleDelete = async () => {
|
|
104
|
+
if (!confirmDelete) {
|
|
105
|
+
setConfirmDelete(true);
|
|
106
|
+
setTimeout(() => setConfirmDelete(false), 3e3);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await deleteApiKey();
|
|
111
|
+
setCurrentKey(null);
|
|
112
|
+
setNewKey(null);
|
|
113
|
+
setConfirmDelete(false);
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const handleRegenerate = () => {
|
|
118
|
+
if (!confirmRegenerate) {
|
|
119
|
+
setConfirmRegenerate(true);
|
|
120
|
+
setTimeout(() => setConfirmRegenerate(false), 3e3);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
handleCreate();
|
|
124
|
+
};
|
|
125
|
+
if (loading) {
|
|
126
|
+
return /* @__PURE__ */ jsx("div", { className: "h-14 animate-pulse rounded-md bg-border/50" });
|
|
127
|
+
}
|
|
128
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
129
|
+
error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive mb-4", children: error }),
|
|
130
|
+
newKey && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-green-500/30 bg-green-500/5 p-4 mb-4", children: [
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-2", children: [
|
|
132
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-green-600 dark:text-green-400", children: "API key created \u2014 copy it now. You won't be able to see it again." }),
|
|
133
|
+
/* @__PURE__ */ jsx(
|
|
134
|
+
"button",
|
|
135
|
+
{
|
|
136
|
+
onClick: () => setNewKey(null),
|
|
137
|
+
className: "text-xs text-muted-foreground hover:text-foreground shrink-0",
|
|
138
|
+
children: "Dismiss"
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
143
|
+
/* @__PURE__ */ jsx("code", { className: "flex-1 rounded-md bg-muted px-3 py-2 text-xs font-mono break-all select-all", children: newKey }),
|
|
144
|
+
/* @__PURE__ */ jsx(CopyButton, { text: newKey })
|
|
145
|
+
] })
|
|
146
|
+
] }),
|
|
147
|
+
currentKey ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
148
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
149
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-muted p-2", children: /* @__PURE__ */ jsx(KeyIcon, { size: 16 }) }),
|
|
150
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
151
|
+
/* @__PURE__ */ jsxs("code", { className: "text-sm font-mono", children: [
|
|
152
|
+
currentKey.keyPrefix,
|
|
153
|
+
"..."
|
|
154
|
+
] }),
|
|
155
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [
|
|
156
|
+
"Created ",
|
|
157
|
+
formatDate(currentKey.createdAt),
|
|
158
|
+
currentKey.lastUsedAt && /* @__PURE__ */ jsxs("span", { className: "ml-2", children: [
|
|
159
|
+
"\xB7 Last used ",
|
|
160
|
+
timeAgo(currentKey.lastUsedAt)
|
|
161
|
+
] })
|
|
162
|
+
] })
|
|
163
|
+
] })
|
|
164
|
+
] }),
|
|
165
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
166
|
+
/* @__PURE__ */ jsxs(
|
|
167
|
+
"button",
|
|
168
|
+
{
|
|
169
|
+
onClick: handleRegenerate,
|
|
170
|
+
disabled: creating,
|
|
171
|
+
className: `inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${confirmRegenerate ? "border-yellow-500 text-yellow-600 hover:bg-yellow-500/10" : "border-border text-muted-foreground hover:bg-accent hover:text-foreground"} disabled:opacity-50`,
|
|
172
|
+
children: [
|
|
173
|
+
/* @__PURE__ */ jsx(RefreshIcon, { size: 12 }),
|
|
174
|
+
creating ? "Generating..." : confirmRegenerate ? "Confirm regenerate" : "Regenerate"
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
/* @__PURE__ */ jsxs(
|
|
179
|
+
"button",
|
|
180
|
+
{
|
|
181
|
+
onClick: handleDelete,
|
|
182
|
+
className: `inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${confirmDelete ? "border-destructive text-destructive hover:bg-destructive/10" : "border-border text-muted-foreground hover:text-destructive hover:border-destructive/50"}`,
|
|
183
|
+
children: [
|
|
184
|
+
/* @__PURE__ */ jsx(TrashIcon, { size: 12 }),
|
|
185
|
+
confirmDelete ? "Confirm delete" : "Delete"
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
] })
|
|
190
|
+
] }) }) : /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed bg-card p-6 flex flex-col items-center text-center", children: [
|
|
191
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-3", children: "No API key configured" }),
|
|
192
|
+
/* @__PURE__ */ jsx(
|
|
193
|
+
"button",
|
|
194
|
+
{
|
|
195
|
+
onClick: handleCreate,
|
|
196
|
+
disabled: creating,
|
|
197
|
+
className: "inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 disabled:pointer-events-none",
|
|
198
|
+
children: creating ? "Creating..." : "Create API key"
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
] })
|
|
202
|
+
] });
|
|
203
|
+
}
|
|
204
|
+
function SettingsSecretsPage() {
|
|
205
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
206
|
+
Section,
|
|
207
|
+
{
|
|
208
|
+
title: "API Key",
|
|
209
|
+
description: "Authenticates external requests to /api endpoints. Pass via the x-api-key header.",
|
|
210
|
+
children: /* @__PURE__ */ jsx(ApiKeySection, {})
|
|
211
|
+
}
|
|
212
|
+
) });
|
|
213
|
+
}
|
|
214
|
+
export {
|
|
215
|
+
SettingsSecretsPage
|
|
216
|
+
};
|