total-recall-brain 3.13.0 → 3.13.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 (152) hide show
  1. package/README.md +91 -108
  2. package/bin/antigravity.mjs +81 -52
  3. package/bin/total-recall.mjs +32 -57
  4. package/extension/background.js +1 -1
  5. package/extension/options/options.js +9 -3
  6. package/extension/sidepanel/sidepanel.js +0 -2
  7. package/frontend/index.html +4 -3
  8. package/frontend/package.json +1 -0
  9. package/frontend/src/App.tsx +109 -152
  10. package/frontend/src/api.ts +605 -23
  11. package/frontend/src/components/BrainSelector.tsx +211 -199
  12. package/frontend/src/components/DocumentEditorModal.tsx +57 -0
  13. package/frontend/src/components/DocumentTable.tsx +47 -0
  14. package/frontend/src/components/Graph3D.tsx +120 -71
  15. package/frontend/src/components/UsageChart.tsx +3 -3
  16. package/frontend/src/components/brand/BrandMark.tsx +95 -0
  17. package/frontend/src/index.css +286 -86
  18. package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
  19. package/frontend/src/pages/ChatPage.tsx +103 -45
  20. package/frontend/src/pages/DesignDocsPage.tsx +5 -5
  21. package/frontend/src/pages/FilesPage.tsx +10 -6
  22. package/frontend/src/pages/GraphPage.tsx +5 -3
  23. package/frontend/src/pages/HelpPage.tsx +2 -2
  24. package/frontend/src/pages/InboxPage.tsx +82 -0
  25. package/frontend/src/pages/InstructionsPage.tsx +1 -1
  26. package/frontend/src/pages/IntegrationsPage.tsx +21 -10
  27. package/frontend/src/pages/LoginPage.tsx +95 -77
  28. package/frontend/src/pages/MemoryPage.tsx +31 -8
  29. package/frontend/src/pages/OnboardingPage.tsx +690 -0
  30. package/frontend/src/pages/OpenWikiPage.tsx +412 -323
  31. package/frontend/src/pages/SettingsPage.tsx +825 -922
  32. package/frontend/src/pages/SkillsPage.tsx +272 -147
  33. package/frontend/src/pages/UsagePage.tsx +23 -31
  34. package/frontend/src/pages/VaultPage.tsx +223 -0
  35. package/frontend/src/types.ts +13 -0
  36. package/package.json +4 -4
  37. package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
  38. package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
  39. package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
  40. package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
  41. package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
  42. package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
  43. package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
  44. package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
  45. package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
  46. package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
  47. package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
  48. package/src/cli/brain.mjs +63 -5
  49. package/src/cli/connect.mjs +11 -19
  50. package/src/cli/daemon.mjs +5 -4
  51. package/src/cli/deploy-ui.mjs +8 -3
  52. package/src/cli/deploy.mjs +84 -27
  53. package/src/cli/ingest/index.mjs +1 -1
  54. package/src/cli/init.mjs +63 -34
  55. package/src/cli/remember.mjs +3 -3
  56. package/src/cli/secret.mjs +812 -0
  57. package/src/cli/setup.mjs +34 -7
  58. package/src/cli/share.mjs +2 -2
  59. package/src/cli/skill.mjs +396 -35
  60. package/src/cli/sync.mjs +4 -4
  61. package/src/cli/task.mjs +287 -0
  62. package/src/core/clarity-rewriter.mjs +5 -5
  63. package/src/core/config.mjs +44 -2
  64. package/src/core/conflict-detector.mjs +5 -5
  65. package/src/core/crons.mjs +54 -0
  66. package/src/core/daemon-loop.mjs +87 -235
  67. package/src/core/dream.mjs +170 -10
  68. package/src/core/env-import.mjs +456 -0
  69. package/src/core/fact-seeker.mjs +14 -14
  70. package/src/core/import-rules.mjs +20 -7
  71. package/src/core/inference-engine.mjs +3 -3
  72. package/src/core/migrate.mjs +14 -8
  73. package/src/core/okf-adapter.mjs +2 -2
  74. package/src/core/operation-validator.mjs +39 -331
  75. package/src/core/optimizer.mjs +7 -0
  76. package/src/core/post-mortem.mjs +2 -2
  77. package/src/core/project-brain.mjs +349 -0
  78. package/src/core/provider-catalog.mjs +296 -0
  79. package/src/core/quick-capture.mjs +1 -1
  80. package/src/core/remote-vault-sync.mjs +194 -0
  81. package/src/core/research-queue.mjs +7 -11
  82. package/src/core/runtime.mjs +33 -9
  83. package/src/core/scheduler.mjs +59 -5
  84. package/src/core/schema.mjs +14 -0
  85. package/src/core/search.mjs +1 -1
  86. package/src/core/secrets-env-export.mjs +323 -0
  87. package/src/core/secrets-rotate.mjs +245 -0
  88. package/src/core/secrets-store.mjs +645 -0
  89. package/src/core/semantic-index.mjs +3 -3
  90. package/src/core/skills-registry.mjs +1062 -0
  91. package/src/core/source-adapters.mjs +4 -1
  92. package/src/core/ssss-host-extension.mjs +94 -0
  93. package/src/core/ssss-kernel-bridge.mjs +530 -0
  94. package/src/core/surface.mjs +14 -0
  95. package/src/core/task-envelope.mjs +298 -0
  96. package/src/core/task-executors.mjs +600 -0
  97. package/src/core/validated-write.mjs +124 -56
  98. package/src/core/vault.mjs +21 -34
  99. package/src/core/webauthn-store.mjs +272 -0
  100. package/src/server/api.mjs +26 -16
  101. package/src/server/auth.mjs +51 -0
  102. package/src/server/index.mjs +3 -3
  103. package/src/server/keys.mjs +30 -4
  104. package/src/server/rest.mjs +198 -25
  105. package/src/server/route-manifest.json +156 -0
  106. package/src/server/routes/_shared.mjs +40 -1
  107. package/src/server/routes/docs.mjs +260 -0
  108. package/src/server/routes/memory.mjs +145 -32
  109. package/src/server/routes/research.mjs +30 -0
  110. package/src/server/routes/secrets.mjs +470 -0
  111. package/src/server/routes/share.mjs +2 -2
  112. package/src/server/routes/skills.mjs +452 -54
  113. package/src/server/routes/sync.mjs +48 -0
  114. package/src/server/routes/webauthn.mjs +184 -0
  115. package/src/server/update-route-manifest.mjs +39 -0
  116. package/templates/onboarding-interview.md +32 -22
  117. package/templates/openwiki/README.md +10 -0
  118. package/templates/openwiki/architecture.md +5 -0
  119. package/templates/openwiki/ide.md +10 -0
  120. package/templates/openwiki/memory.md +11 -0
  121. package/templates/openwiki/secrets.md +7 -0
  122. package/templates/openwiki/skills.md +7 -0
  123. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
  124. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
  125. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
  126. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
  127. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
  128. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
  129. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
  130. package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
  131. package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
  132. package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
  133. package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
  134. package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
  135. package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
  136. package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
  137. package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
  138. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
  139. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
  140. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
  141. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
  142. package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
  143. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
  144. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
  145. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
  146. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
  147. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
  148. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
  149. /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
  150. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
  151. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
  152. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/scan-skill.mjs +0 -0
