@cybermem/dashboard 0.5.16 → 0.8.5

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.
@@ -1,86 +1,103 @@
1
- "use client"
2
-
3
- import { Button } from "@/components/ui/button"
4
- import { Input } from "@/components/ui/input"
5
- import { Label } from "@/components/ui/label"
6
- import { useDashboard } from "@/lib/data/dashboard-context"
7
- import { Check, Copy, Eye, EyeOff, FileCode, Info, Monitor, X } from "lucide-react"
8
- import Image from "next/image"
9
- import { useEffect, useState } from "react"
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import { Input } from "@/components/ui/input";
5
+ import { Label } from "@/components/ui/label";
6
+ import { useDashboard } from "@/lib/data/dashboard-context";
7
+ import {
8
+ Check,
9
+ Copy,
10
+ Eye,
11
+ EyeOff,
12
+ FileCode,
13
+ Info,
14
+ Monitor,
15
+ X,
16
+ } from "lucide-react";
17
+ import Image from "next/image";
18
+ import { useEffect, useState } from "react";
10
19
 
11
20
  export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
12
- const { clientConfigs } = useDashboard()
13
- const clients = clientConfigs
14
- const [selectedClient, setSelectedClient] = useState("claude")
15
- const [copiedId, setCopiedId] = useState<string | null>(null)
16
- const [apiKey, setApiKey] = useState("")
17
- const [baseUrl, setBaseUrl] = useState("http://localhost:8080")
18
- const [isLoading, setIsLoading] = useState(true)
19
- const [isKeyVisible, setIsKeyVisible] = useState(false)
20
- const [showRegenConfirm, setShowRegenConfirm] = useState(false)
21
- const [regenInputValue, setRegenInputValue] = useState("")
22
- const [isManaged, setIsManaged] = useState(false) // true = local mode, no API key needed
21
+ const { clientConfigs } = useDashboard();
22
+ const clients = clientConfigs;
23
+ const [selectedClient, setSelectedClient] = useState("claude");
24
+ const [copiedId, setCopiedId] = useState<string | null>(null);
25
+ const [apiKey, setApiKey] = useState("");
26
+ const [baseUrl, setBaseUrl] = useState("http://localhost:8080");
27
+ const [isLoading, setIsLoading] = useState(true);
28
+ const [isKeyVisible, setIsKeyVisible] = useState(false);
29
+ const [showRegenConfirm, setShowRegenConfirm] = useState(false);
30
+ const [regenInputValue, setRegenInputValue] = useState("");
31
+ const [isManaged, setIsManaged] = useState(false); // true = local mode, no API key needed
23
32
 
24
33
  useEffect(() => {
25
34
  // Try to get key from local storage first (simulating persistence)
26
- const localKey = localStorage.getItem("om_api_key")
35
+ const localKey = localStorage.getItem("om_api_key");
27
36
  if (localKey) {
28
- setApiKey(localKey)
29
- // We still fetch settings for the endpoint
30
- fetch("/api/settings")
31
- .then(res => res.json())
32
- .then(data => {
33
- let srvEndpoint = data.endpoint
34
- if (srvEndpoint.includes('localhost') && typeof window !== "undefined" && !window.location.hostname.includes('localhost')) {
35
- const port = srvEndpoint.split(':').pop()?.split('/')[0] || '8626'
36
- srvEndpoint = `${window.location.protocol}//${window.location.hostname}:${port}`
37
- }
38
- setBaseUrl(srvEndpoint)
39
- setIsLoading(false)
37
+ setApiKey(localKey);
38
+ // We still fetch settings for the endpoint
39
+ fetch("/api/settings")
40
+ .then((res) => res.json())
41
+ .then((data) => {
42
+ let srvEndpoint = data.endpoint;
43
+ if (
44
+ srvEndpoint.includes("localhost") &&
45
+ typeof window !== "undefined" &&
46
+ !window.location.hostname.includes("localhost")
47
+ ) {
48
+ const port = srvEndpoint.split(":").pop()?.split("/")[0] || "8626";
49
+ srvEndpoint = `${window.location.protocol}//${window.location.hostname}:${port}`;
50
+ }
51
+ setBaseUrl(srvEndpoint);
52
+ setIsLoading(false);
40
53
  })
41
- .catch(err => setIsLoading(false))
54
+ .catch((err) => setIsLoading(false));
42
55
  } else {
43
- fetch("/api/settings")
44
- .then(res => res.json())
45
- .then(data => {
46
- setApiKey(data.apiKey !== 'not-set' ? data.apiKey : '')
47
- setIsManaged(data.isManaged || false)
48
- let srvEndpoint = data.endpoint
49
- if (srvEndpoint.includes('localhost') && typeof window !== "undefined" && !window.location.hostname.includes('localhost')) {
50
- const port = srvEndpoint.split(':').pop()?.split('/')[0] || '8626'
51
- srvEndpoint = `${window.location.protocol}//${window.location.hostname}:${port}`
52
- }
53
- setBaseUrl(srvEndpoint)
54
- setIsLoading(false)
55
- })
56
- .catch(err => {
57
- console.error("Failed to fetch settings:", err)
58
- setIsLoading(false)
59
- })
56
+ fetch("/api/settings")
57
+ .then((res) => res.json())
58
+ .then((data) => {
59
+ setApiKey(data.apiKey !== "not-set" ? data.apiKey : "");
60
+ setIsManaged(data.isManaged || false);
61
+ let srvEndpoint = data.endpoint;
62
+ if (
63
+ srvEndpoint.includes("localhost") &&
64
+ typeof window !== "undefined" &&
65
+ !window.location.hostname.includes("localhost")
66
+ ) {
67
+ const port = srvEndpoint.split(":").pop()?.split("/")[0] || "8626";
68
+ srvEndpoint = `${window.location.protocol}//${window.location.hostname}:${port}`;
69
+ }
70
+ setBaseUrl(srvEndpoint);
71
+ setIsLoading(false);
72
+ })
73
+ .catch((err) => {
74
+ console.error("Failed to fetch settings:", err);
75
+ setIsLoading(false);
76
+ });
60
77
  }
61
- }, [])
78
+ }, []);
62
79
 
