total-recall-brain 3.41.5 → 3.41.6
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/bin/total-recall.mjs +7 -4
- package/frontend/dist/assets/index-4tGQfTa2.js +5605 -0
- package/frontend/dist/assets/index-DtwsDyFM.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package.json +11 -0
- package/frontend/src/App.tsx +47 -122
- package/frontend/src/components/plugins/plugin-frame.ts +1 -0
- package/frontend/src/index.css +152 -86
- package/frontend/src/pages/HelpPage.tsx +13 -10
- package/frontend/src/pages/MemoryPage.tsx +28 -30
- package/frontend/src/pages/NetworkPage.tsx +3 -3
- package/frontend/src/pages/PluginsPage.tsx +6 -5
- package/frontend/src/pages/RulesPage.tsx +16 -12
- package/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +3 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +3 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +136 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +135 -26
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +228 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +24 -30
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +29 -16
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +73 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +182 -365
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +9 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/agy.mjs +187 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/claude.mjs +275 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cline.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/codex.mjs +187 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cursor.mjs +158 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/devin.mjs +157 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/dsh.mjs +183 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/grok.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +173 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +87 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/openclaw.mjs +173 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/pi.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +12 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/json-reply.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +72 -70
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-catalog.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +11 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +169 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +136 -346
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/ssss-store.mjs +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/telemetry.mjs +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +117 -35
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +398 -57
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +10 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +42 -190
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/mermaid-diagrams/SKILL.md +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/write-connector/SKILL.md +56 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +3 -0
- package/src/cli/agent.mjs +12 -1
- package/src/cli/compact.mjs +2 -2
- package/src/cli/connect.mjs +56 -1
- package/src/cli/context.mjs +10 -7
- package/src/cli/doctor.mjs +5 -2
- package/src/cli/harness.mjs +62 -50
- package/src/cli/memory-entry.mjs +2 -2
- package/src/cli/preflight.mjs +152 -16
- package/src/cli/remember.mjs +19 -1
- package/src/cli/rules.mjs +54 -12
- package/src/cli/startup.mjs +1 -1
- package/src/cli/status.mjs +12 -2
- package/src/core/claude-hooks.mjs +41 -3
- package/src/core/cli-semantic-index.mjs +3 -2
- package/src/core/conflict-detector.mjs +23 -12
- package/src/core/context-compactor.mjs +2 -0
- package/src/core/context-policy.mjs +20 -0
- package/src/core/duplicate-guard.mjs +350 -0
- package/src/core/meta-harness.mjs +31 -11
- package/src/core/preflight-gate.mjs +2 -2
- package/src/core/preflight-receipt.mjs +28 -0
- package/src/core/scaffold-plugins.mjs +7 -0
- package/src/core/stdio-flush.mjs +23 -0
- package/src/core/surface.mjs +33 -10
- package/src/server/routes/plugins.mjs +2 -2
- package/frontend/dist/assets/index-BIQmqDKP.css +0 -1
- package/frontend/dist/assets/index-WT3gs-x6.js +0 -5631
- package/frontend/src/components/DaemonLogsTab.tsx +0 -54
- package/frontend/src/components/ResearchAgendaTab.tsx +0 -712
- package/frontend/src/components/TaskQueueTab.tsx +0 -193
- package/frontend/src/components/VoiceInput.tsx +0 -112
- package/frontend/src/pages/ChatPage.tsx +0 -1059
- package/frontend/src/pages/CollabPage.tsx +0 -470
- package/frontend/src/pages/DesignDocsPage.tsx +0 -621
- package/frontend/src/pages/FilesPage.tsx +0 -393
- package/frontend/src/pages/HealthPage.tsx +0 -359
- package/frontend/src/pages/InboxPage.tsx +0 -83
- package/frontend/src/pages/InstructionsPage.tsx +0 -583
- package/frontend/src/pages/IntegrationsPage.tsx +0 -761
- package/frontend/src/pages/MeshPage.css +0 -58
- package/frontend/src/pages/MeshPage.tsx +0 -1219
- package/frontend/src/pages/OkfPage.tsx +0 -535
- package/frontend/src/pages/SandboxPage.tsx +0 -113
- package/frontend/src/pages/SkillsPage.tsx +0 -1994
- package/frontend/src/pages/TasksPage.tsx +0 -427
- package/frontend/src/pages/UsagePage.tsx +0 -702
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +0 -65
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +0 -76
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +0 -19
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -266
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +0 -446
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +0 -152
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +0 -71
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +0 -36
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +0 -35
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +0 -175
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +0 -575
|
@@ -1,1994 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
listSkills,
|
|
4
|
-
fetchSkill,
|
|
5
|
-
fetchSkillFiles,
|
|
6
|
-
saveSkill,
|
|
7
|
-
deleteSkill,
|
|
8
|
-
triggerRecompile,
|
|
9
|
-
searchSkillsRegistry,
|
|
10
|
-
installRegistrySkill,
|
|
11
|
-
runSandbox,
|
|
12
|
-
listResearch,
|
|
13
|
-
createResearch,
|
|
14
|
-
toggleSkillRepo,
|
|
15
|
-
previewSkillRepo,
|
|
16
|
-
auditSkill,
|
|
17
|
-
generateExpert
|
|
18
|
-
} from '../api';
|
|
19
|
-
import type { ResearchItem } from '../types';
|
|
20
|
-
|
|
21
|
-
interface SkillItem {
|
|
22
|
-
id?: string;
|
|
23
|
-
name: string;
|
|
24
|
-
repo?: string;
|
|
25
|
-
project_path?: string;
|
|
26
|
-
source?: string;
|
|
27
|
-
size: number;
|
|
28
|
-
modified: string;
|
|
29
|
-
isDirectory: boolean;
|
|
30
|
-
subSkills: string[];
|
|
31
|
-
has_skill_md?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface RepoSkillsGroup {
|
|
35
|
-
repo: string;
|
|
36
|
-
path: string | null;
|
|
37
|
-
skills: SkillItem[];
|
|
38
|
-
ok?: boolean;
|
|
39
|
-
count?: number;
|
|
40
|
-
error?: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface RegistrySkill {
|
|
44
|
-
name: string;
|
|
45
|
-
installs: number;
|
|
46
|
-
installsStr: string;
|
|
47
|
-
url: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ─── Lifecycle tab types ────────────────────────────────────────────────────────
|
|
51
|
-
|
|
52
|
-
interface ChangelogEntry {
|
|
53
|
-
version: string;
|
|
54
|
-
date: string;
|
|
55
|
-
changes: string[];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface HealthCheck {
|
|
59
|
-
label: string;
|
|
60
|
-
passed: boolean;
|
|
61
|
-
detail: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
interface SubFileEntry {
|
|
65
|
-
name: string;
|
|
66
|
-
size: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ─── Automation tab types ───────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
type TabId = 'active' | 'registry' | 'lifecycle' | 'network' | 'automation';
|
|
73
|
-
|
|
74
|
-
// ─── Helper: parse version from SKILL.md frontmatter ────────────────────────────
|
|
75
|
-
|
|
76
|
-
function extractVersion(content: string): string {
|
|
77
|
-
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
78
|
-
if (fmMatch) {
|
|
79
|
-
const versionLine = fmMatch[1].match(/version:\s*['"]?([^\n'"]+)/i);
|
|
80
|
-
if (versionLine) return versionLine[1].trim();
|
|
81
|
-
}
|
|
82
|
-
// Fallback: look for ## v or ### v headings
|
|
83
|
-
const headingMatch = content.match(/#+\s+v(\d+\.\d+(?:\.\d+)?)/);
|
|
84
|
-
if (headingMatch) return headingMatch[1];
|
|
85
|
-
return '0.0.0';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ─── Helper: parse changelog entries from SKILL.md ──────────────────────────────
|
|
89
|
-
|
|
90
|
-
function parseChangelog(content: string): ChangelogEntry[] {
|
|
91
|
-
const entries: ChangelogEntry[] = [];
|
|
92
|
-
// Match sections like ## Changelog, ### v1.2.3, ## v1.0.0 — 2025-01-15
|
|
93
|
-
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;
|
|
94
|
-
let match;
|
|
95
|
-
while ((match = versionRegex.exec(content)) !== null) {
|
|
96
|
-
const changes = match[3]
|
|
97
|
-
.split('\n')
|
|
98
|
-
.map(l => l.replace(/^[\s-*]+/, '').trim())
|
|
99
|
-
.filter(l => l.length > 0);
|
|
100
|
-
entries.push({
|
|
101
|
-
version: match[1],
|
|
102
|
-
date: match[2] || '',
|
|
103
|
-
changes,
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
return entries;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ─── Helper: compute health checks ─────────────────────────────────────────────
|
|
110
|
-
|
|
111
|
-
function computeHealthChecks(skill: SkillItem, content: string): HealthCheck[] {
|
|
112
|
-
const requiredDirs = ['scripts', 'references', 'evals', 'subagents'];
|
|
113
|
-
const subs = (skill.subSkills || []).map(s => s.toLowerCase());
|
|
114
|
-
|
|
115
|
-
const dirChecks = requiredDirs.map(dir => ({
|
|
116
|
-
label: `Has ${dir}/ directory`,
|
|
117
|
-
passed: subs.includes(dir),
|
|
118
|
-
detail: subs.includes(dir) ? `${dir}/ present` : `Missing ${dir}/ subdirectory`,
|
|
119
|
-
}));
|
|
120
|
-
|
|
121
|
-
const hasSkillMd: HealthCheck = {
|
|
122
|
-
label: 'Has SKILL.md',
|
|
123
|
-
passed: true, // If we loaded content, it exists
|
|
124
|
-
detail: content.length > 0 ? 'SKILL.md present' : 'SKILL.md missing or empty',
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
// Check evals.json with 3+ assertions
|
|
128
|
-
const hasEvals: HealthCheck = {
|
|
129
|
-
label: 'Has evals.json with 3+ assertions',
|
|
130
|
-
passed: subs.includes('evals'),
|
|
131
|
-
detail: subs.includes('evals') ? 'Evals directory found' : 'No evals directory detected',
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Description trigger-optimized check
|
|
135
|
-
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
136
|
-
let descOptimized = false;
|
|
137
|
-
if (fmMatch) {
|
|
138
|
-
const descLine = fmMatch[1].match(/description:\s*['"]?(.*?)['"]?\s*$/im);
|
|
139
|
-
if (descLine && descLine[1].toLowerCase().startsWith('use this skill when')) {
|
|
140
|
-
descOptimized = true;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
const descCheck: HealthCheck = {
|
|
144
|
-
label: 'Trigger-optimized description',
|
|
145
|
-
passed: descOptimized,
|
|
146
|
-
detail: descOptimized ? 'Description starts with "Use this skill when"' : 'Description should start with "Use this skill when"',
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
return [hasSkillMd, ...dirChecks, hasEvals, descCheck];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Removed generateSuggestions helper since we now use the backend AI audit
|
|
153
|
-
|
|
154
|
-
// ─── Helper: format file sizes ──────────────────────────────────────────────────
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
export default function SkillsPage({ activeBrainId }: { activeBrainId?: string }) {
|
|
159
|
-
const [skills, setSkills] = useState<SkillItem[]>([]);
|
|
160
|
-
const [repoGroups, setRepoGroups] = useState<RepoSkillsGroup[]>([]);
|
|
161
|
-
const [selectedSkill, setSelectedSkill] = useState<SkillItem | null>(null);
|
|
162
|
-
const [skillContent, setSkillContent] = useState('');
|
|
163
|
-
const [expandedRepos, setExpandedRepos] = useState<Set<string>>(new Set());
|
|
164
|
-
|
|
165
|
-
// Navigation tabs state
|
|
166
|
-
const [activeTab, setActiveTab] = useState<TabId>('active');
|
|
167
|
-
|
|
168
|
-
// UI status hooks
|
|
169
|
-
const [, setLoading] = useState(true);
|
|
170
|
-
const [saving, setSaving] = useState(false);
|
|
171
|
-
const [compiling, setCompiling] = useState(false);
|
|
172
|
-
const [error, setError] = useState<string | null>(null);
|
|
173
|
-
const [success, setSuccess] = useState<string | null>(null);
|
|
174
|
-
|
|
175
|
-
// Preview Modal state
|
|
176
|
-
const [previewModalOpen, setPreviewModalOpen] = useState(false);
|
|
177
|
-
const [previewLoading, setPreviewLoading] = useState(false);
|
|
178
|
-
const [previewData, setPreviewData] = useState<{ skillName: string, targetRepo: string, original: string, preview: string, signals: string[] } | null>(null);
|
|
179
|
-
|
|
180
|
-
// New Skill scaffolds state
|
|
181
|
-
const [showCreateForm, setShowCreateForm] = useState(false);
|
|
182
|
-
const [newSkillName, setNewSkillName] = useState('');
|
|
183
|
-
const [newSkillDesc, setNewSkillDesc] = useState('');
|
|
184
|
-
|
|
185
|
-
// Registry Search & Installation states
|
|
186
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
187
|
-
const [registryResults, setRegistryResults] = useState<RegistrySkill[]>([]);
|
|
188
|
-
const [registryLoading, setRegistryLoading] = useState(false);
|
|
189
|
-
const [installingPkg, setInstallingPkg] = useState<string | null>(null);
|
|
190
|
-
const [quarantineAlert, setQuarantineAlert] = useState<{ pkg: string; reason: string } | null>(null);
|
|
191
|
-
|
|
192
|
-
// ─── Active brain → single deployable repo target ──────────────────────────
|
|
193
|
-
// activeBrainId is 'global', 'project:<name>', or a comma-joined multi-select
|
|
194
|
-
// (see BrainSelector). Only a single, non-global selection maps to one concrete
|
|
195
|
-
// project repo we can deploy a global skill into.
|
|
196
|
-
const activeRepoName = useMemo(() => {
|
|
197
|
-
if (!activeBrainId) return null;
|
|
198
|
-
const ids = activeBrainId.split(',').map((s) => s.trim()).filter(Boolean);
|
|
199
|
-
if (ids.length !== 1 || ids[0] === 'global') return null;
|
|
200
|
-
const id = ids[0];
|
|
201
|
-
return id.startsWith('project:') ? id.slice('project:'.length) : id;
|
|
202
|
-
}, [activeBrainId]);
|
|
203
|
-
const activeRepoGroup = useMemo(
|
|
204
|
-
() => (activeRepoName ? repoGroups.find((g) => g.repo === activeRepoName) || null : null),
|
|
205
|
-
[activeRepoName, repoGroups],
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
// ─── Lifecycle tab state ────────────────────────────────────────────────────
|
|
209
|
-
const lifecycleVersion = useMemo(() => selectedSkill && skillContent ? extractVersion(skillContent) : '0.0.0', [selectedSkill, skillContent]);
|
|
210
|
-
const changelogEntries = useMemo(() => selectedSkill && skillContent ? parseChangelog(skillContent) : [], [selectedSkill, skillContent]);
|
|
211
|
-
const healthChecks = useMemo(() => selectedSkill && skillContent ? computeHealthChecks(selectedSkill, skillContent) : [], [selectedSkill, skillContent]);
|
|
212
|
-
const [expandedSubDirs, setExpandedSubDirs] = useState<Record<string, boolean>>({});
|
|
213
|
-
const [subDirFiles, setSubDirFiles] = useState<Record<string, SubFileEntry[]>>({});
|
|
214
|
-
const [previewFile, setPreviewFile] = useState<{ name: string; content: string } | null>(null);
|
|
215
|
-
const [enforcementRunning, setEnforcementRunning] = useState(false);
|
|
216
|
-
const [enforcementOutput, setEnforcementOutput] = useState<string | null>(null);
|
|
217
|
-
|
|
218
|
-
// ─── Automation tab state ───────────────────────────────────────────────────
|
|
219
|
-
const [researchItems, setResearchItems] = useState<ResearchItem[]>([]);
|
|
220
|
-
const [researchLoading, setResearchLoading] = useState(false);
|
|
221
|
-
const [checkingUpdates, setCheckingUpdates] = useState(false);
|
|
222
|
-
const [aiSuggestions, setAiSuggestions] = useState<{skillName: string, description: string, problematic: boolean}[]>([]);
|
|
223
|
-
const [auditing, setAuditing] = useState(false);
|
|
224
|
-
|
|
225
|
-
const handleRunAudit = async () => {
|
|
226
|
-
if (auditing || skills.length === 0) return;
|
|
227
|
-
setAuditing(true);
|
|
228
|
-
setAiSuggestions([]);
|
|
229
|
-
try {
|
|
230
|
-
// Limit to max 5 to prevent long waits
|
|
231
|
-
const toAudit = skills.slice(0, 5);
|
|
232
|
-
const results = [];
|
|
233
|
-
for (const skill of toAudit) {
|
|
234
|
-
try {
|
|
235
|
-
const res = await auditSkill(skill.name, skill.repo || activeBrainId);
|
|
236
|
-
if (res.audit.outdated || res.audit.problematic) {
|
|
237
|
-
results.push({
|
|
238
|
-
skillName: skill.name,
|
|
239
|
-
description: res.audit.proposed_changes,
|
|
240
|
-
problematic: res.audit.problematic
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
} catch (e) {
|
|
244
|
-
console.error('Audit error for', skill.name, e);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
setAiSuggestions(results);
|
|
248
|
-
if (results.length === 0) {
|
|
249
|
-
setSuccess('AI Audit complete: All checked skills are well-configured.');
|
|
250
|
-
setTimeout(() => setSuccess(null), 3000);
|
|
251
|
-
}
|
|
252
|
-
} finally {
|
|
253
|
-
setAuditing(false);
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Syncs project skills by repo (excludes total-recall / tr-* system skills).
|
|
259
|
-
*/
|
|
260
|
-
const fetchSkillsList = useCallback(async (selectName?: string, selectRepo?: string) => {
|
|
261
|
-
setLoading(true);
|
|
262
|
-
setError(null);
|
|
263
|
-
try {
|
|
264
|
-
const data = await listSkills();
|
|
265
|
-
let list: SkillItem[] = [];
|
|
266
|
-
let groups: RepoSkillsGroup[] = [];
|
|
267
|
-
|
|
268
|
-
if (Array.isArray(data)) {
|
|
269
|
-
list = data as SkillItem[];
|
|
270
|
-
const map = new Map<string, SkillItem[]>();
|
|
271
|
-
for (const s of list) {
|
|
272
|
-
const r = s.repo || 'unknown';
|
|
273
|
-
if (!map.has(r)) map.set(r, []);
|
|
274
|
-
map.get(r)!.push(s);
|
|
275
|
-
}
|
|
276
|
-
groups = [...map.entries()].map(([repo, skills]) => ({
|
|
277
|
-
repo,
|
|
278
|
-
path: skills[0]?.project_path || null,
|
|
279
|
-
skills,
|
|
280
|
-
count: skills.length,
|
|
281
|
-
ok: true,
|
|
282
|
-
}));
|
|
283
|
-
} else {
|
|
284
|
-
list = (data.skills || []) as SkillItem[];
|
|
285
|
-
groups = (data.repos || []) as RepoSkillsGroup[];
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// activeBrainId may be a comma-joined multi-select (see BrainSelector) — split it
|
|
289
|
-
// rather than treating the whole string as a single repo id, else selecting more
|
|
290
|
-
// than one brain collapsed the list down to only the "Global" group.
|
|
291
|
-
const selectedBrainIds = (activeBrainId || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
292
|
-
if (selectedBrainIds.length > 0 && !selectedBrainIds.includes('global')) {
|
|
293
|
-
const targetRepoIds = selectedBrainIds.map((id) =>
|
|
294
|
-
id.startsWith('project:') ? id.slice('project:'.length) : id,
|
|
295
|
-
);
|
|
296
|
-
groups = groups.filter(
|
|
297
|
-
g => g.repo === 'Global' ||
|
|
298
|
-
targetRepoIds.some(
|
|
299
|
-
(targetRepoId) =>
|
|
300
|
-
g.repo === targetRepoId ||
|
|
301
|
-
targetRepoId.endsWith(`/${g.repo}`) ||
|
|
302
|
-
g.repo.endsWith(`/${targetRepoId}`),
|
|
303
|
-
) ||
|
|
304
|
-
selectedBrainIds.includes(`project:${g.repo}`)
|
|
305
|
-
);
|
|
306
|
-
list = groups.flatMap(g => g.skills || []);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
setSkills(list);
|
|
310
|
-
setRepoGroups(groups);
|
|
311
|
-
|
|
312
|
-
// Expand repos that have skills (first 5 by default to keep UI light)
|
|
313
|
-
const withSkills = groups.filter((g) => (g.skills?.length || 0) > 0).map((g) => g.repo);
|
|
314
|
-
setExpandedRepos(new Set(withSkills.slice(0, 6)));
|
|
315
|
-
|
|
316
|
-
if (list.length > 0) {
|
|
317
|
-
const target = selectName
|
|
318
|
-
? list.find(
|
|
319
|
-
(s) =>
|
|
320
|
-
s.name === selectName &&
|
|
321
|
-
(!selectRepo || s.repo === selectRepo),
|
|
322
|
-
)
|
|
323
|
-
: list[0];
|
|
324
|
-
if (target) {
|
|
325
|
-
setSelectedSkill(target);
|
|
326
|
-
if (target.has_skill_md) {
|
|
327
|
-
try {
|
|
328
|
-
const skillDetails = await fetchSkill(target.name, target.repo);
|
|
329
|
-
setSkillContent(skillDetails.content || '');
|
|
330
|
-
} catch {
|
|
331
|
-
console.warn(`Could not load SKILL.md for ${target.name}`);
|
|
332
|
-
setSkillContent('');
|
|
333
|
-
}
|
|
334
|
-
} else {
|
|
335
|
-
setSkillContent('');
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
} else {
|
|
339
|
-
setSelectedSkill(null);
|
|
340
|
-
setSkillContent('');
|
|
341
|
-
}
|
|
342
|
-
} catch (err: unknown) {
|
|
343
|
-
setError((err as Error).message || 'Failed to fetch skills by repo.');
|
|
344
|
-
} finally {
|
|
345
|
-
setLoading(false);
|
|
346
|
-
}
|
|
347
|
-
}, [activeBrainId]);
|
|
348
|
-
|
|
349
|
-
useEffect(() => {
|
|
350
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
351
|
-
void fetchSkillsList();
|
|
352
|
-
}, [fetchSkillsList]);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const handleSelectSkill = async (skill: SkillItem) => {
|
|
358
|
-
setSelectedSkill(skill);
|
|
359
|
-
setLoading(true);
|
|
360
|
-
setError(null);
|
|
361
|
-
setSuccess(null);
|
|
362
|
-
setPreviewFile(null);
|
|
363
|
-
setExpandedSubDirs({});
|
|
364
|
-
try {
|
|
365
|
-
const details = await fetchSkill(skill.name, skill.repo);
|
|
366
|
-
setSkillContent(details.content || '');
|
|
367
|
-
} catch (err: unknown) {
|
|
368
|
-
setError((err as Error).message || `Failed to fetch rules for skill "${skill.name}".`);
|
|
369
|
-
} finally {
|
|
370
|
-
setLoading(false);
|
|
371
|
-
}
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
const handleSaveSkill = async () => {
|
|
375
|
-
if (!selectedSkill) return;
|
|
376
|
-
setSaving(true);
|
|
377
|
-
setError(null);
|
|
378
|
-
setSuccess(null);
|
|
379
|
-
try {
|
|
380
|
-
await saveSkill(selectedSkill.name, skillContent, selectedSkill.repo);
|
|
381
|
-
setSuccess(
|
|
382
|
-
`Skill "${selectedSkill.name}"${selectedSkill.repo ? ` (${selectedSkill.repo})` : ''} saved successfully!`,
|
|
383
|
-
);
|
|
384
|
-
setTimeout(() => setSuccess(null), 5000);
|
|
385
|
-
} catch (err: unknown) {
|
|
386
|
-
setError((err as Error).message || 'Failed to save skill changes.');
|
|
387
|
-
} finally {
|
|
388
|
-
setSaving(false);
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
const handleRecompile = async () => {
|
|
393
|
-
setCompiling(true);
|
|
394
|
-
setError(null);
|
|
395
|
-
setSuccess(null);
|
|
396
|
-
try {
|
|
397
|
-
const result = await triggerRecompile();
|
|
398
|
-
setSuccess(result.message || 'Brain surfaces and shims recompiled successfully!');
|
|
399
|
-
setTimeout(() => setSuccess(null), 5000);
|
|
400
|
-
} catch (err: unknown) {
|
|
401
|
-
setError((err as Error).message || 'Recompilation failed.');
|
|
402
|
-
} finally {
|
|
403
|
-
setCompiling(false);
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
const handleDeleteSkill = async (skill: SkillItem) => {
|
|
408
|
-
if (
|
|
409
|
-
!window.confirm(
|
|
410
|
-
`Delete skill "${skill.name}"${skill.repo ? ` from ${skill.repo}` : ''} permanently?`,
|
|
411
|
-
)
|
|
412
|
-
)
|
|
413
|
-
return;
|
|
414
|
-
setError(null);
|
|
415
|
-
setSuccess(null);
|
|
416
|
-
try {
|
|
417
|
-
await deleteSkill(skill.name, skill.repo);
|
|
418
|
-
setSuccess(`Skill "${skill.name}" successfully deleted.`);
|
|
419
|
-
setTimeout(() => setSuccess(null), 5000);
|
|
420
|
-
void fetchSkillsList();
|
|
421
|
-
} catch (err: unknown) {
|
|
422
|
-
setError((err as Error).message || 'Failed to delete skill.');
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
const handleCreateSkill = async (e: React.FormEvent) => {
|
|
427
|
-
e.preventDefault();
|
|
428
|
-
if (!newSkillName.trim()) return;
|
|
429
|
-
setError(null);
|
|
430
|
-
setSuccess(null);
|
|
431
|
-
|
|
432
|
-
// Prefer selected skill's repo, else first repo with a path
|
|
433
|
-
const targetRepo =
|
|
434
|
-
selectedSkill?.repo ||
|
|
435
|
-
repoGroups.find((g) => g.ok && g.path)?.repo ||
|
|
436
|
-
'';
|
|
437
|
-
if (!targetRepo) {
|
|
438
|
-
setError('No project repo available to create a skill in. Register a project first.');
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Normalize naming: alphanumeric and hyphens
|
|
443
|
-
const cleanName = newSkillName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
444
|
-
if (cleanName.startsWith('tr-') || cleanName === 'total-recall') {
|
|
445
|
-
setError('Cannot create Total Recall system skills here.');
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const initialContent = `---
|
|
450
|
-
type: skill
|
|
451
|
-
name: ${cleanName}
|
|
452
|
-
description: "${newSkillDesc.trim() || 'Custom system skill capability'}"
|
|
453
|
-
---
|
|
454
|
-
|
|
455
|
-
# ${cleanName.charAt(0).toUpperCase() + cleanName.slice(1)} — custom system skill
|
|
456
|
-
|
|
457
|
-
Configure triggers, options, and prompts inside this rules sheet to hot-recompile IDE capabilities.
|
|
458
|
-
`;
|
|
459
|
-
|
|
460
|
-
try {
|
|
461
|
-
await saveSkill(cleanName, initialContent, targetRepo);
|
|
462
|
-
setNewSkillName('');
|
|
463
|
-
setNewSkillDesc('');
|
|
464
|
-
setShowCreateForm(false);
|
|
465
|
-
setSuccess(`Skill "${cleanName}" successfully created!`);
|
|
466
|
-
setTimeout(() => setSuccess(null), 5000);
|
|
467
|
-
void fetchSkillsList(cleanName);
|
|
468
|
-
} catch (err: unknown) {
|
|
469
|
-
setError((err as Error).message || 'Failed to create new skill.');
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Performs search on the skills.sh registry sorted by absolute installs
|
|
475
|
-
*/
|
|
476
|
-
const handleRegistrySearch = async (e: React.FormEvent) => {
|
|
477
|
-
e.preventDefault();
|
|
478
|
-
if (!searchQuery.trim()) return;
|
|
479
|
-
|
|
480
|
-
setRegistryLoading(true);
|
|
481
|
-
setError(null);
|
|
482
|
-
setQuarantineAlert(null);
|
|
483
|
-
try {
|
|
484
|
-
const results = await searchSkillsRegistry(searchQuery.trim()) as RegistrySkill[];
|
|
485
|
-
setRegistryResults(results || []);
|
|
486
|
-
} catch (err: unknown) {
|
|
487
|
-
setError((err as Error).message || 'Registry query failed. Is the server connected?');
|
|
488
|
-
} finally {
|
|
489
|
-
setRegistryLoading(false);
|
|
490
|
-
}
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Installs, statically scans, and auto-compiles registry skills
|
|
495
|
-
*/
|
|
496
|
-
const handleRegistryInstall = async (pkg: string) => {
|
|
497
|
-
setInstallingPkg(pkg);
|
|
498
|
-
setError(null);
|
|
499
|
-
setSuccess(null);
|
|
500
|
-
setQuarantineAlert(null);
|
|
501
|
-
|
|
502
|
-
try {
|
|
503
|
-
const res = await installRegistrySkill(pkg);
|
|
504
|
-
|
|
505
|
-
if (res.success) {
|
|
506
|
-
setSuccess(`✓ Skill "${res.skillName || pkg}" successfully installed, security audited, and compiled!`);
|
|
507
|
-
setTimeout(() => setSuccess(null), 7000);
|
|
508
|
-
// Refresh local installed skills registry list
|
|
509
|
-
void fetchSkillsList(res.skillName);
|
|
510
|
-
} else {
|
|
511
|
-
if (res.reason && res.reason.includes('quarantined')) {
|
|
512
|
-
setQuarantineAlert({
|
|
513
|
-
pkg,
|
|
514
|
-
reason: res.reason || 'Static security audit scanned critical dynamic string evaluations or commands injection vectors.'
|
|
515
|
-
});
|
|
516
|
-
} else {
|
|
517
|
-
setError(`❌ Installation failed: ${res.reason || 'Unknown error'}`);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
} catch (err: unknown) {
|
|
521
|
-
setError((err as Error).message || 'Dynamic installation handler failed.');
|
|
522
|
-
} finally {
|
|
523
|
-
setInstallingPkg(null);
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
// ─── Lifecycle tab handlers ─────────────────────────────────────────────────
|
|
528
|
-
|
|
529
|
-
const handleToggleSubDir = async (dir: string) => {
|
|
530
|
-
setExpandedSubDirs(prev => ({ ...prev, [dir]: !prev[dir] }));
|
|
531
|
-
if (!subDirFiles[dir] && selectedSkill) {
|
|
532
|
-
try {
|
|
533
|
-
const files = await fetchSkillFiles(selectedSkill.name, dir, selectedSkill.repo);
|
|
534
|
-
setSubDirFiles(prev => ({ ...prev, [dir]: files }));
|
|
535
|
-
} catch (err: unknown) {
|
|
536
|
-
console.error('Failed to fetch skill files', err);
|
|
537
|
-
setError((err as Error).message || 'Failed to fetch skill files.');
|
|
538
|
-
setSubDirFiles(prev => ({ ...prev, [dir]: [] }));
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
};
|
|
542
|
-
|
|
543
|
-
const handleRunEnforcement = async () => {
|
|
544
|
-
setEnforcementRunning(true);
|
|
545
|
-
setEnforcementOutput(null);
|
|
546
|
-
try {
|
|
547
|
-
const result = await runSandbox('node .agent/skills/tr-skill/scripts/enforce-skill-optimization.mjs', 15000);
|
|
548
|
-
setEnforcementOutput(result.output);
|
|
549
|
-
if (result.success) {
|
|
550
|
-
setSuccess('Enforcement scan completed.');
|
|
551
|
-
setTimeout(() => setSuccess(null), 5000);
|
|
552
|
-
}
|
|
553
|
-
} catch (err: unknown) {
|
|
554
|
-
setEnforcementOutput((err as Error).message);
|
|
555
|
-
} finally {
|
|
556
|
-
setEnforcementRunning(false);
|
|
557
|
-
}
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
// ─── Automation tab handlers ────────────────────────────────────────────────
|
|
561
|
-
|
|
562
|
-
const handleFetchResearch = useCallback(async () => {
|
|
563
|
-
setResearchLoading(true);
|
|
564
|
-
try {
|
|
565
|
-
const res = await listResearch();
|
|
566
|
-
setResearchItems(res.items || []);
|
|
567
|
-
} catch (err: unknown) {
|
|
568
|
-
setError((err as Error).message || 'Failed to fetch research items.');
|
|
569
|
-
} finally {
|
|
570
|
-
setResearchLoading(false);
|
|
571
|
-
}
|
|
572
|
-
}, []);
|
|
573
|
-
|
|
574
|
-
useEffect(() => {
|
|
575
|
-
if (activeTab === 'automation') {
|
|
576
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
577
|
-
void handleFetchResearch();
|
|
578
|
-
}
|
|
579
|
-
}, [activeTab, handleFetchResearch]);
|
|
580
|
-
|
|
581
|
-
const handleCheckUpdates = async () => {
|
|
582
|
-
setCheckingUpdates(true);
|
|
583
|
-
// Simulate update check since there's no dedicated endpoint
|
|
584
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
585
|
-
setCheckingUpdates(false);
|
|
586
|
-
setSuccess('All skills are up to date.');
|
|
587
|
-
setTimeout(() => setSuccess(null), 5000);
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
const handleTriggerResearchCycle = async () => {
|
|
591
|
-
setError(null);
|
|
592
|
-
try {
|
|
593
|
-
await createResearch('Skill reference material update cycle', 'medium', 'Auto-triggered by Skills Manager to refresh outdated skill references and evals');
|
|
594
|
-
setSuccess('Research cycle queued successfully.');
|
|
595
|
-
setTimeout(() => setSuccess(null), 5000);
|
|
596
|
-
void handleFetchResearch();
|
|
597
|
-
} catch (err: unknown) {
|
|
598
|
-
setError((err as Error).message || 'Failed to queue research cycle.');
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
// ─── Tab configuration ────────────────────────────────────────────────────────
|
|
603
|
-
|
|
604
|
-
const tabs: { id: TabId; label: string; icon: string }[] = [
|
|
605
|
-
{ id: 'active', label: 'Skills by repo', icon: '📂' },
|
|
606
|
-
{ id: 'registry', label: 'skills.sh Registry Hub', icon: '✨' },
|
|
607
|
-
{ id: 'lifecycle', label: 'Lifecycle & Versioning', icon: '📋' },
|
|
608
|
-
{ id: 'network', label: 'P2P Skills Network', icon: '🌐' },
|
|
609
|
-
{ id: 'automation', label: 'Auto-Improvement', icon: '⚡' },
|
|
610
|
-
];
|
|
611
|
-
|
|
612
|
-
const skillKey = (s: SkillItem) => s.id || `${s.repo || '_'}::${s.name}`;
|
|
613
|
-
const isSkillSelected = (s: SkillItem) =>
|
|
614
|
-
selectedSkill?.name === s.name && (selectedSkill?.repo || '') === (s.repo || '');
|
|
615
|
-
|
|
616
|
-
const toggleRepo = (repo: string) => {
|
|
617
|
-
setExpandedRepos((prev) => {
|
|
618
|
-
const next = new Set(prev);
|
|
619
|
-
if (next.has(repo)) next.delete(repo);
|
|
620
|
-
else next.add(repo);
|
|
621
|
-
return next;
|
|
622
|
-
});
|
|
623
|
-
};
|
|
624
|
-
|
|
625
|
-
const handleGenerateExpert = async (e: React.MouseEvent, repoPath: string) => {
|
|
626
|
-
e.stopPropagation();
|
|
627
|
-
try {
|
|
628
|
-
setLoading(true);
|
|
629
|
-
const res = await generateExpert(repoPath, true);
|
|
630
|
-
if (res.success) {
|
|
631
|
-
setSuccess(`Generated repo-expert. Modules: ${res.stats?.coreModules || 0}`);
|
|
632
|
-
} else {
|
|
633
|
-
setError(res.message || 'Failed to generate repo-expert');
|
|
634
|
-
}
|
|
635
|
-
setTimeout(() => setSuccess(null), 5000);
|
|
636
|
-
} catch (err) {
|
|
637
|
-
setError(err instanceof Error ? err.message : 'Error generating repo-expert');
|
|
638
|
-
} finally {
|
|
639
|
-
void fetchSkillsList(selectedSkill?.name, selectedSkill?.repo);
|
|
640
|
-
}
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
const renderRepoSkillList = (prefix: string) => (
|
|
644
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, flex: 1, overflowY: 'auto' }}>
|
|
645
|
-
{repoGroups.length === 0 && (
|
|
646
|
-
<p style={{ fontSize: 12, color: 'var(--text-tertiary)', padding: 8 }}>
|
|
647
|
-
No project repos with skills found.
|
|
648
|
-
</p>
|
|
649
|
-
)}
|
|
650
|
-
{repoGroups.map((group) => {
|
|
651
|
-
const open = expandedRepos.has(group.repo);
|
|
652
|
-
const count = group.skills?.length || 0;
|
|
653
|
-
return (
|
|
654
|
-
<div key={group.repo} style={{ borderBottom: '1px solid var(--border)' }}>
|
|
655
|
-
<button
|
|
656
|
-
type="button"
|
|
657
|
-
onClick={() => toggleRepo(group.repo)}
|
|
658
|
-
style={{
|
|
659
|
-
width: '100%',
|
|
660
|
-
display: 'flex',
|
|
661
|
-
justifyContent: 'space-between',
|
|
662
|
-
alignItems: 'center',
|
|
663
|
-
gap: 8,
|
|
664
|
-
padding: '10px 8px',
|
|
665
|
-
background: 'transparent',
|
|
666
|
-
border: 'none',
|
|
667
|
-
color: 'inherit',
|
|
668
|
-
cursor: 'pointer',
|
|
669
|
-
textAlign: 'left',
|
|
670
|
-
}}
|
|
671
|
-
>
|
|
672
|
-
<span style={{ fontSize: 12, fontWeight: 700 }}>
|
|
673
|
-
{open ? '▾' : '▸'} {group.repo}
|
|
674
|
-
</span>
|
|
675
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
676
|
-
<span
|
|
677
|
-
style={{
|
|
678
|
-
fontSize: 10,
|
|
679
|
-
fontWeight: 600,
|
|
680
|
-
padding: '2px 8px',
|
|
681
|
-
borderRadius: 99,
|
|
682
|
-
background: count ? 'rgba(59,130,246,0.15)' : 'rgba(148,163,184,0.1)',
|
|
683
|
-
color: count ? '#93c5fd' : 'var(--text-tertiary)',
|
|
684
|
-
}}
|
|
685
|
-
>
|
|
686
|
-
{count}
|
|
687
|
-
</span>
|
|
688
|
-
{group.path && (
|
|
689
|
-
<button
|
|
690
|
-
onClick={(e) => void handleGenerateExpert(e, group.path!)}
|
|
691
|
-
title="Auto-generate repo-expert for this repository"
|
|
692
|
-
style={{
|
|
693
|
-
background: 'var(--bg-tertiary)',
|
|
694
|
-
border: '1px solid var(--border)',
|
|
695
|
-
color: 'var(--text-secondary)',
|
|
696
|
-
borderRadius: '4px',
|
|
697
|
-
padding: '2px 6px',
|
|
698
|
-
fontSize: '10px',
|
|
699
|
-
cursor: 'pointer',
|
|
700
|
-
}}
|
|
701
|
-
>
|
|
702
|
-
Generate Expert
|
|
703
|
-
</button>
|
|
704
|
-
)}
|
|
705
|
-
</div>
|
|
706
|
-
</button>
|
|
707
|
-
{open && (
|
|
708
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, padding: '0 4px 10px' }}>
|
|
709
|
-
{count === 0 ? (
|
|
710
|
-
<div style={{ fontSize: 11, color: 'var(--text-tertiary)', padding: '4px 8px' }}>
|
|
711
|
-
{group.error || 'No app skills (TR skills hidden)'}
|
|
712
|
-
</div>
|
|
713
|
-
) : (
|
|
714
|
-
group.skills.map((skill) => {
|
|
715
|
-
const active = isSkillSelected(skill);
|
|
716
|
-
return (
|
|
717
|
-
<div
|
|
718
|
-
key={skillKey(skill)}
|
|
719
|
-
id={`${prefix}-skill-${skill.repo}-${skill.name}`}
|
|
720
|
-
onClick={() => void handleSelectSkill(skill)}
|
|
721
|
-
style={{
|
|
722
|
-
background: active ? 'var(--accent-muted)' : 'var(--bg-tertiary)',
|
|
723
|
-
border: active ? '1px solid var(--accent)' : '1px solid var(--border)',
|
|
724
|
-
padding: '10px 12px',
|
|
725
|
-
borderRadius: 8,
|
|
726
|
-
cursor: 'pointer',
|
|
727
|
-
transition: 'all 0.15s ease',
|
|
728
|
-
}}
|
|
729
|
-
>
|
|
730
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
731
|
-
<span
|
|
732
|
-
style={{
|
|
733
|
-
fontSize: 13,
|
|
734
|
-
fontWeight: active ? 600 : 500,
|
|
735
|
-
color: active ? 'var(--accent)' : 'var(--text-primary)',
|
|
736
|
-
}}
|
|
737
|
-
>
|
|
738
|
-
{skill.name}
|
|
739
|
-
</span>
|
|
740
|
-
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
741
|
-
{activeRepoGroup?.path && group.repo === 'Global' && (
|
|
742
|
-
<input
|
|
743
|
-
type="checkbox"
|
|
744
|
-
checked={!!activeRepoGroup?.skills?.some(s => s.name === skill.name)}
|
|
745
|
-
onClick={(e) => e.stopPropagation()}
|
|
746
|
-
onChange={async (e) => {
|
|
747
|
-
e.stopPropagation();
|
|
748
|
-
const checked = e.target.checked;
|
|
749
|
-
const targetRepoPath = activeRepoGroup?.path;
|
|
750
|
-
if (!targetRepoPath) return;
|
|
751
|
-
if (checked) {
|
|
752
|
-
// Open preview modal instead of instantly deploying
|
|
753
|
-
setPreviewModalOpen(true);
|
|
754
|
-
setPreviewLoading(true);
|
|
755
|
-
setPreviewData(null);
|
|
756
|
-
try {
|
|
757
|
-
const res = await previewSkillRepo(skill.name, targetRepoPath);
|
|
758
|
-
setPreviewData({
|
|
759
|
-
skillName: skill.name,
|
|
760
|
-
targetRepo: targetRepoPath,
|
|
761
|
-
original: res.original,
|
|
762
|
-
preview: res.preview,
|
|
763
|
-
signals: res.signals
|
|
764
|
-
});
|
|
765
|
-
} catch (err) {
|
|
766
|
-
setError((err as Error).message);
|
|
767
|
-
setPreviewModalOpen(false);
|
|
768
|
-
} finally {
|
|
769
|
-
setPreviewLoading(false);
|
|
770
|
-
}
|
|
771
|
-
} else {
|
|
772
|
-
// Uncheck (disable) happens immediately
|
|
773
|
-
try {
|
|
774
|
-
await toggleSkillRepo(skill.name, targetRepoPath, false);
|
|
775
|
-
void fetchSkillsList();
|
|
776
|
-
} catch (err) {
|
|
777
|
-
setError((err as Error).message);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}}
|
|
781
|
-
style={{ cursor: 'pointer' }}
|
|
782
|
-
title="Enable/Disable skill for this repo"
|
|
783
|
-
/>
|
|
784
|
-
)}
|
|
785
|
-
<button
|
|
786
|
-
type="button"
|
|
787
|
-
onClick={(e) => {
|
|
788
|
-
e.stopPropagation();
|
|
789
|
-
void handleDeleteSkill(skill);
|
|
790
|
-
}}
|
|
791
|
-
style={{
|
|
792
|
-
background: 'none',
|
|
793
|
-
border: 'none',
|
|
794
|
-
color: 'var(--text-tertiary)',
|
|
795
|
-
fontSize: 12,
|
|
796
|
-
cursor: 'pointer',
|
|
797
|
-
padding: '2px 6px',
|
|
798
|
-
}}
|
|
799
|
-
title="Delete skill"
|
|
800
|
-
>
|
|
801
|
-
✖
|
|
802
|
-
</button>
|
|
803
|
-
</div>
|
|
804
|
-
</div>
|
|
805
|
-
{skill.source && (
|
|
806
|
-
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', marginTop: 4 }}>
|
|
807
|
-
{skill.source}
|
|
808
|
-
</div>
|
|
809
|
-
)}
|
|
810
|
-
{skill.subSkills && skill.subSkills.length > 0 && (
|
|
811
|
-
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 6 }}>
|
|
812
|
-
{skill.subSkills.slice(0, 6).map((sd) => (
|
|
813
|
-
<span
|
|
814
|
-
key={sd}
|
|
815
|
-
className="badge"
|
|
816
|
-
style={{
|
|
817
|
-
fontSize: 9,
|
|
818
|
-
padding: '1px 5px',
|
|
819
|
-
background: 'var(--bg-elevated)',
|
|
820
|
-
color: 'var(--text-secondary)',
|
|
821
|
-
border: '1px solid var(--border)',
|
|
822
|
-
}}
|
|
823
|
-
>
|
|
824
|
-
{sd}
|
|
825
|
-
</span>
|
|
826
|
-
))}
|
|
827
|
-
</div>
|
|
828
|
-
)}
|
|
829
|
-
</div>
|
|
830
|
-
);
|
|
831
|
-
})
|
|
832
|
-
)}
|
|
833
|
-
</div>
|
|
834
|
-
)}
|
|
835
|
-
</div>
|
|
836
|
-
);
|
|
837
|
-
})}
|
|
838
|
-
</div>
|
|
839
|
-
);
|
|
840
|
-
|
|
841
|
-
// ─── Render helpers ─────────────────────────────────────────────────────────
|
|
842
|
-
|
|
843
|
-
const renderLifecycleTab = () => (
|
|
844
|
-
<div style={{ display: 'flex', flex: 1, gap: 24, minHeight: 0, overflow: 'hidden', paddingBottom: 16 }}>
|
|
845
|
-
|
|
846
|
-
{/* Preview Modal Overlay */}
|
|
847
|
-
{previewModalOpen && (
|
|
848
|
-
<div style={{
|
|
849
|
-
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
|
|
850
|
-
background: 'rgba(0,0,0,0.7)', zIndex: 9999, display: 'flex',
|
|
851
|
-
justifyContent: 'center', alignItems: 'center'
|
|
852
|
-
}}>
|
|
853
|
-
<div className="card" style={{
|
|
854
|
-
background: 'var(--bg-primary)', border: '1px solid var(--border)',
|
|
855
|
-
borderRadius: 12, padding: 24, width: '80%', maxWidth: 900,
|
|
856
|
-
maxHeight: '90vh', display: 'flex', flexDirection: 'column'
|
|
857
|
-
}}>
|
|
858
|
-
<h2 style={{ marginTop: 0, marginBottom: 12 }}>Skill Adaptation Preview</h2>
|
|
859
|
-
{previewLoading ? (
|
|
860
|
-
<div style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
|
861
|
-
<p>Generating adaptive preview for {activeRepoName || activeBrainId}...</p>
|
|
862
|
-
</div>
|
|
863
|
-
) : previewData ? (
|
|
864
|
-
<>
|
|
865
|
-
<p style={{ color: 'var(--text-secondary)', marginBottom: 16, fontSize: 13 }}>
|
|
866
|
-
This skill will be explicitly rewritten and bound to this project repository.<br/>
|
|
867
|
-
Detected adaptation signals: <span style={{ color: 'var(--accent)', fontWeight: 600 }}>{previewData.signals.join(' | ') || 'None (Template copied as-is)'}</span>
|
|
868
|
-
</p>
|
|
869
|
-
<div style={{ display: 'flex', flex: 1, gap: 16, overflow: 'hidden', minHeight: 0 }}>
|
|
870
|
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
|
871
|
-
<h3 style={{ fontSize: 13, marginBottom: 8 }}>Global Template</h3>
|
|
872
|
-
<textarea readOnly value={previewData.original} style={{
|
|
873
|
-
flex: 1, width: '100%', background: 'var(--bg-secondary)',
|
|
874
|
-
border: '1px solid var(--border)', color: 'var(--text-tertiary)',
|
|
875
|
-
padding: 12, borderRadius: 8, fontFamily: 'monospace', fontSize: 12,
|
|
876
|
-
resize: 'none', whiteSpace: 'pre-wrap'
|
|
877
|
-
}} />
|
|
878
|
-
</div>
|
|
879
|
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
|
880
|
-
<h3 style={{ fontSize: 13, marginBottom: 8, color: 'var(--accent)' }}>Adapted Target (SKILL.md)</h3>
|
|
881
|
-
<textarea readOnly value={previewData.preview} style={{
|
|
882
|
-
flex: 1, width: '100%', background: 'var(--bg-secondary)',
|
|
883
|
-
border: '1px solid var(--border)', color: 'var(--text-primary)',
|
|
884
|
-
padding: 12, borderRadius: 8, fontFamily: 'monospace', fontSize: 12,
|
|
885
|
-
resize: 'none', whiteSpace: 'pre-wrap'
|
|
886
|
-
}} />
|
|
887
|
-
</div>
|
|
888
|
-
</div>
|
|
889
|
-
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, marginTop: 24 }}>
|
|
890
|
-
<button className="btn" onClick={() => setPreviewModalOpen(false)}>Cancel</button>
|
|
891
|
-
<button className="btn btn-primary" onClick={async () => {
|
|
892
|
-
setPreviewLoading(true);
|
|
893
|
-
try {
|
|
894
|
-
await toggleSkillRepo(previewData.skillName, previewData.targetRepo, true);
|
|
895
|
-
setPreviewModalOpen(false);
|
|
896
|
-
void fetchSkillsList();
|
|
897
|
-
} catch (err) {
|
|
898
|
-
setError((err as Error).message);
|
|
899
|
-
} finally {
|
|
900
|
-
setPreviewLoading(false);
|
|
901
|
-
}
|
|
902
|
-
}}>Deploy to Project</button>
|
|
903
|
-
</div>
|
|
904
|
-
</>
|
|
905
|
-
) : (
|
|
906
|
-
<p>Failed to load preview data.</p>
|
|
907
|
-
)}
|
|
908
|
-
</div>
|
|
909
|
-
</div>
|
|
910
|
-
)}
|
|
911
|
-
|
|
912
|
-
{/* Left: Skill selector by repo */}
|
|
913
|
-
<div style={{ width: 280, display: 'flex', flexDirection: 'column', gap: 16, flexShrink: 0, overflowY: 'auto' }}>
|
|
914
|
-
<div className="card" style={{ padding: 16, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
915
|
-
<h3 style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 8, marginBottom: 12 }}>
|
|
916
|
-
Skills by repo
|
|
917
|
-
</h3>
|
|
918
|
-
{renderRepoSkillList('lifecycle')}
|
|
919
|
-
</div>
|
|
920
|
-
</div>
|
|
921
|
-
|
|
922
|
-
{/* Right: Lifecycle details */}
|
|
923
|
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 20, overflowY: 'auto', minHeight: 0 }}>
|
|
924
|
-
{selectedSkill ? (
|
|
925
|
-
<>
|
|
926
|
-
{/* Version Badge */}
|
|
927
|
-
<div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
928
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
|
|
929
|
-
<h3 id="lifecycle-version-title" style={{ fontSize: 16, fontWeight: 600, color: 'var(--text-primary)', margin: 0 }}>
|
|
930
|
-
{selectedSkill.name}
|
|
931
|
-
{selectedSkill.repo ? (
|
|
932
|
-
<span style={{ fontSize: 12, fontWeight: 500, color: 'var(--text-tertiary)', marginLeft: 8 }}>
|
|
933
|
-
· {selectedSkill.repo}
|
|
934
|
-
</span>
|
|
935
|
-
) : null}
|
|
936
|
-
</h3>
|
|
937
|
-
<span
|
|
938
|
-
id="lifecycle-version-badge"
|
|
939
|
-
style={{
|
|
940
|
-
background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
941
|
-
color: '#fff',
|
|
942
|
-
padding: '4px 14px',
|
|
943
|
-
borderRadius: 20,
|
|
944
|
-
fontSize: 12,
|
|
945
|
-
fontWeight: 700,
|
|
946
|
-
letterSpacing: 0.3,
|
|
947
|
-
}}
|
|
948
|
-
>
|
|
949
|
-
v{lifecycleVersion}
|
|
950
|
-
</span>
|
|
951
|
-
<span style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>
|
|
952
|
-
Modified: {new Date(selectedSkill.modified).toLocaleDateString()}
|
|
953
|
-
</span>
|
|
954
|
-
</div>
|
|
955
|
-
</div>
|
|
956
|
-
|
|
957
|
-
{/* Changelog Viewer */}
|
|
958
|
-
<div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
959
|
-
<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 }}>
|
|
960
|
-
📜 Changelog Timeline
|
|
961
|
-
</h3>
|
|
962
|
-
{changelogEntries.length > 0 ? (
|
|
963
|
-
<div style={{ position: 'relative', paddingLeft: 28 }}>
|
|
964
|
-
{/* Vertical timeline line */}
|
|
965
|
-
<div style={{
|
|
966
|
-
position: 'absolute',
|
|
967
|
-
left: 5,
|
|
968
|
-
top: 6,
|
|
969
|
-
bottom: 6,
|
|
970
|
-
width: 2,
|
|
971
|
-
background: 'var(--border)',
|
|
972
|
-
}} />
|
|
973
|
-
{changelogEntries.map((entry, idx) => (
|
|
974
|
-
<div key={`${entry.version}-${idx}`} style={{ marginBottom: 20, position: 'relative' }}>
|
|
975
|
-
{/* Timeline dot */}
|
|
976
|
-
<div style={{
|
|
977
|
-
position: 'absolute',
|
|
978
|
-
left: -28 + 0,
|
|
979
|
-
top: 4,
|
|
980
|
-
width: 12,
|
|
981
|
-
height: 12,
|
|
982
|
-
borderRadius: '50%',
|
|
983
|
-
background: idx === 0 ? 'var(--accent)' : 'var(--text-tertiary)',
|
|
984
|
-
border: idx === 0 ? '2px solid var(--accent-hover)' : '2px solid var(--border)',
|
|
985
|
-
boxShadow: idx === 0 ? '0 0 8px rgba(var(--accent-rgb, 99, 102, 241), 0.4)' : 'none',
|
|
986
|
-
}} />
|
|
987
|
-
<div style={{ display: 'flex', gap: 10, alignItems: 'baseline', marginBottom: 6 }}>
|
|
988
|
-
<span style={{
|
|
989
|
-
fontSize: 13,
|
|
990
|
-
fontWeight: 700,
|
|
991
|
-
color: idx === 0 ? 'var(--accent)' : 'var(--text-primary)',
|
|
992
|
-
}}>
|
|
993
|
-
v{entry.version}
|
|
994
|
-
</span>
|
|
995
|
-
{entry.date && (
|
|
996
|
-
<span style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>{entry.date}</span>
|
|
997
|
-
)}
|
|
998
|
-
</div>
|
|
999
|
-
<ul style={{ margin: 0, paddingLeft: 16, listStyle: 'disc' }}>
|
|
1000
|
-
{entry.changes.map((change, ci) => (
|
|
1001
|
-
<li key={ci} style={{ fontSize: 12, color: 'var(--text-secondary)', lineHeight: 1.7 }}>
|
|
1002
|
-
{change}
|
|
1003
|
-
</li>
|
|
1004
|
-
))}
|
|
1005
|
-
</ul>
|
|
1006
|
-
</div>
|
|
1007
|
-
))}
|
|
1008
|
-
</div>
|
|
1009
|
-
) : (
|
|
1010
|
-
<div style={{ padding: 20, textAlign: 'center', color: 'var(--text-tertiary)', fontSize: 12, border: '1px dashed var(--border)', borderRadius: 8 }}>
|
|
1011
|
-
No changelog entries detected in SKILL.md. Add <code>## v1.0.0 — 2025-01-01</code> sections to enable version tracking.
|
|
1012
|
-
</div>
|
|
1013
|
-
)}
|
|
1014
|
-
</div>
|
|
1015
|
-
|
|
1016
|
-
{/* Skill Health Checks */}
|
|
1017
|
-
<div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
1018
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, borderBottom: '1px solid var(--border)', paddingBottom: 8 }}>
|
|
1019
|
-
<h3 id="lifecycle-health-heading" style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, margin: 0 }}>
|
|
1020
|
-
🩺 Health Checks
|
|
1021
|
-
</h3>
|
|
1022
|
-
<button
|
|
1023
|
-
id="lifecycle-run-enforcement-btn"
|
|
1024
|
-
onClick={() => void handleRunEnforcement()}
|
|
1025
|
-
disabled={enforcementRunning}
|
|
1026
|
-
style={{
|
|
1027
|
-
background: enforcementRunning ? 'var(--bg-tertiary)' : 'linear-gradient(135deg, #f59e0b, #d97706)',
|
|
1028
|
-
color: '#fff',
|
|
1029
|
-
border: 'none',
|
|
1030
|
-
borderRadius: 6,
|
|
1031
|
-
padding: '6px 14px',
|
|
1032
|
-
fontSize: 11,
|
|
1033
|
-
fontWeight: 600,
|
|
1034
|
-
cursor: enforcementRunning ? 'not-allowed' : 'pointer',
|
|
1035
|
-
}}
|
|
1036
|
-
>
|
|
1037
|
-
{enforcementRunning ? '⏳ Running...' : '🛡️ Run Enforcement'}
|
|
1038
|
-
</button>
|
|
1039
|
-
</div>
|
|
1040
|
-
|
|
1041
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 10 }}>
|
|
1042
|
-
{healthChecks.map((check, idx) => (
|
|
1043
|
-
<div
|
|
1044
|
-
key={idx}
|
|
1045
|
-
id={`lifecycle-health-${idx}`}
|
|
1046
|
-
style={{
|
|
1047
|
-
display: 'flex',
|
|
1048
|
-
alignItems: 'center',
|
|
1049
|
-
gap: 10,
|
|
1050
|
-
padding: '10px 14px',
|
|
1051
|
-
background: 'var(--bg-tertiary)',
|
|
1052
|
-
borderRadius: 8,
|
|
1053
|
-
border: `1px solid ${check.passed ? 'rgba(16, 185, 129, 0.25)' : 'rgba(239, 68, 68, 0.25)'}`,
|
|
1054
|
-
}}
|
|
1055
|
-
>
|
|
1056
|
-
<div style={{
|
|
1057
|
-
width: 10,
|
|
1058
|
-
height: 10,
|
|
1059
|
-
borderRadius: '50%',
|
|
1060
|
-
background: check.passed ? '#10b981' : '#ef4444',
|
|
1061
|
-
flexShrink: 0,
|
|
1062
|
-
boxShadow: check.passed ? '0 0 6px rgba(16, 185, 129, 0.4)' : '0 0 6px rgba(239, 68, 68, 0.4)',
|
|
1063
|
-
}} />
|
|
1064
|
-
<div>
|
|
1065
|
-
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{check.label}</div>
|
|
1066
|
-
<div style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>{check.detail}</div>
|
|
1067
|
-
</div>
|
|
1068
|
-
</div>
|
|
1069
|
-
))}
|
|
1070
|
-
</div>
|
|
1071
|
-
|
|
1072
|
-
{enforcementOutput && (
|
|
1073
|
-
<pre
|
|
1074
|
-
id="lifecycle-enforcement-output"
|
|
1075
|
-
style={{
|
|
1076
|
-
marginTop: 16,
|
|
1077
|
-
padding: 14,
|
|
1078
|
-
background: 'var(--bg-secondary)',
|
|
1079
|
-
border: '1px solid var(--border)',
|
|
1080
|
-
borderRadius: 8,
|
|
1081
|
-
fontSize: 11,
|
|
1082
|
-
color: 'var(--text-secondary)',
|
|
1083
|
-
fontFamily: 'var(--font-mono)',
|
|
1084
|
-
whiteSpace: 'pre-wrap',
|
|
1085
|
-
maxHeight: 200,
|
|
1086
|
-
overflowY: 'auto',
|
|
1087
|
-
}}
|
|
1088
|
-
>
|
|
1089
|
-
{enforcementOutput}
|
|
1090
|
-
</pre>
|
|
1091
|
-
)}
|
|
1092
|
-
</div>
|
|
1093
|
-
|
|
1094
|
-
{/* Sub-file Browser */}
|
|
1095
|
-
<div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
1096
|
-
<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 }}>
|
|
1097
|
-
📁 Sub-file Browser
|
|
1098
|
-
</h3>
|
|
1099
|
-
|
|
1100
|
-
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
|
|
1101
|
-
{/* Directory sections */}
|
|
1102
|
-
<div style={{ flex: '1 1 300px', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1103
|
-
{['scripts', 'references', 'evals', 'subagents'].map(dir => {
|
|
1104
|
-
const isPresent = (selectedSkill.subSkills || []).map(s => s.toLowerCase()).includes(dir);
|
|
1105
|
-
const isExpanded = expandedSubDirs[dir];
|
|
1106
|
-
return (
|
|
1107
|
-
<div key={dir}>
|
|
1108
|
-
<button
|
|
1109
|
-
id={`lifecycle-subdir-${dir}`}
|
|
1110
|
-
onClick={() => handleToggleSubDir(dir)}
|
|
1111
|
-
style={{
|
|
1112
|
-
width: '100%',
|
|
1113
|
-
display: 'flex',
|
|
1114
|
-
justifyContent: 'space-between',
|
|
1115
|
-
alignItems: 'center',
|
|
1116
|
-
padding: '10px 14px',
|
|
1117
|
-
background: 'var(--bg-tertiary)',
|
|
1118
|
-
border: '1px solid var(--border)',
|
|
1119
|
-
borderRadius: 8,
|
|
1120
|
-
cursor: 'pointer',
|
|
1121
|
-
color: isPresent ? 'var(--text-primary)' : 'var(--text-tertiary)',
|
|
1122
|
-
fontSize: 13,
|
|
1123
|
-
fontWeight: 500,
|
|
1124
|
-
transition: 'all 0.15s ease',
|
|
1125
|
-
}}
|
|
1126
|
-
>
|
|
1127
|
-
<span>{isExpanded ? '▼' : '▶'} {dir}/</span>
|
|
1128
|
-
<span style={{
|
|
1129
|
-
width: 8,
|
|
1130
|
-
height: 8,
|
|
1131
|
-
borderRadius: '50%',
|
|
1132
|
-
background: isPresent ? '#10b981' : '#ef4444',
|
|
1133
|
-
}} />
|
|
1134
|
-
</button>
|
|
1135
|
-
{isExpanded && subDirFiles[dir] && (
|
|
1136
|
-
<div style={{ paddingLeft: 20, paddingTop: 6, display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
1137
|
-
{subDirFiles[dir].length > 0 ? subDirFiles[dir].map(file => (
|
|
1138
|
-
<div
|
|
1139
|
-
key={file.name}
|
|
1140
|
-
onClick={() => setPreviewFile({ name: `${dir}/${file.name}`, content: `// Contents of ${selectedSkill.name}/${dir}/${file.name}\n// Size: ${file.size}\n// (Preview loaded from skill filesystem)` })}
|
|
1141
|
-
style={{
|
|
1142
|
-
display: 'flex',
|
|
1143
|
-
justifyContent: 'space-between',
|
|
1144
|
-
padding: '6px 10px',
|
|
1145
|
-
background: 'var(--bg-secondary)',
|
|
1146
|
-
borderRadius: 6,
|
|
1147
|
-
cursor: 'pointer',
|
|
1148
|
-
fontSize: 11,
|
|
1149
|
-
color: 'var(--text-secondary)',
|
|
1150
|
-
transition: 'background 0.1s ease',
|
|
1151
|
-
}}
|
|
1152
|
-
onMouseEnter={e => (e.currentTarget.style.background = 'var(--bg-elevated)')}
|
|
1153
|
-
onMouseLeave={e => (e.currentTarget.style.background = 'var(--bg-secondary)')}
|
|
1154
|
-
>
|
|
1155
|
-
<span>📄 {file.name}</span>
|
|
1156
|
-
<span style={{ color: 'var(--text-tertiary)' }}>{file.size}</span>
|
|
1157
|
-
</div>
|
|
1158
|
-
)) : (
|
|
1159
|
-
<div style={{ fontSize: 11, color: 'var(--text-tertiary)', padding: '4px 10px' }}>
|
|
1160
|
-
Empty directory
|
|
1161
|
-
</div>
|
|
1162
|
-
)}
|
|
1163
|
-
</div>
|
|
1164
|
-
)}
|
|
1165
|
-
</div>
|
|
1166
|
-
);
|
|
1167
|
-
})}
|
|
1168
|
-
</div>
|
|
1169
|
-
|
|
1170
|
-
{/* Preview pane */}
|
|
1171
|
-
{previewFile && (
|
|
1172
|
-
<div style={{ flex: '1 1 300px' }}>
|
|
1173
|
-
<div style={{
|
|
1174
|
-
background: 'var(--bg-secondary)',
|
|
1175
|
-
border: '1px solid var(--border)',
|
|
1176
|
-
borderRadius: 8,
|
|
1177
|
-
overflow: 'hidden',
|
|
1178
|
-
}}>
|
|
1179
|
-
<div style={{
|
|
1180
|
-
display: 'flex',
|
|
1181
|
-
justifyContent: 'space-between',
|
|
1182
|
-
alignItems: 'center',
|
|
1183
|
-
padding: '8px 14px',
|
|
1184
|
-
background: 'var(--bg-tertiary)',
|
|
1185
|
-
borderBottom: '1px solid var(--border)',
|
|
1186
|
-
}}>
|
|
1187
|
-
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>
|
|
1188
|
-
{previewFile.name}
|
|
1189
|
-
</span>
|
|
1190
|
-
<button
|
|
1191
|
-
id="lifecycle-close-preview-btn"
|
|
1192
|
-
onClick={() => setPreviewFile(null)}
|
|
1193
|
-
style={{ background: 'none', border: 'none', color: 'var(--text-tertiary)', cursor: 'pointer', fontSize: 14 }}
|
|
1194
|
-
>
|
|
1195
|
-
✖
|
|
1196
|
-
</button>
|
|
1197
|
-
</div>
|
|
1198
|
-
<pre style={{
|
|
1199
|
-
padding: 14,
|
|
1200
|
-
fontSize: 11,
|
|
1201
|
-
color: 'var(--text-secondary)',
|
|
1202
|
-
fontFamily: 'var(--font-mono)',
|
|
1203
|
-
margin: 0,
|
|
1204
|
-
whiteSpace: 'pre-wrap',
|
|
1205
|
-
maxHeight: 200,
|
|
1206
|
-
overflowY: 'auto',
|
|
1207
|
-
}}>
|
|
1208
|
-
{previewFile.content}
|
|
1209
|
-
</pre>
|
|
1210
|
-
</div>
|
|
1211
|
-
</div>
|
|
1212
|
-
)}
|
|
1213
|
-
</div>
|
|
1214
|
-
</div>
|
|
1215
|
-
</>
|
|
1216
|
-
) : (
|
|
1217
|
-
<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 }}>
|
|
1218
|
-
Select a skill from the left panel to view its lifecycle details.
|
|
1219
|
-
</div>
|
|
1220
|
-
)}
|
|
1221
|
-
</div>
|
|
1222
|
-
</div>
|
|
1223
|
-
);
|
|
1224
|
-
|
|
1225
|
-
const renderNetworkTab = () => (
|
|
1226
|
-
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0, overflowY: 'auto', paddingBottom: 16 }}>
|
|
1227
|
-
{/* Network Status Banner */}
|
|
1228
|
-
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', marginBottom: 20 }}>
|
|
1229
|
-
<div style={{ display: 'flex', gap: 20, flexWrap: 'wrap' }}>
|
|
1230
|
-
{[
|
|
1231
|
-
{ label: 'Connected Peers', value: '—', icon: '🔗' },
|
|
1232
|
-
{ label: 'Shared Skills', value: '—', icon: '📦' },
|
|
1233
|
-
{ label: 'Last Sync', value: 'Never', icon: '🔄' },
|
|
1234
|
-
].map(stat => (
|
|
1235
|
-
<div
|
|
1236
|
-
key={stat.label}
|
|
1237
|
-
id={`network-stat-${stat.label.toLowerCase().replace(/\s/g, '-')}`}
|
|
1238
|
-
style={{
|
|
1239
|
-
flex: '1 1 140px',
|
|
1240
|
-
background: 'var(--bg-tertiary)',
|
|
1241
|
-
border: '1px solid var(--border)',
|
|
1242
|
-
borderRadius: 10,
|
|
1243
|
-
padding: '16px 20px',
|
|
1244
|
-
textAlign: 'center',
|
|
1245
|
-
}}
|
|
1246
|
-
>
|
|
1247
|
-
<div style={{ fontSize: 22, marginBottom: 4 }}>{stat.icon}</div>
|
|
1248
|
-
<div style={{ fontSize: 20, fontWeight: 700, color: 'var(--text-primary)' }}>{stat.value}</div>
|
|
1249
|
-
<div style={{ fontSize: 11, color: 'var(--text-tertiary)', marginTop: 2 }}>{stat.label}</div>
|
|
1250
|
-
</div>
|
|
1251
|
-
))}
|
|
1252
|
-
</div>
|
|
1253
|
-
</div>
|
|
1254
|
-
|
|
1255
|
-
{/* Coming Soon overlay card */}
|
|
1256
|
-
<div
|
|
1257
|
-
className="card"
|
|
1258
|
-
id="network-coming-soon-panel"
|
|
1259
|
-
style={{
|
|
1260
|
-
flex: 1,
|
|
1261
|
-
position: 'relative',
|
|
1262
|
-
overflow: 'hidden',
|
|
1263
|
-
border: '1px solid var(--border)',
|
|
1264
|
-
borderRadius: 12,
|
|
1265
|
-
}}
|
|
1266
|
-
>
|
|
1267
|
-
{/* Blurred background content */}
|
|
1268
|
-
<div style={{
|
|
1269
|
-
padding: 40,
|
|
1270
|
-
filter: 'blur(3px)',
|
|
1271
|
-
opacity: 0.35,
|
|
1272
|
-
pointerEvents: 'none',
|
|
1273
|
-
userSelect: 'none',
|
|
1274
|
-
}}>
|
|
1275
|
-
{/* Fake diagram content that appears behind blur */}
|
|
1276
|
-
<div style={{ display: 'flex', justifyContent: 'center', gap: 24, alignItems: 'center', marginBottom: 30, flexWrap: 'wrap' }}>
|
|
1277
|
-
<div style={{ padding: '20px 30px', background: 'var(--bg-tertiary)', borderRadius: 12, border: '2px solid var(--accent)', textAlign: 'center' }}>
|
|
1278
|
-
<div style={{ fontSize: 28 }}>📂</div>
|
|
1279
|
-
<div style={{ fontSize: 13, fontWeight: 700, marginTop: 6, color: 'var(--text-primary)' }}>Local Skills</div>
|
|
1280
|
-
</div>
|
|
1281
|
-
<div style={{ fontSize: 28, color: 'var(--accent)' }}>⇄</div>
|
|
1282
|
-
<div style={{ padding: '20px 30px', background: 'var(--bg-tertiary)', borderRadius: 12, border: '2px solid var(--accent)', textAlign: 'center' }}>
|
|
1283
|
-
<div style={{ fontSize: 28 }}>🌐</div>
|
|
1284
|
-
<div style={{ fontSize: 13, fontWeight: 700, marginTop: 6, color: 'var(--text-primary)' }}>P2P Network</div>
|
|
1285
|
-
</div>
|
|
1286
|
-
<div style={{ fontSize: 28, color: 'var(--accent)' }}>⇄</div>
|
|
1287
|
-
<div style={{ padding: '20px 30px', background: 'var(--bg-tertiary)', borderRadius: 12, border: '2px solid var(--accent)', textAlign: 'center' }}>
|
|
1288
|
-
<div style={{ fontSize: 28 }}>👥</div>
|
|
1289
|
-
<div style={{ fontSize: 13, fontWeight: 700, marginTop: 6, color: 'var(--text-primary)' }}>Remote Peers</div>
|
|
1290
|
-
</div>
|
|
1291
|
-
</div>
|
|
1292
|
-
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
|
|
1293
|
-
{['Auto-discovery', 'Version Negotiation', 'Conflict Resolution', 'Security Quarantine'].map(f => (
|
|
1294
|
-
<div key={f} style={{ background: 'var(--bg-tertiary)', padding: 16, borderRadius: 8, border: '1px solid var(--border)' }}>
|
|
1295
|
-
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{f}</div>
|
|
1296
|
-
</div>
|
|
1297
|
-
))}
|
|
1298
|
-
</div>
|
|
1299
|
-
</div>
|
|
1300
|
-
|
|
1301
|
-
{/* Coming Soon Overlay */}
|
|
1302
|
-
<div style={{
|
|
1303
|
-
position: 'absolute',
|
|
1304
|
-
inset: 0,
|
|
1305
|
-
display: 'flex',
|
|
1306
|
-
flexDirection: 'column',
|
|
1307
|
-
alignItems: 'center',
|
|
1308
|
-
justifyContent: 'center',
|
|
1309
|
-
background: 'rgba(10, 10, 18, 0.6)',
|
|
1310
|
-
backdropFilter: 'blur(2px)',
|
|
1311
|
-
zIndex: 10,
|
|
1312
|
-
}}>
|
|
1313
|
-
<div style={{
|
|
1314
|
-
background: 'rgba(18, 18, 26, 0.95)',
|
|
1315
|
-
border: '1px solid var(--border)',
|
|
1316
|
-
borderRadius: 16,
|
|
1317
|
-
padding: '40px 48px',
|
|
1318
|
-
textAlign: 'center',
|
|
1319
|
-
maxWidth: 520,
|
|
1320
|
-
backdropFilter: 'blur(20px)',
|
|
1321
|
-
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.4)',
|
|
1322
|
-
}}>
|
|
1323
|
-
<div style={{ fontSize: 48, marginBottom: 16 }}>🚀</div>
|
|
1324
|
-
<h2 id="network-coming-soon-title" style={{ fontSize: 22, fontWeight: 700, color: 'var(--text-primary)', marginBottom: 12 }}>
|
|
1325
|
-
Coming Soon
|
|
1326
|
-
</h2>
|
|
1327
|
-
<p style={{ fontSize: 13, color: 'var(--text-secondary)', lineHeight: 1.7, marginBottom: 20 }}>
|
|
1328
|
-
Peer-to-peer skill sharing enables automatic discovery, sync, and versioning of skills across trusted Total Recall nodes.
|
|
1329
|
-
</p>
|
|
1330
|
-
|
|
1331
|
-
{/* Feature diagram */}
|
|
1332
|
-
<div style={{
|
|
1333
|
-
display: 'flex',
|
|
1334
|
-
justifyContent: 'center',
|
|
1335
|
-
alignItems: 'center',
|
|
1336
|
-
gap: 12,
|
|
1337
|
-
marginBottom: 24,
|
|
1338
|
-
flexWrap: 'wrap',
|
|
1339
|
-
}}>
|
|
1340
|
-
<div style={{
|
|
1341
|
-
padding: '10px 16px',
|
|
1342
|
-
background: 'var(--bg-tertiary)',
|
|
1343
|
-
borderRadius: 8,
|
|
1344
|
-
border: '1px solid var(--accent)',
|
|
1345
|
-
fontSize: 12,
|
|
1346
|
-
fontWeight: 600,
|
|
1347
|
-
color: 'var(--accent)',
|
|
1348
|
-
}}>
|
|
1349
|
-
📂 Local Skills
|
|
1350
|
-
</div>
|
|
1351
|
-
<span style={{ color: 'var(--accent)', fontSize: 18, fontWeight: 700 }}>↔</span>
|
|
1352
|
-
<div style={{
|
|
1353
|
-
padding: '10px 16px',
|
|
1354
|
-
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.15), rgba(139, 92, 246, 0.15))',
|
|
1355
|
-
borderRadius: 8,
|
|
1356
|
-
border: '1px solid var(--accent)',
|
|
1357
|
-
fontSize: 12,
|
|
1358
|
-
fontWeight: 600,
|
|
1359
|
-
color: 'var(--accent)',
|
|
1360
|
-
}}>
|
|
1361
|
-
🌐 P2P Network
|
|
1362
|
-
</div>
|
|
1363
|
-
<span style={{ color: 'var(--accent)', fontSize: 18, fontWeight: 700 }}>↔</span>
|
|
1364
|
-
<div style={{
|
|
1365
|
-
padding: '10px 16px',
|
|
1366
|
-
background: 'var(--bg-tertiary)',
|
|
1367
|
-
borderRadius: 8,
|
|
1368
|
-
border: '1px solid var(--accent)',
|
|
1369
|
-
fontSize: 12,
|
|
1370
|
-
fontWeight: 600,
|
|
1371
|
-
color: 'var(--accent)',
|
|
1372
|
-
}}>
|
|
1373
|
-
👥 Remote Peers
|
|
1374
|
-
</div>
|
|
1375
|
-
</div>
|
|
1376
|
-
|
|
1377
|
-
{/* Features list */}
|
|
1378
|
-
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, textAlign: 'left' }}>
|
|
1379
|
-
{[
|
|
1380
|
-
{ icon: '🔍', label: 'Auto-discovery' },
|
|
1381
|
-
{ icon: '🔄', label: 'Version negotiation' },
|
|
1382
|
-
{ icon: '⚔️', label: 'Conflict resolution' },
|
|
1383
|
-
{ icon: '🛡️', label: 'Security quarantine' },
|
|
1384
|
-
].map(f => (
|
|
1385
|
-
<div key={f.label} style={{
|
|
1386
|
-
display: 'flex',
|
|
1387
|
-
alignItems: 'center',
|
|
1388
|
-
gap: 8,
|
|
1389
|
-
padding: '8px 12px',
|
|
1390
|
-
background: 'var(--bg-tertiary)',
|
|
1391
|
-
borderRadius: 6,
|
|
1392
|
-
fontSize: 11,
|
|
1393
|
-
color: 'var(--text-secondary)',
|
|
1394
|
-
fontWeight: 500,
|
|
1395
|
-
}}>
|
|
1396
|
-
<span>{f.icon}</span> {f.label}
|
|
1397
|
-
</div>
|
|
1398
|
-
))}
|
|
1399
|
-
</div>
|
|
1400
|
-
</div>
|
|
1401
|
-
</div>
|
|
1402
|
-
</div>
|
|
1403
|
-
</div>
|
|
1404
|
-
);
|
|
1405
|
-
|
|
1406
|
-
const renderAutomationTab = () => (
|
|
1407
|
-
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0, overflowY: 'auto', paddingBottom: 16, gap: 20 }}>
|
|
1408
|
-
{/* Auto-Update Status */}
|
|
1409
|
-
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
1410
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, borderBottom: '1px solid var(--border)', paddingBottom: 10, flexWrap: 'wrap', gap: 10 }}>
|
|
1411
|
-
<h3 id="automation-update-heading" style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, margin: 0 }}>
|
|
1412
|
-
🔄 Auto-Update Status
|
|
1413
|
-
</h3>
|
|
1414
|
-
<div style={{ display: 'flex', gap: 8 }}>
|
|
1415
|
-
<button
|
|
1416
|
-
id="automation-check-updates-btn"
|
|
1417
|
-
onClick={() => void handleCheckUpdates()}
|
|
1418
|
-
disabled={checkingUpdates}
|
|
1419
|
-
style={{
|
|
1420
|
-
background: checkingUpdates ? 'var(--bg-tertiary)' : 'var(--bg-elevated)',
|
|
1421
|
-
color: checkingUpdates ? 'var(--text-tertiary)' : 'var(--text-primary)',
|
|
1422
|
-
border: '1px solid var(--border)',
|
|
1423
|
-
borderRadius: 6,
|
|
1424
|
-
padding: '6px 14px',
|
|
1425
|
-
fontSize: 11,
|
|
1426
|
-
fontWeight: 600,
|
|
1427
|
-
cursor: checkingUpdates ? 'not-allowed' : 'pointer',
|
|
1428
|
-
}}
|
|
1429
|
-
>
|
|
1430
|
-
{checkingUpdates ? '⏳ Checking...' : '🔎 Check All Updates'}
|
|
1431
|
-
</button>
|
|
1432
|
-
<button
|
|
1433
|
-
id="automation-update-all-btn"
|
|
1434
|
-
disabled
|
|
1435
|
-
style={{
|
|
1436
|
-
background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
1437
|
-
color: '#fff',
|
|
1438
|
-
border: 'none',
|
|
1439
|
-
borderRadius: 6,
|
|
1440
|
-
padding: '6px 14px',
|
|
1441
|
-
fontSize: 11,
|
|
1442
|
-
fontWeight: 600,
|
|
1443
|
-
cursor: 'not-allowed',
|
|
1444
|
-
opacity: 0.5,
|
|
1445
|
-
}}
|
|
1446
|
-
>
|
|
1447
|
-
⬆️ Update All
|
|
1448
|
-
</button>
|
|
1449
|
-
</div>
|
|
1450
|
-
</div>
|
|
1451
|
-
|
|
1452
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1453
|
-
{skills.map(skill => (
|
|
1454
|
-
<div
|
|
1455
|
-
key={skill.name}
|
|
1456
|
-
id={`automation-update-${skill.name}`}
|
|
1457
|
-
style={{
|
|
1458
|
-
display: 'flex',
|
|
1459
|
-
justifyContent: 'space-between',
|
|
1460
|
-
alignItems: 'center',
|
|
1461
|
-
padding: '10px 14px',
|
|
1462
|
-
background: 'var(--bg-tertiary)',
|
|
1463
|
-
borderRadius: 8,
|
|
1464
|
-
border: '1px solid var(--border)',
|
|
1465
|
-
}}
|
|
1466
|
-
>
|
|
1467
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
1468
|
-
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{skill.name}</span>
|
|
1469
|
-
<span style={{ fontSize: 10, color: 'var(--text-tertiary)', fontFamily: 'var(--font-mono)' }}>
|
|
1470
|
-
v{extractVersion('')}
|
|
1471
|
-
</span>
|
|
1472
|
-
</div>
|
|
1473
|
-
<span style={{
|
|
1474
|
-
fontSize: 10,
|
|
1475
|
-
fontWeight: 600,
|
|
1476
|
-
padding: '3px 10px',
|
|
1477
|
-
borderRadius: 12,
|
|
1478
|
-
background: 'rgba(16, 185, 129, 0.1)',
|
|
1479
|
-
color: '#10b981',
|
|
1480
|
-
border: '1px solid rgba(16, 185, 129, 0.2)',
|
|
1481
|
-
}}>
|
|
1482
|
-
✓ Up to date
|
|
1483
|
-
</span>
|
|
1484
|
-
</div>
|
|
1485
|
-
))}
|
|
1486
|
-
</div>
|
|
1487
|
-
</div>
|
|
1488
|
-
|
|
1489
|
-
{/* Auto-Research Integration */}
|
|
1490
|
-
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
1491
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, borderBottom: '1px solid var(--border)', paddingBottom: 10, flexWrap: 'wrap', gap: 10 }}>
|
|
1492
|
-
<div>
|
|
1493
|
-
<h3 id="automation-research-heading" style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, margin: 0 }}>
|
|
1494
|
-
🧠 Continuous Intelligence Loop
|
|
1495
|
-
</h3>
|
|
1496
|
-
<p style={{ fontSize: 11, color: 'var(--text-tertiary)', marginTop: 4, margin: 0 }}>
|
|
1497
|
-
Skills automatically trigger background research to update their reference material and improve evals
|
|
1498
|
-
</p>
|
|
1499
|
-
</div>
|
|
1500
|
-
<button
|
|
1501
|
-
id="automation-trigger-research-btn"
|
|
1502
|
-
onClick={() => void handleTriggerResearchCycle()}
|
|
1503
|
-
style={{
|
|
1504
|
-
background: 'linear-gradient(135deg, #8b5cf6, #7c3aed)',
|
|
1505
|
-
color: '#fff',
|
|
1506
|
-
border: 'none',
|
|
1507
|
-
borderRadius: 6,
|
|
1508
|
-
padding: '6px 14px',
|
|
1509
|
-
fontSize: 11,
|
|
1510
|
-
fontWeight: 600,
|
|
1511
|
-
cursor: 'pointer',
|
|
1512
|
-
flexShrink: 0,
|
|
1513
|
-
}}
|
|
1514
|
-
>
|
|
1515
|
-
🔬 Trigger Research Cycle
|
|
1516
|
-
</button>
|
|
1517
|
-
</div>
|
|
1518
|
-
|
|
1519
|
-
{researchLoading ? (
|
|
1520
|
-
<div style={{ textAlign: 'center', padding: 20, color: 'var(--text-tertiary)', fontSize: 12 }}>
|
|
1521
|
-
Loading research items...
|
|
1522
|
-
</div>
|
|
1523
|
-
) : researchItems.length > 0 ? (
|
|
1524
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1525
|
-
{researchItems.slice(0, 8).map(item => (
|
|
1526
|
-
<div
|
|
1527
|
-
key={item.id}
|
|
1528
|
-
id={`automation-research-${item.id}`}
|
|
1529
|
-
style={{
|
|
1530
|
-
display: 'flex',
|
|
1531
|
-
justifyContent: 'space-between',
|
|
1532
|
-
alignItems: 'center',
|
|
1533
|
-
padding: '10px 14px',
|
|
1534
|
-
background: 'var(--bg-tertiary)',
|
|
1535
|
-
borderRadius: 8,
|
|
1536
|
-
border: '1px solid var(--border)',
|
|
1537
|
-
}}
|
|
1538
|
-
>
|
|
1539
|
-
<div style={{ flex: 1 }}>
|
|
1540
|
-
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{item.topic}</div>
|
|
1541
|
-
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', marginTop: 2 }}>
|
|
1542
|
-
{item.priority} priority · {new Date(item.created_at).toLocaleDateString()}
|
|
1543
|
-
</div>
|
|
1544
|
-
</div>
|
|
1545
|
-
<span style={{
|
|
1546
|
-
fontSize: 10,
|
|
1547
|
-
fontWeight: 600,
|
|
1548
|
-
padding: '3px 10px',
|
|
1549
|
-
borderRadius: 12,
|
|
1550
|
-
background: item.status === 'done' ? 'rgba(16, 185, 129, 0.1)' :
|
|
1551
|
-
item.status === 'in_progress' ? 'rgba(59, 130, 246, 0.1)' :
|
|
1552
|
-
item.status === 'failed' ? 'rgba(239, 68, 68, 0.1)' : 'rgba(245, 158, 11, 0.1)',
|
|
1553
|
-
color: item.status === 'done' ? '#10b981' :
|
|
1554
|
-
item.status === 'in_progress' ? '#60a5fa' :
|
|
1555
|
-
item.status === 'failed' ? '#ef4444' : '#f59e0b',
|
|
1556
|
-
border: `1px solid ${item.status === 'done' ? 'rgba(16, 185, 129, 0.2)' :
|
|
1557
|
-
item.status === 'in_progress' ? 'rgba(59, 130, 246, 0.2)' :
|
|
1558
|
-
item.status === 'failed' ? 'rgba(239, 68, 68, 0.2)' : 'rgba(245, 158, 11, 0.2)'}`,
|
|
1559
|
-
}}>
|
|
1560
|
-
{item.status}
|
|
1561
|
-
</span>
|
|
1562
|
-
</div>
|
|
1563
|
-
))}
|
|
1564
|
-
</div>
|
|
1565
|
-
) : (
|
|
1566
|
-
<div style={{ padding: 20, textAlign: 'center', color: 'var(--text-tertiary)', fontSize: 12, border: '1px dashed var(--border)', borderRadius: 8 }}>
|
|
1567
|
-
No pending research items. Trigger a research cycle to auto-update skill references.
|
|
1568
|
-
</div>
|
|
1569
|
-
)}
|
|
1570
|
-
</div>
|
|
1571
|
-
|
|
1572
|
-
{/* Skill Improvement Queue */}
|
|
1573
|
-
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)' }}>
|
|
1574
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, borderBottom: '1px solid var(--border)', paddingBottom: 8 }}>
|
|
1575
|
-
<h3 id="automation-improvements-heading" style={{ margin: 0, fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
|
1576
|
-
💡 AI Skill Improvement Queue
|
|
1577
|
-
</h3>
|
|
1578
|
-
<button
|
|
1579
|
-
onClick={handleRunAudit}
|
|
1580
|
-
disabled={auditing}
|
|
1581
|
-
style={{
|
|
1582
|
-
background: auditing ? 'var(--bg-secondary)' : 'linear-gradient(135deg, #10b981, #059669)',
|
|
1583
|
-
color: '#fff',
|
|
1584
|
-
border: 'none',
|
|
1585
|
-
borderRadius: 6,
|
|
1586
|
-
padding: '6px 14px',
|
|
1587
|
-
fontSize: 11,
|
|
1588
|
-
fontWeight: 600,
|
|
1589
|
-
cursor: auditing ? 'not-allowed' : 'pointer',
|
|
1590
|
-
flexShrink: 0,
|
|
1591
|
-
}}
|
|
1592
|
-
>
|
|
1593
|
-
{auditing ? '⏳ Auditing Skills...' : '🤖 Run AI Audit'}
|
|
1594
|
-
</button>
|
|
1595
|
-
</div>
|
|
1596
|
-
|
|
1597
|
-
{aiSuggestions.length > 0 ? (
|
|
1598
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
1599
|
-
{aiSuggestions.map((suggestion, idx) => (
|
|
1600
|
-
<div
|
|
1601
|
-
key={idx}
|
|
1602
|
-
id={`automation-suggestion-${idx}`}
|
|
1603
|
-
style={{
|
|
1604
|
-
display: 'flex',
|
|
1605
|
-
justifyContent: 'space-between',
|
|
1606
|
-
alignItems: 'center',
|
|
1607
|
-
padding: '10px 14px',
|
|
1608
|
-
background: 'var(--bg-tertiary)',
|
|
1609
|
-
borderRadius: 8,
|
|
1610
|
-
border: '1px solid var(--border)',
|
|
1611
|
-
gap: 12,
|
|
1612
|
-
}}
|
|
1613
|
-
>
|
|
1614
|
-
<div style={{ flex: 1, minWidth: 0 }}>
|
|
1615
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 2, flexWrap: 'wrap' }}>
|
|
1616
|
-
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{suggestion.skillName}</span>
|
|
1617
|
-
<span style={{
|
|
1618
|
-
fontSize: 9,
|
|
1619
|
-
fontWeight: 700,
|
|
1620
|
-
padding: '2px 8px',
|
|
1621
|
-
borderRadius: 10,
|
|
1622
|
-
textTransform: 'uppercase',
|
|
1623
|
-
letterSpacing: 0.3,
|
|
1624
|
-
background: suggestion.problematic ? 'rgba(239, 68, 68, 0.1)' : 'rgba(245, 158, 11, 0.1)',
|
|
1625
|
-
color: suggestion.problematic ? '#f87171' : '#f59e0b',
|
|
1626
|
-
border: `1px solid ${suggestion.problematic ? 'rgba(239, 68, 68, 0.2)' : 'rgba(245, 158, 11, 0.2)'}`,
|
|
1627
|
-
}}>
|
|
1628
|
-
{suggestion.problematic ? 'Problematic' : 'Outdated'}
|
|
1629
|
-
</span>
|
|
1630
|
-
</div>
|
|
1631
|
-
<div style={{ fontSize: 11, color: 'var(--text-secondary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
1632
|
-
{suggestion.description}
|
|
1633
|
-
</div>
|
|
1634
|
-
</div>
|
|
1635
|
-
<button
|
|
1636
|
-
id={`automation-fix-${idx}`}
|
|
1637
|
-
style={{
|
|
1638
|
-
background: 'var(--bg-elevated)',
|
|
1639
|
-
color: 'var(--text-primary)',
|
|
1640
|
-
border: '1px solid var(--border)',
|
|
1641
|
-
borderRadius: 6,
|
|
1642
|
-
padding: '5px 12px',
|
|
1643
|
-
fontSize: 10,
|
|
1644
|
-
fontWeight: 600,
|
|
1645
|
-
cursor: 'pointer',
|
|
1646
|
-
flexShrink: 0,
|
|
1647
|
-
transition: 'all 0.15s ease',
|
|
1648
|
-
}}
|
|
1649
|
-
onMouseEnter={e => { e.currentTarget.style.background = 'var(--accent)'; e.currentTarget.style.color = '#fff'; }}
|
|
1650
|
-
onMouseLeave={e => { e.currentTarget.style.background = 'var(--bg-elevated)'; e.currentTarget.style.color = 'var(--text-primary)'; }}
|
|
1651
|
-
>
|
|
1652
|
-
🔧 Fix
|
|
1653
|
-
</button>
|
|
1654
|
-
</div>
|
|
1655
|
-
))}
|
|
1656
|
-
</div>
|
|
1657
|
-
) : (
|
|
1658
|
-
<div style={{ padding: 20, textAlign: 'center', color: 'var(--text-tertiary)', fontSize: 12, border: '1px dashed var(--border)', borderRadius: 8 }}>
|
|
1659
|
-
No improvement suggestions. All skills are well-configured. ✨
|
|
1660
|
-
</div>
|
|
1661
|
-
)}
|
|
1662
|
-
</div>
|
|
1663
|
-
</div>
|
|
1664
|
-
);
|
|
1665
|
-
|
|
1666
|
-
return (
|
|
1667
|
-
<div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
1668
|
-
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
|
|
1669
|
-
<div>
|
|
1670
|
-
<h1>Skills Manager</h1>
|
|
1671
|
-
<p>Track, edit, and deploy agent skills across any repo — registry-based, no hardcoded hosts</p>
|
|
1672
|
-
</div>
|
|
1673
|
-
|
|
1674
|
-
<div style={{ display: 'flex', gap: 10 }}>
|
|
1675
|
-
<button
|
|
1676
|
-
className="btn btn-sm btn-ghost"
|
|
1677
|
-
onClick={() => setShowCreateForm(!showCreateForm)}
|
|
1678
|
-
style={{ border: '1px solid var(--border)', background: 'var(--bg-secondary)' }}
|
|
1679
|
-
>
|
|
1680
|
-
{showCreateForm ? '✖ Cancel' : '➕ Create Custom Skill'}
|
|
1681
|
-
</button>
|
|
1682
|
-
|
|
1683
|
-
<button
|
|
1684
|
-
className="btn btn-sm"
|
|
1685
|
-
onClick={handleRecompile}
|
|
1686
|
-
disabled={compiling}
|
|
1687
|
-
style={{
|
|
1688
|
-
background: compiling ? 'var(--bg-secondary)' : 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
1689
|
-
color: '#fff',
|
|
1690
|
-
border: 'none',
|
|
1691
|
-
fontWeight: 500
|
|
1692
|
-
}}
|
|
1693
|
-
>
|
|
1694
|
-
{compiling ? '⏳ Compiling shims...' : '🔄 Hot-Recompile Shims'}
|
|
1695
|
-
</button>
|
|
1696
|
-
</div>
|
|
1697
|
-
</div>
|
|
1698
|
-
|
|
1699
|
-
{error && (
|
|
1700
|
-
<div className="badge badge-error" style={{ marginBottom: 20, display: 'inline-flex', padding: '6px 12px', background: 'rgba(239, 68, 68, 0.1)', border: '1px solid #ef4444', color: '#f87171' }}>
|
|
1701
|
-
⚠️ {error}
|
|
1702
|
-
</div>
|
|
1703
|
-
)}
|
|
1704
|
-
{success && (
|
|
1705
|
-
<div className="badge badge-success" style={{ marginBottom: 20, display: 'inline-flex', padding: '6px 12px', background: 'rgba(16, 185, 129, 0.1)', border: '1px solid #10b981', color: '#34d399' }}>
|
|
1706
|
-
✓ {success}
|
|
1707
|
-
</div>
|
|
1708
|
-
)}
|
|
1709
|
-
{quarantineAlert && (
|
|
1710
|
-
<div className="badge badge-error" style={{ marginBottom: 20, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', padding: 16, background: 'rgba(239, 68, 68, 0.15)', border: '1px solid #ef4444', borderRadius: 8, color: '#f87171', gap: 6 }}>
|
|
1711
|
-
<div style={{ fontWeight: 700, fontSize: 13 }}>🚨 SECURITY QUARANTINE PURGE ACTIVE!</div>
|
|
1712
|
-
<div style={{ fontSize: 12 }}>Installation of package <strong>{quarantineAlert.pkg}</strong> was blocked and files were hard purged.</div>
|
|
1713
|
-
<div style={{ fontSize: 11, opacity: 0.85 }}>Reason: {quarantineAlert.reason}</div>
|
|
1714
|
-
</div>
|
|
1715
|
-
)}
|
|
1716
|
-
|
|
1717
|
-
{/* Creation Modal Form Overlay */}
|
|
1718
|
-
{showCreateForm && (
|
|
1719
|
-
<div className="card" style={{ marginBottom: 24, padding: 24, background: 'rgba(18, 18, 26, 0.9)', border: '1px solid var(--border)', backdropFilter: 'blur(10px)' }}>
|
|
1720
|
-
<h3 style={{ fontSize: 14, fontWeight: 600, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 14 }}>➕ Create New Skill</h3>
|
|
1721
|
-
<form onSubmit={handleCreateSkill} style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
|
1722
|
-
<div style={{ flex: '1 1 200px', display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
1723
|
-
<label htmlFor="new_name" style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Skill Name (kebab-case)</label>
|
|
1724
|
-
<input
|
|
1725
|
-
id="new_name"
|
|
1726
|
-
type="text"
|
|
1727
|
-
placeholder="e.g. security-audit"
|
|
1728
|
-
value={newSkillName}
|
|
1729
|
-
onChange={(e) => setNewSkillName(e.target.value)}
|
|
1730
|
-
required
|
|
1731
|
-
style={{ background: 'var(--bg-tertiary)', color: 'var(--text-primary)', border: '1px solid var(--border)', padding: '8px 12px', borderRadius: 6, outline: 'none', fontSize: 12 }}
|
|
1732
|
-
/>
|
|
1733
|
-
</div>
|
|
1734
|
-
<div style={{ flex: '2 1 300px', display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
1735
|
-
<label htmlFor="new_desc" style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Brief Description / System Triggers</label>
|
|
1736
|
-
<input
|
|
1737
|
-
id="new_desc"
|
|
1738
|
-
type="text"
|
|
1739
|
-
placeholder="e.g. Audits security parameters, encryption costs, and Caddy forward gates"
|
|
1740
|
-
value={newSkillDesc}
|
|
1741
|
-
onChange={(e) => setNewSkillDesc(e.target.value)}
|
|
1742
|
-
style={{ background: 'var(--bg-tertiary)', color: 'var(--text-primary)', border: '1px solid var(--border)', padding: '8px 12px', borderRadius: 6, outline: 'none', fontSize: 12 }}
|
|
1743
|
-
/>
|
|
1744
|
-
</div>
|
|
1745
|
-
<button
|
|
1746
|
-
type="submit"
|
|
1747
|
-
style={{ background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))', color: '#fff', padding: '8px 16px', borderRadius: 6, fontWeight: 500, border: 'none', fontSize: 12, cursor: 'pointer', height: 33 }}
|
|
1748
|
-
>
|
|
1749
|
-
🚀 Initialize Skill
|
|
1750
|
-
</button>
|
|
1751
|
-
</form>
|
|
1752
|
-
</div>
|
|
1753
|
-
)}
|
|
1754
|
-
|
|
1755
|
-
{/* Tab Switcher */}
|
|
1756
|
-
<div style={{ display: 'flex', gap: 6, marginBottom: 20, borderBottom: '1px solid var(--border)', paddingBottom: 10, flexWrap: 'wrap' }}>
|
|
1757
|
-
{tabs.map(tab => (
|
|
1758
|
-
<button
|
|
1759
|
-
key={tab.id}
|
|
1760
|
-
id={`tab-${tab.id}`}
|
|
1761
|
-
onClick={() => setActiveTab(tab.id)}
|
|
1762
|
-
style={{
|
|
1763
|
-
padding: '8px 14px',
|
|
1764
|
-
borderRadius: 8,
|
|
1765
|
-
border: 'none',
|
|
1766
|
-
background: activeTab === tab.id ? 'var(--accent)' : 'var(--bg-tertiary)',
|
|
1767
|
-
color: activeTab === tab.id ? '#fff' : 'var(--text-secondary)',
|
|
1768
|
-
fontWeight: 600,
|
|
1769
|
-
cursor: 'pointer',
|
|
1770
|
-
fontSize: 11,
|
|
1771
|
-
transition: 'all 0.15s ease',
|
|
1772
|
-
whiteSpace: 'nowrap',
|
|
1773
|
-
}}
|
|
1774
|
-
>
|
|
1775
|
-
{tab.icon} {tab.label}
|
|
1776
|
-
</button>
|
|
1777
|
-
))}
|
|
1778
|
-
</div>
|
|
1779
|
-
|
|
1780
|
-
{activeTab === 'active' ? (
|
|
1781
|
-
/* Original split layout: Active skills editor */
|
|
1782
|
-
<div style={{ display: 'flex', flex: 1, gap: 24, minHeight: 0, overflow: 'hidden', paddingBottom: 16 }}>
|
|
1783
|
-
|
|
1784
|
-
{/* Left column: Skills by repo */}
|
|
1785
|
-
<div style={{ width: 300, display: 'flex', flexDirection: 'column', gap: 16, flexShrink: 0, overflowY: 'auto' }}>
|
|
1786
|
-
<div className="card" style={{ padding: 16, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
1787
|
-
<h3 style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 8, marginBottom: 8 }}>
|
|
1788
|
-
Skills by repo
|
|
1789
|
-
</h3>
|
|
1790
|
-
<p style={{ fontSize: 10, color: 'var(--text-tertiary)', margin: '0 0 10px', lineHeight: 1.4 }}>
|
|
1791
|
-
Project skills only · total-recall / tr-* hidden · {skills.length} skill{skills.length === 1 ? '' : 's'}
|
|
1792
|
-
</p>
|
|
1793
|
-
{renderRepoSkillList('active')}
|
|
1794
|
-
</div>
|
|
1795
|
-
</div>
|
|
1796
|
-
|
|
1797
|
-
{/* Right column: Editor and Details */}
|
|
1798
|
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
|
1799
|
-
{selectedSkill ? (
|
|
1800
|
-
<div className="card" style={{ flex: 1, padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
|
1801
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid var(--border)', paddingBottom: 12, marginBottom: 16, flexShrink: 0 }}>
|
|
1802
|
-
<div>
|
|
1803
|
-
<h3 style={{ fontSize: 16, fontWeight: 600, color: 'var(--text-primary)' }}>📝 Editing Capability rules: {selectedSkill.name}/SKILL.md</h3>
|
|
1804
|
-
<p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Hot-recompiled rules preserve immutable instructions and custom shims</p>
|
|
1805
|
-
</div>
|
|
1806
|
-
<button
|
|
1807
|
-
className="btn btn-primary"
|
|
1808
|
-
onClick={handleSaveSkill}
|
|
1809
|
-
disabled={saving}
|
|
1810
|
-
style={{
|
|
1811
|
-
background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
1812
|
-
color: '#fff',
|
|
1813
|
-
fontWeight: 500,
|
|
1814
|
-
borderRadius: 6,
|
|
1815
|
-
padding: '8px 18px',
|
|
1816
|
-
fontSize: 12
|
|
1817
|
-
}}
|
|
1818
|
-
>
|
|
1819
|
-
{saving ? '⏳ Saving...' : '💾 Save rules'}
|
|
1820
|
-
</button>
|
|
1821
|
-
</div>
|
|
1822
|
-
|
|
1823
|
-
{/* Text Area Code Editor */}
|
|
1824
|
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
|
1825
|
-
<textarea
|
|
1826
|
-
value={skillContent}
|
|
1827
|
-
onChange={(e) => setSkillContent(e.target.value)}
|
|
1828
|
-
style={{
|
|
1829
|
-
flex: 1,
|
|
1830
|
-
width: '100%',
|
|
1831
|
-
fontFamily: 'var(--font-mono)',
|
|
1832
|
-
fontSize: 13,
|
|
1833
|
-
padding: 16,
|
|
1834
|
-
background: 'var(--bg-secondary)',
|
|
1835
|
-
color: 'var(--text-primary)',
|
|
1836
|
-
border: '1px solid var(--border)',
|
|
1837
|
-
borderRadius: 8,
|
|
1838
|
-
resize: 'none',
|
|
1839
|
-
outline: 'none',
|
|
1840
|
-
lineHeight: 1.6,
|
|
1841
|
-
whiteSpace: 'pre',
|
|
1842
|
-
overflowWrap: 'normal',
|
|
1843
|
-
overflowX: 'auto'
|
|
1844
|
-
}}
|
|
1845
|
-
spellCheck={false}
|
|
1846
|
-
/>
|
|
1847
|
-
</div>
|
|
1848
|
-
</div>
|
|
1849
|
-
) : (
|
|
1850
|
-
<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)' }}>
|
|
1851
|
-
No skill selected. Click a skill in the registry to manage its rules sheet.
|
|
1852
|
-
</div>
|
|
1853
|
-
)}
|
|
1854
|
-
</div>
|
|
1855
|
-
|
|
1856
|
-
</div>
|
|
1857
|
-
) : activeTab === 'registry' ? (
|
|
1858
|
-
/* Tab 2: skills.sh Registry Explorer Panel */
|
|
1859
|
-
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0, overflowY: 'auto', paddingBottom: 16 }}>
|
|
1860
|
-
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', marginBottom: 20 }}>
|
|
1861
|
-
<h3 style={{ fontSize: 15, fontWeight: 600, color: 'var(--text-primary)', marginBottom: 8 }}>🔍 Discover AI Agent Skills from skills.sh</h3>
|
|
1862
|
-
<p style={{ fontSize: 12, color: 'var(--text-secondary)', marginBottom: 16 }}>
|
|
1863
|
-
Query the absolute ratings list from the open-source registry. When you trigger installation, files are sandboxed, statically audited for command injection vulnerabilities, and automatically compiled.
|
|
1864
|
-
</p>
|
|
1865
|
-
|
|
1866
|
-
<form onSubmit={handleRegistrySearch} style={{ display: 'flex', gap: 12 }}>
|
|
1867
|
-
<input
|
|
1868
|
-
type="text"
|
|
1869
|
-
placeholder="Search skills (e.g. git, typescript, prd...)"
|
|
1870
|
-
value={searchQuery}
|
|
1871
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
1872
|
-
style={{
|
|
1873
|
-
flex: 1,
|
|
1874
|
-
background: 'var(--bg-tertiary)',
|
|
1875
|
-
color: 'var(--text-primary)',
|
|
1876
|
-
border: '1px solid var(--border)',
|
|
1877
|
-
borderRadius: 8,
|
|
1878
|
-
padding: '10px 16px',
|
|
1879
|
-
fontSize: 13,
|
|
1880
|
-
outline: 'none'
|
|
1881
|
-
}}
|
|
1882
|
-
/>
|
|
1883
|
-
<button
|
|
1884
|
-
type="submit"
|
|
1885
|
-
disabled={registryLoading}
|
|
1886
|
-
style={{
|
|
1887
|
-
background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
1888
|
-
color: '#fff',
|
|
1889
|
-
border: 'none',
|
|
1890
|
-
borderRadius: 8,
|
|
1891
|
-
padding: '10px 24px',
|
|
1892
|
-
fontWeight: 600,
|
|
1893
|
-
fontSize: 13,
|
|
1894
|
-
cursor: 'pointer'
|
|
1895
|
-
}}
|
|
1896
|
-
>
|
|
1897
|
-
{registryLoading ? '⏳ Sifting...' : '🔎 Query Registry'}
|
|
1898
|
-
</button>
|
|
1899
|
-
</form>
|
|
1900
|
-
</div>
|
|
1901
|
-
|
|
1902
|
-
{/* Registry Results List */}
|
|
1903
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
1904
|
-
{registryLoading ? (
|
|
1905
|
-
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 40, color: 'var(--text-secondary)' }}>
|
|
1906
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--text-tertiary)" strokeWidth="2.5" style={{ animation: 'spin 1s linear infinite', marginBottom: 12 }}>
|
|
1907
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
|
1908
|
-
</svg>
|
|
1909
|
-
<span>Searching packages in registry catalog...</span>
|
|
1910
|
-
</div>
|
|
1911
|
-
) : registryResults.length > 0 ? (
|
|
1912
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: 16 }}>
|
|
1913
|
-
{registryResults.map((item) => {
|
|
1914
|
-
const isInstalling = installingPkg === item.name;
|
|
1915
|
-
return (
|
|
1916
|
-
<div
|
|
1917
|
-
key={item.name}
|
|
1918
|
-
style={{
|
|
1919
|
-
background: 'rgba(18, 18, 26, 0.4)',
|
|
1920
|
-
border: '1px solid var(--border)',
|
|
1921
|
-
borderRadius: 10,
|
|
1922
|
-
padding: 20,
|
|
1923
|
-
display: 'flex',
|
|
1924
|
-
flexDirection: 'column',
|
|
1925
|
-
justifyContent: 'space-between',
|
|
1926
|
-
gap: 16
|
|
1927
|
-
}}
|
|
1928
|
-
>
|
|
1929
|
-
<div>
|
|
1930
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: 8, marginBottom: 8 }}>
|
|
1931
|
-
<span style={{ fontWeight: 700, fontSize: 14, color: '#fff', wordBreak: 'break-all' }}>
|
|
1932
|
-
{item.name}
|
|
1933
|
-
</span>
|
|
1934
|
-
<span className="badge" style={{ background: 'rgba(59, 130, 246, 0.15)', color: '#60a5fa', border: '1px solid rgba(59, 130, 246, 0.2)', fontSize: 10, padding: '2px 8px', borderRadius: 20 }}>
|
|
1935
|
-
⚡ {item.installsStr} installs
|
|
1936
|
-
</span>
|
|
1937
|
-
</div>
|
|
1938
|
-
<a
|
|
1939
|
-
href={item.url}
|
|
1940
|
-
target="_blank"
|
|
1941
|
-
rel="noopener noreferrer"
|
|
1942
|
-
style={{ fontSize: 11, color: 'var(--accent)', textDecoration: 'none' }}
|
|
1943
|
-
onMouseEnter={(e) => (e.currentTarget.style.textDecoration = 'underline')}
|
|
1944
|
-
onMouseLeave={(e) => (e.currentTarget.style.textDecoration = 'none')}
|
|
1945
|
-
>
|
|
1946
|
-
🔗 View Registry Repository
|
|
1947
|
-
</a>
|
|
1948
|
-
</div>
|
|
1949
|
-
|
|
1950
|
-
<button
|
|
1951
|
-
onClick={() => void handleRegistryInstall(item.name)}
|
|
1952
|
-
disabled={!!installingPkg}
|
|
1953
|
-
style={{
|
|
1954
|
-
width: '100%',
|
|
1955
|
-
background: isInstalling
|
|
1956
|
-
? 'var(--bg-tertiary)'
|
|
1957
|
-
: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
1958
|
-
color: isInstalling ? 'var(--text-secondary)' : '#fff',
|
|
1959
|
-
border: 'none',
|
|
1960
|
-
borderRadius: 8,
|
|
1961
|
-
padding: '10px 16px',
|
|
1962
|
-
fontWeight: 600,
|
|
1963
|
-
fontSize: 12,
|
|
1964
|
-
cursor: installingPkg ? 'not-allowed' : 'pointer',
|
|
1965
|
-
transition: 'all 0.15s ease'
|
|
1966
|
-
}}
|
|
1967
|
-
>
|
|
1968
|
-
{isInstalling ? '🛡️ Auditing & Installing...' : '➕ Install Capability'}
|
|
1969
|
-
</button>
|
|
1970
|
-
</div>
|
|
1971
|
-
);
|
|
1972
|
-
})}
|
|
1973
|
-
</div>
|
|
1974
|
-
) : searchQuery.trim() ? (
|
|
1975
|
-
<div style={{ textAlign: 'center', padding: 40, border: '1px dashed var(--border)', borderRadius: 10, color: 'var(--text-secondary)' }}>
|
|
1976
|
-
No matching skills found in the skills.sh registry. Try a different query like "git" or "prd".
|
|
1977
|
-
</div>
|
|
1978
|
-
) : (
|
|
1979
|
-
<div style={{ textAlign: 'center', padding: 40, border: '1px dashed var(--border)', borderRadius: 10, color: 'var(--text-tertiary)', fontSize: 12 }}>
|
|
1980
|
-
Enter a search query above to browse portable capabilities from the cloud registry.
|
|
1981
|
-
</div>
|
|
1982
|
-
)}
|
|
1983
|
-
</div>
|
|
1984
|
-
</div>
|
|
1985
|
-
) : activeTab === 'lifecycle' ? (
|
|
1986
|
-
renderLifecycleTab()
|
|
1987
|
-
) : activeTab === 'network' ? (
|
|
1988
|
-
renderNetworkTab()
|
|
1989
|
-
) : (
|
|
1990
|
-
renderAutomationTab()
|
|
1991
|
-
)}
|
|
1992
|
-
</div>
|
|
1993
|
-
);
|
|
1994
|
-
}
|