@@ -1,7 +1,8 @@
1
- import { useState, useEffect, useCallback } from 'react';
1
+ import { useState, useEffect, useCallback, useMemo } from 'react';
2
2
  import {
3
3
  listSkills,
4
4
  fetchSkill,
5
+ fetchSkillFiles,
5
6
  saveSkill,
6
7
  deleteSkill,
7
8
  triggerRecompile,
@@ -14,11 +15,25 @@ import {
14
15
  import type { ResearchItem } from '../types';
15
16
 
16
17
  interface SkillItem {
18
+ id?: string;
17
19
  name: string;
20
+ repo?: string;
21
+ project_path?: string;
22
+ source?: string;
18
23
  size: number;
19
24
  modified: string;
20
25
  isDirectory: boolean;
21
26
  subSkills: string[];
27
+ has_skill_md?: boolean;
28
+ }
29
+
30
+ interface RepoSkillsGroup {
31
+ repo: string;
32
+ path: string | null;
33
+ skills: SkillItem[];
34
+ ok?: boolean;
35
+ count?: number;
36
+ error?: string;
22
37
  }
23
38
 
24
39
  interface RegistrySkill {
@@ -156,16 +171,14 @@ function generateSuggestions(skills: SkillItem[]): ImprovementSuggestion[] {
156
171
 
157
172
  // ─── Helper: format file sizes ──────────────────────────────────────────────────
158
173
 
159
- function formatBytes(bytes: number): string {
160
- if (bytes < 1024) return `${bytes} B`;
161
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
162
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
163
- }
174
+
164
175
 
165
176
  export default function SkillsPage() {
166
177
  const [skills, setSkills] = useState<SkillItem[]>([]);
178
+ const [repoGroups, setRepoGroups] = useState<RepoSkillsGroup[]>([]);
167
179
  const [selectedSkill, setSelectedSkill] = useState<SkillItem | null>(null);
168
180
  const [skillContent, setSkillContent] = useState('');
181
+ const [expandedRepos, setExpandedRepos] = useState<Set<string>>(new Set());
169
182
 
170
183
  // Navigation tabs state
171
184
  const [activeTab, setActiveTab] = useState<TabId>('active');
@@ -190,9 +203,9 @@ export default function SkillsPage() {
190
203
  const [quarantineAlert, setQuarantineAlert] = useState<{ pkg: string; reason: string } | null>(null);
191
204
 
192
205
  // ─── Lifecycle tab state ────────────────────────────────────────────────────
193
- const [lifecycleVersion, setLifecycleVersion] = useState('0.0.0');
194
- const [changelogEntries, setChangelogEntries] = useState<ChangelogEntry[]>([]);
195
- const [healthChecks, setHealthChecks] = useState<HealthCheck[]>([]);
206
+ const lifecycleVersion = useMemo(() => selectedSkill && skillContent ? extractVersion(skillContent) : '0.0.0', [selectedSkill, skillContent]);
207
+ const changelogEntries = useMemo(() => selectedSkill && skillContent ? parseChangelog(skillContent) : [], [selectedSkill, skillContent]);
208
+ const healthChecks = useMemo(() => selectedSkill && skillContent ? computeHealthChecks(selectedSkill, skillContent) : [], [selectedSkill, skillContent]);
196
209
  const [expandedSubDirs, setExpandedSubDirs] = useState<Record<string, boolean>>({});
197
210
  const [subDirFiles, setSubDirFiles] = useState<Record<string, SubFileEntry[]>>({});
198
211
  const [previewFile, setPreviewFile] = useState<{ name: string; content: string } | null>(null);
@@ -203,28 +216,65 @@ export default function SkillsPage() {
203
216
  const [researchItems, setResearchItems] = useState<ResearchItem[]>([]);
204
217
  const [researchLoading, setResearchLoading] = useState(false);
205
218
  const [checkingUpdates, setCheckingUpdates] = useState(false);
206
- const [improvementSuggestions, setImprovementSuggestions] = useState<ImprovementSuggestion[]>([]);
219
+ const improvementSuggestions = useMemo(() => skills.length > 0 ? generateSuggestions(skills) : [], [skills]);
207
220
 
208
221
  /**
209
- * Syncs the locally installed skills from the backend active registry
222
+ * Syncs project skills by repo (excludes total-recall / tr-* system skills).
210
223
  */
211
- const fetchSkillsList = async (selectName?: string) => {
224
+ const fetchSkillsList = async (selectName?: string, selectRepo?: string) => {
212
225
  setLoading(true);
213
226
  setError(null);
214
227
  try {
215
- const list = await listSkills() as unknown as SkillItem[];
216
- setSkills(list || []);
217
-
218
- if (list && list.length > 0) {
219
- const target = selectName ? list.find((s: SkillItem) => s.name === selectName) : list[0];
228
+ const data = await listSkills();
229
+ let list: SkillItem[] = [];
230
+ let groups: RepoSkillsGroup[] = [];
231
+
232
+ if (Array.isArray(data)) {
233
+ list = data as SkillItem[];
234
+ const map = new Map<string, SkillItem[]>();
235
+ for (const s of list) {
236
+ const r = s.repo || 'unknown';
237
+ if (!map.has(r)) map.set(r, []);
238
+ map.get(r)!.push(s);
239
+ }
240
+ groups = [...map.entries()].map(([repo, skills]) => ({
241
+ repo,
242
+ path: skills[0]?.project_path || null,
243
+ skills,
244
+ count: skills.length,
245
+ ok: true,
246
+ }));
247
+ } else {
248
+ list = (data.skills || []) as SkillItem[];
249
+ groups = (data.repos || []) as RepoSkillsGroup[];
250
+ }
251
+
252
+ setSkills(list);
253
+ setRepoGroups(groups);
254
+
255
+ // Expand repos that have skills (first 5 by default to keep UI light)
256
+ const withSkills = groups.filter((g) => (g.skills?.length || 0) > 0).map((g) => g.repo);
257
+ setExpandedRepos(new Set(withSkills.slice(0, 6)));
258
+
259
+ if (list.length > 0) {
260
+ const target = selectName
261
+ ? list.find(
262
+ (s) =>
263
+ s.name === selectName &&
264
+ (!selectRepo || s.repo === selectRepo),
265
+ )
266
+ : list[0];
220
267
  if (target) {
221
268
  setSelectedSkill(target);
222
- const skillDetails = await fetchSkill(target.name);
269
+ const skillDetails = await fetchSkill(target.name, target.repo);
223
270
  setSkillContent(skillDetails.content || '');
224
271
  }
272
+ } else {
273
+ setSelectedSkill(null);
274
+ setSkillContent('');
225
275
  }
226
276
  } catch (err: unknown) {
227
- setError((err as Error).message || 'Failed to fetch active skills list.');
277
+ setError((err as Error).message || 'Failed to fetch skills by repo.');
228
278
  } finally {
229
279
  setLoading(false);
230
280
  }
@@ -235,21 +285,8 @@ export default function SkillsPage() {
235
285
  void fetchSkillsList();
236
286
  }, []);
237
287
 
238
- // ─── Lifecycle: recompute when selectedSkill or content changes ─────────────
239
- useEffect(() => {
240
- if (selectedSkill && skillContent) {
241
- setLifecycleVersion(extractVersion(skillContent));
242
- setChangelogEntries(parseChangelog(skillContent));
243
- setHealthChecks(computeHealthChecks(selectedSkill, skillContent));
244
- }
245
- }, [selectedSkill, skillContent]);
246
288
 
247
- // ─── Automation: generate suggestions when skills list changes ──────────────
248
- useEffect(() => {
249
- if (skills.length > 0) {
250
- setImprovementSuggestions(generateSuggestions(skills));
251
- }
252
- }, [skills]);
289
+
253
290
 
254
291
  const handleSelectSkill = async (skill: SkillItem) => {
255
292
  setSelectedSkill(skill);
@@ -259,7 +296,7 @@ export default function SkillsPage() {
259
296
  setPreviewFile(null);
260
297
  setExpandedSubDirs({});
261
298
  try {
262
- const details = await fetchSkill(skill.name);
299
+ const details = await fetchSkill(skill.name, skill.repo);
263
300
  setSkillContent(details.content || '');
264
301
  } catch (err: unknown) {
265
302
  setError((err as Error).message || `Failed to fetch rules for skill "${skill.name}".`);
@@ -274,8 +311,10 @@ export default function SkillsPage() {
274
311
  setError(null);
275
312
  setSuccess(null);
276
313
  try {
277
- await saveSkill(selectedSkill.name, skillContent);
278
- setSuccess(`Skill "${selectedSkill.name}" rules sheet saved successfully!`);
314
+ await saveSkill(selectedSkill.name, skillContent, selectedSkill.repo);
315
+ setSuccess(
316
+ `Skill "${selectedSkill.name}"${selectedSkill.repo ? ` (${selectedSkill.repo})` : ''} saved successfully!`,
317
+ );
279
318
  setTimeout(() => setSuccess(null), 5000);
280
319
  } catch (err: unknown) {
281
320
  setError((err as Error).message || 'Failed to save skill changes.');
@@ -300,11 +339,16 @@ export default function SkillsPage() {
300
339
  };
301
340
 
302
341
  const handleDeleteSkill = async (skill: SkillItem) => {
303
- if (!window.confirm(`Are you sure you want to permanently delete the skill "${skill.name}" and all its rules sheets?`)) return;
342
+ if (
343
+ !window.confirm(
344
+ `Delete skill "${skill.name}"${skill.repo ? ` from ${skill.repo}` : ''} permanently?`,
345
+ )
346
+ )
347
+ return;
304
348
  setError(null);
305
349
  setSuccess(null);
306
350
  try {
307
- await deleteSkill(skill.name);
351
+ await deleteSkill(skill.name, skill.repo);
308
352
  setSuccess(`Skill "${skill.name}" successfully deleted.`);
309
353
  setTimeout(() => setSuccess(null), 5000);
310
354
  void fetchSkillsList();
@@ -318,9 +362,23 @@ export default function SkillsPage() {
318
362
  if (!newSkillName.trim()) return;
319
363
  setError(null);
320
364
  setSuccess(null);
365
+
366
+ // Prefer selected skill's repo, else first repo with a path
367
+ const targetRepo =
368
+ selectedSkill?.repo ||
369
+ repoGroups.find((g) => g.ok && g.path)?.repo ||
370
+ '';
371
+ if (!targetRepo) {
372
+ setError('No project repo available to create a skill in. Register a project first.');
373
+ return;
374
+ }
321
375
 
322
376
  // Normalize naming: alphanumeric and hyphens
323
377
  const cleanName = newSkillName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
378
+ if (cleanName.startsWith('tr-') || cleanName === 'total-recall') {
379
+ setError('Cannot create Total Recall system skills here.');
380
+ return;
381
+ }
324
382
 
325
383
  const initialContent = `---
326
384
  type: skill
@@ -334,7 +392,7 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
334
392
  `;
335
393
 
336
394
  try {
337
- await saveSkill(cleanName, initialContent);
395
+ await saveSkill(cleanName, initialContent, targetRepo);
338
396
  setNewSkillName('');
339
397
  setNewSkillDesc('');
340
398
  setShowCreateForm(false);
@@ -402,22 +460,16 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
402
460
 
403
461
  // ─── Lifecycle tab handlers ─────────────────────────────────────────────────
404
462
 
405
- const handleToggleSubDir = (dir: string) => {
463
+ const handleToggleSubDir = async (dir: string) => {
406
464
  setExpandedSubDirs(prev => ({ ...prev, [dir]: !prev[dir] }));
407
- // Generate synthetic sub-file entries from skill metadata
408
- if (!subDirFiles[dir]) {
409
- const syntheticFiles: SubFileEntry[] = [];
410
- if (dir === 'scripts') {
411
- syntheticFiles.push({ name: 'enforce-skill-optimization.mjs', size: '4.2 KB' });
412
- syntheticFiles.push({ name: 'scaffold.mjs', size: '2.1 KB' });
413
- } else if (dir === 'references') {
414
- syntheticFiles.push({ name: 'ARCHITECTURE.md', size: '8.5 KB' });
415
- } else if (dir === 'evals') {
416
- syntheticFiles.push({ name: 'evals.json', size: '1.3 KB' });
417
- } else if (dir === 'subagents') {
418
- syntheticFiles.push({ name: 'agent-config.json', size: '0.9 KB' });
465
+ if (!subDirFiles[dir] && selectedSkill) {
466
+ try {
467
+ const files = await fetchSkillFiles(selectedSkill.name, dir, selectedSkill.repo);
468
+ setSubDirFiles(prev => ({ ...prev, [dir]: files }));
469
+ } catch (err) {
470
+ console.error('Failed to fetch skill files', err);
471
+ setSubDirFiles(prev => ({ ...prev, [dir]: [] }));
419
472
  }
420
- setSubDirFiles(prev => ({ ...prev, [dir]: syntheticFiles }));
421
473
  }
422
474
  };
423
475
 
@@ -482,48 +534,170 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
482
534
  // ─── Tab configuration ────────────────────────────────────────────────────────
483
535
 
484
536
  const tabs: { id: TabId; label: string; icon: string }[] = [
485
- { id: 'active', label: 'Active Skills Registry', icon: '📂' },
537
+ { id: 'active', label: 'Skills by repo', icon: '📂' },
486
538
  { id: 'registry', label: 'skills.sh Registry Hub', icon: '✨' },
487
539
  { id: 'lifecycle', label: 'Lifecycle & Versioning', icon: '📋' },
488
540
  { id: 'network', label: 'P2P Skills Network', icon: '🌐' },
489
541
  { id: 'automation', label: 'Auto-Improvement', icon: '⚡' },
490
542
  ];
491
543
 
544
+ const skillKey = (s: SkillItem) => s.id || `${s.repo || '_'}::${s.name}`;
545
+ const isSkillSelected = (s: SkillItem) =>
546
+ selectedSkill?.name === s.name && (selectedSkill?.repo || '') === (s.repo || '');
547
+
548
+ const toggleRepo = (repo: string) => {
549
+ setExpandedRepos((prev) => {
550
+ const next = new Set(prev);
551
+ if (next.has(repo)) next.delete(repo);
552
+ else next.add(repo);
553
+ return next;
554
+ });
555
+ };
556
+
557
+ const renderRepoSkillList = (prefix: string) => (
558
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8, flex: 1, overflowY: 'auto' }}>
559
+ {repoGroups.length === 0 && (
560
+ <p style={{ fontSize: 12, color: 'var(--text-tertiary)', padding: 8 }}>
561
+ No project repos with skills found.
562
+ </p>
563
+ )}
564
+ {repoGroups.map((group) => {
565
+ const open = expandedRepos.has(group.repo);
566
+ const count = group.skills?.length || 0;
567
+ return (
568
+ <div key={group.repo} style={{ borderBottom: '1px solid var(--border)' }}>
569
+ <button
570
+ type="button"
571
+ onClick={() => toggleRepo(group.repo)}
572
+ style={{
573
+ width: '100%',
574
+ display: 'flex',
575
+ justifyContent: 'space-between',
576
+ alignItems: 'center',
577
+ gap: 8,
578
+ padding: '10px 8px',
579
+ background: 'transparent',
580
+ border: 'none',
581
+ color: 'inherit',
582
+ cursor: 'pointer',
583
+ textAlign: 'left',
584
+ }}
585
+ >
586
+ <span style={{ fontSize: 12, fontWeight: 700 }}>
587
+ {open ? '▾' : '▸'} {group.repo}
588
+ </span>
589
+ <span
590
+ style={{
591
+ fontSize: 10,
592
+ fontWeight: 600,
593
+ padding: '2px 8px',
594
+ borderRadius: 99,
595
+ background: count ? 'rgba(59,130,246,0.15)' : 'rgba(148,163,184,0.1)',
596
+ color: count ? '#93c5fd' : 'var(--text-tertiary)',
597
+ }}
598
+ >
599
+ {count}
600
+ </span>
601
+ </button>
602
+ {open && (
603
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, padding: '0 4px 10px' }}>
604
+ {count === 0 ? (
605
+ <div style={{ fontSize: 11, color: 'var(--text-tertiary)', padding: '4px 8px' }}>
606
+ {group.error || 'No app skills (TR skills hidden)'}
607
+ </div>
608
+ ) : (
609
+ group.skills.map((skill) => {
610
+ const active = isSkillSelected(skill);
611
+ return (
612
+ <div
613
+ key={skillKey(skill)}
614
+ id={`${prefix}-skill-${skill.repo}-${skill.name}`}
615
+ onClick={() => void handleSelectSkill(skill)}
616
+ style={{
617
+ background: active ? 'var(--accent-muted)' : 'var(--bg-tertiary)',
618
+ border: active ? '1px solid var(--accent)' : '1px solid var(--border)',
619
+ padding: '10px 12px',
620
+ borderRadius: 8,
621
+ cursor: 'pointer',
622
+ transition: 'all 0.15s ease',
623
+ }}
624
+ >
625
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
626
+ <span
627
+ style={{
628
+ fontSize: 13,
629
+ fontWeight: active ? 600 : 500,
630
+ color: active ? 'var(--accent)' : 'var(--text-primary)',
631
+ }}
632
+ >
633
+ {skill.name}
634
+ </span>
635
+ <button
636
+ type="button"
637
+ onClick={(e) => {
638
+ e.stopPropagation();
639
+ void handleDeleteSkill(skill);
640
+ }}
641
+ style={{
642
+ background: 'none',
643
+ border: 'none',
644
+ color: 'var(--text-tertiary)',
645
+ fontSize: 12,
646
+ cursor: 'pointer',
647
+ padding: '2px 6px',
648
+ }}
649
+ title="Delete skill"
650
+ >
651
+ ✖
652
+ </button>
653
+ </div>
654
+ {skill.source && (
655
+ <div style={{ fontSize: 10, color: 'var(--text-tertiary)', marginTop: 4 }}>
656
+ {skill.source}
657
+ </div>
658
+ )}
659
+ {skill.subSkills && skill.subSkills.length > 0 && (
660
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 6 }}>
661
+ {skill.subSkills.slice(0, 6).map((sd) => (
662
+ <span
663
+ key={sd}
664
+ className="badge"
665
+ style={{
666
+ fontSize: 9,
667
+ padding: '1px 5px',
668
+ background: 'var(--bg-elevated)',
669
+ color: 'var(--text-secondary)',
670
+ border: '1px solid var(--border)',
671
+ }}
672
+ >
673
+ {sd}
674
+ </span>
675
+ ))}
676
+ </div>
677
+ )}
678
+ </div>
679
+ );
680
+ })
681
+ )}
682
+ </div>
683
+ )}
684
+ </div>
685
+ );
686
+ })}
687
+ </div>
688
+ );
689
+
492
690
  // ─── Render helpers ─────────────────────────────────────────────────────────
493
691
 
494
692
  const renderLifecycleTab = () => (
495
693
  <div style={{ display: 'flex', flex: 1, gap: 24, minHeight: 0, overflow: 'hidden', paddingBottom: 16 }}>
496
- {/* Left: Skill selector (reused) */}
497
- <div style={{ width: 260, display: 'flex', flexDirection: 'column', gap: 16, flexShrink: 0, overflowY: 'auto' }}>
694
+ {/* Left: Skill selector by repo */}
695
+ <div style={{ width: 280, display: 'flex', flexDirection: 'column', gap: 16, flexShrink: 0, overflowY: 'auto' }}>
498
696
  <div className="card" style={{ padding: 16, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', flex: 1, display: 'flex', flexDirection: 'column' }}>
499
697
  <h3 style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 8, marginBottom: 12 }}>
500
- Select Skill
698
+ Skills by repo
501
699
  </h3>
502
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6, flex: 1, overflowY: 'auto' }}>
503
- {skills.map(skill => {
504
- const isActive = selectedSkill?.name === skill.name;
505
- return (
506
- <div
507
- key={skill.name}
508
- id={`lifecycle-skill-${skill.name}`}
509
- onClick={() => void handleSelectSkill(skill)}
510
- style={{
511
- background: isActive ? 'var(--accent-muted)' : 'var(--bg-tertiary)',
512
- border: isActive ? '1px solid var(--accent)' : '1px solid var(--border)',
513
- padding: '10px 12px',
514
- borderRadius: 8,
515
- cursor: 'pointer',
516
- transition: 'all 0.15s ease',
517
- fontSize: 13,
518
- fontWeight: isActive ? 600 : 400,
519
- color: isActive ? 'var(--accent)' : 'var(--text-primary)',
520
- }}
521
- >
522
- {skill.name}
523
- </div>
524
- );
525
- })}
526
- </div>
700
+ {renderRepoSkillList('lifecycle')}
527
701
  </div>
528
702
  </div>
529
703
 
@@ -536,6 +710,11 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
536
710
  <div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
537
711
  <h3 id="lifecycle-version-title" style={{ fontSize: 16, fontWeight: 600, color: 'var(--text-primary)', margin: 0 }}>
538
712
  {selectedSkill.name}
713
+ {selectedSkill.repo ? (
714
+ <span style={{ fontSize: 12, fontWeight: 500, color: 'var(--text-tertiary)', marginLeft: 8 }}>
715
+ · {selectedSkill.repo}
716
+ </span>
717
+ ) : null}
539
718
  </h3>
540
719
  <span
541
720
  id="lifecycle-version-badge"
@@ -1254,8 +1433,8 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
1254
1433
  <div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
1255
1434
  <div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
1256
1435
  <div>
1257
- <h1>Sovereign Skills Manager</h1>
1258
- <p>Read, write, edit, and organize system skill sheets and nested sub-skills dynamically</p>
1436
+ <h1>Skills Manager</h1>
1437
+ <p>Track, edit, and deploy agent skills across any repo — registry-based, no hardcoded hosts</p>
1259
1438
  </div>
1260
1439
 
1261
1440
  <div style={{ display: 'flex', gap: 10 }}>
@@ -1304,7 +1483,7 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
1304
1483
  {/* Creation Modal Form Overlay */}
1305
1484
  {showCreateForm && (
1306
1485
  <div className="card" style={{ marginBottom: 24, padding: 24, background: 'rgba(18, 18, 26, 0.9)', border: '1px solid var(--border)', backdropFilter: 'blur(10px)' }}>
1307
- <h3 style={{ fontSize: 14, fontWeight: 600, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 14 }}>➕ Create New Sovereign Skill Capability</h3>
1486
+ <h3 style={{ fontSize: 14, fontWeight: 600, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 14 }}>➕ Create New Skill</h3>
1308
1487
  <form onSubmit={handleCreateSkill} style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'flex-end' }}>
1309
1488
  <div style={{ flex: '1 1 200px', display: 'flex', flexDirection: 'column', gap: 6 }}>
1310
1489
  <label htmlFor="new_name" style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Skill Name (kebab-case)</label>
@@ -1368,70 +1547,16 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
1368
1547
  /* Original split layout: Active skills editor */
1369
1548
  <div style={{ display: 'flex', flex: 1, gap: 24, minHeight: 0, overflow: 'hidden', paddingBottom: 16 }}>
1370
1549
 
1371
- {/* Left column: Skills Sidebar */}
1372
- <div style={{ width: 280, display: 'flex', flexDirection: 'column', gap: 16, flexShrink: 0, overflowY: 'auto' }}>
1550
+ {/* Left column: Skills by repo */}
1551
+ <div style={{ width: 300, display: 'flex', flexDirection: 'column', gap: 16, flexShrink: 0, overflowY: 'auto' }}>
1373
1552
  <div className="card" style={{ padding: 16, background: 'rgba(18, 18, 26, 0.5)', border: '1px solid var(--border)', flex: 1, display: 'flex', flexDirection: 'column' }}>
1374
- <h3 style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 8, marginBottom: 12 }}>
1375
- Active Skills Registry
1553
+ <h3 style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 8, marginBottom: 8 }}>
1554
+ Skills by repo
1376
1555
  </h3>
1377
-
1378
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8, flex: 1, overflowY: 'auto' }}>
1379
- {skills.map((skill) => {
1380
- const isActive = selectedSkill?.name === skill.name;
1381
- return (
1382
- <div
1383
- key={skill.name}
1384
- onClick={() => void handleSelectSkill(skill)}
1385
- style={{
1386
- background: isActive ? 'var(--accent-muted)' : 'var(--bg-tertiary)',
1387
- border: isActive ? '1px solid var(--accent)' : '1px solid var(--border)',
1388
- padding: '12px 14px',
1389
- borderRadius: 8,
1390
- cursor: 'pointer',
1391
- transition: 'all 0.15s ease',
1392
- position: 'relative'
1393
- }}
1394
- >
1395
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
1396
- <span style={{ fontSize: 13, fontWeight: 600, color: isActive ? 'var(--accent)' : 'var(--text-primary)' }}>
1397
- {skill.name}
1398
- </span>
1399
- {skill.name !== 'total-recall' && (
1400
- <button
1401
- onClick={(e) => {
1402
- e.stopPropagation();
1403
- void handleDeleteSkill(skill);
1404
- }}
1405
- style={{
1406
- background: 'none',
1407
- border: 'none',
1408
- color: 'var(--text-tertiary)',
1409
- fontSize: 12,
1410
- cursor: 'pointer',
1411
- padding: '2px 6px'
1412
- }}
1413
- onMouseEnter={(e) => (e.currentTarget.style.color = '#ff6b6b')}
1414
- onMouseLeave={(e) => (e.currentTarget.style.color = 'var(--text-tertiary)')}
1415
- >
1416
- ✖
1417
- </button>
1418
- )}
1419
- </div>
1420
-
1421
- {/* Sub Skills tags */}
1422
- {skill.subSkills && skill.subSkills.length > 0 && (
1423
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 8 }}>
1424
- {skill.subSkills.map(sd => (
1425
- <span key={sd} className="badge" style={{ fontSize: 9, padding: '1px 5px', background: 'var(--bg-elevated)', color: 'var(--text-secondary)', border: '1px solid var(--border)' }}>
1426
- 🧩 {sd}
1427
- </span>
1428
- ))}
1429
- </div>
1430
- )}
1431
- </div>
1432
- );
1433
- })}
1434
- </div>
1556
+ <p style={{ fontSize: 10, color: 'var(--text-tertiary)', margin: '0 0 10px', lineHeight: 1.4 }}>
1557
+ Project skills only · total-recall / tr-* hidden · {skills.length} skill{skills.length === 1 ? '' : 's'}
1558
+ </p>
1559
+ {renderRepoSkillList('active')}
1435
1560
  </div>
1436
1561
  </div>
1437
1562