63
80
  const generateApiKey = () => {
64
81
  // Legacy - redirected to confirmRegenerate logic via UI state
65
- }
82
+ };
66
83
 
67
84
  const confirmRegenerate = async () => {
68
85
  try {
69
- const res = await fetch('/api/settings/regenerate', { method: 'POST' })
70
- if (!res.ok) throw new Error('Failed to regenerate key')
71
- const data = await res.json()
72
-
73
- const newKey = data.apiKey
74
- setApiKey(newKey)
75
- localStorage.setItem("om_api_key", newKey)
76
- setIsKeyVisible(true)
77
- setShowRegenConfirm(false)
78
- setRegenInputValue("")
86
+ const res = await fetch("/api/settings/regenerate", { method: "POST" });
87
+ if (!res.ok) throw new Error("Failed to regenerate key");
88
+ const data = await res.json();
89
+
90
+ const newKey = data.apiKey;
91
+ setApiKey(newKey);
92
+ localStorage.setItem("om_api_key", newKey);
93
+ setIsKeyVisible(true);
94
+ setShowRegenConfirm(false);
95
+ setRegenInputValue("");
79
96
  } catch (e) {
80
- console.error(e)
81
- alert("Failed to regenerate key on server.")
97
+ console.error(e);
98
+ alert("Failed to regenerate key on server.");
82
99
  }
83
- }
100
+ };
84
101
 
85
102
  const getMcpConfig = (clientId: string) => {
86
103
  // Local mode: use stdio (command-based) - no server needed, runs via npx
@@ -89,53 +106,59 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
89
106
  mcpServers: {
90
107
  cybermem: {
91
108
  command: "npx",
92
- args: ["@cybermem/mcp"]
93
- }
94
- }
95
- }
109
+ args: ["@cybermem/mcp"],
110
+ },
111
+ },
112
+ };
96
113
  }
97
114
 
98
- // Remote mode: use stdio with --url and --api-key args (universal for all clients)
115
+ // Remote mode: use stdio with --url and --token args (universal for all clients)
99
116
  return {
100
117
  mcpServers: {
101
118
  cybermem: {
102
119
  command: "npx",
103
120
  args: [
104
- "-y", "@cybermem/mcp",
105
- "--url", baseUrl,
106
- "--api-key", apiKey || "sk-your-generated-key"
107
- ]
108
- }
109
- }
110
- }
111
- }
121
+ "-y",
122
+ "@cybermem/mcp",
123
+ "--url",
124
+ baseUrl,
125
+ "--token",
126
+ apiKey || "sk-your-generated-token",
127
+ ],
128
+ },
129
+ },
130
+ };
131
+ };
112
132
 
