total-recall-brain 3.1.2 → 3.2.2
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/extension/background.js +99 -0
- package/extension/content-script.css +16 -0
- package/extension/content-script.js +134 -0
- package/extension/icons/README.md +27 -0
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-16.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/lib/brain-client.js +47 -0
- package/extension/manifest.json +39 -0
- package/extension/options/options.css +218 -0
- package/extension/options/options.html +72 -0
- package/extension/options/options.js +99 -0
- package/extension/popup/popup.css +221 -0
- package/extension/popup/popup.html +44 -0
- package/extension/popup/popup.js +107 -0
- package/extension/sidepanel/sidepanel.css +340 -0
- package/extension/sidepanel/sidepanel.html +38 -0
- package/extension/sidepanel/sidepanel.js +229 -0
- package/frontend/README.md +73 -0
- package/frontend/index.html +17 -0
- package/frontend/package.json +31 -0
- package/frontend/src/App.css +2 -0
- package/frontend/src/App.tsx +390 -0
- package/frontend/src/api.ts +688 -0
- package/frontend/src/assets/hero.png +0 -0
- package/frontend/src/assets/react.svg +1 -0
- package/frontend/src/assets/vite.svg +1 -0
- package/frontend/src/components/BrainSelector.tsx +272 -0
- package/frontend/src/components/DaemonLogsTab.tsx +54 -0
- package/frontend/src/components/Graph3D.tsx +850 -0
- package/frontend/src/components/MarkdownUtils.tsx +99 -0
- package/frontend/src/components/ResearchAgendaTab.tsx +707 -0
- package/frontend/src/components/TaskQueueTab.tsx +185 -0
- package/frontend/src/index.css +1589 -0
- package/frontend/src/main.tsx +10 -0
- package/frontend/src/pages/ApiKeysPage.tsx +288 -0
- package/frontend/src/pages/AutomationsPage.tsx +354 -0
- package/frontend/src/pages/ChatPage.tsx +831 -0
- package/frontend/src/pages/DeploymentsPage.tsx +275 -0
- package/frontend/src/pages/FilesPage.tsx +384 -0
- package/frontend/src/pages/HealthPage.tsx +91 -0
- package/frontend/src/pages/IntegrationsPage.tsx +382 -0
- package/frontend/src/pages/LoginPage.tsx +237 -0
- package/frontend/src/pages/MemoryPage.tsx +1192 -0
- package/frontend/src/pages/SandboxPage.tsx +113 -0
- package/frontend/src/pages/SettingsPage.tsx +662 -0
- package/frontend/src/pages/SkillsPage.tsx +618 -0
- package/frontend/src/pages/TasksPage.tsx +392 -0
- package/frontend/src/pages/UsagePage.tsx +260 -0
- package/frontend/src/types.ts +163 -0
- package/frontend/tsconfig.app.json +25 -0
- package/frontend/tsconfig.json +7 -0
- package/frontend/tsconfig.node.json +24 -0
- package/frontend/vite.config.ts +24 -0
- package/package.json +8 -1
- package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +35 -8
- package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +13 -8
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +28 -1
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +28 -1
- package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +830 -81
- package/src/cli/backup.mjs +172 -1
- package/src/cli/setup.mjs +24 -0
- package/src/server/rest.mjs +56 -7
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
listSkills,
|
|
4
|
+
fetchSkill,
|
|
5
|
+
saveSkill,
|
|
6
|
+
deleteSkill,
|
|
7
|
+
triggerRecompile,
|
|
8
|
+
searchSkillsRegistry,
|
|
9
|
+
installRegistrySkill
|
|
10
|
+
} from '../api';
|
|
11
|
+
|
|
12
|
+
interface SkillItem {
|
|
13
|
+
name: string;
|
|
14
|
+
size: number;
|
|
15
|
+
modified: string;
|
|
16
|
+
isDirectory: boolean;
|
|
17
|
+
subSkills: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface RegistrySkill {
|
|
21
|
+
name: string;
|
|
22
|
+
installs: number;
|
|
23
|
+
installsStr: string;
|
|
24
|
+
url: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function SkillsPage() {
|
|
28
|
+
const [skills, setSkills] = useState<SkillItem[]>([]);
|
|
29
|
+
const [selectedSkill, setSelectedSkill] = useState<SkillItem | null>(null);
|
|
30
|
+
const [skillContent, setSkillContent] = useState('');
|
|
31
|
+
|
|
32
|
+
// Navigation tabs state
|
|
33
|
+
const [activeTab, setActiveTab] = useState<'active' | 'registry'>('active');
|
|
34
|
+
|
|
35
|
+
// UI status hooks
|
|
36
|
+
const [, setLoading] = useState(true);
|
|
37
|
+
const [saving, setSaving] = useState(false);
|
|
38
|
+
const [compiling, setCompiling] = useState(false);
|
|
39
|
+
const [error, setError] = useState<string | null>(null);
|
|
40
|
+
const [success, setSuccess] = useState<string | null>(null);
|
|
41
|
+
|
|
42
|
+
// New Skill scaffolds state
|
|
43
|
+
const [showCreateForm, setShowCreateForm] = useState(false);
|
|
44
|
+
const [newSkillName, setNewSkillName] = useState('');
|
|
45
|
+
const [newSkillDesc, setNewSkillDesc] = useState('');
|
|
46
|
+
|
|
47
|
+
// Registry Search & Installation states
|
|
48
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
49
|
+
const [registryResults, setRegistryResults] = useState<RegistrySkill[]>([]);
|
|
50
|
+
const [registryLoading, setRegistryLoading] = useState(false);
|
|
51
|
+
const [installingPkg, setInstallingPkg] = useState<string | null>(null);
|
|
52
|
+
const [quarantineAlert, setQuarantineAlert] = useState<{ pkg: string; reason: string } | null>(null);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Syncs the locally installed skills from the backend active registry
|
|
56
|
+
*/
|
|
57
|
+
const fetchSkillsList = async (selectName?: string) => {
|
|
58
|
+
setLoading(true);
|
|
59
|
+
setError(null);
|
|
60
|
+
try {
|
|
61
|
+
const list = await listSkills() as unknown as SkillItem[];
|
|
62
|
+
setSkills(list || []);
|
|
63
|
+
|
|
64
|
+
if (list && list.length > 0) {
|
|
65
|
+
const target = selectName ? list.find((s: SkillItem) => s.name === selectName) : list[0];
|
|
66
|
+
if (target) {
|
|
67
|
+
setSelectedSkill(target);
|
|
68
|
+
const skillDetails = await fetchSkill(target.name);
|
|
69
|
+
setSkillContent(skillDetails.content || '');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch (err: unknown) {
|
|
73
|
+
setError((err as Error).message || 'Failed to fetch active skills list.');
|
|
74
|
+
} finally {
|
|
75
|
+
setLoading(false);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- legitimate data fetch on mount
|
|
81
|
+
void fetchSkillsList();
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
const handleSelectSkill = async (skill: SkillItem) => {
|
|
85
|
+
setSelectedSkill(skill);
|
|
86
|
+
setLoading(true);
|
|
87
|
+
setError(null);
|
|
88
|
+
setSuccess(null);
|
|
89
|
+
try {
|
|
90
|
+
const details = await fetchSkill(skill.name);
|
|
91
|
+
setSkillContent(details.content || '');
|
|
92
|
+
} catch (err: unknown) {
|
|
93
|
+
setError((err as Error).message || `Failed to fetch rules for skill "${skill.name}".`);
|
|
94
|
+
} finally {
|
|
95
|
+
setLoading(false);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handleSaveSkill = async () => {
|
|
100
|
+
if (!selectedSkill) return;
|
|
101
|
+
setSaving(true);
|
|
102
|
+
setError(null);
|
|
103
|
+
setSuccess(null);
|
|
104
|
+
try {
|
|
105
|
+
await saveSkill(selectedSkill.name, skillContent);
|
|
106
|
+
setSuccess(`Skill "${selectedSkill.name}" rules sheet saved successfully!`);
|
|
107
|
+
setTimeout(() => setSuccess(null), 5000);
|
|
108
|
+
} catch (err: unknown) {
|
|
109
|
+
setError((err as Error).message || 'Failed to save skill changes.');
|
|
110
|
+
} finally {
|
|
111
|
+
setSaving(false);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleRecompile = async () => {
|
|
116
|
+
setCompiling(true);
|
|
117
|
+
setError(null);
|
|
118
|
+
setSuccess(null);
|
|
119
|
+
try {
|
|
120
|
+
const result = await triggerRecompile();
|
|
121
|
+
setSuccess(result.message || 'Brain surfaces and shims recompiled successfully!');
|
|
122
|
+
setTimeout(() => setSuccess(null), 5000);
|
|
123
|
+
} catch (err: unknown) {
|
|
124
|
+
setError((err as Error).message || 'Recompilation failed.');
|
|
125
|
+
} finally {
|
|
126
|
+
setCompiling(false);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleDeleteSkill = async (skill: SkillItem) => {
|
|
131
|
+
if (!window.confirm(`Are you sure you want to permanently delete the skill "${skill.name}" and all its rules sheets?`)) return;
|
|
132
|
+
setError(null);
|
|
133
|
+
setSuccess(null);
|
|
134
|
+
try {
|
|
135
|
+
await deleteSkill(skill.name);
|
|
136
|
+
setSuccess(`Skill "${skill.name}" successfully deleted.`);
|
|
137
|
+
setTimeout(() => setSuccess(null), 5000);
|
|
138
|
+
void fetchSkillsList();
|
|
139
|
+
} catch (err: unknown) {
|
|
140
|
+
setError((err as Error).message || 'Failed to delete skill.');
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const handleCreateSkill = async (e: React.FormEvent) => {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
if (!newSkillName.trim()) return;
|
|
147
|
+
setError(null);
|
|
148
|
+
setSuccess(null);
|
|
149
|
+
|
|
150
|
+
// Normalize naming: alphanumeric and hyphens
|
|
151
|
+
const cleanName = newSkillName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
152
|
+
|
|
153
|
+
const initialContent = `---
|
|
154
|
+
type: skill
|
|
155
|
+
name: ${cleanName}
|
|
156
|
+
description: "${newSkillDesc.trim() || 'Custom system skill capability'}"
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
# ${cleanName.charAt(0).toUpperCase() + cleanName.slice(1)} — custom system skill
|
|
160
|
+
|
|
161
|
+
Configure triggers, options, and prompts inside this rules sheet to hot-recompile IDE capabilities.
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
await saveSkill(cleanName, initialContent);
|
|
166
|
+
setNewSkillName('');
|
|
167
|
+
setNewSkillDesc('');
|
|
168
|
+
setShowCreateForm(false);
|
|
169
|
+
setSuccess(`Skill "${cleanName}" successfully created!`);
|
|
170
|
+
setTimeout(() => setSuccess(null), 5000);
|
|
171
|
+
void fetchSkillsList(cleanName);
|
|
172
|
+
} catch (err: unknown) {
|
|
173
|
+
setError((err as Error).message || 'Failed to create new skill.');
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Performs search on the skills.sh registry sorted by absolute installs
|
|
179
|
+
*/
|
|
180
|
+
const handleRegistrySearch = async (e: React.FormEvent) => {
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
if (!searchQuery.trim()) return;
|
|
183
|
+
|
|
184
|
+
setRegistryLoading(true);
|
|
185
|
+
setError(null);
|
|
186
|
+
setQuarantineAlert(null);
|
|
187
|
+
try {
|
|
188
|
+
const results = await searchSkillsRegistry(searchQuery.trim()) as RegistrySkill[];
|
|
189
|
+
setRegistryResults(results || []);
|
|
190
|
+
} catch (err: unknown) {
|
|
191
|
+
setError((err as Error).message || 'Registry query failed. Is the server connected?');
|
|
192
|
+
} finally {
|
|
193
|
+
setRegistryLoading(false);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Installs, statically scans, and auto-compiles registry skills
|
|
199
|
+
*/
|
|
200
|
+
const handleRegistryInstall = async (pkg: string) => {
|
|
201
|
+
setInstallingPkg(pkg);
|
|
202
|
+
setError(null);
|
|
203
|
+
setSuccess(null);
|
|
204
|
+
setQuarantineAlert(null);
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const res = await installRegistrySkill(pkg);
|
|
208
|
+
|
|
209
|
+
if (res.success) {
|
|
210
|
+
setSuccess(`✓ Skill "${res.skillName || pkg}" successfully installed, security audited, and compiled!`);
|
|
211
|
+
setTimeout(() => setSuccess(null), 7000);
|
|
212
|
+
// Refresh local installed skills registry list
|
|
213
|
+
void fetchSkillsList(res.skillName);
|
|
214
|
+
} else {
|
|
215
|
+
if (res.reason && res.reason.includes('quarantined')) {
|
|
216
|
+
setQuarantineAlert({
|
|
217
|
+
pkg,
|
|
218
|
+
reason: res.reason || 'Static security audit scanned critical dynamic string evaluations or commands injection vectors.'
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
setError(`❌ Installation failed: ${res.reason || 'Unknown error'}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch (err: unknown) {
|
|
225
|
+
setError((err as Error).message || 'Dynamic installation handler failed.');
|
|
226
|
+
} finally {
|
|
227
|
+
setInstallingPkg(null);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
233
|
+
<div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
|
|
234
|
+
<div>
|
|
235
|
+
<h1>Sovereign Skills Manager</h1>
|
|
236
|
+
<p>Read, write, edit, and organize system skill sheets and nested sub-skills dynamically</p>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
<div style={{ display: 'flex', gap: 10 }}>
|
|
240
|
+
<button
|
|
241
|
+
className="btn btn-sm btn-ghost"
|
|
242
|
+
onClick={() => setShowCreateForm(!showCreateForm)}
|
|
243
|
+
style={{ border: '1px solid var(--border)', background: 'var(--bg-secondary)' }}
|
|
244
|
+
>
|
|
245
|
+
{showCreateForm ? '✖ Cancel' : '➕ Create Custom Skill'}
|
|
246
|
+
</button>
|
|
247
|
+
|
|
248
|
+
<button
|
|
249
|
+
className="btn btn-sm"
|
|
250
|
+
onClick={handleRecompile}
|
|
251
|
+
disabled={compiling}
|
|
252
|
+
style={{
|
|
253
|
+
background: compiling ? 'var(--bg-secondary)' : 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
254
|
+
color: '#fff',
|
|
255
|
+
border: 'none',
|
|
256
|
+
fontWeight: 500
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
{compiling ? '⏳ Compiling shims...' : '🔄 Hot-Recompile Shims'}
|
|
260
|
+
</button>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
{error && (
|
|
265
|
+
<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' }}>
|
|
266
|
+
⚠️ {error}
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
269
|
+
{success && (
|
|
270
|
+
<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' }}>
|
|
271
|
+
✓ {success}
|
|
272
|
+
</div>
|
|
273
|
+
)}
|
|
274
|
+
{quarantineAlert && (
|
|
275
|
+
<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 }}>
|
|
276
|
+
<div style={{ fontWeight: 700, fontSize: 13 }}>🚨 SECURITY QUARANTINE PURGE ACTIVE!</div>
|
|
277
|
+
<div style={{ fontSize: 12 }}>Installation of package <strong>{quarantineAlert.pkg}</strong> was blocked and files were hard purged.</div>
|
|
278
|
+
<div style={{ fontSize: 11, opacity: 0.85 }}>Reason: {quarantineAlert.reason}</div>
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
|
|
282
|
+
{/* Creation Modal Form Overlay */}
|
|
283
|
+
{showCreateForm && (
|
|
284
|
+
<div className="card" style={{ marginBottom: 24, padding: 24, background: 'rgba(18, 18, 26, 0.9)', border: '1px solid var(--border)', backdropFilter: 'blur(10px)' }}>
|
|
285
|
+
<h3 style={{ fontSize: 14, fontWeight: 600, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 14 }}>➕ Create New Sovereign Skill Capability</h3>
|
|
286
|
+
<form onSubmit={handleCreateSkill} style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
|
287
|
+
<div style={{ flex: '1 1 200px', display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
288
|
+
<label htmlFor="new_name" style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Skill Name (kebab-case)</label>
|
|
289
|
+
<input
|
|
290
|
+
id="new_name"
|
|
291
|
+
type="text"
|
|
292
|
+
placeholder="e.g. security-audit"
|
|
293
|
+
value={newSkillName}
|
|
294
|
+
onChange={(e) => setNewSkillName(e.target.value)}
|
|
295
|
+
required
|
|
296
|
+
style={{ background: 'var(--bg-tertiary)', color: 'var(--text-primary)', border: '1px solid var(--border)', padding: '8px 12px', borderRadius: 6, outline: 'none', fontSize: 12 }}
|
|
297
|
+
/>
|
|
298
|
+
</div>
|
|
299
|
+
<div style={{ flex: '2 1 300px', display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
300
|
+
<label htmlFor="new_desc" style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Brief Description / System Triggers</label>
|
|
301
|
+
<input
|
|
302
|
+
id="new_desc"
|
|
303
|
+
type="text"
|
|
304
|
+
placeholder="e.g. Audits security parameters, encryption costs, and Caddy forward gates"
|
|
305
|
+
value={newSkillDesc}
|
|
306
|
+
onChange={(e) => setNewSkillDesc(e.target.value)}
|
|
307
|
+
style={{ background: 'var(--bg-tertiary)', color: 'var(--text-primary)', border: '1px solid var(--border)', padding: '8px 12px', borderRadius: 6, outline: 'none', fontSize: 12 }}
|
|
308
|
+
/>
|
|
309
|
+
</div>
|
|
310
|
+
<button
|
|
311
|
+
type="submit"
|
|
312
|
+
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 }}
|
|
313
|
+
>
|
|
314
|
+
🚀 Initialize Skill
|
|
315
|
+
</button>
|
|
316
|
+
</form>
|
|
317
|
+
</div>
|
|
318
|
+
)}
|
|
319
|
+
|
|
320
|
+
{/* Tab Switcher */}
|
|
321
|
+
<div style={{ display: 'flex', gap: 8, marginBottom: 20, borderBottom: '1px solid var(--border)', paddingBottom: 10 }}>
|
|
322
|
+
<button
|
|
323
|
+
onClick={() => setActiveTab('active')}
|
|
324
|
+
style={{
|
|
325
|
+
padding: '8px 16px',
|
|
326
|
+
borderRadius: 8,
|
|
327
|
+
border: 'none',
|
|
328
|
+
background: activeTab === 'active' ? 'var(--accent)' : 'var(--bg-tertiary)',
|
|
329
|
+
color: activeTab === 'active' ? '#fff' : 'var(--text-secondary)',
|
|
330
|
+
fontWeight: 600,
|
|
331
|
+
cursor: 'pointer',
|
|
332
|
+
fontSize: 12,
|
|
333
|
+
transition: 'all 0.15s ease'
|
|
334
|
+
}}
|
|
335
|
+
>
|
|
336
|
+
📂 Active Skills Registry
|
|
337
|
+
</button>
|
|
338
|
+
<button
|
|
339
|
+
onClick={() => setActiveTab('registry')}
|
|
340
|
+
style={{
|
|
341
|
+
padding: '8px 16px',
|
|
342
|
+
borderRadius: 8,
|
|
343
|
+
border: 'none',
|
|
344
|
+
background: activeTab === 'registry' ? 'var(--accent)' : 'var(--bg-tertiary)',
|
|
345
|
+
color: activeTab === 'registry' ? '#fff' : 'var(--text-secondary)',
|
|
346
|
+
fontWeight: 600,
|
|
347
|
+
cursor: 'pointer',
|
|
348
|
+
fontSize: 12,
|
|
349
|
+
transition: 'all 0.15s ease'
|
|
350
|
+
}}
|
|
351
|
+
>
|
|
352
|
+
✨ skills.sh Registry Hub
|
|
353
|
+
</button>
|
|
354
|
+
</div>
|
|
355
|
+
|
|
356
|
+
{activeTab === 'active' ? (
|
|
357
|
+
/* Original split layout: Active skills editor */
|
|
358
|
+
<div style={{ display: 'flex', flex: 1, gap: 24, minHeight: 0, overflow: 'hidden', paddingBottom: 16 }}>
|
|
359
|
+
|
|
360
|
+
{/* Left column: Skills Sidebar */}
|
|
361
|
+
<div style={{ width: 280, display: 'flex', flexDirection: 'column', gap: 16, flexShrink: 0, overflowY: 'auto' }}>
|
|
362
|
+
<div className="card" style={{ padding: 16, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
363
|
+
<h3 style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 8, marginBottom: 12 }}>
|
|
364
|
+
Active Skills Registry
|
|
365
|
+
</h3>
|
|
366
|
+
|
|
367
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, flex: 1, overflowY: 'auto' }}>
|
|
368
|
+
{skills.map((skill) => {
|
|
369
|
+
const isActive = selectedSkill?.name === skill.name;
|
|
370
|
+
return (
|
|
371
|
+
<div
|
|
372
|
+
key={skill.name}
|
|
373
|
+
onClick={() => void handleSelectSkill(skill)}
|
|
374
|
+
style={{
|
|
375
|
+
background: isActive ? 'var(--accent-muted)' : 'var(--bg-tertiary)',
|
|
376
|
+
border: isActive ? '1px solid var(--accent)' : '1px solid var(--border)',
|
|
377
|
+
padding: '12px 14px',
|
|
378
|
+
borderRadius: 8,
|
|
379
|
+
cursor: 'pointer',
|
|
380
|
+
transition: 'all 0.15s ease',
|
|
381
|
+
position: 'relative'
|
|
382
|
+
}}
|
|
383
|
+
>
|
|
384
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
385
|
+
<span style={{ fontSize: 13, fontWeight: 600, color: isActive ? 'var(--accent)' : 'var(--text-primary)' }}>
|
|
386
|
+
{skill.name}
|
|
387
|
+
</span>
|
|
388
|
+
{skill.name !== 'total-recall' && (
|
|
389
|
+
<button
|
|
390
|
+
onClick={(e) => {
|
|
391
|
+
e.stopPropagation();
|
|
392
|
+
void handleDeleteSkill(skill);
|
|
393
|
+
}}
|
|
394
|
+
style={{
|
|
395
|
+
background: 'none',
|
|
396
|
+
border: 'none',
|
|
397
|
+
color: 'var(--text-tertiary)',
|
|
398
|
+
fontSize: 12,
|
|
399
|
+
cursor: 'pointer',
|
|
400
|
+
padding: '2px 6px'
|
|
401
|
+
}}
|
|
402
|
+
onMouseEnter={(e) => (e.currentTarget.style.color = '#ff6b6b')}
|
|
403
|
+
onMouseLeave={(e) => (e.currentTarget.style.color = 'var(--text-tertiary)')}
|
|
404
|
+
>
|
|
405
|
+
✖
|
|
406
|
+
</button>
|
|
407
|
+
)}
|
|
408
|
+
</div>
|
|
409
|
+
|
|
410
|
+
{/* Sub Skills tags */}
|
|
411
|
+
{skill.subSkills && skill.subSkills.length > 0 && (
|
|
412
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 8 }}>
|
|
413
|
+
{skill.subSkills.map(sd => (
|
|
414
|
+
<span key={sd} className="badge" style={{ fontSize: 9, padding: '1px 5px', background: 'var(--bg-elevated)', color: 'var(--text-secondary)', border: '1px solid var(--border)' }}>
|
|
415
|
+
🧩 {sd}
|
|
416
|
+
</span>
|
|
417
|
+
))}
|
|
418
|
+
</div>
|
|
419
|
+
)}
|
|
420
|
+
</div>
|
|
421
|
+
);
|
|
422
|
+
})}
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
|
|
427
|
+
{/* Right column: Editor and Details */}
|
|
428
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
|
429
|
+
{selectedSkill ? (
|
|
430
|
+
<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 }}>
|
|
431
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid var(--border)', paddingBottom: 12, marginBottom: 16, flexShrink: 0 }}>
|
|
432
|
+
<div>
|
|
433
|
+
<h3 style={{ fontSize: 16, fontWeight: 600, color: 'var(--text-primary)' }}>📝 Editing Capability rules: {selectedSkill.name}/SKILL.md</h3>
|
|
434
|
+
<p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Hot-recompiled rules preserve immutable instructions and custom shims</p>
|
|
435
|
+
</div>
|
|
436
|
+
<button
|
|
437
|
+
className="btn btn-primary"
|
|
438
|
+
onClick={handleSaveSkill}
|
|
439
|
+
disabled={saving}
|
|
440
|
+
style={{
|
|
441
|
+
background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
442
|
+
color: '#fff',
|
|
443
|
+
fontWeight: 500,
|
|
444
|
+
borderRadius: 6,
|
|
445
|
+
padding: '8px 18px',
|
|
446
|
+
fontSize: 12
|
|
447
|
+
}}
|
|
448
|
+
>
|
|
449
|
+
{saving ? '⏳ Saving...' : '💾 Save rules'}
|
|
450
|
+
</button>
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
{/* Text Area Code Editor */}
|
|
454
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
|
455
|
+
<textarea
|
|
456
|
+
value={skillContent}
|
|
457
|
+
onChange={(e) => setSkillContent(e.target.value)}
|
|
458
|
+
style={{
|
|
459
|
+
flex: 1,
|
|
460
|
+
width: '100%',
|
|
461
|
+
fontFamily: 'var(--font-mono)',
|
|
462
|
+
fontSize: 13,
|
|
463
|
+
padding: 16,
|
|
464
|
+
background: 'var(--bg-secondary)',
|
|
465
|
+
color: 'var(--text-primary)',
|
|
466
|
+
border: '1px solid var(--border)',
|
|
467
|
+
borderRadius: 8,
|
|
468
|
+
resize: 'none',
|
|
469
|
+
outline: 'none',
|
|
470
|
+
lineHeight: 1.6,
|
|
471
|
+
whiteSpace: 'pre',
|
|
472
|
+
overflowWrap: 'normal',
|
|
473
|
+
overflowX: 'auto'
|
|
474
|
+
}}
|
|
475
|
+
spellCheck={false}
|
|
476
|
+
/>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
) : (
|
|
480
|
+
<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)' }}>
|
|
481
|
+
No skill selected. Click a skill in the registry to manage its rules sheet.
|
|
482
|
+
</div>
|
|
483
|
+
)}
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
</div>
|
|
487
|
+
) : (
|
|
488
|
+
/* Tab 2: skills.sh Registry Explorer Panel */
|
|
489
|
+
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0, overflowY: 'auto', paddingBottom: 16 }}>
|
|
490
|
+
<div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', marginBottom: 20 }}>
|
|
491
|
+
<h3 style={{ fontSize: 15, fontWeight: 600, color: 'var(--text-primary)', marginBottom: 8 }}>🔍 Discover AI Agent Skills from skills.sh</h3>
|
|
492
|
+
<p style={{ fontSize: 12, color: 'var(--text-secondary)', marginBottom: 16 }}>
|
|
493
|
+
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.
|
|
494
|
+
</p>
|
|
495
|
+
|
|
496
|
+
<form onSubmit={handleRegistrySearch} style={{ display: 'flex', gap: 12 }}>
|
|
497
|
+
<input
|
|
498
|
+
type="text"
|
|
499
|
+
placeholder="Search skills (e.g. git, typescript, prd...)"
|
|
500
|
+
value={searchQuery}
|
|
501
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
502
|
+
style={{
|
|
503
|
+
flex: 1,
|
|
504
|
+
background: 'var(--bg-tertiary)',
|
|
505
|
+
color: 'var(--text-primary)',
|
|
506
|
+
border: '1px solid var(--border)',
|
|
507
|
+
borderRadius: 8,
|
|
508
|
+
padding: '10px 16px',
|
|
509
|
+
fontSize: 13,
|
|
510
|
+
outline: 'none'
|
|
511
|
+
}}
|
|
512
|
+
/>
|
|
513
|
+
<button
|
|
514
|
+
type="submit"
|
|
515
|
+
disabled={registryLoading}
|
|
516
|
+
style={{
|
|
517
|
+
background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
518
|
+
color: '#fff',
|
|
519
|
+
border: 'none',
|
|
520
|
+
borderRadius: 8,
|
|
521
|
+
padding: '10px 24px',
|
|
522
|
+
fontWeight: 600,
|
|
523
|
+
fontSize: 13,
|
|
524
|
+
cursor: 'pointer'
|
|
525
|
+
}}
|
|
526
|
+
>
|
|
527
|
+
{registryLoading ? '⏳ Sifting...' : '🔎 Query Registry'}
|
|
528
|
+
</button>
|
|
529
|
+
</form>
|
|
530
|
+
</div>
|
|
531
|
+
|
|
532
|
+
{/* Registry Results List */}
|
|
533
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
534
|
+
{registryLoading ? (
|
|
535
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 40, color: 'var(--text-secondary)' }}>
|
|
536
|
+
<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 }}>
|
|
537
|
+
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
|
538
|
+
</svg>
|
|
539
|
+
<span>Searching packages in registry catalog...</span>
|
|
540
|
+
</div>
|
|
541
|
+
) : registryResults.length > 0 ? (
|
|
542
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: 16 }}>
|
|
543
|
+
{registryResults.map((item) => {
|
|
544
|
+
const isInstalling = installingPkg === item.name;
|
|
545
|
+
return (
|
|
546
|
+
<div
|
|
547
|
+
key={item.name}
|
|
548
|
+
style={{
|
|
549
|
+
background: 'rgba(18, 18, 26, 0.4)',
|
|
550
|
+
border: '1px solid var(--border)',
|
|
551
|
+
borderRadius: 10,
|
|
552
|
+
padding: 20,
|
|
553
|
+
display: 'flex',
|
|
554
|
+
flexDirection: 'column',
|
|
555
|
+
justifyContent: 'space-between',
|
|
556
|
+
gap: 16
|
|
557
|
+
}}
|
|
558
|
+
>
|
|
559
|
+
<div>
|
|
560
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: 8, marginBottom: 8 }}>
|
|
561
|
+
<span style={{ fontWeight: 700, fontSize: 14, color: '#fff', wordBreak: 'break-all' }}>
|
|
562
|
+
{item.name}
|
|
563
|
+
</span>
|
|
564
|
+
<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 }}>
|
|
565
|
+
⚡ {item.installsStr} installs
|
|
566
|
+
</span>
|
|
567
|
+
</div>
|
|
568
|
+
<a
|
|
569
|
+
href={item.url}
|
|
570
|
+
target="_blank"
|
|
571
|
+
rel="noopener noreferrer"
|
|
572
|
+
style={{ fontSize: 11, color: 'var(--accent)', textDecoration: 'none' }}
|
|
573
|
+
onMouseEnter={(e) => (e.currentTarget.style.textDecoration = 'underline')}
|
|
574
|
+
onMouseLeave={(e) => (e.currentTarget.style.textDecoration = 'none')}
|
|
575
|
+
>
|
|
576
|
+
🔗 View Registry Repository
|
|
577
|
+
</a>
|
|
578
|
+
</div>
|
|
579
|
+
|
|
580
|
+
<button
|
|
581
|
+
onClick={() => void handleRegistryInstall(item.name)}
|
|
582
|
+
disabled={!!installingPkg}
|
|
583
|
+
style={{
|
|
584
|
+
width: '100%',
|
|
585
|
+
background: isInstalling
|
|
586
|
+
? 'var(--bg-tertiary)'
|
|
587
|
+
: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
|
|
588
|
+
color: isInstalling ? 'var(--text-secondary)' : '#fff',
|
|
589
|
+
border: 'none',
|
|
590
|
+
borderRadius: 8,
|
|
591
|
+
padding: '10px 16px',
|
|
592
|
+
fontWeight: 600,
|
|
593
|
+
fontSize: 12,
|
|
594
|
+
cursor: installingPkg ? 'not-allowed' : 'pointer',
|
|
595
|
+
transition: 'all 0.15s ease'
|
|
596
|
+
}}
|
|
597
|
+
>
|
|
598
|
+
{isInstalling ? '🛡️ Auditing & Installing...' : '➕ Install Capability'}
|
|
599
|
+
</button>
|
|
600
|
+
</div>
|
|
601
|
+
);
|
|
602
|
+
})}
|
|
603
|
+
</div>
|
|
604
|
+
) : searchQuery.trim() ? (
|
|
605
|
+
<div style={{ textAlign: 'center', padding: 40, border: '1px dashed var(--border)', borderRadius: 10, color: 'var(--text-secondary)' }}>
|
|
606
|
+
No matching skills found in the skills.sh registry. Try a different query like "git" or "prd".
|
|
607
|
+
</div>
|
|
608
|
+
) : (
|
|
609
|
+
<div style={{ textAlign: 'center', padding: 40, border: '1px dashed var(--border)', borderRadius: 10, color: 'var(--text-tertiary)', fontSize: 12 }}>
|
|
610
|
+
Enter a search query above to browse portable capabilities from the cloud registry.
|
|
611
|
+
</div>
|
|
612
|
+
)}
|
|
613
|
+
</div>
|
|
614
|
+
</div>
|
|
615
|
+
)}
|
|
616
|
+
</div>
|
|
617
|
+
);
|
|
618
|
+
}
|