@cybermem/dashboard 0.5.16 → 0.8.7

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