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.
Files changed (63) hide show
  1. package/extension/background.js +99 -0
  2. package/extension/content-script.css +16 -0
  3. package/extension/content-script.js +134 -0
  4. package/extension/icons/README.md +27 -0
  5. package/extension/icons/icon-128.png +0 -0
  6. package/extension/icons/icon-16.png +0 -0
  7. package/extension/icons/icon-48.png +0 -0
  8. package/extension/lib/brain-client.js +47 -0
  9. package/extension/manifest.json +39 -0
  10. package/extension/options/options.css +218 -0
  11. package/extension/options/options.html +72 -0
  12. package/extension/options/options.js +99 -0
  13. package/extension/popup/popup.css +221 -0
  14. package/extension/popup/popup.html +44 -0
  15. package/extension/popup/popup.js +107 -0
  16. package/extension/sidepanel/sidepanel.css +340 -0
  17. package/extension/sidepanel/sidepanel.html +38 -0
  18. package/extension/sidepanel/sidepanel.js +229 -0
  19. package/frontend/README.md +73 -0
  20. package/frontend/index.html +17 -0
  21. package/frontend/package.json +31 -0
  22. package/frontend/src/App.css +2 -0
  23. package/frontend/src/App.tsx +390 -0
  24. package/frontend/src/api.ts +688 -0
  25. package/frontend/src/assets/hero.png +0 -0
  26. package/frontend/src/assets/react.svg +1 -0
  27. package/frontend/src/assets/vite.svg +1 -0
  28. package/frontend/src/components/BrainSelector.tsx +272 -0
  29. package/frontend/src/components/DaemonLogsTab.tsx +54 -0
  30. package/frontend/src/components/Graph3D.tsx +850 -0
  31. package/frontend/src/components/MarkdownUtils.tsx +99 -0
  32. package/frontend/src/components/ResearchAgendaTab.tsx +707 -0
  33. package/frontend/src/components/TaskQueueTab.tsx +185 -0
  34. package/frontend/src/index.css +1589 -0
  35. package/frontend/src/main.tsx +10 -0
  36. package/frontend/src/pages/ApiKeysPage.tsx +288 -0
  37. package/frontend/src/pages/AutomationsPage.tsx +354 -0
  38. package/frontend/src/pages/ChatPage.tsx +831 -0
  39. package/frontend/src/pages/DeploymentsPage.tsx +275 -0
  40. package/frontend/src/pages/FilesPage.tsx +384 -0
  41. package/frontend/src/pages/HealthPage.tsx +91 -0
  42. package/frontend/src/pages/IntegrationsPage.tsx +382 -0
  43. package/frontend/src/pages/LoginPage.tsx +237 -0
  44. package/frontend/src/pages/MemoryPage.tsx +1192 -0
  45. package/frontend/src/pages/SandboxPage.tsx +113 -0
  46. package/frontend/src/pages/SettingsPage.tsx +662 -0
  47. package/frontend/src/pages/SkillsPage.tsx +618 -0
  48. package/frontend/src/pages/TasksPage.tsx +392 -0
  49. package/frontend/src/pages/UsagePage.tsx +260 -0
  50. package/frontend/src/types.ts +163 -0
  51. package/frontend/tsconfig.app.json +25 -0
  52. package/frontend/tsconfig.json +7 -0
  53. package/frontend/tsconfig.node.json +24 -0
  54. package/frontend/vite.config.ts +24 -0
  55. package/package.json +8 -1
  56. package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +35 -8
  57. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +13 -8
  58. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +28 -1
  59. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +28 -1
  60. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +830 -81
  61. package/src/cli/backup.mjs +172 -1
  62. package/src/cli/setup.mjs +24 -0
  63. 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
+ }