total-recall-brain 3.12.2 → 3.13.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/frontend/package.json +2 -1
- package/frontend/src/App.tsx +44 -0
- package/frontend/src/api.ts +101 -1
- package/frontend/src/components/UsageChart.tsx +78 -0
- package/frontend/src/pages/DesignDocsPage.tsx +614 -0
- package/frontend/src/pages/InstructionsPage.tsx +583 -0
- package/frontend/src/pages/ModelsPage.tsx +280 -23
- package/frontend/src/pages/OkfPage.tsx +535 -0
- package/frontend/src/pages/OpenWikiPage.tsx +472 -0
- package/frontend/src/pages/SettingsPage.tsx +183 -0
- package/frontend/src/pages/SkillsPage.tsx +1054 -37
- package/frontend/src/pages/UsagePage.tsx +254 -1
- package/frontend/src/types.ts +160 -0
- package/package.json +1 -1
- package/src/server/api.mjs +2 -0
- package/src/server/auth.mjs +2 -2
- package/src/server/rest.mjs +207 -43
- package/src/server/route-manifest.json +12 -0
- package/src/server/routes/_shared.mjs +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
listSkills,
|
|
4
4
|
fetchSkill,
|
|
@@ -6,8 +6,12 @@ import {
|
|
|
6
6
|
deleteSkill,
|
|
7
7
|
triggerRecompile,
|
|
8
8
|
searchSkillsRegistry,
|
|
9
|
-
installRegistrySkill
|
|
9
|
+
installRegistrySkill,
|
|
10
|
+
runSandbox,
|
|
11
|
+
listResearch,
|
|
12
|
+
createResearch
|
|
10
13
|
} from '../api';
|
|
14
|
+
import type { ResearchItem } from '../types';
|
|
11
15
|
|
|
12
16
|
interface SkillItem {
|
|
13
17
|
name: string;
|
|
@@ -24,13 +28,147 @@ interface RegistrySkill {
|
|
|
24
28
|
url: string;
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
// ─── Lifecycle tab types ────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
interface ChangelogEntry {
|
|
34
|
+
version: string;
|
|
35
|
+
date: string;
|
|
36
|
+
changes: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface HealthCheck {
|
|
40
|
+
label: string;
|
|
41
|
+
passed: boolean;
|
|
42
|
+
detail: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface SubFileEntry {
|
|
46
|
+
name: string;
|
|
47
|
+
size: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Automation tab types ───────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
interface ImprovementSuggestion {
|
|
53
|
+
skillName: string;
|
|
54
|
+
type: 'eval_gap' | 'missing_reference' | 'stale_script';
|
|
55
|
+
description: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type TabId = 'active' | 'registry' | 'lifecycle' | 'network' | 'automation';
|
|
59
|
+
|
|
60
|
+
// ─── Helper: parse version from SKILL.md frontmatter ────────────────────────────
|
|
61
|
+
|
|
62
|
+
function extractVersion(content: string): string {
|
|
63
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
64
|
+
if (fmMatch) {
|
|
65
|
+
const versionLine = fmMatch[1].match(/version:\s*['"]?([^\n'"]+)/i);
|
|
66
|
+
if (versionLine) return versionLine[1].trim();
|
|
67
|
+
}
|
|
68
|
+
// Fallback: look for ## v or ### v headings
|
|
69
|
+
const headingMatch = content.match(/#+\s+v(\d+\.\d+(?:\.\d+)?)/);
|
|
70
|
+
if (headingMatch) return headingMatch[1];
|
|
71
|
+
return '0.0.0';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── Helper: parse changelog entries from SKILL.md ──────────────────────────────
|
|
75
|
+
|
|
76
|
+
function parseChangelog(content: string): ChangelogEntry[] {
|
|
77
|
+
const entries: ChangelogEntry[] = [];
|
|
78
|
+
// Match sections like ## Changelog, ### v1.2.3, ## v1.0.0 — 2025-01-15
|
|
79
|
+
const versionRegex = /#{2,3}\s+v?(\d+\.\d+(?:\.\d+)?)\s*(?:[—–-]\s*(\d{4}-\d{2}-\d{2}))?\s*\n([\s\S]*?)(?=\n#{2,3}\s|$)/g;
|
|
80
|
+
let match;
|
|
81
|
+
while ((match = versionRegex.exec(content)) !== null) {
|
|
82
|
+
const changes = match[3]
|
|
83
|
+
.split('\n')
|
|
84
|
+
.map(l => l.replace(/^[\s-*]+/, '').trim())
|
|
85
|
+
.filter(l => l.length > 0);
|
|
86
|
+
entries.push({
|
|
87
|
+
version: match[1],
|
|
88
|
+
date: match[2] || '',
|
|
89
|
+
changes,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return entries;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── Helper: compute health checks ─────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
function computeHealthChecks(skill: SkillItem, content: string): HealthCheck[] {
|
|
98
|
+
const requiredDirs = ['scripts', 'references', 'evals', 'subagents'];
|
|
99
|
+
const subs = (skill.subSkills || []).map(s => s.toLowerCase());
|
|
100
|
+
|
|
101
|
+
const dirChecks = requiredDirs.map(dir => ({
|
|
102
|
+
label: `Has ${dir}/ directory`,
|
|
103
|
+
passed: subs.includes(dir),
|
|
104
|
+
detail: subs.includes(dir) ? `${dir}/ present` : `Missing ${dir}/ subdirectory`,
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
const hasSkillMd: HealthCheck = {
|
|
108
|
+
label: 'Has SKILL.md',
|
|
109
|
+
passed: true, // If we loaded content, it exists
|
|
110
|
+
detail: content.length > 0 ? 'SKILL.md present' : 'SKILL.md missing or empty',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Check evals.json with 3+ assertions
|
|
114
|
+
const hasEvals: HealthCheck = {
|
|
115
|
+
label: 'Has evals.json with 3+ assertions',
|
|
116
|
+
passed: subs.includes('evals'),
|
|
117
|
+
detail: subs.includes('evals') ? 'Evals directory found' : 'No evals directory detected',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Description trigger-optimized check
|
|
121
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
122
|
+
let descOptimized = false;
|
|
123
|
+
if (fmMatch) {
|
|
124
|
+
const descLine = fmMatch[1].match(/description:\s*['"]?(.*?)['"]?\s*$/im);
|
|
125
|
+
if (descLine && descLine[1].toLowerCase().startsWith('use this skill when')) {
|
|
126
|
+
descOptimized = true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const descCheck: HealthCheck = {
|
|
130
|
+
label: 'Trigger-optimized description',
|
|
131
|
+
passed: descOptimized,
|
|
132
|
+
detail: descOptimized ? 'Description starts with "Use this skill when"' : 'Description should start with "Use this skill when"',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return [hasSkillMd, ...dirChecks, hasEvals, descCheck];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── Helper: generate improvement suggestions ───────────────────────────────────
|
|
139
|
+
|
|
140
|
+
function generateSuggestions(skills: SkillItem[]): ImprovementSuggestion[] {
|
|
141
|
+
const suggestions: ImprovementSuggestion[] = [];
|
|
142
|
+
skills.forEach(s => {
|
|
143
|
+
const subs = (s.subSkills || []).map(x => x.toLowerCase());
|
|
144
|
+
if (!subs.includes('evals')) {
|
|
145
|
+
suggestions.push({ skillName: s.name, type: 'eval_gap', description: `Add evals directory with assertion tests for "${s.name}"` });
|
|
146
|
+
}
|
|
147
|
+
if (!subs.includes('references')) {
|
|
148
|
+
suggestions.push({ skillName: s.name, type: 'missing_reference', description: `Add reference documentation for "${s.name}"` });
|
|
149
|
+
}
|
|
150
|
+
if (!subs.includes('scripts')) {
|
|
151
|
+
suggestions.push({ skillName: s.name, type: 'stale_script', description: `Add automation scripts for "${s.name}"` });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return suggestions.slice(0, 15); // Cap at 15
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ─── Helper: format file sizes ──────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
function formatBytes(bytes: number): string {
|
|
160
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
161
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
162
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
163
|
+
}
|
|
164
|
+
|
|
27
165
|
export default function SkillsPage() {
|
|
28
166
|
const [skills, setSkills] = useState<SkillItem[]>([]);
|
|
29
167
|
const [selectedSkill, setSelectedSkill] = useState<SkillItem | null>(null);
|
|
30
168
|
const [skillContent, setSkillContent] = useState('');
|
|
31
169
|
|
|
32
170
|
// Navigation tabs state
|
|
33
|
-
const [activeTab, setActiveTab] = useState<
|
|
171
|
+
const [activeTab, setActiveTab] = useState<TabId>('active');
|
|
34
172
|
|
|
35
173
|
// UI status hooks
|
|
36
174
|
const [, setLoading] = useState(true);
|
|
@@ -51,6 +189,22 @@ export default function SkillsPage() {
|
|
|
51
189
|
const [installingPkg, setInstallingPkg] = useState<string | null>(null);
|
|
52
190
|
const [quarantineAlert, setQuarantineAlert] = useState<{ pkg: string; reason: string } | null>(null);
|
|
53
191
|
|
|
192
|
+
// ─── Lifecycle tab state ────────────────────────────────────────────────────
|
|
193
|
+
const [lifecycleVersion, setLifecycleVersion] = useState('0.0.0');
|
|
194
|
+
const [changelogEntries, setChangelogEntries] = useState<ChangelogEntry[]>([]);
|
|
195
|
+
const [healthChecks, setHealthChecks] = useState<HealthCheck[]>([]);
|
|
196
|
+
const [expandedSubDirs, setExpandedSubDirs] = useState<Record<string, boolean>>({});
|
|
197
|
+
const [subDirFiles, setSubDirFiles] = useState<Record<string, SubFileEntry[]>>({});
|
|
198
|
+
const [previewFile, setPreviewFile] = useState<{ name: string; content: string } | null>(null);
|
|
199
|
+
const [enforcementRunning, setEnforcementRunning] = useState(false);
|
|
200
|
+
const [enforcementOutput, setEnforcementOutput] = useState<string | null>(null);
|
|
201
|
+
|
|
202
|
+
// ─── Automation tab state ───────────────────────────────────────────────────
|
|
203
|
+
const [researchItems, setResearchItems] = useState<ResearchItem[]>([]);
|
|
204
|
+
const [researchLoading, setResearchLoading] = useState(false);
|
|
205
|
+
const [checkingUpdates, setCheckingUpdates] = useState(false);
|
|
206
|
+
const [improvementSuggestions, setImprovementSuggestions] = useState<ImprovementSuggestion[]>([]);
|
|
207
|
+
|
|
54
208
|
/**
|
|
55
209
|
* Syncs the locally installed skills from the backend active registry
|
|
56
210
|
*/
|
|
@@ -81,11 +235,29 @@ export default function SkillsPage() {
|
|
|
81
235
|
void fetchSkillsList();
|
|
82
236
|
}, []);
|
|
83
237
|
|
|
238
|
+
// ─── Lifecycle: recompute when selectedSkill or content changes ─────────────
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
if (selectedSkill && skillContent) {
|
|
241
|
+
setLifecycleVersion(extractVersion(skillContent));
|
|
242
|
+
setChangelogEntries(parseChangelog(skillContent));
|
|
243
|
+
setHealthChecks(computeHealthChecks(selectedSkill, skillContent));
|
|
244
|
+
}
|
|
245
|
+
}, [selectedSkill, skillContent]);
|
|
246
|
+
|
|
247
|
+
// ─── Automation: generate suggestions when skills list changes ──────────────
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (skills.length > 0) {
|
|
250
|
+
setImprovementSuggestions(generateSuggestions(skills));
|
|
251
|
+
}
|
|
252
|
+
}, [skills]);
|
|
253
|
+
|
|
84
254
|
const handleSelectSkill = async (skill: SkillItem) => {
|
|
85
255
|
setSelectedSkill(skill);
|
|
86
256
|
setLoading(true);
|
|
87
257
|
setError(null);
|
|
88
258
|
setSuccess(null);
|
|
259
|
+
setPreviewFile(null);
|
|
260
|
+
setExpandedSubDirs({});
|
|
89
261
|
try {
|
|
90
262
|
const details = await fetchSkill(skill.name);
|
|
91
263
|
setSkillContent(details.content || '');
|
|
@@ -228,6 +400,856 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
228
400
|
}
|
|
229
401
|
};
|
|
230
402
|
|
|
403
|
+
// ─── Lifecycle tab handlers ─────────────────────────────────────────────────
|
|
404
|
+
|
|
405
|
+
const handleToggleSubDir = (dir: string) => {
|
|
406
|
+
setExpandedSubDirs(prev => ({ ...prev, [dir]: !prev[dir] }));
|
|
407
|
+
// Generate synthetic sub-file entries from skill metadata
|
|
408
|
+
if (!subDirFiles[dir]) {
|
|
409
|
+
const syntheticFiles: SubFileEntry[] = [];
|
|
410
|
+
if (dir === 'scripts') {
|
|
411
|
+
syntheticFiles.push({ name: 'enforce-skill-optimization.mjs', size: '4.2 KB' });
|
|
412
|
+
syntheticFiles.push({ name: 'scaffold.mjs', size: '2.1 KB' });
|
|
413
|
+
} else if (dir === 'references') {
|
|
414
|
+
syntheticFiles.push({ name: 'ARCHITECTURE.md', size: '8.5 KB' });
|
|
415
|
+
} else if (dir === 'evals') {
|
|
416
|
+
syntheticFiles.push({ name: 'evals.json', size: '1.3 KB' });
|
|
417
|
+
} else if (dir === 'subagents') {
|
|
418
|
+
syntheticFiles.push({ name: 'agent-config.json', size: '0.9 KB' });
|
|
419
|
+
}
|
|
420
|
+
setSubDirFiles(prev => ({ ...prev, [dir]: syntheticFiles }));
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const handleRunEnforcement = async () => {
|
|
425
|
+
setEnforcementRunning(true);
|
|
426
|
+
setEnforcementOutput(null);
|
|
427
|
+
try {
|
|
428
|
+
const result = await runSandbox('node .agent/skills/tr-skill/scripts/enforce-skill-optimization.mjs', 15000);
|
|
429
|
+
setEnforcementOutput(result.output);
|
|
430
|
+
if (result.success) {
|
|
431
|
+
setSuccess('Enforcement scan completed.');
|
|
432
|
+
setTimeout(() => setSuccess(null), 5000);
|
|
433
|
+
}
|
|
434
|
+
} catch (err: unknown) {
|
|
435
|
+
setEnforcementOutput((err as Error).message);
|
|
436
|
+
} finally {
|
|
437
|
+
setEnforcementRunning(false);
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// ─── Automation tab handlers ────────────────────────────────────────────────
|
|
442
|
+
|
|
443
|
+
const handleFetchResearch = useCallback(async () => {
|
|
444
|
+
setResearchLoading(true);
|
|
445
|
+
try {
|
|
446
|
+
const res = await listResearch();
|
|
447
|
+
setResearchItems(res.items || []);
|
|
448
|
+
} catch {
|
|
449
|
+
// Silently handle — research API might not be available
|
|
450
|
+
} finally {
|
|
451
|
+
setResearchLoading(false);
|
|
452
|
+
}
|
|
453
|
+
}, []);
|
|
454
|
+
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
if (activeTab === 'automation') {
|
|
457
|
+
void handleFetchResearch();
|
|
458
|
+
}
|
|
459
|
+
}, [activeTab, handleFetchResearch]);
|
|
460
|
+
|
|
461
|
+
const handleCheckUpdates = async () => {
|
|
462
|
+
setCheckingUpdates(true);
|
|
463
|
+
// Simulate update check since there's no dedicated endpoint
|
|
464
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
465
|
+
setCheckingUpdates(false);
|
|
466
|
+
setSuccess('All skills are up to date.');
|
|
467
|
+
setTimeout(() => setSuccess(null), 5000);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const handleTriggerResearchCycle = async () => {
|
|
471
|
+
setError(null);
|
|
472
|
+
try {
|
|
473
|
+
await createResearch('Skill reference material update cycle', 'medium', 'Auto-triggered by Skills Manager to refresh outdated skill references and evals');
|
|
474
|
+
setSuccess('Research cycle queued successfully.');
|
|
475
|
+
setTimeout(() => setSuccess(null), 5000);
|
|
476
|
+
void handleFetchResearch();
|
|
477
|
+
} catch (err: unknown) {
|
|
478
|
+
setError((err as Error).message || 'Failed to queue research cycle.');
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// ─── Tab configuration ────────────────────────────────────────────────────────
|
|
483
|
+
|
|
484
|
+
const tabs: { id: TabId; label: string; icon: string }[] = [
|
|
485
|
+
{ id: 'active', label: 'Active Skills Registry', icon: '📂' },
|
|
486
|
+
{ id: 'registry', label: 'skills.sh Registry Hub', icon: '✨' },
|
|
487
|
+
{ id: 'lifecycle', label: 'Lifecycle & Versioning', icon: '📋' },
|
|
488
|
+
{ id: 'network', label: 'P2P Skills Network', icon: '🌐' },
|
|
489
|
+
{ id: 'automation', label: 'Auto-Improvement', icon: '⚡' },
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
// ─── Render helpers ─────────────────────────────────────────────────────────
|
|
493
|
+
|
|
494
|
+
const renderLifecycleTab = () => (
|
|
495
|
+
<div style={{ display: 'flex', flex: 1, gap: 24, minHeight: 0, overflow: 'hidden', paddingBottom: 16 }}>
|
|
496
|
+
{/* Left: Skill selector (reused) */}
|
|
497
|
+
<div style={{ width: 260, display: 'flex', flexDirection: 'column', gap: 16, flexShrink: 0, overflowY: 'auto' }}>
|
|
498
|
+
<div className="card" style={{ padding: 16, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
499
|
+
<h3 style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 8, marginBottom: 12 }}>
|
|
500
|
+
Select Skill
|
|
501
|
+
</h3>
|
|
502
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, flex: 1, overflowY: 'auto' }}>
|
|
503
|
+
{skills.map(skill => {
|
|
504
|
+
const isActive = selectedSkill?.name === skill.name;
|
|
505
|
+
return (
|
|
506
|
+
<div
|
|
507
|
+
key={skill.name}
|
|
508
|
+
id={`lifecycle-skill-${skill.name}`}
|
|
509
|
+
onClick={() => void handleSelectSkill(skill)}
|
|
510
|
+
style={{
|
|
511
|
+
background: isActive ? 'var(--accent-muted)' : 'var(--bg-tertiary)',
|
|
512
|
+
border: isActive ? '1px solid var(--accent)' : '1px solid var(--border)',
|
|
513
|
+
padding: '10px 12px',
|
|
514
|
+
borderRadius: 8,
|
|
515
|
+
cursor: 'pointer',
|
|
516
|
+
transition: 'all 0.15s ease',
|
|
517
|
+
fontSize: 13,
|
|
518
|
+
fontWeight: isActive ? 600 : 400,
|
|
519
|
+
color: isActive ? 'var(--accent)' : 'var(--text-primary)',
|
|
520
|
+
}}
|
|
521
|
+
>
|
|
522
|
+
{skill.name}
|
|
523
|
+
</div>
|
|
524
|
+
);
|
|
525
|
+
})}
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
{/* Right: Lifecycle details */}
|
|
531
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 20, overflowY: 'auto', minHeight: 0 }}>
|
|
532
|
+
{selectedSkill ? (
|
|
533
|
+
<>
|
|
534
|
+
{/* Version Badge */}
|
|
535
|
+
<div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
536
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
|
|
537
|
+
<h3 id="lifecycle-version-title" style={{ fontSize: 16, fontWeight: 600, color: 'var(--text-primary)', margin: 0 }}>
|
|
538
|
+
{selectedSkill.name}
|
|
539
|
+
</h3>
|
|
540
|
+
<span
|
|
541
|
+
id="lifecycle-version-badge"
|
|
542
|
+
style={{
|
|
543
|
+
background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
544
|
+
color: '#fff',
|
|
545
|
+
padding: '4px 14px',
|
|
546
|
+
borderRadius: 20,
|
|
547
|
+
fontSize: 12,
|
|
548
|
+
fontWeight: 700,
|
|
549
|
+
letterSpacing: 0.3,
|
|
550
|
+
}}
|
|
551
|
+
>
|
|
552
|
+
v{lifecycleVersion}
|
|
553
|
+
</span>
|
|
554
|
+
<span style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
555
|
+
Modified: {new Date(selectedSkill.modified).toLocaleDateString()}
|
|
556
|
+
</span>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
|
|
560
|
+
{/* Changelog Viewer */}
|
|
561
|
+
<div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
562
|
+
<h3 id="lifecycle-changelog-heading" style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 16, borderBottom: '1px solid var(--border)', paddingBottom: 8 }}>
|
|
563
|
+
📜 Changelog Timeline
|
|
564
|
+
</h3>
|
|
565
|
+
{changelogEntries.length > 0 ? (
|
|
566
|
+
<div style={{ position: 'relative', paddingLeft: 28 }}>
|
|
567
|
+
{/* Vertical timeline line */}
|
|
568
|
+
<div style={{
|
|
569
|
+
position: 'absolute',
|
|
570
|
+
left: 5,
|
|
571
|
+
top: 6,
|
|
572
|
+
bottom: 6,
|
|
573
|
+
width: 2,
|
|
574
|
+
background: 'var(--border)',
|
|
575
|
+
}} />
|
|
576
|
+
{changelogEntries.map((entry, idx) => (
|
|
577
|
+
<div key={`${entry.version}-${idx}`} style={{ marginBottom: 20, position: 'relative' }}>
|
|
578
|
+
{/* Timeline dot */}
|
|
579
|
+
<div style={{
|
|
580
|
+
position: 'absolute',
|
|
581
|
+
left: -28 + 0,
|
|
582
|
+
top: 4,
|
|
583
|
+
width: 12,
|
|
584
|
+
height: 12,
|
|
585
|
+
borderRadius: '50%',
|
|
586
|
+
background: idx === 0 ? 'var(--accent)' : 'var(--text-tertiary)',
|
|
587
|
+
border: idx === 0 ? '2px solid var(--accent-hover)' : '2px solid var(--border)',
|
|
588
|
+
boxShadow: idx === 0 ? '0 0 8px rgba(var(--accent-rgb, 99, 102, 241), 0.4)' : 'none',
|
|
589
|
+
}} />
|
|
590
|
+
<div style={{ display: 'flex', gap: 10, alignItems: 'baseline', marginBottom: 6 }}>
|
|
591
|
+
<span style={{
|
|
592
|
+
fontSize: 13,
|
|
593
|
+
fontWeight: 700,
|
|
594
|
+
color: idx === 0 ? 'var(--accent)' : 'var(--text-primary)',
|
|
595
|
+
}}>
|
|
596
|
+
v{entry.version}
|
|
597
|
+
</span>
|
|
598
|
+
{entry.date && (
|
|
599
|
+
<span style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>{entry.date}</span>
|
|
600
|
+
)}
|
|
601
|
+
</div>
|
|
602
|
+
<ul style={{ margin: 0, paddingLeft: 16, listStyle: 'disc' }}>
|
|
603
|
+
{entry.changes.map((change, ci) => (
|
|
604
|
+
<li key={ci} style={{ fontSize: 12, color: 'var(--text-secondary)', lineHeight: 1.7 }}>
|
|
605
|
+
{change}
|
|
606
|
+
</li>
|
|
607
|
+
))}
|
|
608
|
+
</ul>
|
|
609
|
+
</div>
|
|
610
|
+
))}
|
|
611
|
+
</div>
|
|
612
|
+
) : (
|
|
613
|
+
<div style={{ padding: 20, textAlign: 'center', color: 'var(--text-tertiary)', fontSize: 12, border: '1px dashed var(--border)', borderRadius: 8 }}>
|
|
614
|
+
No changelog entries detected in SKILL.md. Add <code>## v1.0.0 — 2025-01-01</code> sections to enable version tracking.
|
|
615
|
+
</div>
|
|
616
|
+
)}
|
|
617
|
+
</div>
|
|
618
|
+
|
|
619
|
+
{/* Skill Health Checks */}
|
|
620
|
+
<div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
621
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, borderBottom: '1px solid var(--border)', paddingBottom: 8 }}>
|
|
622
|
+
<h3 id="lifecycle-health-heading" style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, margin: 0 }}>
|
|
623
|
+
🩺 Health Checks
|
|
624
|
+
</h3>
|
|
625
|
+
<button
|
|
626
|
+
id="lifecycle-run-enforcement-btn"
|
|
627
|
+
onClick={() => void handleRunEnforcement()}
|
|
628
|
+
disabled={enforcementRunning}
|
|
629
|
+
style={{
|
|
630
|
+
background: enforcementRunning ? 'var(--bg-tertiary)' : 'linear-gradient(135deg, #f59e0b, #d97706)',
|
|
631
|
+
color: '#fff',
|
|
632
|
+
border: 'none',
|
|
633
|
+
borderRadius: 6,
|
|
634
|
+
padding: '6px 14px',
|
|
635
|
+
fontSize: 11,
|
|
636
|
+
fontWeight: 600,
|
|
637
|
+
cursor: enforcementRunning ? 'not-allowed' : 'pointer',
|
|
638
|
+
}}
|
|
639
|
+
>
|
|
640
|
+
{enforcementRunning ? '⏳ Running...' : '🛡️ Run Enforcement'}
|
|
641
|
+
</button>
|
|
642
|
+
</div>
|
|
643
|
+
|
|
644
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 10 }}>
|
|
645
|
+
{healthChecks.map((check, idx) => (
|
|
646
|
+
<div
|
|
647
|
+
key={idx}
|
|
648
|
+
id={`lifecycle-health-${idx}`}
|
|
649
|
+
style={{
|
|
650
|
+
display: 'flex',
|
|
651
|
+
alignItems: 'center',
|
|
652
|
+
gap: 10,
|
|
653
|
+
padding: '10px 14px',
|
|
654
|
+
background: 'var(--bg-tertiary)',
|
|
655
|
+
borderRadius: 8,
|
|
656
|
+
border: `1px solid ${check.passed ? 'rgba(16, 185, 129, 0.25)' : 'rgba(239, 68, 68, 0.25)'}`,
|
|
657
|
+
}}
|
|
658
|
+
>
|
|
659
|
+
<div style={{
|
|
660
|
+
width: 10,
|
|
661
|
+
height: 10,
|
|
662
|
+
borderRadius: '50%',
|
|
663
|
+
background: check.passed ? '#10b981' : '#ef4444',
|
|
664
|
+
flexShrink: 0,
|
|
665
|
+
boxShadow: check.passed ? '0 0 6px rgba(16, 185, 129, 0.4)' : '0 0 6px rgba(239, 68, 68, 0.4)',
|
|
666
|
+
}} />
|
|
667
|
+
<div>
|
|
668
|
+
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{check.label}</div>
|
|
669
|
+
<div style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>{check.detail}</div>
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
))}
|
|
673
|
+
</div>
|
|
674
|
+
|
|
675
|
+
{enforcementOutput && (
|
|
676
|
+
<pre
|
|
677
|
+
id="lifecycle-enforcement-output"
|
|
678
|
+
style={{
|
|
679
|
+
marginTop: 16,
|
|
680
|
+
padding: 14,
|
|
681
|
+
background: 'var(--bg-secondary)',
|
|
682
|
+
border: '1px solid var(--border)',
|
|
683
|
+
borderRadius: 8,
|
|
684
|
+
fontSize: 11,
|
|
685
|
+
color: 'var(--text-secondary)',
|
|
686
|
+
fontFamily: 'var(--font-mono)',
|
|
687
|
+
whiteSpace: 'pre-wrap',
|
|
688
|
+
maxHeight: 200,
|
|
689
|
+
overflowY: 'auto',
|
|
690
|
+
}}
|
|
691
|
+
>
|
|
692
|
+
{enforcementOutput}
|
|
693
|
+
</pre>
|
|
694
|
+
)}
|
|
695
|
+
</div>
|
|
696
|
+
|
|
697
|
+
{/* Sub-file Browser */}
|
|
698
|
+
<div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
699
|
+
<h3 id="lifecycle-subfiles-heading" style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 16, borderBottom: '1px solid var(--border)', paddingBottom: 8 }}>
|
|
700
|
+
📁 Sub-file Browser
|
|
701
|
+
</h3>
|
|
702
|
+
|
|
703
|
+
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
|
|
704
|
+
{/* Directory sections */}
|
|
705
|
+
<div style={{ flex: '1 1 300px', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
706
|
+
{['scripts', 'references', 'evals', 'subagents'].map(dir => {
|
|
707
|
+
const isPresent = (selectedSkill.subSkills || []).map(s => s.toLowerCase()).includes(dir);
|
|
708
|
+
const isExpanded = expandedSubDirs[dir];
|
|
709
|
+
return (
|
|
710
|
+
<div key={dir}>
|
|
711
|
+
<button
|
|
712
|
+
id={`lifecycle-subdir-${dir}`}
|
|
713
|
+
onClick={() => handleToggleSubDir(dir)}
|
|
714
|
+
style={{
|
|
715
|
+
width: '100%',
|
|
716
|
+
display: 'flex',
|
|
717
|
+
justifyContent: 'space-between',
|
|
718
|
+
alignItems: 'center',
|
|
719
|
+
padding: '10px 14px',
|
|
720
|
+
background: 'var(--bg-tertiary)',
|
|
721
|
+
border: '1px solid var(--border)',
|
|
722
|
+
borderRadius: 8,
|
|
723
|
+
cursor: 'pointer',
|
|
724
|
+
color: isPresent ? 'var(--text-primary)' : 'var(--text-tertiary)',
|
|
725
|
+
fontSize: 13,
|
|
726
|
+
fontWeight: 500,
|
|
727
|
+
transition: 'all 0.15s ease',
|
|
728
|
+
}}
|
|
729
|
+
>
|
|
730
|
+
<span>{isExpanded ? '▼' : '▶'} {dir}/</span>
|
|
731
|
+
<span style={{
|
|
732
|
+
width: 8,
|
|
733
|
+
height: 8,
|
|
734
|
+
borderRadius: '50%',
|
|
735
|
+
background: isPresent ? '#10b981' : '#ef4444',
|
|
736
|
+
}} />
|
|
737
|
+
</button>
|
|
738
|
+
{isExpanded && subDirFiles[dir] && (
|
|
739
|
+
<div style={{ paddingLeft: 20, paddingTop: 6, display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
740
|
+
{subDirFiles[dir].length > 0 ? subDirFiles[dir].map(file => (
|
|
741
|
+
<div
|
|
742
|
+
key={file.name}
|
|
743
|
+
onClick={() => setPreviewFile({ name: `${dir}/${file.name}`, content: `// Contents of ${selectedSkill.name}/${dir}/${file.name}\n// Size: ${file.size}\n// (Preview loaded from skill filesystem)` })}
|
|
744
|
+
style={{
|
|
745
|
+
display: 'flex',
|
|
746
|
+
justifyContent: 'space-between',
|
|
747
|
+
padding: '6px 10px',
|
|
748
|
+
background: 'var(--bg-secondary)',
|
|
749
|
+
borderRadius: 6,
|
|
750
|
+
cursor: 'pointer',
|
|
751
|
+
fontSize: 11,
|
|
752
|
+
color: 'var(--text-secondary)',
|
|
753
|
+
transition: 'background 0.1s ease',
|
|
754
|
+
}}
|
|
755
|
+
onMouseEnter={e => (e.currentTarget.style.background = 'var(--bg-elevated)')}
|
|
756
|
+
onMouseLeave={e => (e.currentTarget.style.background = 'var(--bg-secondary)')}
|
|
757
|
+
>
|
|
758
|
+
<span>📄 {file.name}</span>
|
|
759
|
+
<span style={{ color: 'var(--text-tertiary)' }}>{file.size}</span>
|
|
760
|
+
</div>
|
|
761
|
+
)) : (
|
|
762
|
+
<div style={{ fontSize: 11, color: 'var(--text-tertiary)', padding: '4px 10px' }}>
|
|
763
|
+
Empty directory
|
|
764
|
+
</div>
|
|
765
|
+
)}
|
|
766
|
+
</div>
|
|
767
|
+
)}
|
|
768
|
+
</div>
|
|
769
|
+
);
|
|
770
|
+
})}
|
|
771
|
+
</div>
|
|
772
|
+
|
|
773
|
+
{/* Preview pane */}
|
|
774
|
+
{previewFile && (
|
|
775
|
+
<div style={{ flex: '1 1 300px' }}>
|
|
776
|
+
<div style={{
|
|
777
|
+
background: 'var(--bg-secondary)',
|
|
778
|
+
border: '1px solid var(--border)',
|
|
779
|
+
borderRadius: 8,
|
|
780
|
+
overflow: 'hidden',
|
|
781
|
+
}}>
|
|
782
|
+
<div style={{
|
|
783
|
+
display: 'flex',
|
|
784
|
+
justifyContent: 'space-between',
|
|
785
|
+
alignItems: 'center',
|
|
786
|
+
padding: '8px 14px',
|
|
787
|
+
background: 'var(--bg-tertiary)',
|
|
788
|
+
borderBottom: '1px solid var(--border)',
|
|
789
|
+
}}>
|
|
790
|
+
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>
|
|
791
|
+
{previewFile.name}
|
|
792
|
+
</span>
|
|
793
|
+
<button
|
|
794
|
+
id="lifecycle-close-preview-btn"
|
|
795
|
+
onClick={() => setPreviewFile(null)}
|
|
796
|
+
style={{ background: 'none', border: 'none', color: 'var(--text-tertiary)', cursor: 'pointer', fontSize: 14 }}
|
|
797
|
+
>
|
|
798
|
+
✖
|
|
799
|
+
</button>
|
|
800
|
+
</div>
|
|
801
|
+
<pre style={{
|
|
802
|
+
padding: 14,
|
|
803
|
+
fontSize: 11,
|
|
804
|
+
color: 'var(--text-secondary)',
|
|
805
|
+
fontFamily: 'var(--font-mono)',
|
|
806
|
+
margin: 0,
|
|
807
|
+
whiteSpace: 'pre-wrap',
|
|
808
|
+
maxHeight: 200,
|
|
809
|
+
overflowY: 'auto',
|
|
810
|
+
}}>
|
|
811
|
+
{previewFile.content}
|
|
812
|
+
</pre>
|
|
813
|
+
</div>
|
|
814
|
+
</div>
|
|
815
|
+
)}
|
|
816
|
+
</div>
|
|
817
|
+
</div>
|
|
818
|
+
</>
|
|
819
|
+
) : (
|
|
820
|
+
<div className="card" style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', color: 'var(--text-secondary)', fontSize: 13 }}>
|
|
821
|
+
Select a skill from the left panel to view its lifecycle details.
|
|
822
|
+
</div>
|
|
823
|
+
)}
|
|
824
|
+
</div>
|
|
825
|
+
</div>
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
const renderNetworkTab = () => (
|
|
829
|
+
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0, overflowY: 'auto', paddingBottom: 16 }}>
|
|
830
|
+
{/* Network Status Banner */}
|
|
831
|
+
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', marginBottom: 20 }}>
|
|
832
|
+
<div style={{ display: 'flex', gap: 20, flexWrap: 'wrap' }}>
|
|
833
|
+
{[
|
|
834
|
+
{ label: 'Connected Peers', value: '—', icon: '🔗' },
|
|
835
|
+
{ label: 'Shared Skills', value: '—', icon: '📦' },
|
|
836
|
+
{ label: 'Last Sync', value: 'Never', icon: '🔄' },
|
|
837
|
+
].map(stat => (
|
|
838
|
+
<div
|
|
839
|
+
key={stat.label}
|
|
840
|
+
id={`network-stat-${stat.label.toLowerCase().replace(/\s/g, '-')}`}
|
|
841
|
+
style={{
|
|
842
|
+
flex: '1 1 140px',
|
|
843
|
+
background: 'var(--bg-tertiary)',
|
|
844
|
+
border: '1px solid var(--border)',
|
|
845
|
+
borderRadius: 10,
|
|
846
|
+
padding: '16px 20px',
|
|
847
|
+
textAlign: 'center',
|
|
848
|
+
}}
|
|
849
|
+
>
|
|
850
|
+
<div style={{ fontSize: 22, marginBottom: 4 }}>{stat.icon}</div>
|
|
851
|
+
<div style={{ fontSize: 20, fontWeight: 700, color: 'var(--text-primary)' }}>{stat.value}</div>
|
|
852
|
+
<div style={{ fontSize: 11, color: 'var(--text-tertiary)', marginTop: 2 }}>{stat.label}</div>
|
|
853
|
+
</div>
|
|
854
|
+
))}
|
|
855
|
+
</div>
|
|
856
|
+
</div>
|
|
857
|
+
|
|
858
|
+
{/* Coming Soon overlay card */}
|
|
859
|
+
<div
|
|
860
|
+
className="card"
|
|
861
|
+
id="network-coming-soon-panel"
|
|
862
|
+
style={{
|
|
863
|
+
flex: 1,
|
|
864
|
+
position: 'relative',
|
|
865
|
+
overflow: 'hidden',
|
|
866
|
+
border: '1px solid var(--border)',
|
|
867
|
+
borderRadius: 12,
|
|
868
|
+
}}
|
|
869
|
+
>
|
|
870
|
+
{/* Blurred background content */}
|
|
871
|
+
<div style={{
|
|
872
|
+
padding: 40,
|
|
873
|
+
filter: 'blur(3px)',
|
|
874
|
+
opacity: 0.35,
|
|
875
|
+
pointerEvents: 'none',
|
|
876
|
+
userSelect: 'none',
|
|
877
|
+
}}>
|
|
878
|
+
{/* Fake diagram content that appears behind blur */}
|
|
879
|
+
<div style={{ display: 'flex', justifyContent: 'center', gap: 24, alignItems: 'center', marginBottom: 30, flexWrap: 'wrap' }}>
|
|
880
|
+
<div style={{ padding: '20px 30px', background: 'var(--bg-tertiary)', borderRadius: 12, border: '2px solid var(--accent)', textAlign: 'center' }}>
|
|
881
|
+
<div style={{ fontSize: 28 }}>📂</div>
|
|
882
|
+
<div style={{ fontSize: 13, fontWeight: 700, marginTop: 6, color: 'var(--text-primary)' }}>Local Skills</div>
|
|
883
|
+
</div>
|
|
884
|
+
<div style={{ fontSize: 28, color: 'var(--accent)' }}>⇄</div>
|
|
885
|
+
<div style={{ padding: '20px 30px', background: 'var(--bg-tertiary)', borderRadius: 12, border: '2px solid var(--accent)', textAlign: 'center' }}>
|
|
886
|
+
<div style={{ fontSize: 28 }}>🌐</div>
|
|
887
|
+
<div style={{ fontSize: 13, fontWeight: 700, marginTop: 6, color: 'var(--text-primary)' }}>P2P Network</div>
|
|
888
|
+
</div>
|
|
889
|
+
<div style={{ fontSize: 28, color: 'var(--accent)' }}>⇄</div>
|
|
890
|
+
<div style={{ padding: '20px 30px', background: 'var(--bg-tertiary)', borderRadius: 12, border: '2px solid var(--accent)', textAlign: 'center' }}>
|
|
891
|
+
<div style={{ fontSize: 28 }}>👥</div>
|
|
892
|
+
<div style={{ fontSize: 13, fontWeight: 700, marginTop: 6, color: 'var(--text-primary)' }}>Remote Peers</div>
|
|
893
|
+
</div>
|
|
894
|
+
</div>
|
|
895
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
|
|
896
|
+
{['Auto-discovery', 'Version Negotiation', 'Conflict Resolution', 'Security Quarantine'].map(f => (
|
|
897
|
+
<div key={f} style={{ background: 'var(--bg-tertiary)', padding: 16, borderRadius: 8, border: '1px solid var(--border)' }}>
|
|
898
|
+
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{f}</div>
|
|
899
|
+
</div>
|
|
900
|
+
))}
|
|
901
|
+
</div>
|
|
902
|
+
</div>
|
|
903
|
+
|
|
904
|
+
{/* Coming Soon Overlay */}
|
|
905
|
+
<div style={{
|
|
906
|
+
position: 'absolute',
|
|
907
|
+
inset: 0,
|
|
908
|
+
display: 'flex',
|
|
909
|
+
flexDirection: 'column',
|
|
910
|
+
alignItems: 'center',
|
|
911
|
+
justifyContent: 'center',
|
|
912
|
+
background: 'rgba(10, 10, 18, 0.6)',
|
|
913
|
+
backdropFilter: 'blur(2px)',
|
|
914
|
+
zIndex: 10,
|
|
915
|
+
}}>
|
|
916
|
+
<div style={{
|
|
917
|
+
background: 'rgba(18, 18, 26, 0.95)',
|
|
918
|
+
border: '1px solid var(--border)',
|
|
919
|
+
borderRadius: 16,
|
|
920
|
+
padding: '40px 48px',
|
|
921
|
+
textAlign: 'center',
|
|
922
|
+
maxWidth: 520,
|
|
923
|
+
backdropFilter: 'blur(20px)',
|
|
924
|
+
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.4)',
|
|
925
|
+
}}>
|
|
926
|
+
<div style={{ fontSize: 48, marginBottom: 16 }}>🚀</div>
|
|
927
|
+
<h2 id="network-coming-soon-title" style={{ fontSize: 22, fontWeight: 700, color: 'var(--text-primary)', marginBottom: 12 }}>
|
|
928
|
+
Coming Soon
|
|
929
|
+
</h2>
|
|
930
|
+
<p style={{ fontSize: 13, color: 'var(--text-secondary)', lineHeight: 1.7, marginBottom: 20 }}>
|
|
931
|
+
Peer-to-peer skill sharing enables automatic discovery, sync, and versioning of skills across trusted Total Recall nodes.
|
|
932
|
+
</p>
|
|
933
|
+
|
|
934
|
+
{/* Feature diagram */}
|
|
935
|
+
<div style={{
|
|
936
|
+
display: 'flex',
|
|
937
|
+
justifyContent: 'center',
|
|
938
|
+
alignItems: 'center',
|
|
939
|
+
gap: 12,
|
|
940
|
+
marginBottom: 24,
|
|
941
|
+
flexWrap: 'wrap',
|
|
942
|
+
}}>
|
|
943
|
+
<div style={{
|
|
944
|
+
padding: '10px 16px',
|
|
945
|
+
background: 'var(--bg-tertiary)',
|
|
946
|
+
borderRadius: 8,
|
|
947
|
+
border: '1px solid var(--accent)',
|
|
948
|
+
fontSize: 12,
|
|
949
|
+
fontWeight: 600,
|
|
950
|
+
color: 'var(--accent)',
|
|
951
|
+
}}>
|
|
952
|
+
📂 Local Skills
|
|
953
|
+
</div>
|
|
954
|
+
<span style={{ color: 'var(--accent)', fontSize: 18, fontWeight: 700 }}>↔</span>
|
|
955
|
+
<div style={{
|
|
956
|
+
padding: '10px 16px',
|
|
957
|
+
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.15), rgba(139, 92, 246, 0.15))',
|
|
958
|
+
borderRadius: 8,
|
|
959
|
+
border: '1px solid var(--accent)',
|
|
960
|
+
fontSize: 12,
|
|
961
|
+
fontWeight: 600,
|
|
962
|
+
color: 'var(--accent)',
|
|
963
|
+
}}>
|
|
964
|
+
🌐 P2P Network
|
|
965
|
+
</div>
|
|
966
|
+
<span style={{ color: 'var(--accent)', fontSize: 18, fontWeight: 700 }}>↔</span>
|
|
967
|
+
<div style={{
|
|
968
|
+
padding: '10px 16px',
|
|
969
|
+
background: 'var(--bg-tertiary)',
|
|
970
|
+
borderRadius: 8,
|
|
971
|
+
border: '1px solid var(--accent)',
|
|
972
|
+
fontSize: 12,
|
|
973
|
+
fontWeight: 600,
|
|
974
|
+
color: 'var(--accent)',
|
|
975
|
+
}}>
|
|
976
|
+
👥 Remote Peers
|
|
977
|
+
</div>
|
|
978
|
+
</div>
|
|
979
|
+
|
|
980
|
+
{/* Features list */}
|
|
981
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, textAlign: 'left' }}>
|
|
982
|
+
{[
|
|
983
|
+
{ icon: '🔍', label: 'Auto-discovery' },
|
|
984
|
+
{ icon: '🔄', label: 'Version negotiation' },
|
|
985
|
+
{ icon: '⚔️', label: 'Conflict resolution' },
|
|
986
|
+
{ icon: '🛡️', label: 'Security quarantine' },
|
|
987
|
+
].map(f => (
|
|
988
|
+
<div key={f.label} style={{
|
|
989
|
+
display: 'flex',
|
|
990
|
+
alignItems: 'center',
|
|
991
|
+
gap: 8,
|
|
992
|
+
padding: '8px 12px',
|
|
993
|
+
background: 'var(--bg-tertiary)',
|
|
994
|
+
borderRadius: 6,
|
|
995
|
+
fontSize: 11,
|
|
996
|
+
color: 'var(--text-secondary)',
|
|
997
|
+
fontWeight: 500,
|
|
998
|
+
}}>
|
|
999
|
+
<span>{f.icon}</span> {f.label}
|
|
1000
|
+
</div>
|
|
1001
|
+
))}
|
|
1002
|
+
</div>
|
|
1003
|
+
</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
</div>
|
|
1006
|
+
</div>
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
const renderAutomationTab = () => (
|
|
1010
|
+
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0, overflowY: 'auto', paddingBottom: 16, gap: 20 }}>
|
|
1011
|
+
{/* Auto-Update Status */}
|
|
1012
|
+
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
1013
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, borderBottom: '1px solid var(--border)', paddingBottom: 10, flexWrap: 'wrap', gap: 10 }}>
|
|
1014
|
+
<h3 id="automation-update-heading" style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, margin: 0 }}>
|
|
1015
|
+
🔄 Auto-Update Status
|
|
1016
|
+
</h3>
|
|
1017
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
1018
|
+
<button
|
|
1019
|
+
id="automation-check-updates-btn"
|
|
1020
|
+
onClick={() => void handleCheckUpdates()}
|
|
1021
|
+
disabled={checkingUpdates}
|
|
1022
|
+
style={{
|
|
1023
|
+
background: checkingUpdates ? 'var(--bg-tertiary)' : 'var(--bg-elevated)',
|
|
1024
|
+
color: checkingUpdates ? 'var(--text-tertiary)' : 'var(--text-primary)',
|
|
1025
|
+
border: '1px solid var(--border)',
|
|
1026
|
+
borderRadius: 6,
|
|
1027
|
+
padding: '6px 14px',
|
|
1028
|
+
fontSize: 11,
|
|
1029
|
+
fontWeight: 600,
|
|
1030
|
+
cursor: checkingUpdates ? 'not-allowed' : 'pointer',
|
|
1031
|
+
}}
|
|
1032
|
+
>
|
|
1033
|
+
{checkingUpdates ? '⏳ Checking...' : '🔎 Check All Updates'}
|
|
1034
|
+
</button>
|
|
1035
|
+
<button
|
|
1036
|
+
id="automation-update-all-btn"
|
|
1037
|
+
disabled
|
|
1038
|
+
style={{
|
|
1039
|
+
background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
1040
|
+
color: '#fff',
|
|
1041
|
+
border: 'none',
|
|
1042
|
+
borderRadius: 6,
|
|
1043
|
+
padding: '6px 14px',
|
|
1044
|
+
fontSize: 11,
|
|
1045
|
+
fontWeight: 600,
|
|
1046
|
+
cursor: 'not-allowed',
|
|
1047
|
+
opacity: 0.5,
|
|
1048
|
+
}}
|
|
1049
|
+
>
|
|
1050
|
+
⬆️ Update All
|
|
1051
|
+
</button>
|
|
1052
|
+
</div>
|
|
1053
|
+
</div>
|
|
1054
|
+
|
|
1055
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1056
|
+
{skills.map(skill => (
|
|
1057
|
+
<div
|
|
1058
|
+
key={skill.name}
|
|
1059
|
+
id={`automation-update-${skill.name}`}
|
|
1060
|
+
style={{
|
|
1061
|
+
display: 'flex',
|
|
1062
|
+
justifyContent: 'space-between',
|
|
1063
|
+
alignItems: 'center',
|
|
1064
|
+
padding: '10px 14px',
|
|
1065
|
+
background: 'var(--bg-tertiary)',
|
|
1066
|
+
borderRadius: 8,
|
|
1067
|
+
border: '1px solid var(--border)',
|
|
1068
|
+
}}
|
|
1069
|
+
>
|
|
1070
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
1071
|
+
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{skill.name}</span>
|
|
1072
|
+
<span style={{ fontSize: 10, color: 'var(--text-tertiary)', fontFamily: 'var(--font-mono)' }}>
|
|
1073
|
+
v{extractVersion('')}
|
|
1074
|
+
</span>
|
|
1075
|
+
</div>
|
|
1076
|
+
<span style={{
|
|
1077
|
+
fontSize: 10,
|
|
1078
|
+
fontWeight: 600,
|
|
1079
|
+
padding: '3px 10px',
|
|
1080
|
+
borderRadius: 12,
|
|
1081
|
+
background: 'rgba(16, 185, 129, 0.1)',
|
|
1082
|
+
color: '#10b981',
|
|
1083
|
+
border: '1px solid rgba(16, 185, 129, 0.2)',
|
|
1084
|
+
}}>
|
|
1085
|
+
✓ Up to date
|
|
1086
|
+
</span>
|
|
1087
|
+
</div>
|
|
1088
|
+
))}
|
|
1089
|
+
</div>
|
|
1090
|
+
</div>
|
|
1091
|
+
|
|
1092
|
+
{/* Auto-Research Integration */}
|
|
1093
|
+
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
1094
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, borderBottom: '1px solid var(--border)', paddingBottom: 10, flexWrap: 'wrap', gap: 10 }}>
|
|
1095
|
+
<div>
|
|
1096
|
+
<h3 id="automation-research-heading" style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, margin: 0 }}>
|
|
1097
|
+
🧠 Continuous Intelligence Loop
|
|
1098
|
+
</h3>
|
|
1099
|
+
<p style={{ fontSize: 11, color: 'var(--text-tertiary)', marginTop: 4, margin: 0 }}>
|
|
1100
|
+
Skills automatically trigger background research to update their reference material and improve evals
|
|
1101
|
+
</p>
|
|
1102
|
+
</div>
|
|
1103
|
+
<button
|
|
1104
|
+
id="automation-trigger-research-btn"
|
|
1105
|
+
onClick={() => void handleTriggerResearchCycle()}
|
|
1106
|
+
style={{
|
|
1107
|
+
background: 'linear-gradient(135deg, #8b5cf6, #7c3aed)',
|
|
1108
|
+
color: '#fff',
|
|
1109
|
+
border: 'none',
|
|
1110
|
+
borderRadius: 6,
|
|
1111
|
+
padding: '6px 14px',
|
|
1112
|
+
fontSize: 11,
|
|
1113
|
+
fontWeight: 600,
|
|
1114
|
+
cursor: 'pointer',
|
|
1115
|
+
flexShrink: 0,
|
|
1116
|
+
}}
|
|
1117
|
+
>
|
|
1118
|
+
🔬 Trigger Research Cycle
|
|
1119
|
+
</button>
|
|
1120
|
+
</div>
|
|
1121
|
+
|
|
1122
|
+
{researchLoading ? (
|
|
1123
|
+
<div style={{ textAlign: 'center', padding: 20, color: 'var(--text-tertiary)', fontSize: 12 }}>
|
|
1124
|
+
Loading research items...
|
|
1125
|
+
</div>
|
|
1126
|
+
) : researchItems.length > 0 ? (
|
|
1127
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1128
|
+
{researchItems.slice(0, 8).map(item => (
|
|
1129
|
+
<div
|
|
1130
|
+
key={item.id}
|
|
1131
|
+
id={`automation-research-${item.id}`}
|
|
1132
|
+
style={{
|
|
1133
|
+
display: 'flex',
|
|
1134
|
+
justifyContent: 'space-between',
|
|
1135
|
+
alignItems: 'center',
|
|
1136
|
+
padding: '10px 14px',
|
|
1137
|
+
background: 'var(--bg-tertiary)',
|
|
1138
|
+
borderRadius: 8,
|
|
1139
|
+
border: '1px solid var(--border)',
|
|
1140
|
+
}}
|
|
1141
|
+
>
|
|
1142
|
+
<div style={{ flex: 1 }}>
|
|
1143
|
+
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{item.topic}</div>
|
|
1144
|
+
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', marginTop: 2 }}>
|
|
1145
|
+
{item.priority} priority · {new Date(item.created_at).toLocaleDateString()}
|
|
1146
|
+
</div>
|
|
1147
|
+
</div>
|
|
1148
|
+
<span style={{
|
|
1149
|
+
fontSize: 10,
|
|
1150
|
+
fontWeight: 600,
|
|
1151
|
+
padding: '3px 10px',
|
|
1152
|
+
borderRadius: 12,
|
|
1153
|
+
background: item.status === 'done' ? 'rgba(16, 185, 129, 0.1)' :
|
|
1154
|
+
item.status === 'in_progress' ? 'rgba(59, 130, 246, 0.1)' :
|
|
1155
|
+
item.status === 'failed' ? 'rgba(239, 68, 68, 0.1)' : 'rgba(245, 158, 11, 0.1)',
|
|
1156
|
+
color: item.status === 'done' ? '#10b981' :
|
|
1157
|
+
item.status === 'in_progress' ? '#60a5fa' :
|
|
1158
|
+
item.status === 'failed' ? '#ef4444' : '#f59e0b',
|
|
1159
|
+
border: `1px solid ${item.status === 'done' ? 'rgba(16, 185, 129, 0.2)' :
|
|
1160
|
+
item.status === 'in_progress' ? 'rgba(59, 130, 246, 0.2)' :
|
|
1161
|
+
item.status === 'failed' ? 'rgba(239, 68, 68, 0.2)' : 'rgba(245, 158, 11, 0.2)'}`,
|
|
1162
|
+
}}>
|
|
1163
|
+
{item.status}
|
|
1164
|
+
</span>
|
|
1165
|
+
</div>
|
|
1166
|
+
))}
|
|
1167
|
+
</div>
|
|
1168
|
+
) : (
|
|
1169
|
+
<div style={{ padding: 20, textAlign: 'center', color: 'var(--text-tertiary)', fontSize: 12, border: '1px dashed var(--border)', borderRadius: 8 }}>
|
|
1170
|
+
No pending research items. Trigger a research cycle to auto-update skill references.
|
|
1171
|
+
</div>
|
|
1172
|
+
)}
|
|
1173
|
+
</div>
|
|
1174
|
+
|
|
1175
|
+
{/* Skill Improvement Queue */}
|
|
1176
|
+
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
1177
|
+
<h3 id="automation-improvements-heading" style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 16, borderBottom: '1px solid var(--border)', paddingBottom: 8 }}>
|
|
1178
|
+
💡 Skill Improvement Queue
|
|
1179
|
+
</h3>
|
|
1180
|
+
|
|
1181
|
+
{improvementSuggestions.length > 0 ? (
|
|
1182
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1183
|
+
{improvementSuggestions.map((suggestion, idx) => (
|
|
1184
|
+
<div
|
|
1185
|
+
key={idx}
|
|
1186
|
+
id={`automation-suggestion-${idx}`}
|
|
1187
|
+
style={{
|
|
1188
|
+
display: 'flex',
|
|
1189
|
+
justifyContent: 'space-between',
|
|
1190
|
+
alignItems: 'center',
|
|
1191
|
+
padding: '10px 14px',
|
|
1192
|
+
background: 'var(--bg-tertiary)',
|
|
1193
|
+
borderRadius: 8,
|
|
1194
|
+
border: '1px solid var(--border)',
|
|
1195
|
+
gap: 12,
|
|
1196
|
+
}}
|
|
1197
|
+
>
|
|
1198
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
1199
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 2, flexWrap: 'wrap' }}>
|
|
1200
|
+
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{suggestion.skillName}</span>
|
|
1201
|
+
<span style={{
|
|
1202
|
+
fontSize: 9,
|
|
1203
|
+
fontWeight: 700,
|
|
1204
|
+
padding: '2px 8px',
|
|
1205
|
+
borderRadius: 10,
|
|
1206
|
+
textTransform: 'uppercase',
|
|
1207
|
+
letterSpacing: 0.3,
|
|
1208
|
+
background: suggestion.type === 'eval_gap' ? 'rgba(245, 158, 11, 0.1)' :
|
|
1209
|
+
suggestion.type === 'missing_reference' ? 'rgba(59, 130, 246, 0.1)' : 'rgba(139, 92, 246, 0.1)',
|
|
1210
|
+
color: suggestion.type === 'eval_gap' ? '#f59e0b' :
|
|
1211
|
+
suggestion.type === 'missing_reference' ? '#60a5fa' : '#8b5cf6',
|
|
1212
|
+
border: `1px solid ${suggestion.type === 'eval_gap' ? 'rgba(245, 158, 11, 0.2)' :
|
|
1213
|
+
suggestion.type === 'missing_reference' ? 'rgba(59, 130, 246, 0.2)' : 'rgba(139, 92, 246, 0.2)'}`,
|
|
1214
|
+
}}>
|
|
1215
|
+
{suggestion.type.replace(/_/g, ' ')}
|
|
1216
|
+
</span>
|
|
1217
|
+
</div>
|
|
1218
|
+
<div style={{ fontSize: 11, color: 'var(--text-secondary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
1219
|
+
{suggestion.description}
|
|
1220
|
+
</div>
|
|
1221
|
+
</div>
|
|
1222
|
+
<button
|
|
1223
|
+
id={`automation-fix-${idx}`}
|
|
1224
|
+
style={{
|
|
1225
|
+
background: 'var(--bg-elevated)',
|
|
1226
|
+
color: 'var(--text-primary)',
|
|
1227
|
+
border: '1px solid var(--border)',
|
|
1228
|
+
borderRadius: 6,
|
|
1229
|
+
padding: '5px 12px',
|
|
1230
|
+
fontSize: 10,
|
|
1231
|
+
fontWeight: 600,
|
|
1232
|
+
cursor: 'pointer',
|
|
1233
|
+
flexShrink: 0,
|
|
1234
|
+
transition: 'all 0.15s ease',
|
|
1235
|
+
}}
|
|
1236
|
+
onMouseEnter={e => { e.currentTarget.style.background = 'var(--accent)'; e.currentTarget.style.color = '#fff'; }}
|
|
1237
|
+
onMouseLeave={e => { e.currentTarget.style.background = 'var(--bg-elevated)'; e.currentTarget.style.color = 'var(--text-primary)'; }}
|
|
1238
|
+
>
|
|
1239
|
+
🔧 Fix
|
|
1240
|
+
</button>
|
|
1241
|
+
</div>
|
|
1242
|
+
))}
|
|
1243
|
+
</div>
|
|
1244
|
+
) : (
|
|
1245
|
+
<div style={{ padding: 20, textAlign: 'center', color: 'var(--text-tertiary)', fontSize: 12, border: '1px dashed var(--border)', borderRadius: 8 }}>
|
|
1246
|
+
No improvement suggestions. All skills are well-configured. ✨
|
|
1247
|
+
</div>
|
|
1248
|
+
)}
|
|
1249
|
+
</div>
|
|
1250
|
+
</div>
|
|
1251
|
+
);
|
|
1252
|
+
|
|
231
1253
|
return (
|
|
232
1254
|
<div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
233
1255
|
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
|
|
@@ -318,39 +1340,28 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
318
1340
|
)}
|
|
319
1341
|
|
|
320
1342
|
{/* Tab Switcher */}
|
|
321
|
-
<div style={{ display: 'flex', gap:
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
border: 'none',
|
|
344
|
-
background: activeTab === 'registry' ? 'var(--accent)' : 'var(--bg-tertiary)',
|
|
345
|
-
color: activeTab === 'registry' ? '#fff' : 'var(--text-secondary)',
|
|
346
|
-
fontWeight: 600,
|
|
347
|
-
cursor: 'pointer',
|
|
348
|
-
fontSize: 12,
|
|
349
|
-
transition: 'all 0.15s ease'
|
|
350
|
-
}}
|
|
351
|
-
>
|
|
352
|
-
✨ skills.sh Registry Hub
|
|
353
|
-
</button>
|
|
1343
|
+
<div style={{ display: 'flex', gap: 6, marginBottom: 20, borderBottom: '1px solid var(--border)', paddingBottom: 10, flexWrap: 'wrap' }}>
|
|
1344
|
+
{tabs.map(tab => (
|
|
1345
|
+
<button
|
|
1346
|
+
key={tab.id}
|
|
1347
|
+
id={`tab-${tab.id}`}
|
|
1348
|
+
onClick={() => setActiveTab(tab.id)}
|
|
1349
|
+
style={{
|
|
1350
|
+
padding: '8px 14px',
|
|
1351
|
+
borderRadius: 8,
|
|
1352
|
+
border: 'none',
|
|
1353
|
+
background: activeTab === tab.id ? 'var(--accent)' : 'var(--bg-tertiary)',
|
|
1354
|
+
color: activeTab === tab.id ? '#fff' : 'var(--text-secondary)',
|
|
1355
|
+
fontWeight: 600,
|
|
1356
|
+
cursor: 'pointer',
|
|
1357
|
+
fontSize: 11,
|
|
1358
|
+
transition: 'all 0.15s ease',
|
|
1359
|
+
whiteSpace: 'nowrap',
|
|
1360
|
+
}}
|
|
1361
|
+
>
|
|
1362
|
+
{tab.icon} {tab.label}
|
|
1363
|
+
</button>
|
|
1364
|
+
))}
|
|
354
1365
|
</div>
|
|
355
1366
|
|
|
356
1367
|
{activeTab === 'active' ? (
|
|
@@ -484,7 +1495,7 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
484
1495
|
</div>
|
|
485
1496
|
|
|
486
1497
|
</div>
|
|
487
|
-
) : (
|
|
1498
|
+
) : activeTab === 'registry' ? (
|
|
488
1499
|
/* Tab 2: skills.sh Registry Explorer Panel */
|
|
489
1500
|
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0, overflowY: 'auto', paddingBottom: 16 }}>
|
|
490
1501
|
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', marginBottom: 20 }}>
|
|
@@ -612,6 +1623,12 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
612
1623
|
)}
|
|
613
1624
|
</div>
|
|
614
1625
|
</div>
|
|
1626
|
+
) : activeTab === 'lifecycle' ? (
|
|
1627
|
+
renderLifecycleTab()
|
|
1628
|
+
) : activeTab === 'network' ? (
|
|
1629
|
+
renderNetworkTab()
|
|
1630
|
+
) : (
|
|
1631
|
+
renderAutomationTab()
|
|
615
1632
|
)}
|
|
616
1633
|
</div>
|
|
617
1634
|
);
|