@cybermem/dashboard 0.1.0 → 0.5.1

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.
@@ -5,6 +5,7 @@ import { Input } from "@/components/ui/input"
5
5
  import { Label } from "@/components/ui/label"
6
6
  import { useDashboard } from "@/lib/data/dashboard-context"
7
7
  import { Check, Copy, Eye, EyeOff, FileCode, Info, Monitor, X } from "lucide-react"
8
+ import Image from "next/image"
8
9
  import { useEffect, useState } from "react"
9
10
 
10
11
  export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
@@ -18,6 +19,7 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
18
19
  const [isKeyVisible, setIsKeyVisible] = useState(false)
19
20
  const [showRegenConfirm, setShowRegenConfirm] = useState(false)
20
21
  const [regenInputValue, setRegenInputValue] = useState("")
22
+ const [isManaged, setIsManaged] = useState(false) // true = local mode, no API key needed
21
23
 
22
24
  useEffect(() => {
23
25
  // Try to get key from local storage first (simulating persistence)
@@ -30,7 +32,8 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
30
32
  .then(data => {
31
33
  let srvEndpoint = data.endpoint
32
34
  if (srvEndpoint.includes('localhost') && typeof window !== "undefined" && !window.location.hostname.includes('localhost')) {
33
- srvEndpoint = `${window.location.protocol}//${window.location.hostname}:8080`
35
+ const port = srvEndpoint.split(':').pop()?.split('/')[0] || '8626'
36
+ srvEndpoint = `${window.location.protocol}//${window.location.hostname}:${port}`
34
37
  }
35
38
  setBaseUrl(srvEndpoint)
36
39
  setIsLoading(false)
@@ -41,9 +44,11 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
41
44
  .then(res => res.json())
42
45
  .then(data => {
43
46
  setApiKey(data.apiKey !== 'not-set' ? data.apiKey : '')
47
+ setIsManaged(data.isManaged || false)
44
48
  let srvEndpoint = data.endpoint
45
49
  if (srvEndpoint.includes('localhost') && typeof window !== "undefined" && !window.location.hostname.includes('localhost')) {
46
- srvEndpoint = `${window.location.protocol}//${window.location.hostname}:8080`
50
+ const port = srvEndpoint.split(':').pop()?.split('/')[0] || '8626'
51
+ srvEndpoint = `${window.location.protocol}//${window.location.hostname}:${port}`
47
52
  }
48
53
  setBaseUrl(srvEndpoint)
49
54
  setIsLoading(false)
@@ -81,6 +86,19 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
81
86
  const isAntigravity = clientId === 'antigravity'
82
87
  const urlKey = isAntigravity ? 'serverUrl' : 'url'
83
88
 
89
+ // Local mode: use stdio (command-based) - no server needed, runs via npx
90
+ if (isManaged) {
91
+ return {
92
+ mcpServers: {
93
+ cybermem: {
94
+ command: "npx",
95
+ args: ["@cybermem/mcp-core"]
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ // Remote mode: use SSE URL with API key header
84
102
  return {
85
103
  mcpServers: {
86
104
  cybermem: {
@@ -100,27 +118,39 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
100
118
 
101
119
  // Handle TOML config (Codex)
102
120
  if (config?.configType === 'toml') {
121
+ if (isManaged) {
122
+ return `# CyberMem Configuration (Local Mode)\n[mcp]\ncommand = "npx"\nargs = ["@cybermem/mcp-core"]`;
123
+ }
103
124
  return `# CyberMem Configuration\n[mcp]\nserver_url = "${baseUrl}/mcp"\napi_key = "${maskKey ? displayKey : actualKey}"`;
104
125
  }
105
126
 
106
127
  // Handle command-based configs (Claude Code, Gemini CLI, etc.)
107
- if ((config?.configType === 'command' || config?.configType === 'cmd') && config?.command) {
108
- let cmd = config.command.replace("http://localhost:8080", baseUrl);
128
+ if ((config?.configType === 'command' || config?.configType === 'cmd')) {
129
+ // Select command based on mode
130
+ let cmd = isManaged ? config?.localCommand : config?.remoteCommand;
131
+
132
+ // Fallback to legacy 'command' field if new fields not present
133
+ if (!cmd) {
134
+ cmd = config?.command?.replace("http://localhost:8080", baseUrl) || '';
135
+ }
109
136
 
110
- // Add API key as header if it's missing and it's a CLI that usually supports it
111
- if (config.id === 'claude-code' || config.id === 'gemini-cli') {
137
+ // Substitute {{ENDPOINT}} placeholder with actual endpoint
138
+ cmd = cmd.replace('{{ENDPOINT}}', `${baseUrl}/mcp`);
139
+
140
+ // Remote mode - inject API key for SSE transport commands
141
+ if (!isManaged && cmd.includes('--transport sse')) {
112
142
  const headerPart = `--header "x-api-key: ${maskKey ? displayKey : actualKey}"`;
113
143
  if (!cmd.includes('x-api-key')) {
114
- // Insert before transport or just at the end
115
144
  cmd = cmd.replace('mcp add', `mcp add ${headerPart}`);
116
145
  }
117
146
  }
147
+
118
148
  return cmd;
119
149
  }
120
150
 
121
151
  // Default to JSON config
122
152
  const jsonConfig = getMcpConfig(selectedClient);
123
- if (maskKey) {
153
+ if (!isManaged && maskKey) {
124
154
  (jsonConfig.mcpServers.cybermem as any).headers["x-api-key"] = displayKey;
125
155
  }
126
156
  return JSON.stringify(jsonConfig, null, 2);
@@ -193,7 +223,7 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
193
223
  <div className="flex items-center justify-between px-6 pt-6 pb-2 flex-none">
194
224
  <div className="flex items-center gap-3">
195
225
  <div className="p-2 bg-white/5 rounded-lg border border-white/10 shadow-inner">
196
- <img src="/icons/mcp.png" alt="MCP Logo" className="w-5 h-5 drop-shadow-[0_0_5px_rgba(255,255,255,0.3)]" />
226
+ <Image src="/icons/mcp.png" alt="MCP Logo" width={20} height={20} className="drop-shadow-[0_0_5px_rgba(255,255,255,0.3)]" />
197
227
  </div>
198
228
  <h2 className="text-xl font-semibold text-white text-shadow-sm">Integrate MCP Client</h2>
199
229
  </div>
@@ -231,7 +261,7 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
231
261
  >
232
262
  <div className="mb-2 transition-transform duration-300 group-hover:scale-110">
233
263
  {client.icon ? (
234
- <img src={client.icon} alt={client.name} className="w-8 h-8 object-contain drop-shadow-lg" />
264
+ <Image src={client.icon} alt={client.name} width={32} height={32} className="object-contain drop-shadow-lg" />
235
265
  ) : (
236
266
  <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">
237
267
  <span className="text-sm font-bold">?</span>
@@ -262,7 +292,8 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
262
292
 
263
293
  {renderInstructions()}
264
294
 
265
- {/* API Key Control Row (Standardized) */}
295
+ {/* API Key Control Row - Only show in remote mode */}
296
+ {!isManaged ? (
266
297
  <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">
267
298
  <div className="space-y-2">
268
299
  <Label htmlFor="mcp-api-key" className="text-neutral-200">Master API Key</Label>
@@ -338,6 +369,17 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
338
369
  </div>
339
370
  </div>
340
371
  </div>
372
+ ) : (
373
+ <div className="bg-emerald-500/5 border border-emerald-500/20 rounded-lg p-4 mb-4">
374
+ <div className="flex items-start gap-3">
375
+ <Info className="h-4 w-4 shrink-0 text-emerald-400 mt-0.5" />
376
+ <div className="space-y-1">
377
+ <p className="text-sm font-medium text-emerald-200">Local Mode Active</p>
378
+ <p className="text-xs text-emerald-200/60">No API key required for connection from your laptop. Just copy the config below.</p>
379
+ </div>
380
+ </div>
381
+ </div>
382
+ )}
341
383
 
342
384
  <div className="relative group">
343
385
  <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">
@@ -365,12 +407,14 @@ export default function MCPConfigModal({ onClose }: { onClose: () => void }) {
365
407
  </div>
366
408
  </div>
367
409
 
368
- <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">
410
+ {!isManaged && (
411
+ <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">
369
412
  <Info className="h-4 w-4 shrink-0 text-white mt-0.5" />
370
413
  <p>
371
414
  This configuration includes your generated API key. Keep it secure and do not share it publicly.
372
415
  </p>
373
416
  </div>
417
+ )}
374
418
  </div>
375
419
  </section>
376
420
  </div>
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import { Card, CardContent } from "@/components/ui/card"
4
+ import { useDashboard } from "@/lib/data/dashboard-context"
4
5
  import MetricCard from "./metric-card"
5
6
 
6
7
  // Types
@@ -29,7 +30,19 @@ interface MetricsGridProps {
29
30
  }
30
31
  }
31
32
 
33
+
32
34
  export default function MetricsGrid({ stats, trends }: MetricsGridProps) {
35
+ const { clientConfigs } = useDashboard()
36
+
37
+ const getClientDisplayName = (rawName: string) => {
38
+ if (!rawName || rawName === "N/A" || rawName === "unknown") return rawName
39
+
40
+ const nameLower = rawName.toLowerCase()
41
+ // Find matching client config (e.g. "antigravity" matches "antigravity-client")
42
+ const config = clientConfigs.find((c: any) => nameLower.includes(c.match))
43
+ return config ? config.name : rawName
44
+ }
45
+
33
46
  const formatTimestamp = (timestamp: number) => {
34
47
  if (timestamp <= 0) return "No activity"
35
48
  const date = new Date(timestamp)
@@ -86,7 +99,7 @@ export default function MetricsGrid({ stats, trends }: MetricsGridProps) {
86
99
  <Card className="bg-white/5 border-white/10 backdrop-blur-md text-white shadow-lg overflow-hidden">
87
100
  <CardContent className="pt-6 pb-6 relative">
88
101
  <div className="text-sm font-medium text-slate-400 mb-2">Top Writer</div>
89
- <div className="text-4xl font-bold text-white mb-1 truncate">{stats.topWriter.name}</div>
102
+ <div className="text-4xl font-bold text-white mb-1 truncate">{getClientDisplayName(stats.topWriter.name)}</div>
90
103
  <div className="text-xl text-white/80 whitespace-nowrap">
91
104
  {stats.topWriter.count > 0 ? `${stats.topWriter.count.toLocaleString()} writes` : ""}
92
105
  </div>
@@ -98,7 +111,7 @@ export default function MetricsGrid({ stats, trends }: MetricsGridProps) {
98
111
  <CardContent className="pt-6 pb-6 relative">
99
112
  <div className="text-sm font-medium text-slate-400 mb-2">Top Reader</div>
100
113
  <div className="text-4xl font-bold text-white mb-1 truncate">
101
- {stats.topReader.count > 0 ? stats.topReader.name : "N/A"}
114
+ {stats.topReader.count > 0 ? getClientDisplayName(stats.topReader.name) : "N/A"}
102
115
  </div>
103
116
  <div className="text-xl text-white/80 whitespace-nowrap">
104
117
  {stats.topReader.count > 0 ? `${stats.topReader.count.toLocaleString()} reads` : ""}
@@ -111,7 +124,7 @@ export default function MetricsGrid({ stats, trends }: MetricsGridProps) {
111
124
  <CardContent className="pt-6 pb-6 relative">
112
125
  <div className="text-sm font-medium text-slate-400 mb-2">Last Writer</div>
113
126
  <div className="text-4xl font-bold text-white mb-1 truncate">
114
- {stats.lastWriter.name !== "N/A" ? stats.lastWriter.name : "N/A"}
127
+ {stats.lastWriter.name !== "N/A" ? getClientDisplayName(stats.lastWriter.name) : "N/A"}
115
128
  </div>
116
129
  <div className="text-xl text-white/80 whitespace-nowrap">
117
130
  {stats.lastWriter.timestamp > 0 ? formatTimestamp(stats.lastWriter.timestamp) : "No activity"}
@@ -124,7 +137,7 @@ export default function MetricsGrid({ stats, trends }: MetricsGridProps) {
124
137
  <CardContent className="pt-6 pb-6 relative">
125
138
  <div className="text-sm font-medium text-slate-400 mb-2">Last Reader</div>
126
139
  <div className="text-4xl font-bold text-white mb-1 truncate">
127
- {stats.lastReader.name !== "N/A" ? stats.lastReader.name : "N/A"}
140
+ {stats.lastReader.name !== "N/A" ? getClientDisplayName(stats.lastReader.name) : "N/A"}
128
141
  </div>
129
142
  <div className="text-xl text-white/80 whitespace-nowrap">
130
143
  {stats.lastReader.timestamp > 0 ? formatTimestamp(stats.lastReader.timestamp) : "No activity"}
@@ -0,0 +1,72 @@
1
+ "use client"
2
+
3
+ import { Button } from "@/components/ui/button"
4
+ import { AlertTriangle, Settings, X } from "lucide-react"
5
+
6
+ interface PasswordAlertModalProps {
7
+ onChangePassword: () => void
8
+ onDismiss: () => void
9
+ }
10
+
11
+ export default function PasswordAlertModal({
12
+ onChangePassword,
13
+ onDismiss
14
+ }: PasswordAlertModalProps) {
15
+ const handleDontShowAgain = () => {
16
+ localStorage.setItem("hidePasswordWarning", "true")
17
+ onDismiss()
18
+ }
19
+
20
+ return (
21
+ <div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4">
22
+ <div
23
+ className="bg-[#0B1116]/90 backdrop-blur-xl border border-amber-500/30 rounded-2xl shadow-2xl max-w-md w-full overflow-hidden animate-in fade-in zoom-in-95 duration-200"
24
+ style={{
25
+ backgroundImage: `
26
+ radial-gradient(circle at 0% 0%, rgba(251, 191, 36, 0.05) 0%, transparent 50%),
27
+ radial-gradient(circle at 100% 100%, rgba(251, 191, 36, 0.05) 0%, transparent 50%)
28
+ `
29
+ }}
30
+ >
31
+ {/* Header */}
32
+ <div className="px-6 pt-6 pb-4 flex items-start gap-4">
33
+ <div className="p-3 bg-amber-500/10 rounded-full border border-amber-500/20 shrink-0">
34
+ <AlertTriangle className="w-6 h-6 text-amber-400 drop-shadow-[0_0_10px_rgba(251,191,36,0.5)]" />
35
+ </div>
36
+ <div className="flex-1">
37
+ <h2 className="text-lg font-semibold text-white">Default Password Detected</h2>
38
+ <p className="text-neutral-400 text-sm mt-1">
39
+ You&apos;re using the default admin password. For security, we recommend changing it now.
40
+ </p>
41
+ </div>
42
+ <Button
43
+ variant="ghost"
44
+ size="icon"
45
+ onClick={onDismiss}
46
+ className="text-neutral-400 hover:text-white hover:bg-white/10 rounded-full -mt-1 -mr-2"
47
+ >
48
+ <X className="w-4 h-4" />
49
+ </Button>
50
+ </div>
51
+
52
+ {/* Actions */}
53
+ <div className="px-6 pb-6 flex flex-col gap-3">
54
+ <Button
55
+ onClick={onChangePassword}
56
+ className="w-full bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/20 text-emerald-400 font-medium transition-colors gap-2"
57
+ >
58
+ <Settings className="w-4 h-4" />
59
+ Change Password
60
+ </Button>
61
+ <Button
62
+ variant="ghost"
63
+ onClick={handleDontShowAgain}
64
+ className="w-full text-neutral-400 hover:text-white hover:bg-white/10"
65
+ >
66
+ Don&apos;t show again
67
+ </Button>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ )
72
+ }