total-recall-brain 3.41.4 → 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.
Files changed (147) hide show
  1. package/bin/total-recall.mjs +10 -6
  2. package/docs/reference/cli-reference.md +18 -0
  3. package/frontend/dist/assets/index-4tGQfTa2.js +5605 -0
  4. package/frontend/dist/assets/index-DtwsDyFM.css +1 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/frontend/package.json +11 -0
  7. package/frontend/src/App.tsx +47 -122
  8. package/frontend/src/components/plugins/plugin-frame.ts +1 -0
  9. package/frontend/src/index.css +152 -86
  10. package/frontend/src/pages/HelpPage.tsx +13 -10
  11. package/frontend/src/pages/MemoryPage.tsx +28 -30
  12. package/frontend/src/pages/NetworkPage.tsx +3 -3
  13. package/frontend/src/pages/PluginsPage.tsx +6 -5
  14. package/frontend/src/pages/RulesPage.tsx +16 -12
  15. package/package.json +4 -3
  16. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +3114 -36
  17. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +4 -1
  18. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +3114 -36
  19. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +4 -1
  20. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +3121 -33
  21. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +4 -1
  22. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +3122 -34
  23. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +4 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +3127 -49
  25. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -2
  26. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +1 -1
  27. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +136 -7
  28. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +135 -26
  29. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +3121 -33
  30. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +5 -2
  31. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +228 -9
  32. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +24 -30
  33. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +29 -16
  34. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +73 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +182 -365
  36. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +40 -14
  38. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +4 -4
  39. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/agy.mjs +187 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/claude.mjs +275 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cline.mjs +132 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/codex.mjs +187 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cursor.mjs +158 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/devin.mjs +157 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/dsh.mjs +183 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/grok.mjs +132 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +173 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +87 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/openclaw.mjs +173 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/pi.mjs +129 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +12 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/json-reply.mjs +22 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +72 -70
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-catalog.mjs +195 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +11 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +36 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +169 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +136 -346
  59. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +1 -1
  60. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/ssss-store.mjs +20 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/telemetry.mjs +43 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +156 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +117 -35
  64. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +51 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +398 -57
  66. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +304 -46
  67. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +367 -41
  68. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +5 -2
  69. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +33 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +2 -2
  71. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +159 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +197 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +153 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +261 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +151 -4
  76. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +114 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +96 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +899 -662
  79. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +8987 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/mermaid-diagrams/SKILL.md +44 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/write-connector/SKILL.md +56 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3097 -81
  83. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +4 -1
  84. package/src/cli/agent.mjs +12 -1
  85. package/src/cli/compact.mjs +108 -0
  86. package/src/cli/connect.mjs +56 -1
  87. package/src/cli/context.mjs +55 -23
  88. package/src/cli/doctor.mjs +5 -2
  89. package/src/cli/forget.mjs +2 -0
  90. package/src/cli/harness.mjs +62 -50
  91. package/src/cli/memory-entry.mjs +2 -2
  92. package/src/cli/preflight.mjs +152 -16
  93. package/src/cli/release.mjs +386 -0
  94. package/src/cli/remember.mjs +19 -1
  95. package/src/cli/rules.mjs +54 -12
  96. package/src/cli/startup.mjs +1 -1
  97. package/src/cli/status.mjs +12 -2
  98. package/src/core/claude-hooks.mjs +41 -3
  99. package/src/core/cli-commands.mjs +2 -0
  100. package/src/core/cli-semantic-index.mjs +3 -2
  101. package/src/core/conflict-detector.mjs +23 -12
  102. package/src/core/context-compactor.mjs +145 -0
  103. package/src/core/context-policy.mjs +20 -0
  104. package/src/core/duplicate-guard.mjs +350 -0
  105. package/src/core/meta-harness.mjs +31 -11
  106. package/src/core/preflight-gate.mjs +2 -2
  107. package/src/core/preflight-receipt.mjs +29 -1
  108. package/src/core/scaffold-plugins.mjs +7 -0
  109. package/src/core/stdio-flush.mjs +23 -0
  110. package/src/core/surface.mjs +33 -10
  111. package/src/server/invoke-durability.mjs +39 -0
  112. package/src/server/invoke-write-child.mjs +25 -0
  113. package/src/server/routes/invoke.mjs +7 -0
  114. package/src/server/routes/plugins.mjs +2 -2
  115. package/frontend/dist/assets/index-BIQmqDKP.css +0 -1
  116. package/frontend/dist/assets/index-CRzjQLu7.js +0 -5631
  117. package/frontend/src/components/DaemonLogsTab.tsx +0 -54
  118. package/frontend/src/components/ResearchAgendaTab.tsx +0 -712
  119. package/frontend/src/components/TaskQueueTab.tsx +0 -193
  120. package/frontend/src/components/VoiceInput.tsx +0 -112
  121. package/frontend/src/pages/ChatPage.tsx +0 -1059
  122. package/frontend/src/pages/CollabPage.tsx +0 -470
  123. package/frontend/src/pages/DesignDocsPage.tsx +0 -621
  124. package/frontend/src/pages/FilesPage.tsx +0 -393
  125. package/frontend/src/pages/HealthPage.tsx +0 -359
  126. package/frontend/src/pages/InboxPage.tsx +0 -83
  127. package/frontend/src/pages/InstructionsPage.tsx +0 -583
  128. package/frontend/src/pages/IntegrationsPage.tsx +0 -761
  129. package/frontend/src/pages/MeshPage.css +0 -58
  130. package/frontend/src/pages/MeshPage.tsx +0 -1219
  131. package/frontend/src/pages/OkfPage.tsx +0 -535
  132. package/frontend/src/pages/SandboxPage.tsx +0 -113
  133. package/frontend/src/pages/SkillsPage.tsx +0 -1994
  134. package/frontend/src/pages/TasksPage.tsx +0 -427
  135. package/frontend/src/pages/UsagePage.tsx +0 -702
  136. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +0 -65
  137. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +0 -76
  138. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +0 -19
  139. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -266
  140. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +0 -446
  141. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +0 -152
  142. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +0 -71
  143. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +0 -36
  144. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +0 -35
  145. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +0 -175
  146. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +0 -32
  147. 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
- }