113
133
  const getConfigContent = (maskKey = false) => {
114
- const config = (clients as any[]).find(c => c.id === selectedClient);
115
- const displayKey = maskKey ? "••••••••••••••••" : (apiKey || "sk-your-generated-key");
116
- const actualKey = apiKey || "sk-your-generated-key";
134
+ const config = (clients as any[]).find((c) => c.id === selectedClient);
135
+ const displayKey = maskKey
136
+ ? "••••••••••••••••"
137
+ : apiKey || "sk-your-generated-token";
138
+ const actualKey = apiKey || "sk-your-generated-token";
117
139
 
118
140
  // Handle TOML config (Codex)
119
- if (config?.configType === 'toml') {
141
+ if (config?.configType === "toml") {
120
142
  if (isManaged) {
121
143
  return `# CyberMem Configuration (Local Mode)\n[mcp]\ncommand = "npx"\nargs = ["@cybermem/mcp"]`;
122
144
  }
123
- return `# CyberMem Configuration (Remote Mode)\n[mcp]\ncommand = "npx"\nargs = ["@cybermem/mcp", "--url", "${baseUrl}", "--api-key", "${maskKey ? displayKey : actualKey}"]`;
145
+ return `# CyberMem Configuration (Remote Mode)\n[mcp]\ncommand = "npx"\nargs = ["@cybermem/mcp", "--url", "${baseUrl}", "--token", "${maskKey ? displayKey : actualKey}"]`;
124
146
  }
125
147
 
126
148
  // Handle command-based configs (Claude Code, Gemini CLI, etc.)
127
- if ((config?.configType === 'command' || config?.configType === 'cmd')) {
149
+ if (config?.configType === "command" || config?.configType === "cmd") {
128
150
  // Select command based on mode
129
151
  let cmd = isManaged ? config?.localCommand : config?.remoteCommand;
130
152
 
131
153
  // Fallback to legacy 'command' field if new fields not present
132
154
  if (!cmd) {
133
- cmd = config?.command?.replace("http://localhost:8080", baseUrl) || '';
155
+ cmd = config?.command?.replace("http://localhost:8080", baseUrl) || "";
134
156
  }
135
157
 
136
158
  // Substitute placeholders with actual values
137
- cmd = cmd.replace('{{ENDPOINT}}', baseUrl);
138
- cmd = cmd.replace('{{API_KEY}}', maskKey ? displayKey : actualKey);
159
+ cmd = cmd.replace("{{ENDPOINT}}", baseUrl);
160
+ cmd = cmd.replace("{{API_KEY}}", maskKey ? displayKey : actualKey);
161
+ cmd = cmd.replace("{{TOKEN}}", maskKey ? displayKey : actualKey);
139
162
 
140
163
  return cmd;
141
164
  }
@@ -143,45 +166,50 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
143
166
  // Default to JSON config
144
167
  const jsonConfig = getMcpConfig(selectedClient);
145
168
  if (!isManaged && maskKey) {
146
- // Mask the API key in args array
169
+ // Mask the token in args array
147
170
  const args = (jsonConfig.mcpServers.cybermem as any).args;
148
- const apiKeyIdx = args.indexOf('--api-key');
149
- if (apiKeyIdx !== -1 && args[apiKeyIdx + 1]) {
150
- args[apiKeyIdx + 1] = displayKey;
171
+ const tokenIdx = args.indexOf("--token");
172
+ if (tokenIdx !== -1 && args[tokenIdx + 1]) {
173
+ args[tokenIdx + 1] = displayKey;
151
174
  }
152
175
  }
153
176
  return JSON.stringify(jsonConfig, null, 2);
154
- }
177
+ };
155
178
 
156
179
  const copyToClipboard = (text: string, id: string) => {
157
- navigator.clipboard.writeText(text)
158
- setCopiedId(id)
159
- setTimeout(() => setCopiedId(null), 2000)
160
- }
180
+ navigator.clipboard.writeText(text);
181
+ setCopiedId(id);
182
+ setTimeout(() => setCopiedId(null), 2000);
183
+ };
161
184
 
162
185
  const highlightJSON = (obj: any) => {
163
- const json = JSON.stringify(obj, null, 2)
164
- return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, (match) => {
165
- let cls = "text-orange-300"
166
- if (/^"/.test(match)) {
167
- if (/:$/.test(match)) {
168
- cls = "text-emerald-300"
169
- } else {
170
- cls = "text-yellow-200"
186
+ const json = JSON.stringify(obj, null, 2);
187
+ return json.replace(
188
+ /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
189
+ (match) => {
190
+ let cls = "text-orange-300";
191
+ if (/^"/.test(match)) {
192
+ if (/:$/.test(match)) {
193
+ cls = "text-emerald-300";
194
+ } else {
195
+ cls = "text-yellow-200";
196
+ }
197
+ } else if (/true|false/.test(match)) {
198
+ cls = "text-blue-300";
199
+ } else if (/null/.test(match)) {
200
+ cls = "text-gray-400";
171
201
  }
172
- } else if (/true|false/.test(match)) {
173
- cls = "text-blue-300"
174
- } else if (/null/.test(match)) {
175
- cls = "text-gray-400"
176
- }
177
- return `<span class="${cls}">${match}</span>`
178
- })
179
- }
202
+ return `<span class="${cls}">${match}</span>`;
203
+ },
204
+ );
205
+ };
180
206
 
