@dtoolkit/dbrain 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -7
- package/dist/cli/compact.d.ts +10 -0
- package/dist/cli/compact.d.ts.map +1 -0
- package/dist/cli/compact.js +44 -0
- package/dist/cli/compact.js.map +1 -0
- package/dist/cli/configure.d.ts +2 -0
- package/dist/cli/configure.d.ts.map +1 -0
- package/dist/cli/configure.js +126 -0
- package/dist/cli/configure.js.map +1 -0
- package/dist/cli/index.js +63 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +18 -0
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/keys.d.ts +11 -0
- package/dist/cli/keys.d.ts.map +1 -0
- package/dist/cli/keys.js +85 -0
- package/dist/cli/keys.js.map +1 -0
- package/dist/cli/link.d.ts +12 -0
- package/dist/cli/link.d.ts.map +1 -0
- package/dist/cli/link.js +118 -0
- package/dist/cli/link.js.map +1 -0
- package/dist/cli/start.d.ts.map +1 -1
- package/dist/cli/start.js +31 -1
- package/dist/cli/start.js.map +1 -1
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +23 -1
- package/dist/cli/status.js.map +1 -1
- package/dist/core/compact.d.ts +18 -0
- package/dist/core/compact.d.ts.map +1 -0
- package/dist/core/compact.js +117 -0
- package/dist/core/compact.js.map +1 -0
- package/dist/core/config.d.ts +23 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +31 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/connections.d.ts +9 -0
- package/dist/core/connections.d.ts.map +1 -0
- package/dist/core/connections.js +25 -0
- package/dist/core/connections.js.map +1 -0
- package/dist/core/db.d.ts.map +1 -1
- package/dist/core/db.js +36 -0
- package/dist/core/db.js.map +1 -1
- package/dist/dashboard/index.html +250 -26
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +216 -15
- package/dist/mcp/server.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +30 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/compact.d.ts +3 -0
- package/dist/server/routes/compact.d.ts.map +1 -0
- package/dist/server/routes/compact.js +29 -0
- package/dist/server/routes/compact.js.map +1 -0
- package/dist/server/routes/entities.d.ts.map +1 -1
- package/dist/server/routes/entities.js +6 -1
- package/dist/server/routes/entities.js.map +1 -1
- package/dist/server/routes/facts.d.ts.map +1 -1
- package/dist/server/routes/facts.js +55 -2
- package/dist/server/routes/facts.js.map +1 -1
- package/dist/server/routes/health.d.ts.map +1 -1
- package/dist/server/routes/health.js +46 -2
- package/dist/server/routes/health.js.map +1 -1
- package/dist/server/routes/keys.d.ts +3 -0
- package/dist/server/routes/keys.d.ts.map +1 -0
- package/dist/server/routes/keys.js +73 -0
- package/dist/server/routes/keys.js.map +1 -0
- package/dist/server/routes/permissions.d.ts +3 -0
- package/dist/server/routes/permissions.d.ts.map +1 -0
- package/dist/server/routes/permissions.js +9 -0
- package/dist/server/routes/permissions.js.map +1 -0
- package/dist/server/routes/proxy.d.ts +3 -0
- package/dist/server/routes/proxy.d.ts.map +1 -0
- package/dist/server/routes/proxy.js +37 -0
- package/dist/server/routes/proxy.js.map +1 -0
- package/dist/server/routes/search.d.ts.map +1 -1
- package/dist/server/routes/search.js +113 -41
- package/dist/server/routes/search.js.map +1 -1
- package/dist/server/routes/workspace.d.ts.map +1 -1
- package/dist/server/routes/workspace.js +5 -0
- package/dist/server/routes/workspace.js.map +1 -1
- package/package.json +4 -2
|
@@ -94,6 +94,48 @@ html,body{height:100%;font-family:var(--font);background:var(--bg);color:var(--t
|
|
|
94
94
|
}
|
|
95
95
|
@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}
|
|
96
96
|
.brain-name{font-size:13px;font-weight:600;color:var(--text)}
|
|
97
|
+
.brain-type-badge{font-size:9px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;padding:2px 6px;border-radius:4px;line-height:1}
|
|
98
|
+
.brain-type-badge.personal{background:oklch(0.90 0.10 250);color:oklch(0.35 0.15 250)}
|
|
99
|
+
.brain-type-badge.shared{background:oklch(0.90 0.10 148);color:oklch(0.30 0.15 148)}
|
|
100
|
+
.sb-user{display:flex;align-items:center;gap:8px;padding:8px 12px;margin-bottom:8px}
|
|
101
|
+
.sb-user-info{flex:1;min-width:0}
|
|
102
|
+
.sb-user-name{font-size:12px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block}
|
|
103
|
+
.sb-user-role{font-size:10px;color:var(--text-3);font-family:var(--mono)}
|
|
104
|
+
.sb-logout{padding:3px 8px;border:1px solid var(--border);border-radius:5px;background:none;color:var(--text-3);font-size:10px;cursor:pointer;flex-shrink:0;transition:all .15s}
|
|
105
|
+
.sb-logout:hover{border-color:#ef4444;color:#ef4444;background:oklch(0.95 0.05 25)}
|
|
106
|
+
.sb-brains-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-3);padding:0 12px;margin-bottom:6px}
|
|
107
|
+
.sb-brain-item{display:flex;align-items:center;gap:8px;padding:6px 12px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--text-2);transition:background .15s}
|
|
108
|
+
.sb-brain-item:hover{background:var(--surface-2)}
|
|
109
|
+
.sb-brain-item.current{color:var(--text);font-weight:600}
|
|
110
|
+
.sb-brain-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
|
|
111
|
+
.sb-brain-dot.online{background:var(--online);box-shadow:0 0 4px var(--online)}
|
|
112
|
+
.sb-brain-dot.offline{background:#ef4444}
|
|
113
|
+
.sb-brain-meta{font-size:10px;color:var(--text-3);margin-left:auto;font-family:var(--mono)}
|
|
114
|
+
.author-badge{font-size:10px;font-family:var(--mono);color:var(--text-3);font-style:italic}
|
|
115
|
+
.origin-badge{font-size:9px;font-weight:600;padding:1px 5px;border-radius:3px;background:oklch(0.90 0.10 148);color:oklch(0.30 0.15 148);font-family:var(--mono)}
|
|
116
|
+
.keys-table{width:100%;border-collapse:collapse;font-size:12px;font-family:var(--mono)}
|
|
117
|
+
.keys-table th{text-align:left;padding:8px 10px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);font-size:11px;text-transform:uppercase;letter-spacing:.5px}
|
|
118
|
+
.keys-table td{padding:8px 10px;border-bottom:1px solid var(--border-subtle);color:var(--text)}
|
|
119
|
+
.keys-table tr:hover td{background:var(--surface-2)}
|
|
120
|
+
.conn-status{display:inline-flex;align-items:center;gap:5px;font-size:12px}
|
|
121
|
+
.conn-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
|
|
122
|
+
.conn-dot.online{background:var(--online);box-shadow:0 0 4px var(--online)}
|
|
123
|
+
.conn-dot.offline{background:#ef4444}
|
|
124
|
+
.conn-card{padding:14px 16px;background:var(--surface);border:1px solid var(--border-subtle);border-radius:10px;margin-bottom:10px}
|
|
125
|
+
.conn-card-head{display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
|
126
|
+
.conn-card-name{font-size:14px;font-weight:600;color:var(--text)}
|
|
127
|
+
.conn-card-url{font-size:11px;font-family:var(--mono);color:var(--text-3)}
|
|
128
|
+
.conn-card-stats{font-size:12px;color:var(--text-2);margin-top:4px}
|
|
129
|
+
.form-row{display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;margin-bottom:16px}
|
|
130
|
+
.form-field{display:flex;flex-direction:column;gap:4px}
|
|
131
|
+
.form-field label{font-size:11px;font-weight:600;color:var(--text-3);text-transform:uppercase;letter-spacing:.3px}
|
|
132
|
+
.form-field input,.form-field select{padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:var(--surface);color:var(--text);font-size:12px;font-family:var(--mono)}
|
|
133
|
+
.form-btn{padding:6px 14px;border:none;border-radius:6px;background:var(--accent);color:white;font-size:12px;font-weight:600;cursor:pointer}
|
|
134
|
+
.form-btn:hover{opacity:.85}
|
|
135
|
+
.form-btn.danger{background:#ef4444}
|
|
136
|
+
.inline-msg{font-size:12px;padding:8px 12px;border-radius:6px;margin-bottom:12px}
|
|
137
|
+
.inline-msg.success{background:oklch(0.90 0.10 148);color:oklch(0.30 0.15 148)}
|
|
138
|
+
.inline-msg.error{background:oklch(0.90 0.10 25);color:oklch(0.40 0.15 25)}
|
|
97
139
|
.brain-meta{font-size:11px;color:var(--text-3);line-height:1.7;font-family:var(--mono)}
|
|
98
140
|
.main{margin-left:var(--sb);flex:1;min-width:0}
|
|
99
141
|
.topbar{
|
|
@@ -283,7 +325,7 @@ const { useState, useEffect, useMemo, useCallback } = React;
|
|
|
283
325
|
|
|
284
326
|
const TOKEN_KEY = 'dbrain_token';
|
|
285
327
|
const THEME_KEY = 'dbrain_theme';
|
|
286
|
-
const API_BASE = `http://${window.location.hostname}
|
|
328
|
+
const API_BASE = `http://${window.location.hostname}:${Number(window.location.port) - 1}`;
|
|
287
329
|
|
|
288
330
|
/* ── PALETTES ─────────────────────────────────────────── */
|
|
289
331
|
const PALETTES = {
|
|
@@ -297,7 +339,19 @@ function applyPalette(name) {
|
|
|
297
339
|
}
|
|
298
340
|
|
|
299
341
|
/* ── API HELPER ───────────────────────────────────────── */
|
|
342
|
+
let _activeBrainPrefix = '';
|
|
343
|
+
function setActiveBrainPrefix(name) { _activeBrainPrefix = name ? `/proxy/${encodeURIComponent(name)}` : ''; }
|
|
344
|
+
|
|
300
345
|
function api(path, opts = {}) {
|
|
346
|
+
const token = localStorage.getItem(TOKEN_KEY) || '';
|
|
347
|
+
const prefix = _activeBrainPrefix;
|
|
348
|
+
return fetch(`${API_BASE}${prefix}${path}`, {
|
|
349
|
+
...opts,
|
|
350
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', ...opts.headers },
|
|
351
|
+
}).then(r => { if (!r.ok) throw new Error(r.status); return r.json(); });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function localApi(path, opts = {}) {
|
|
301
355
|
const token = localStorage.getItem(TOKEN_KEY) || '';
|
|
302
356
|
return fetch(`${API_BASE}${path}`, {
|
|
303
357
|
...opts,
|
|
@@ -362,13 +416,25 @@ function BackIcon() {
|
|
|
362
416
|
}
|
|
363
417
|
|
|
364
418
|
/* ── SIDEBAR ──────────────────────────────────────────── */
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
419
|
+
const BrainIcon = () => (
|
|
420
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
|
421
|
+
<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2z" />
|
|
422
|
+
<path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2z" />
|
|
423
|
+
</svg>
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
function Sidebar({ route, nav, palette, onTogglePalette, brain, connections, user, onLogout, activeBrain, onBrainSwitch }) {
|
|
427
|
+
const brainItems = [
|
|
428
|
+
{ name: null, label: brain.name || 'My Brain', badge: brain.brainType, online: true },
|
|
429
|
+
...(connections || []).map(c => ({
|
|
430
|
+
name: c.name,
|
|
431
|
+
label: c.brainName || c.name,
|
|
432
|
+
badge: 'shared',
|
|
433
|
+
online: c.online,
|
|
434
|
+
})),
|
|
435
|
+
];
|
|
436
|
+
|
|
437
|
+
const toolLinks = [
|
|
372
438
|
{ href: '#/api', label: 'REST API', icon: (
|
|
373
439
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round">
|
|
374
440
|
<polyline points="16 18 22 12 16 6" /><polyline points="8 6 2 12 8 18" />
|
|
@@ -380,12 +446,18 @@ function Sidebar({ route, nav, palette, onTogglePalette, brain }) {
|
|
|
380
446
|
<line x1="6" y1="7" x2="9.5" y2="10.5" /><line x1="18" y1="7" x2="14.5" y2="10.5" />
|
|
381
447
|
<line x1="6" y1="17" x2="9.5" y2="13.5" /><line x1="18" y1="17" x2="14.5" y2="13.5" />
|
|
382
448
|
</svg>) },
|
|
383
|
-
|
|
449
|
+
brain.brainType === 'shared' && { href: '#/keys', label: 'API Keys', icon: (
|
|
450
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round">
|
|
451
|
+
<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" />
|
|
452
|
+
</svg>) },
|
|
453
|
+
].filter(Boolean);
|
|
454
|
+
|
|
384
455
|
const active = (href) => {
|
|
385
456
|
const p = href.replace('#', '');
|
|
386
457
|
if (p === '/dashboard') return route === '/' || route === '/dashboard' || route.startsWith('/entity') || route.startsWith('/conversation');
|
|
387
458
|
return route.startsWith(p);
|
|
388
459
|
};
|
|
460
|
+
|
|
389
461
|
return (
|
|
390
462
|
<aside className="sidebar">
|
|
391
463
|
<div className="sb-logo">
|
|
@@ -396,7 +468,17 @@ function Sidebar({ route, nav, palette, onTogglePalette, brain }) {
|
|
|
396
468
|
</div>
|
|
397
469
|
</div>
|
|
398
470
|
<nav className="sb-nav">
|
|
399
|
-
{
|
|
471
|
+
{brainItems.map(b => (
|
|
472
|
+
<button key={b.name ?? '_local'} className={`sb-link ${activeBrain === b.name ? 'active' : ''}`}
|
|
473
|
+
onClick={() => b.online && onBrainSwitch(b.name)}
|
|
474
|
+
style={!b.online ? {opacity:.5,cursor:'default'} : {}}>
|
|
475
|
+
<span className={`sb-brain-dot ${b.online ? 'online' : 'offline'}`} style={{marginRight:-2}}></span>
|
|
476
|
+
{b.label}
|
|
477
|
+
<span className={`brain-type-badge ${b.badge}`} style={{marginLeft:'auto'}}>{b.badge}</span>
|
|
478
|
+
</button>
|
|
479
|
+
))}
|
|
480
|
+
<div className="sb-divider" style={{margin:'6px 0'}}></div>
|
|
481
|
+
{toolLinks.map(l => (
|
|
400
482
|
<button key={l.href} className={`sb-link ${active(l.href) ? 'active' : ''}`} onClick={() => nav(l.href)}>
|
|
401
483
|
{l.icon}{l.label}
|
|
402
484
|
</button>
|
|
@@ -411,17 +493,13 @@ function Sidebar({ route, nav, palette, onTogglePalette, brain }) {
|
|
|
411
493
|
<button className={`palette-toggle-opt ${palette === 'ocean' ? 'active' : ''}`} onClick={() => onTogglePalette('ocean')}>● Dark</button>
|
|
412
494
|
</div>
|
|
413
495
|
</div>
|
|
414
|
-
<div className="
|
|
415
|
-
<div className="
|
|
416
|
-
<span className="
|
|
417
|
-
<span className="
|
|
496
|
+
{user && <div className="sb-user">
|
|
497
|
+
<div className="sb-user-info">
|
|
498
|
+
<span className="sb-user-name">{user.userName}</span>
|
|
499
|
+
<span className="sb-user-role">{user.isAdmin ? 'admin' : user.permissions}</span>
|
|
418
500
|
</div>
|
|
419
|
-
<
|
|
420
|
-
|
|
421
|
-
{(brain.facts || 0).toLocaleString()} facts<br />
|
|
422
|
-
{brain.conversations || 0} conversations
|
|
423
|
-
</div>
|
|
424
|
-
</div>
|
|
501
|
+
<button className="sb-logout" onClick={onLogout}>Logout</button>
|
|
502
|
+
</div>}
|
|
425
503
|
</div>
|
|
426
504
|
</aside>
|
|
427
505
|
);
|
|
@@ -459,7 +537,7 @@ function LoginPage({ onLogin }) {
|
|
|
459
537
|
}
|
|
460
538
|
|
|
461
539
|
/* ── DASHBOARD PAGE ───────────────────────────────────── */
|
|
462
|
-
function DashboardPage({ nav, overview, conversations }) {
|
|
540
|
+
function DashboardPage({ nav, overview, conversations, brainName }) {
|
|
463
541
|
const [q, setQ] = useState('');
|
|
464
542
|
const [srcFilter, setSrcFilter] = useState('all');
|
|
465
543
|
|
|
@@ -486,7 +564,7 @@ function DashboardPage({ nav, overview, conversations }) {
|
|
|
486
564
|
return (
|
|
487
565
|
<>
|
|
488
566
|
<div className="topbar">
|
|
489
|
-
<span className="topbar-title">Overview</span>
|
|
567
|
+
<span className="topbar-title">{brainName || 'Overview'}</span>
|
|
490
568
|
<span className="topbar-sep">·</span>
|
|
491
569
|
<div className="search-bar">
|
|
492
570
|
<SearchIcon />
|
|
@@ -616,6 +694,8 @@ function EntityDetailPage({ id, nav }) {
|
|
|
616
694
|
<div className="fact-meta">
|
|
617
695
|
<TierPill tier={f.tier} count={null} />
|
|
618
696
|
<span>accessed {f.access_count || 0}x · last {f.last_accessed ? f.last_accessed.split('T')[0] : 'n/a'}</span>
|
|
697
|
+
{f.author_id && <span className="author-badge">by {f.author_id}</span>}
|
|
698
|
+
{f.origin_brain && <span className="origin-badge">{f.origin_brain}</span>}
|
|
619
699
|
</div>
|
|
620
700
|
</div>
|
|
621
701
|
))}
|
|
@@ -830,6 +910,125 @@ dbrain connect claude http://server:7878 --token=sk-dbr_...
|
|
|
830
910
|
);
|
|
831
911
|
}
|
|
832
912
|
|
|
913
|
+
/* ── KEYS PAGE (shared brains) ───────────────────────── */
|
|
914
|
+
function KeysPage() {
|
|
915
|
+
const [keys, setKeys] = useState([]);
|
|
916
|
+
const [loading, setLoading] = useState(true);
|
|
917
|
+
const [isAdmin, setIsAdmin] = useState(true);
|
|
918
|
+
const [userId, setUserId] = useState('');
|
|
919
|
+
const [userName, setUserName] = useState('');
|
|
920
|
+
const [perms, setPerms] = useState('read+write');
|
|
921
|
+
const [created, setCreated] = useState(null);
|
|
922
|
+
const [err, setErr] = useState('');
|
|
923
|
+
|
|
924
|
+
const load = () => localApi('/keys').then(setKeys).catch(e => { if (String(e).includes('403')) setIsAdmin(false); }).finally(() => setLoading(false));
|
|
925
|
+
useEffect(() => { load(); }, []);
|
|
926
|
+
|
|
927
|
+
const create = () => {
|
|
928
|
+
if (!userId.trim() || !userName.trim()) return;
|
|
929
|
+
setErr(''); setCreated(null);
|
|
930
|
+
localApi('/keys', { method: 'POST', body: JSON.stringify({ userId: userId.trim(), userName: userName.trim(), permissions: perms }) })
|
|
931
|
+
.then(k => { setCreated(k); setUserId(''); setUserName(''); load(); })
|
|
932
|
+
.catch(() => setErr('Failed to create key'));
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
const revoke = (id) => {
|
|
936
|
+
if (!confirm('Revoke this key?')) return;
|
|
937
|
+
localApi(`/keys/${id}`, { method: 'DELETE' }).then(() => load()).catch(() => {});
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
if (!isAdmin) return (
|
|
941
|
+
<>
|
|
942
|
+
<div className="topbar"><span className="topbar-title">API Keys</span></div>
|
|
943
|
+
<div className="page"><div className="empty" style={{padding:40}}>
|
|
944
|
+
<p style={{marginBottom:8}}>Admin access required</p>
|
|
945
|
+
<p style={{fontSize:12,color:'var(--text-3)'}}>Log in with the master token to manage API keys.</p>
|
|
946
|
+
</div></div>
|
|
947
|
+
</>
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
return (
|
|
951
|
+
<>
|
|
952
|
+
<div className="topbar"><span className="topbar-title">API Keys</span></div>
|
|
953
|
+
<div className="page">
|
|
954
|
+
<div className="sec-head"><span className="sec-title">Create Key</span></div>
|
|
955
|
+
<div className="form-row">
|
|
956
|
+
<div className="form-field"><label>User ID</label><input value={userId} onChange={e => setUserId(e.target.value)} placeholder="e.g. ivan" /></div>
|
|
957
|
+
<div className="form-field"><label>Display Name</label><input value={userName} onChange={e => setUserName(e.target.value)} placeholder="e.g. Iván Campillo" /></div>
|
|
958
|
+
<div className="form-field"><label>Permissions</label>
|
|
959
|
+
<select value={perms} onChange={e => setPerms(e.target.value)}>
|
|
960
|
+
<option value="read+write">read+write</option><option value="read">read</option><option value="write">write</option>
|
|
961
|
+
</select>
|
|
962
|
+
</div>
|
|
963
|
+
<button className="form-btn" onClick={create}>Create</button>
|
|
964
|
+
</div>
|
|
965
|
+
{created && <div className="inline-msg success">Key created. Token: <strong>{created.token}</strong> — save it now, it won't be shown again.</div>}
|
|
966
|
+
{err && <div className="inline-msg error">{err}</div>}
|
|
967
|
+
|
|
968
|
+
<div className="sec-head" style={{marginTop:24}}><span className="sec-title">Active Keys</span><span className="sec-badge">{keys.filter(k => k.status === 'active').length}</span></div>
|
|
969
|
+
{loading ? <div className="empty">Loading...</div> : (
|
|
970
|
+
<table className="keys-table">
|
|
971
|
+
<thead><tr><th>User</th><th>Permissions</th><th>Token</th><th>Last Used</th><th>Status</th><th></th></tr></thead>
|
|
972
|
+
<tbody>
|
|
973
|
+
{keys.map(k => (
|
|
974
|
+
<tr key={k.id}>
|
|
975
|
+
<td>{k.userName} <span style={{color:'var(--text-3)'}}>({k.userId})</span></td>
|
|
976
|
+
<td>{k.permissions}</td>
|
|
977
|
+
<td style={{color:'var(--text-3)'}}>{k.tokenPreview}</td>
|
|
978
|
+
<td style={{color:'var(--text-3)'}}>{k.lastUsed ? k.lastUsed.split('T')[0] : 'never'}</td>
|
|
979
|
+
<td>{k.status === 'active' ? <span style={{color:'var(--online)'}}>active</span> : <span style={{color:'#ef4444'}}>revoked</span>}</td>
|
|
980
|
+
<td>{k.status === 'active' && <button className="form-btn danger" onClick={() => revoke(k.id)} style={{padding:'3px 8px',fontSize:10}}>Revoke</button>}</td>
|
|
981
|
+
</tr>
|
|
982
|
+
))}
|
|
983
|
+
{keys.length === 0 && <tr><td colSpan="6" style={{textAlign:'center',color:'var(--text-3)',padding:20}}>No API keys</td></tr>}
|
|
984
|
+
</tbody>
|
|
985
|
+
</table>
|
|
986
|
+
)}
|
|
987
|
+
</div>
|
|
988
|
+
</>
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/* ── CONNECTIONS PAGE (personal brains) ──────────────── */
|
|
993
|
+
function ConnectionsPage() {
|
|
994
|
+
const [conns, setConns] = useState([]);
|
|
995
|
+
const [loading, setLoading] = useState(true);
|
|
996
|
+
|
|
997
|
+
useEffect(() => {
|
|
998
|
+
localApi('/connections').then(setConns).catch(() => {}).finally(() => setLoading(false));
|
|
999
|
+
}, []);
|
|
1000
|
+
|
|
1001
|
+
return (
|
|
1002
|
+
<>
|
|
1003
|
+
<div className="topbar"><span className="topbar-title">Connected Brains</span></div>
|
|
1004
|
+
<div className="page">
|
|
1005
|
+
{loading ? <div className="empty">Loading...</div> : conns.length === 0 ? (
|
|
1006
|
+
<div className="empty" style={{padding:40}}>
|
|
1007
|
+
<p style={{marginBottom:8}}>No connected brains</p>
|
|
1008
|
+
<p style={{fontSize:12,color:'var(--text-3)'}}>Use <code style={{background:'var(--surface-2)',padding:'2px 6px',borderRadius:4}}>dbrain link <url> --token <token></code> to connect to a shared brain.</p>
|
|
1009
|
+
</div>
|
|
1010
|
+
) : (
|
|
1011
|
+
<>
|
|
1012
|
+
<div className="sec-head"><span className="sec-title">Connections</span><span className="sec-badge">{conns.length}</span></div>
|
|
1013
|
+
{conns.map(c => (
|
|
1014
|
+
<div key={c.name} className="conn-card">
|
|
1015
|
+
<div className="conn-card-head">
|
|
1016
|
+
<span className={`conn-dot ${c.online ? 'online' : 'offline'}`}></span>
|
|
1017
|
+
<span className="conn-card-name">{c.brainName || c.name}</span>
|
|
1018
|
+
<span className="brain-type-badge shared">shared</span>
|
|
1019
|
+
</div>
|
|
1020
|
+
<div className="conn-card-url">{c.url}</div>
|
|
1021
|
+
{c.online && <div className="conn-card-stats">{c.entities} entities · {c.facts} facts</div>}
|
|
1022
|
+
{!c.online && <div className="conn-card-stats" style={{color:'#ef4444'}}>offline</div>}
|
|
1023
|
+
</div>
|
|
1024
|
+
))}
|
|
1025
|
+
</>
|
|
1026
|
+
)}
|
|
1027
|
+
</div>
|
|
1028
|
+
</>
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
833
1032
|
/* ── APP ──────────────────────────────────────────────── */
|
|
834
1033
|
function App() {
|
|
835
1034
|
const { route, segs, nav } = useRouter();
|
|
@@ -838,6 +1037,9 @@ function App() {
|
|
|
838
1037
|
const [health, setHealth] = useState(null);
|
|
839
1038
|
const [overview, setOverview] = useState(null);
|
|
840
1039
|
const [conversations, setConversations] = useState(null);
|
|
1040
|
+
const [connections, setConnections] = useState([]);
|
|
1041
|
+
const [user, setUser] = useState(null);
|
|
1042
|
+
const [activeBrain, setActiveBrain] = useState(null);
|
|
841
1043
|
const authed = Boolean(token);
|
|
842
1044
|
|
|
843
1045
|
const togglePalette = (p) => {
|
|
@@ -850,10 +1052,24 @@ function App() {
|
|
|
850
1052
|
|
|
851
1053
|
useEffect(() => {
|
|
852
1054
|
if (!authed) return;
|
|
853
|
-
|
|
1055
|
+
localApi('/health').then(setHealth).catch(() => { localStorage.removeItem(TOKEN_KEY); setToken(''); });
|
|
1056
|
+
localApi('/connections').then(setConnections).catch(() => {});
|
|
1057
|
+
localApi('/me').then(setUser).catch(() => {});
|
|
1058
|
+
}, [authed]);
|
|
1059
|
+
|
|
1060
|
+
useEffect(() => {
|
|
1061
|
+
if (!authed) return;
|
|
1062
|
+
setActiveBrainPrefix(activeBrain);
|
|
1063
|
+
setOverview(null);
|
|
1064
|
+
setConversations(null);
|
|
854
1065
|
api('/memory/summary').then(setOverview).catch(() => {});
|
|
855
1066
|
api('/conversations?limit=200').then(setConversations).catch(() => {});
|
|
856
|
-
}, [authed]);
|
|
1067
|
+
}, [authed, activeBrain]);
|
|
1068
|
+
|
|
1069
|
+
const handleBrainSwitch = useCallback((name) => {
|
|
1070
|
+
setActiveBrain(name);
|
|
1071
|
+
nav('#/dashboard');
|
|
1072
|
+
}, []);
|
|
857
1073
|
|
|
858
1074
|
useEffect(() => {
|
|
859
1075
|
if (!authed && route !== '/login') nav('#/login');
|
|
@@ -866,24 +1082,32 @@ function App() {
|
|
|
866
1082
|
|
|
867
1083
|
const brain = {
|
|
868
1084
|
name: health.name || 'dBrain',
|
|
1085
|
+
brainType: health.brainType || 'personal',
|
|
869
1086
|
version: health.version || '0.1.0',
|
|
870
1087
|
entities: health.entities || 0,
|
|
871
1088
|
facts: overview ? overview.reduce((s, e) => s + e.total, 0) : (health.facts || 0),
|
|
872
1089
|
conversations: health.conversations || 0,
|
|
873
1090
|
};
|
|
874
1091
|
|
|
1092
|
+
const activeBrainName = activeBrain
|
|
1093
|
+
? (connections.find(c => c.name === activeBrain)?.brainName || activeBrain)
|
|
1094
|
+
: null;
|
|
1095
|
+
|
|
875
1096
|
const renderPage = () => {
|
|
876
1097
|
const [sec, ...rest] = segs;
|
|
877
1098
|
if (sec === 'entity') return <EntityDetailPage id={rest[0]} nav={nav} />;
|
|
878
1099
|
if (sec === 'conversation') return <ConversationDetailPage id={rest[0]} nav={nav} />;
|
|
879
1100
|
if (sec === 'api') return <APIDocsPage />;
|
|
880
1101
|
if (sec === 'mcp') return <MCPDocsPage />;
|
|
881
|
-
|
|
1102
|
+
if (sec === 'keys' && brain.brainType === 'shared') return <KeysPage />;
|
|
1103
|
+
return <DashboardPage nav={nav} overview={overview} conversations={conversations} brainName={activeBrainName} />;
|
|
882
1104
|
};
|
|
883
1105
|
|
|
884
1106
|
return (
|
|
885
1107
|
<div className="shell">
|
|
886
|
-
<Sidebar route={route} nav={nav} palette={palette} onTogglePalette={togglePalette} brain={brain}
|
|
1108
|
+
<Sidebar route={route} nav={nav} palette={palette} onTogglePalette={togglePalette} brain={brain} connections={connections} user={user}
|
|
1109
|
+
activeBrain={activeBrain} onBrainSwitch={handleBrainSwitch}
|
|
1110
|
+
onLogout={() => { localStorage.removeItem(TOKEN_KEY); setToken(''); setHealth(null); setUser(null); setActiveBrain(null); nav('#/login'); }} />
|
|
887
1111
|
<div className="main">{renderPage()}</div>
|
|
888
1112
|
</div>
|
|
889
1113
|
);
|
package/dist/mcp/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAc/C,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,aAgrBnD;AA0BD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,eAAe,QAmC5C"}
|