@harbinger-ai/harbinger 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bounty/actions.js +45 -0
- package/lib/chat/actions.js +715 -0
- package/lib/chat/components/agents-page.js +918 -0
- package/lib/chat/components/agents-page.jsx +869 -0
- package/lib/chat/components/app-sidebar.js +17 -1
- package/lib/chat/components/app-sidebar.jsx +19 -1
- package/lib/chat/components/icons.js +145 -0
- package/lib/chat/components/icons.jsx +171 -0
- package/lib/chat/components/index.js +2 -0
- package/lib/chat/components/mcp-page.js +383 -55
- package/lib/chat/components/mcp-page.jsx +404 -101
- package/lib/chat/components/page-layout.js +41 -2
- package/lib/chat/components/page-layout.jsx +40 -2
- package/lib/chat/components/settings-layout.js +3 -2
- package/lib/chat/components/settings-layout.jsx +2 -1
- package/lib/chat/components/settings-providers-page.js +872 -0
- package/lib/chat/components/settings-providers-page.jsx +917 -0
- package/lib/chat/components/settings-secrets-page.js +91 -66
- package/lib/chat/components/settings-secrets-page.jsx +83 -72
- package/lib/chat/components/targets-page.js +554 -96
- package/lib/chat/components/targets-page.jsx +464 -114
- package/lib/mcp/actions.js +120 -0
- package/lib/mcp/registry.js +164 -0
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { motion } from "framer-motion";
|
|
5
|
+
import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, RefreshIcon, SpinnerIcon } from "./icons.js";
|
|
5
6
|
import { createNewApiKey, getApiKeys, deleteApiKey } from "../actions.js";
|
|
6
7
|
function timeAgo(ts) {
|
|
7
8
|
if (!ts) return "Never";
|
|
@@ -46,7 +47,7 @@ function CopyButton({ text }) {
|
|
|
46
47
|
"button",
|
|
47
48
|
{
|
|
48
49
|
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-
|
|
50
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors",
|
|
50
51
|
children: [
|
|
51
52
|
copied ? /* @__PURE__ */ jsx(CheckIcon, { size: 14 }) : /* @__PURE__ */ jsx(CopyIcon, { size: 14 }),
|
|
52
53
|
copied ? "Copied" : "Copy"
|
|
@@ -54,11 +55,10 @@ function CopyButton({ text }) {
|
|
|
54
55
|
}
|
|
55
56
|
);
|
|
56
57
|
}
|
|
57
|
-
function
|
|
58
|
-
return /* @__PURE__ */ jsxs("div", { className: "pb-
|
|
59
|
-
/* @__PURE__ */ jsx("
|
|
60
|
-
description && /* @__PURE__ */ jsx("p", { className: "text-
|
|
61
|
-
children
|
|
58
|
+
function SectionHeader({ label, description }) {
|
|
59
|
+
return /* @__PURE__ */ jsxs("div", { className: "pb-2 mb-4", children: [
|
|
60
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider", children: label }),
|
|
61
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 font-mono", children: description })
|
|
62
62
|
] });
|
|
63
63
|
}
|
|
64
64
|
function ApiKeySection() {
|
|
@@ -123,93 +123,118 @@ function ApiKeySection() {
|
|
|
123
123
|
handleCreate();
|
|
124
124
|
};
|
|
125
125
|
if (loading) {
|
|
126
|
-
return /* @__PURE__ */ jsx("div", { className: "h-
|
|
126
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx("div", { className: "h-16 animate-shimmer rounded-lg border border-white/[0.06] bg-[--card]" }) });
|
|
127
127
|
}
|
|
128
128
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
129
|
-
error && /* @__PURE__ */ jsx("
|
|
130
|
-
newKey && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-green-500/
|
|
131
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-
|
|
132
|
-
/* @__PURE__ */
|
|
129
|
+
error && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-[--destructive]/20 bg-[--destructive]/5 px-3 py-2 mb-4", children: /* @__PURE__ */ jsx("p", { className: "text-xs font-mono text-[--destructive]", children: error }) }),
|
|
130
|
+
newKey && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-green-500/20 bg-green-500/5 p-4 mb-4", children: [
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mb-2", children: [
|
|
132
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
133
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
|
|
134
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
|
|
135
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] text-green-500 ml-1", children: "new api key" }),
|
|
133
138
|
/* @__PURE__ */ jsx(
|
|
134
139
|
"button",
|
|
135
140
|
{
|
|
136
141
|
onClick: () => setNewKey(null),
|
|
137
|
-
className: "text-
|
|
142
|
+
className: "text-[10px] font-mono text-muted-foreground hover:text-foreground ml-auto",
|
|
138
143
|
children: "Dismiss"
|
|
139
144
|
}
|
|
140
145
|
)
|
|
141
146
|
] }),
|
|
147
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-mono text-green-500 mb-2", children: "Copy this key now. You won't be able to see it again." }),
|
|
142
148
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
143
|
-
/* @__PURE__ */ jsx("code", { className: "flex-1 rounded-md bg-
|
|
149
|
+
/* @__PURE__ */ jsx("code", { className: "flex-1 rounded-md bg-black/30 border border-white/[0.04] px-3 py-2 text-[11px] font-mono break-all select-all text-foreground/80", children: newKey }),
|
|
144
150
|
/* @__PURE__ */ jsx(CopyButton, { text: newKey })
|
|
145
151
|
] })
|
|
146
152
|
] }),
|
|
147
|
-
currentKey ? /* @__PURE__ */ jsx(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
currentKey ? /* @__PURE__ */ jsx(
|
|
154
|
+
motion.div,
|
|
155
|
+
{
|
|
156
|
+
initial: { opacity: 0, y: 8 },
|
|
157
|
+
animate: { opacity: 1, y: 0 },
|
|
158
|
+
className: "rounded-lg border border-white/[0.06] bg-[--card] hover:border-[--cyan]/20 transition-colors",
|
|
159
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-4", children: [
|
|
160
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
|
|
161
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
|
|
162
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
|
|
163
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
|
|
154
164
|
] }),
|
|
155
|
-
/* @__PURE__ */
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
"
|
|
160
|
-
|
|
165
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-[--cyan]/10 p-2", children: /* @__PURE__ */ jsx(KeyIcon, { size: 16, className: "text-[--cyan]" }) }),
|
|
166
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
167
|
+
/* @__PURE__ */ jsxs("code", { className: "text-sm font-mono text-foreground", children: [
|
|
168
|
+
currentKey.keyPrefix,
|
|
169
|
+
"..."
|
|
170
|
+
] }),
|
|
171
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground mt-0.5 font-mono", children: [
|
|
172
|
+
"Created ",
|
|
173
|
+
formatDate(currentKey.createdAt),
|
|
174
|
+
currentKey.lastUsedAt && /* @__PURE__ */ jsxs("span", { className: "ml-2", children: [
|
|
175
|
+
"\\u00b7 Last used ",
|
|
176
|
+
timeAgo(currentKey.lastUsedAt)
|
|
177
|
+
] })
|
|
161
178
|
] })
|
|
179
|
+
] }),
|
|
180
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
181
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[--success]" }),
|
|
182
|
+
/* @__PURE__ */ jsxs(
|
|
183
|
+
"button",
|
|
184
|
+
{
|
|
185
|
+
onClick: handleRegenerate,
|
|
186
|
+
disabled: creating,
|
|
187
|
+
className: `inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border transition-colors disabled:opacity-50 ${confirmRegenerate ? "border-yellow-500/30 text-yellow-500 hover:bg-yellow-500/10" : "border-white/[0.06] text-muted-foreground hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan]"}`,
|
|
188
|
+
children: [
|
|
189
|
+
creating ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(RefreshIcon, { size: 12 }),
|
|
190
|
+
creating ? "Generating..." : confirmRegenerate ? "Confirm" : "Regenerate"
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
),
|
|
194
|
+
/* @__PURE__ */ jsxs(
|
|
195
|
+
"button",
|
|
196
|
+
{
|
|
197
|
+
onClick: handleDelete,
|
|
198
|
+
className: `inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border transition-colors ${confirmDelete ? "border-[--destructive]/30 text-[--destructive] hover:bg-[--destructive]/10" : "border-white/[0.06] text-muted-foreground hover:text-[--destructive] hover:border-[--destructive]/20"}`,
|
|
199
|
+
children: [
|
|
200
|
+
/* @__PURE__ */ jsx(TrashIcon, { size: 12 }),
|
|
201
|
+
confirmDelete ? "Confirm" : "Delete"
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
)
|
|
162
205
|
] })
|
|
163
206
|
] })
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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(
|
|
207
|
+
}
|
|
208
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border border-white/[0.06] bg-[--card]", children: [
|
|
209
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-[--cyan]/10 p-4 mb-4", children: /* @__PURE__ */ jsx(KeyIcon, { size: 24, className: "text-[--cyan]" }) }),
|
|
210
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium mb-1", children: "No API key configured" }),
|
|
211
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground font-mono mb-4", children: "Generate a key to authenticate external requests to /api endpoints." }),
|
|
212
|
+
/* @__PURE__ */ jsxs(
|
|
193
213
|
"button",
|
|
194
214
|
{
|
|
195
215
|
onClick: handleCreate,
|
|
196
216
|
disabled: creating,
|
|
197
|
-
className: "inline-flex items-center gap-
|
|
198
|
-
children:
|
|
217
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 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",
|
|
218
|
+
children: [
|
|
219
|
+
creating ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(KeyIcon, { size: 12 }),
|
|
220
|
+
creating ? "Creating..." : "Create API Key"
|
|
221
|
+
]
|
|
199
222
|
}
|
|
200
223
|
)
|
|
201
224
|
] })
|
|
202
225
|
] });
|
|
203
226
|
}
|
|
204
227
|
function SettingsSecretsPage() {
|
|
205
|
-
return /* @__PURE__ */
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
228
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
229
|
+
/* @__PURE__ */ jsx(
|
|
230
|
+
SectionHeader,
|
|
231
|
+
{
|
|
232
|
+
label: "API Key",
|
|
233
|
+
description: "Authenticates external requests to /api endpoints. Pass via the x-api-key header."
|
|
234
|
+
}
|
|
235
|
+
),
|
|
236
|
+
/* @__PURE__ */ jsx(ApiKeySection, {})
|
|
237
|
+
] });
|
|
213
238
|
}
|
|
214
239
|
export {
|
|
215
240
|
SettingsSecretsPage
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { motion } from 'framer-motion';
|
|
5
|
+
import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, RefreshIcon, SpinnerIcon } from './icons.js';
|
|
5
6
|
import { createNewApiKey, getApiKeys, deleteApiKey } from '../actions.js';
|
|
6
7
|
|
|
7
8
|
function timeAgo(ts) {
|
|
@@ -19,7 +20,7 @@ function timeAgo(ts) {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
function formatDate(ts) {
|
|
22
|
-
if (!ts) return '
|
|
23
|
+
if (!ts) return '\u2014';
|
|
23
24
|
return new Date(ts).toLocaleDateString(undefined, {
|
|
24
25
|
year: 'numeric',
|
|
25
26
|
month: 'short',
|
|
@@ -50,7 +51,7 @@ function CopyButton({ text }) {
|
|
|
50
51
|
return (
|
|
51
52
|
<button
|
|
52
53
|
onClick={handleCopy}
|
|
53
|
-
className="inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border border-
|
|
54
|
+
className="inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors"
|
|
54
55
|
>
|
|
55
56
|
{copied ? <CheckIcon size={14} /> : <CopyIcon size={14} />}
|
|
56
57
|
{copied ? 'Copied' : 'Copy'}
|
|
@@ -58,25 +59,20 @@ function CopyButton({ text }) {
|
|
|
58
59
|
);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
//
|
|
62
|
-
// Section wrapper — reusable for each secrets section
|
|
63
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
62
|
+
// ─── Section Header ──────────────────────────────────────────────────────────
|
|
64
63
|
|
|
65
|
-
function
|
|
64
|
+
function SectionHeader({ label, description }) {
|
|
66
65
|
return (
|
|
67
|
-
<div className="pb-
|
|
68
|
-
<
|
|
66
|
+
<div className="pb-2 mb-4">
|
|
67
|
+
<span className="font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider">{label}</span>
|
|
69
68
|
{description && (
|
|
70
|
-
<p className="text-
|
|
69
|
+
<p className="text-xs text-muted-foreground mt-1 font-mono">{description}</p>
|
|
71
70
|
)}
|
|
72
|
-
{children}
|
|
73
71
|
</div>
|
|
74
72
|
);
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
//
|
|
78
|
-
// API Key section
|
|
79
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
75
|
+
// ─── API Key section ─────────────────────────────────────────────────────────
|
|
80
76
|
|
|
81
77
|
function ApiKeySection() {
|
|
82
78
|
const [currentKey, setCurrentKey] = useState(null);
|
|
@@ -92,7 +88,6 @@ function ApiKeySection() {
|
|
|
92
88
|
const result = await getApiKeys();
|
|
93
89
|
setCurrentKey(result);
|
|
94
90
|
} catch {
|
|
95
|
-
// ignore
|
|
96
91
|
} finally {
|
|
97
92
|
setLoading(false);
|
|
98
93
|
}
|
|
@@ -133,9 +128,7 @@ function ApiKeySection() {
|
|
|
133
128
|
setCurrentKey(null);
|
|
134
129
|
setNewKey(null);
|
|
135
130
|
setConfirmDelete(false);
|
|
136
|
-
} catch {
|
|
137
|
-
// ignore
|
|
138
|
-
}
|
|
131
|
+
} catch {}
|
|
139
132
|
};
|
|
140
133
|
|
|
141
134
|
const handleRegenerate = () => {
|
|
@@ -148,31 +141,43 @@ function ApiKeySection() {
|
|
|
148
141
|
};
|
|
149
142
|
|
|
150
143
|
if (loading) {
|
|
151
|
-
return
|
|
144
|
+
return (
|
|
145
|
+
<div className="flex flex-col gap-3">
|
|
146
|
+
<div className="h-16 animate-shimmer rounded-lg border border-white/[0.06] bg-[--card]" />
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
152
149
|
}
|
|
153
150
|
|
|
154
151
|
return (
|
|
155
152
|
<div>
|
|
156
153
|
{error && (
|
|
157
|
-
<
|
|
154
|
+
<div className="rounded-md border border-[--destructive]/20 bg-[--destructive]/5 px-3 py-2 mb-4">
|
|
155
|
+
<p className="text-xs font-mono text-[--destructive]">{error}</p>
|
|
156
|
+
</div>
|
|
158
157
|
)}
|
|
159
158
|
|
|
160
159
|
{/* New key banner */}
|
|
161
160
|
{newKey && (
|
|
162
|
-
<div className="rounded-lg border border-green-500/
|
|
163
|
-
<div className="flex items-
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
<div className="rounded-lg border border-green-500/20 bg-green-500/5 p-4 mb-4">
|
|
162
|
+
<div className="flex items-center gap-1.5 mb-2">
|
|
163
|
+
<div className="flex items-center gap-1">
|
|
164
|
+
<div className="w-2 h-2 rounded-full bg-[#ff5f57]" />
|
|
165
|
+
<div className="w-2 h-2 rounded-full bg-[#febc2e]" />
|
|
166
|
+
<div className="w-2 h-2 rounded-full bg-[#28c840]" />
|
|
167
|
+
</div>
|
|
168
|
+
<span className="font-mono text-[9px] text-green-500 ml-1">new api key</span>
|
|
167
169
|
<button
|
|
168
170
|
onClick={() => setNewKey(null)}
|
|
169
|
-
className="text-
|
|
171
|
+
className="text-[10px] font-mono text-muted-foreground hover:text-foreground ml-auto"
|
|
170
172
|
>
|
|
171
173
|
Dismiss
|
|
172
174
|
</button>
|
|
173
175
|
</div>
|
|
176
|
+
<p className="text-xs font-mono text-green-500 mb-2">
|
|
177
|
+
Copy this key now. You won't be able to see it again.
|
|
178
|
+
</p>
|
|
174
179
|
<div className="flex items-center gap-2">
|
|
175
|
-
<code className="flex-1 rounded-md bg-
|
|
180
|
+
<code className="flex-1 rounded-md bg-black/30 border border-white/[0.04] px-3 py-2 text-[11px] font-mono break-all select-all text-foreground/80">
|
|
176
181
|
{newKey}
|
|
177
182
|
</code>
|
|
178
183
|
<CopyButton text={newKey} />
|
|
@@ -181,58 +186,73 @@ function ApiKeySection() {
|
|
|
181
186
|
)}
|
|
182
187
|
|
|
183
188
|
{currentKey ? (
|
|
184
|
-
<div
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
</p>
|
|
198
|
-
</div>
|
|
189
|
+
<motion.div
|
|
190
|
+
initial={{ opacity: 0, y: 8 }}
|
|
191
|
+
animate={{ opacity: 1, y: 0 }}
|
|
192
|
+
className="rounded-lg border border-white/[0.06] bg-[--card] hover:border-[--cyan]/20 transition-colors"
|
|
193
|
+
>
|
|
194
|
+
<div className="flex items-center gap-3 p-4">
|
|
195
|
+
<div className="flex items-center gap-1 shrink-0">
|
|
196
|
+
<div className="w-2 h-2 rounded-full bg-[#ff5f57]" />
|
|
197
|
+
<div className="w-2 h-2 rounded-full bg-[#febc2e]" />
|
|
198
|
+
<div className="w-2 h-2 rounded-full bg-[#28c840]" />
|
|
199
|
+
</div>
|
|
200
|
+
<div className="shrink-0 rounded-md bg-[--cyan]/10 p-2">
|
|
201
|
+
<KeyIcon size={16} className="text-[--cyan]" />
|
|
199
202
|
</div>
|
|
200
|
-
<div className="flex
|
|
203
|
+
<div className="flex-1 min-w-0">
|
|
204
|
+
<code className="text-sm font-mono text-foreground">{currentKey.keyPrefix}...</code>
|
|
205
|
+
<p className="text-[10px] text-muted-foreground mt-0.5 font-mono">
|
|
206
|
+
Created {formatDate(currentKey.createdAt)}
|
|
207
|
+
{currentKey.lastUsedAt && (
|
|
208
|
+
<span className="ml-2">\u00b7 Last used {timeAgo(currentKey.lastUsedAt)}</span>
|
|
209
|
+
)}
|
|
210
|
+
</p>
|
|
211
|
+
</div>
|
|
212
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
213
|
+
<div className="w-2 h-2 rounded-full bg-[--success]" />
|
|
201
214
|
<button
|
|
202
215
|
onClick={handleRegenerate}
|
|
203
216
|
disabled={creating}
|
|
204
|
-
className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${
|
|
217
|
+
className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border transition-colors disabled:opacity-50 ${
|
|
205
218
|
confirmRegenerate
|
|
206
|
-
? 'border-yellow-500 text-yellow-
|
|
207
|
-
: 'border-
|
|
208
|
-
}
|
|
219
|
+
? 'border-yellow-500/30 text-yellow-500 hover:bg-yellow-500/10'
|
|
220
|
+
: 'border-white/[0.06] text-muted-foreground hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan]'
|
|
221
|
+
}`}
|
|
209
222
|
>
|
|
210
|
-
<RefreshIcon size={12} />
|
|
211
|
-
{creating ? 'Generating...' : confirmRegenerate ? 'Confirm
|
|
223
|
+
{creating ? <SpinnerIcon size={12} /> : <RefreshIcon size={12} />}
|
|
224
|
+
{creating ? 'Generating...' : confirmRegenerate ? 'Confirm' : 'Regenerate'}
|
|
212
225
|
</button>
|
|
213
226
|
<button
|
|
214
227
|
onClick={handleDelete}
|
|
215
|
-
className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${
|
|
228
|
+
className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border transition-colors ${
|
|
216
229
|
confirmDelete
|
|
217
|
-
? 'border-destructive text-destructive hover:bg-destructive/10'
|
|
218
|
-
: 'border-
|
|
230
|
+
? 'border-[--destructive]/30 text-[--destructive] hover:bg-[--destructive]/10'
|
|
231
|
+
: 'border-white/[0.06] text-muted-foreground hover:text-[--destructive] hover:border-[--destructive]/20'
|
|
219
232
|
}`}
|
|
220
233
|
>
|
|
221
234
|
<TrashIcon size={12} />
|
|
222
|
-
{confirmDelete ? 'Confirm
|
|
235
|
+
{confirmDelete ? 'Confirm' : 'Delete'}
|
|
223
236
|
</button>
|
|
224
237
|
</div>
|
|
225
238
|
</div>
|
|
226
|
-
</div>
|
|
239
|
+
</motion.div>
|
|
227
240
|
) : (
|
|
228
|
-
<div className="
|
|
229
|
-
<
|
|
241
|
+
<div className="flex flex-col items-center justify-center py-12 text-center rounded-lg border border-white/[0.06] bg-[--card]">
|
|
242
|
+
<div className="rounded-full bg-[--cyan]/10 p-4 mb-4">
|
|
243
|
+
<KeyIcon size={24} className="text-[--cyan]" />
|
|
244
|
+
</div>
|
|
245
|
+
<p className="text-sm font-mono font-medium mb-1">No API key configured</p>
|
|
246
|
+
<p className="text-[11px] text-muted-foreground font-mono mb-4">
|
|
247
|
+
Generate a key to authenticate external requests to /api endpoints.
|
|
248
|
+
</p>
|
|
230
249
|
<button
|
|
231
250
|
onClick={handleCreate}
|
|
232
251
|
disabled={creating}
|
|
233
|
-
className="inline-flex items-center gap-
|
|
252
|
+
className="inline-flex items-center gap-1.5 rounded-md px-4 py-2 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"
|
|
234
253
|
>
|
|
235
|
-
{creating ?
|
|
254
|
+
{creating ? <SpinnerIcon size={12} /> : <KeyIcon size={12} />}
|
|
255
|
+
{creating ? 'Creating...' : 'Create API Key'}
|
|
236
256
|
</button>
|
|
237
257
|
</div>
|
|
238
258
|
)}
|
|
@@ -240,25 +260,16 @@ function ApiKeySection() {
|
|
|
240
260
|
);
|
|
241
261
|
}
|
|
242
262
|
|
|
243
|
-
//
|
|
244
|
-
// Main page
|
|
245
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
263
|
+
// ─── Main Page ───────────────────────────────────────────────────────────────
|
|
246
264
|
|
|
247
265
|
export function SettingsSecretsPage() {
|
|
248
266
|
return (
|
|
249
267
|
<div>
|
|
250
|
-
<
|
|
251
|
-
|
|
268
|
+
<SectionHeader
|
|
269
|
+
label="API Key"
|
|
252
270
|
description="Authenticates external requests to /api endpoints. Pass via the x-api-key header."
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
</Section>
|
|
256
|
-
|
|
257
|
-
{/* Future sections go here, e.g.:
|
|
258
|
-
<Section title="GitHub Token" description="...">
|
|
259
|
-
<GitHubTokenSection />
|
|
260
|
-
</Section>
|
|
261
|
-
*/}
|
|
271
|
+
/>
|
|
272
|
+
<ApiKeySection />
|
|
262
273
|
</div>
|
|
263
274
|
);
|
|
264
275
|
}
|