181
- const selectedConfig = (clients as any[]).find(c => c.id === selectedClient)
207
+ const selectedConfig = (clients as any[]).find(
208
+ (c) => c.id === selectedClient,
209
+ );
182
210
 
183
211
  const renderInstructions = () => {
184
- if (!selectedConfig) return null
212
+ if (!selectedConfig) return null;
185
213
 
186
214
  return (
187
215
  <div className="space-y-4 text-sm text-neutral-300">
@@ -189,19 +217,28 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
189
217
  {selectedConfig.steps.length > 0 && (
190
218
  <ol className="list-decimal list-inside space-y-2 ml-2 text-neutral-400">
191
219
  {selectedConfig.steps.map((step: string, i: number) => (
192
- <li key={i} dangerouslySetInnerHTML={{
193
- __html: step
194
- .replace(/\*\*(.*?)\*\*/g, '<span class="text-white font-medium">$1</span>')
195
- .replace(/`([^`]+)`/g, '<code class="text-emerald-400 bg-emerald-500/10 px-1 py-0.5 rounded">$1</code>')
196
- }} />
220
+ <li
221
+ key={i}
222
+ dangerouslySetInnerHTML={{
223
+ __html: step
224
+ .replace(
225
+ /\*\*(.*?)\*\*/g,
226
+ '<span class="text-white font-medium">$1</span>',
227
+ )
228
+ .replace(
229
+ /`([^`]+)`/g,
230
+ '<code class="text-emerald-400 bg-emerald-500/10 px-1 py-0.5 rounded">$1</code>',
231
+ ),
232
+ }}
233
+ />
197
234
  ))}
198
235
  </ol>
199
236
  )}
200
237
  </div>
201
- )
202
- }
238
+ );
239
+ };
203
240
 
204
- const configContent = getConfigContent()
241
+ const configContent = getConfigContent();
205
242
 
206
243
  return (
207
244
  <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200">
@@ -213,16 +250,24 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
213
250
  radial-gradient(circle at 100% 0%, oklch(0.6 0 0 / 0.05) 0%, transparent 50%),
214
251
  radial-gradient(circle at 100% 100%, oklch(0.65 0 0 / 0.05) 0%, transparent 50%),
215
252
  radial-gradient(circle at 0% 100%, oklch(0.6 0 0 / 0.05) 0%, transparent 50%)
216
- `
253
+ `,
217
254
  }}
218
255
  >
219
256
  {/* Header */}
220
257
  <div className="flex items-center justify-between px-6 pt-6 pb-2 flex-none">
221
258
  <div className="flex items-center gap-3">
222
- <div className="p-2 bg-white/5 rounded-lg border border-white/10 shadow-inner">
223
- <Image src="/icons/mcp.png" alt="MCP Logo" width={20} height={20} className="drop-shadow-[0_0_5px_rgba(255,255,255,0.3)]" />
224
- </div>
225
- <h2 className="text-xl font-semibold text-white text-shadow-sm">Integrate MCP Client</h2>
259
+ <div className="p-2 bg-white/5 rounded-lg border border-white/10 shadow-inner">
260
+ <Image
261
+ src="/icons/mcp.png"
262
+ alt="MCP Logo"
263
+ width={20}
264
+ height={20}
265
+ className="drop-shadow-[0_0_5px_rgba(255,255,255,0.3)]"
266
+ />
267
+ </div>
268
+ <h2 className="text-xl font-semibold text-white text-shadow-sm">
269
+ Integrate MCP Client
270
+ </h2>
226
271
  </div>
227
272
  <Button
228
273
  variant="ghost"
@@ -236,38 +281,46 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
236
281
 
237
282
  {/* Content */}
238
283
  <div className="p-6 space-y-6 overflow-y-auto flex-1 min-h-0">
239
-
240
284
  {/* Client Selector */}
241
285
  <section>
242
286
  <h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2 text-shadow-sm">
243
- <Monitor className="w-5 h-5 text-white drop-shadow-[0_0_8px_rgba(255,255,255,0.5)]" />
244
- Select Client
287
+ <Monitor className="w-5 h-5 text-white drop-shadow-[0_0_8px_rgba(255,255,255,0.5)]" />
288
+ Select Client
245
289
  </h3>
246
290
  <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3">
247
291
  {clients.map((client) => (
248
292
  <button
249
- key={client.id}
250
- onClick={() => setSelectedClient(client.id)}
251
- className={`
293
+ key={client.id}
294
+ onClick={() => setSelectedClient(client.id)}
295
+ className={`
252
296
  relative group flex flex-col items-center justify-center p-3 rounded-xl transition-all duration-300 border
253
- ${selectedClient === client.id
254
- ? "bg-emerald-500/10 border-emerald-500/50 shadow-[0_0_15px_rgba(16,185,129,0.1)] backdrop-blur-sm"
255
- : "bg-white/5 border-white/5 hover:bg-white/10 hover:border-white/20 backdrop-blur-sm"
297
+ ${
298
+ selectedClient === client.id
299
+ ? "bg-emerald-500/10 border-emerald-500/50 shadow-[0_0_15px_rgba(16,185,129,0.1)] backdrop-blur-sm"
300
+ : "bg-white/5 border-white/5 hover:bg-white/10 hover:border-white/20 backdrop-blur-sm"
256
301
  }
257
302
  `}
303
+ >
304
+ <div className="mb-2 transition-transform duration-300 group-hover:scale-110">
305
+ {client.icon ? (
306
+ <Image
307
+ src={client.icon}
308
+ alt={client.name}
309
+ width={32}
310
+ height={32}
311
+ className="object-contain drop-shadow-lg"
312
+ />
313
+ ) : (
314
+ <div className="w-8 h-8 flex items-center justify-center text-white/50 bg-white/5 rounded-full border border-white/10 transition-transform duration-300">
315
+ <span className="text-sm font-bold">?</span>
316
+ </div>
317
+ )}
318
+ </div>
319
+ <span
320
+ className={`text-[10px] font-medium text-center transition-colors ${selectedClient === client.id ? "text-emerald-400 text-shadow-emerald" : "text-neutral-400 group-hover:text-white"}`}
258
321
  >
259
- <div className="mb-2 transition-transform duration-300 group-hover:scale-110">
260
- {client.icon ? (
261
- <Image src={client.icon} alt={client.name} width={32} height={32} className="object-contain drop-shadow-lg" />
262
- ) : (
263
- <div className="w-8 h-8 flex items-center justify-center text-white/50 bg-white/5 rounded-full border border-white/10 transition-transform duration-300">
264
- <span className="text-sm font-bold">?</span>
265
- </div>
266
- )}
267
- </div>
268
- <span className={`text-[10px] font-medium text-center transition-colors ${selectedClient === client.id ? "text-emerald-400 text-shadow-emerald" : "text-neutral-400 group-hover:text-white"}`}>
269
- {client.name}
270
- </span>
322
+ {client.name}
323
+ </span>
271
324
  </button>
272
325
  ))}
273
326
  </div>
@@ -275,25 +328,41 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
275
328
 
276
329
  {/* Instructions */}
277
330
  <section>
278
- <h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2 text-shadow-sm">
279
- <FileCode className="w-5 h-5 text-white drop-shadow-[0_0_8px_rgba(255,255,255,0.5)]" />
280
- {selectedClient === "codex" ? "Configuration" : (selectedClient === "other" ? "Configuration JSON" : "Integration Instructions")}
281
- </h3>
331
+ <h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2 text-shadow-sm">
332
+ <FileCode className="w-5 h-5 text-white drop-shadow-[0_0_8px_rgba(255,255,255,0.5)]" />
333
+ {selectedClient === "codex"
334
+ ? "Configuration"
335
+ : selectedClient === "other"
336
+ ? "Configuration JSON"
337
+ : "Integration Instructions"}
338
+ </h3>
282
339
 
283
- <div className="bg-white/5 border border-white/10 rounded-lg p-5 space-y-4 shadow-[inset_0_0_20px_rgba(255,255,255,0.02)] backdrop-blur-sm">
284
- {selectedClient === "chatgpt" && (
285
- <div className="px-3 py-2 rounded-lg bg-emerald-500/10 border border-emerald-500/20 text-xs text-emerald-200">
286
- <p>Requires Developer Mode. <a href="https://platform.openai.com/docs/guides/developer-mode" target="_blank" rel="noopener noreferrer" className="underline hover:text-white">Read OpenAI Documentation</a></p>
287
- </div>
288
- )}
340
+ <div className="bg-white/5 border border-white/10 rounded-lg p-5 space-y-4 shadow-[inset_0_0_20px_rgba(255,255,255,0.02)] backdrop-blur-sm">
341
+ {selectedClient === "chatgpt" && (
342
+ <div className="px-3 py-2 rounded-lg bg-emerald-500/10 border border-emerald-500/20 text-xs text-emerald-200">
343
+ <p>
344
+ Requires Developer Mode.{" "}
345
+ <a
346
+ href="https://platform.openai.com/docs/guides/developer-mode"
347
+ target="_blank"
348
+ rel="noopener noreferrer"
349
+ className="underline hover:text-white"
350
+ >
351
+ Read OpenAI Documentation
352
+ </a>
353
+ </p>
354
+ </div>
355
+ )}
289
356
 
290
- {renderInstructions()}
357
+ {renderInstructions()}
291
358
 
292
- {/* API Key Control Row - Only show in remote mode */}
293
- {!isManaged ? (
359
+ {/* API Key Control Row - Only show in remote mode */}
360
+ {!isManaged ? (
294
361
  <div className="bg-white/5 border border-white/10 rounded-lg p-5 space-y-4 shadow-[inset_0_0_20px_rgba(255,255,255,0.02)] backdrop-blur-sm mb-4">
295
362
  <div className="space-y-2">
296
- <Label htmlFor="mcp-api-key" className="text-neutral-200">Master API Key</Label>
363
+ <Label htmlFor="mcp-api-key" className="text-neutral-200">
364
+ Security Token
365
+ </Label>
297
366
  <div className="flex gap-2">
298
367
  <div className="relative flex-1">
299
368
  <Input
@@ -303,116 +372,154 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
303
372
  className="bg-black/40 border-white/10 text-white focus-visible:border-emerald-500/30 focus-visible:ring-emerald-500/10 placeholder:text-neutral-600 shadow-inner pr-10 font-mono"
304
373
  type={isKeyVisible ? "text" : "password"}
305
374
  />
306
- <button
307
- type="button"
308
- onClick={() => setIsKeyVisible(!isKeyVisible)}
309
- className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-400 hover:text-white transition-colors"
310
- >
311
- {isKeyVisible ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
375
+ <button
376
+ type="button"
377
+ onClick={() => setIsKeyVisible(!isKeyVisible)}
378
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-400 hover:text-white transition-colors"
379
+ >
380
+ {isKeyVisible ? (
381
+ <EyeOff className="w-4 h-4" />
382
+ ) : (
383
+ <Eye className="w-4 h-4" />
384
+ )}
312
385
  </button>
313
386
  </div>
314
387
  <Button
315
- size="icon"
316
- variant="ghost"
317
- className="h-10 w-10 border border-white/10 bg-white/5 hover:bg-white/10 text-neutral-400 hover:text-white"
318
- onClick={() => copyToClipboard(apiKey, "apikey")}
319
- title="Copy API Key"
320
- >
321
- {copiedId === "apikey" ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
388
+ size="icon"
389
+ variant="ghost"
390
+ className="h-10 w-10 border border-white/10 bg-white/5 hover:bg-white/10 text-neutral-400 hover:text-white"
391
+ onClick={() => copyToClipboard(apiKey, "apikey")}
392
+ title="Copy API Key"
393
+ >
394
+ {copiedId === "apikey" ? (
395
+ <Check className="h-4 w-4 text-emerald-400" />
396
+ ) : (
397
+ <Copy className="h-4 w-4" />
398
+ )}
322
399
  </Button>
323
400
  </div>
324
401
 
325
402
  {/* Regeneration Controls */}
326
403
  <div className="flex justify-end pt-2">
327
404
  {showRegenConfirm ? (
328
- <div className="flex items-center gap-2 animate-in fade-in slide-in-from-right-4 duration-200">
329
- <span className="text-xs text-red-400 font-medium">Warning: Disconnects clients.</span>
330
- <Input
331
- value={regenInputValue}
332
- onChange={(e) => setRegenInputValue(e.target.value)}
333
- placeholder="Type 'agree'"
334
- className="h-8 w-28 bg-red-500/10 border-red-500/30 text-red-200 text-xs placeholder:text-red-500/30 focus-visible:border-red-500/50"
335
- />
336
- <Button
337
- size="sm"
338
- variant="ghost"
339
- className="h-8 px-3 text-neutral-400 hover:text-white hover:bg-white/10"
340
- onClick={() => {
341
- setShowRegenConfirm(false)
342
- setRegenInputValue("")
343
- }}
344
- >
345
- Cancel
346
- </Button>
347
- <Button
348
- size="sm"
349
- disabled={regenInputValue !== 'agree'}
350
- className="h-8 px-3 bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/20 disabled:opacity-50 disabled:cursor-not-allowed"
351
- onClick={confirmRegenerate}
352
- >
353
- Confirm
354
- </Button>
355
- </div>
405
+ <div className="flex items-center gap-2 animate-in fade-in slide-in-from-right-4 duration-200">
406
+ <span className="text-xs text-red-400 font-medium">
407
+ Warning: Disconnects clients.
408
+ </span>
409
+ <Input
410
+ value={regenInputValue}
411
+ onChange={(e) => setRegenInputValue(e.target.value)}
412
+ placeholder="Type 'agree'"
413
+ className="h-8 w-28 bg-red-500/10 border-red-500/30 text-red-200 text-xs placeholder:text-red-500/30 focus-visible:border-red-500/50"
414
+ />
415
+ <Button
416
+ size="sm"
417
+ variant="ghost"
418
+ className="h-8 px-3 text-neutral-400 hover:text-white hover:bg-white/10"
419
+ onClick={() => {
420
+ setShowRegenConfirm(false);
421
+ setRegenInputValue("");
422
+ }}
423
+ >
424
+ Cancel
425
+ </Button>
426
+ <Button
427
+ size="sm"
428
+ disabled={regenInputValue !== "agree"}
429
+ className="h-8 px-3 bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/20 disabled:opacity-50 disabled:cursor-not-allowed"
430
+ onClick={confirmRegenerate}
431
+ >
432
+ Confirm
433
+ </Button>
434
+ </div>
356
435
  ) : (
357
- <Button
358
- size="sm"
359
- variant="ghost"
360
- className="h-8 px-2 text-neutral-400 hover:text-white hover:bg-white/10"
361
- onClick={() => setShowRegenConfirm(true)}
362
- >
363
- Regenerate Key
364
- </Button>
436
+ <Button
437
+ size="sm"
438
+ variant="ghost"
439
+ className="h-8 px-2 text-neutral-400 hover:text-white hover:bg-white/10"
440
+ onClick={() => setShowRegenConfirm(true)}
441
+ >
442
+ Regenerate Key
443
+ </Button>
365
444
  )}
366
445
  </div>
367
446
  </div>
368
447
  </div>
369
- ) : (
448
+ ) : (
370
449
  <div className="bg-emerald-500/5 border border-emerald-500/20 rounded-lg p-4 mb-4">
371
450
  <div className="flex items-start gap-3">
372
451
  <Info className="h-4 w-4 shrink-0 text-emerald-400 mt-0.5" />
373
452
  <div className="space-y-1">
374
- <p className="text-sm font-medium text-emerald-200">Local Mode Active</p>
375
- <p className="text-xs text-emerald-200/60">No API key required for connection from your laptop. Just copy the config below.</p>
453
+ <p className="text-sm font-medium text-emerald-200">
454
+ Local Mode Active
455
+ </p>
456
+ <p className="text-xs text-emerald-200/60">
457
+ No token required for connection from your laptop. Just
458
+ copy the config below.
459
+ </p>
376
460
  </div>
377
461
  </div>
378
462
  </div>
379
- )}
380
-
381
- <div className="relative group">
382
- <div className="relative pl-5 py-5 pr-24 rounded-lg bg-[#0F161C] border border-white/10 font-mono text-xs md:text-sm text-white overflow-x-auto shadow-[0_0_20px_rgba(0,0,0,0.3)] inset-shadow">
383
- <pre className="text-shadow-sm">
384
- {(() => {
385
- const config = (clients as any[]).find(c => c.id === selectedClient);
386
- if (config?.configType === 'json') {
387
- return <code dangerouslySetInnerHTML={{ __html: highlightJSON(JSON.parse(getConfigContent(!isKeyVisible))) }} />;
388
- } else {
389
- return getConfigContent(!isKeyVisible);
390
- }
391
- })()}
392
- </pre>
393
- </div>
394
- <div className="absolute top-5 right-4 opacity-0 group-hover:opacity-100 transition-opacity">
395
- <Button
396
- size="sm"
397
- variant="ghost"
398
- className="h-8 px-3 text-white bg-black/40 backdrop-blur border border-white/5 shadow-[0_0_10px_rgba(255,255,255,0.05)] hover:bg-white/10 hover:text-white font-medium"
399
- onClick={() => copyToClipboard(getConfigContent(false), "config")}
400
- >
401
- {copiedId === "config" ? <Check className="h-4 w-4 stroke-[2.5] text-emerald-400 mr-2 drop-shadow-[0_0_5px_rgba(52,211,153,0.5)]" /> : <Copy className="h-4 w-4 stroke-[2.5] mr-2 text-white drop-shadow-[0_0_5px_rgba(255,255,255,0.3)]" />}
402
- {copiedId === "config" ? <span className="text-emerald-400 text-shadow-sm">Copied</span> : <span className="text-white text-shadow-sm">Copy</span>}
403
- </Button>
404
- </div>
463
+ )}
464
+
465
+ <div className="relative group">
466
+ <div className="relative pl-5 py-5 pr-24 rounded-lg bg-[#0F161C] border border-white/10 font-mono text-xs md:text-sm text-white overflow-x-auto shadow-[0_0_20px_rgba(0,0,0,0.3)] inset-shadow">
467
+ <pre className="text-shadow-sm">
468
+ {(() => {
469
+ const config = (clients as any[]).find(
470
+ (c) => c.id === selectedClient,
471
+ );
472
+ if (config?.configType === "json") {
473
+ return (
474
+ <code
475
+ dangerouslySetInnerHTML={{
476
+ __html: highlightJSON(
477
+ JSON.parse(getConfigContent(!isKeyVisible)),
478
+ ),
479
+ }}
480
+ />
481
+ );
482
+ } else {
483
+ return getConfigContent(!isKeyVisible);
484
+ }
485
+ })()}
486
+ </pre>
487
+ </div>
488
+ <div className="absolute top-5 right-4 opacity-0 group-hover:opacity-100 transition-opacity">
489
+ <Button
490
+ size="sm"
491
+ variant="ghost"
492
+ className="h-8 px-3 text-white bg-black/40 backdrop-blur border border-white/5 shadow-[0_0_10px_rgba(255,255,255,0.05)] hover:bg-white/10 hover:text-white font-medium"
493
+ onClick={() =>
494
+ copyToClipboard(getConfigContent(false), "config")
495
+ }
496
+ >
497
+ {copiedId === "config" ? (
498
+ <Check className="h-4 w-4 stroke-[2.5] text-emerald-400 mr-2 drop-shadow-[0_0_5px_rgba(52,211,153,0.5)]" />
499
+ ) : (
500
+ <Copy className="h-4 w-4 stroke-[2.5] mr-2 text-white drop-shadow-[0_0_5px_rgba(255,255,255,0.3)]" />
501
+ )}
502
+ {copiedId === "config" ? (
503
+ <span className="text-emerald-400 text-shadow-sm">
504
+ Copied
505
+ </span>
506
+ ) : (
507
+ <span className="text-white text-shadow-sm">Copy</span>
508
+ )}
509
+ </Button>
405
510
  </div>
511
+ </div>
406
512
 
407
- {!isManaged && (
513
+ {!isManaged && (
408
514
  <div className="flex items-start gap-3 p-3 rounded-lg bg-emerald-500/5 border border-emerald-500/10 text-emerald-200/70 text-xs mt-4">
409
515
  <Info className="h-4 w-4 shrink-0 text-white mt-0.5" />
410
516
  <p>
411
- This configuration includes your generated API key. Keep it secure and do not share it publicly.
517
+ This configuration includes your generated Security Token.
518
+ Keep it secure and do not share it publicly.
412
519
  </p>
413
520
  </div>
414
- )}
415
- </div>
521
+ )}
522
+ </div>
416
523
  </section>
417
524
  </div>
418
525
 
@@ -423,7 +530,9 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
423
530
  variant="ghost"
424
531
  className="bg-emerald-500/10 hover:bg-emerald-500/20 border border-emerald-500/20 text-emerald-400 hover:text-emerald-300 mr-auto"
425
532
  >
426
- <a href="https://docs.cybermem.dev" target="_blank">Read Documentation</a>
533
+ <a href="https://docs.cybermem.dev" target="_blank">
534
+ Read Documentation
535
+ </a>
427
536
  </Button>
428
537
  <Button
429
538
  onClick={onClose}
@@ -434,5 +543,5 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
434
543
  </div>
435
544
  </div>
436
545
  </div>
437
- )
546
+ );
438
547
  }