total-recall-brain 3.40.0 → 3.40.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 (187) hide show
  1. package/README.md +152 -41
  2. package/bin/total-recall.mjs +16 -76
  3. package/docs/reference/cli-reference.md +10 -10
  4. package/extension/background.js +6 -2
  5. package/extension/config.js +8 -0
  6. package/extension/lib/brain-client.js +29 -4
  7. package/extension/options/options.html +1 -0
  8. package/extension/sidepanel/sidepanel.html +1 -0
  9. package/frontend/dist/assets/{index-CvQRdT2j.css → index-AGUes-VG.css} +1 -1
  10. package/frontend/dist/assets/index-Bt5iVPZj.js +5618 -0
  11. package/frontend/dist/index.html +2 -2
  12. package/frontend/src/api/keys.ts +9 -2
  13. package/frontend/src/api/plugins.ts +4 -0
  14. package/frontend/src/components/plugins/card/CardEngine.tsx +2 -1
  15. package/frontend/src/components/plugins/card/card.css +1 -1
  16. package/frontend/src/pages/IntegrationsPage.tsx +315 -8
  17. package/frontend/src/pages/PluginsPage.tsx +532 -87
  18. package/package.json +6 -5
  19. package/scaffold/.agent/skills/meta-harness/references/optimized/original.md +1 -1
  20. package/scaffold/.agent/skills/meta-harness/references/optimized/section-09.md +1 -1
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +10 -10
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -6
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +0 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/src/browser-session.mjs +309 -4
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +44 -34
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/src/google-oauth.mjs +6 -4
  27. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-catalog.mjs +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-rotation-recipes.mjs +118 -4
  29. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/auth.mjs +2 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +3 -3
  31. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +1 -1
  32. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-rotate.mjs +24 -6
  33. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +164 -123
  34. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +65 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +76 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +19 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +290 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +446 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +152 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +71 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +36 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +35 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +175 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +18 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/code-quality/LICENSE +21 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/code-quality/README.md +84 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +79 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/check.mjs +6 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/report.mjs +6 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +27 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/snapshot.mjs +163 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +757 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/config.schema.json +86 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/detect.mjs +89 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +145 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +210 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/state.mjs +20 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +2869 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +28 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +186 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +18 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +272 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +129 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/LICENSE +21 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/README.md +73 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/cli.mjs +375 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/echo-tool.mjs +23 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/group.mjs +262 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/project-id.mjs +74 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +2869 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +31 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +221 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/schemas/cli_command.schema.json +107 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/skills/composable-cli/SKILL.md +63 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +313 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compile.mjs +19 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compose.mjs +144 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/contract.mjs +110 -0
  79. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/library.mjs +23 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/registry.mjs +192 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/resolve.mjs +221 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/risk.mjs +126 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/ssss-documents.mjs +29 -0
  84. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +18 -0
  85. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +273 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/embeddings/LICENSE +21 -0
  87. package/scaffold/.agent/skills/total-recall/plugins/embeddings/README.md +20 -0
  88. package/scaffold/.agent/skills/total-recall/plugins/embeddings/cli.mjs +310 -0
  89. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +2826 -0
  90. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +11 -0
  91. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +62 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +297 -0
  93. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/LICENSE +21 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +43 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/cli.mjs +258 -0
  96. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4005 -0
  97. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +20 -0
  98. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +125 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +18 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +278 -0
  101. package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +9 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +6 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4721 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -21
  105. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +14 -1
  106. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +10 -0
  107. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/headscale-client.mjs +5 -1
  108. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +4 -13
  109. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-access-verify.mjs +22 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-files.mjs +18 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +2 -0
  112. package/scaffold/.agent/skills/total-recall/plugins/mesh/test-support/memory-root.mjs +14 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/openwiki/LICENSE +21 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +35 -0
  115. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +124 -0
  116. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4097 -0
  117. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +12 -0
  118. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +165 -0
  119. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +127 -0
  120. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +18 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +279 -0
  122. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +539 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/security/LICENSE +21 -0
  124. package/scaffold/.agent/skills/total-recall/plugins/security/README.md +67 -0
  125. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +932 -0
  126. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3256 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +25 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +297 -0
  129. package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +4 -0
  130. package/scaffold/.agent/skills/total-recall/plugins/security/skills/security/SKILL.md +37 -0
  131. package/scaffold/.agent/skills/total-recall/plugins/security/src/android.mjs +21 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/security/src/devices.mjs +94 -0
  133. package/scaffold/.agent/skills/total-recall/plugins/security/src/machines.mjs +164 -0
  134. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +41 -0
  135. package/scaffold/.agent/skills/total-recall/plugins/security/src/windows.mjs +42 -0
  136. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +18 -0
  137. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +95 -0
  138. package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +98 -0
  139. package/scaffold/.agent/skills/total-recall/references/auth.md +1 -1
  140. package/src/cli/backup.mjs +71 -3
  141. package/src/cli/brief.mjs +33 -9
  142. package/src/cli/command.mjs +2 -7
  143. package/src/cli/config.mjs +1 -0
  144. package/src/cli/connect.mjs +5 -0
  145. package/src/cli/context.mjs +2 -1
  146. package/src/cli/daemon.mjs +36 -1
  147. package/src/cli/doctor.mjs +6 -0
  148. package/src/cli/harness.mjs +3 -0
  149. package/src/cli/help.mjs +26 -0
  150. package/src/cli/init.mjs +4 -4
  151. package/src/cli/memory-entry.mjs +1 -0
  152. package/src/cli/memory-init.mjs +2 -0
  153. package/src/cli/plugin/index.mjs +11 -0
  154. package/src/cli/plugin/install.mjs +2 -1
  155. package/src/cli/rebuild.mjs +3 -1
  156. package/src/cli/startup.mjs +2 -1
  157. package/src/cli/update.mjs +19 -0
  158. package/src/core/automatic-brain-updates.mjs +67 -0
  159. package/src/core/brain-updates.mjs +21 -2
  160. package/src/core/cli-commands.mjs +71 -0
  161. package/src/core/cli-maintenance.mjs +45 -0
  162. package/src/core/cli-quick-reference.mjs +20 -0
  163. package/src/core/cli-semantic-index.mjs +722 -0
  164. package/src/core/context-compiler.mjs +27 -1
  165. package/src/core/crons.mjs +2 -13
  166. package/src/core/daemon-control.mjs +6 -4
  167. package/src/core/daemon-loop.mjs +12 -8
  168. package/src/core/default-plugins.mjs +5 -0
  169. package/src/core/global-brain-updates.mjs +39 -0
  170. package/src/core/mesh-brain-updates.mjs +30 -0
  171. package/src/core/meta-harness.mjs +108 -0
  172. package/src/core/plugin-dependencies.mjs +2 -1
  173. package/src/core/plugin-loader.mjs +2 -1
  174. package/src/core/plugin-runner.mjs +12 -4
  175. package/src/core/plugin-setup.mjs +20 -0
  176. package/src/core/plugin-store.mjs +22 -4
  177. package/src/core/plugin-tasks.mjs +1 -1
  178. package/src/core/project-brain.mjs +2 -0
  179. package/src/core/project-status.mjs +64 -0
  180. package/src/core/required-plugins.mjs +46 -0
  181. package/src/core/scaffold-plugins.mjs +17 -3
  182. package/src/core/source-brain-updates.mjs +47 -0
  183. package/src/core/startup-health.mjs +5 -3
  184. package/src/core/surface.mjs +44 -10
  185. package/src/server/routes/extension.mjs +53 -8
  186. package/templates/onboarding-interview.md +1 -1
  187. package/frontend/dist/assets/index-SOVUlZqm.js +0 -5596
@@ -13,6 +13,7 @@ import {
13
13
  deployPluginToStore,
14
14
  runPluginCommand,
15
15
  fetchPluginReadme,
16
+ setPluginActivation,
16
17
  type PluginInfo,
17
18
  type BundledPlugin,
18
19
  type PeersResponse,
@@ -43,6 +44,41 @@ const PEER_STATUS_LABEL: Record<PeerNode["status"], string> = {
43
44
  const mono: CSSProperties = { fontFamily: "var(--font-mono)", fontSize: "12px" }
44
45
  const muted: CSSProperties = { color: "var(--text-secondary)", fontSize: "13px", lineHeight: 1.5 }
45
46
 
47
+ const DEFAULT_CORE_PLUGINS = new Set([
48
+ "auth",
49
+ "mesh",
50
+ "openwiki",
51
+ "composable-cli",
52
+ "cli-agents",
53
+ "code-quality",
54
+ "security",
55
+ "embeddings",
56
+ "git-sentinel"
57
+ ])
58
+
59
+ const AGENT_RECIPE_PROMPT = `You are my coding agent. Author a bespoke Total Recall plugin for [DOMAIN] following the Total Recall Plugin Recipe:
60
+
61
+ 1. Scaffold:
62
+ total-recall scaffold-plugin <id> --title "<Name>" --description "<Description>"
63
+
64
+ 2. Manifest (plugin.json):
65
+ - id: lowercase kebab-case
66
+ - name: Clear Title
67
+ - branding: { name, color, badge }
68
+ - cli: command name and subcommands with effect (read/write)
69
+ - ui: elements declaring the Web Component tag <tr-plugin-<id>-card>
70
+ - secrets: declare required keys, never store plain text
71
+
72
+ 3. Web Component (ui/card.js):
73
+ - Register <tr-plugin-<id>-card>
74
+ - Standardized layout with clean borders and >=44px touch targets
75
+ - Authentic purpose-built domain tools (NO fake audio equalizers or fake skins)
76
+ - Real interactive controls executing plugin subcommands via host.run(subcommand, args)
77
+
78
+ 4. Distribution:
79
+ - Private: keep in .agent/skills/total-recall/plugins/<id>
80
+ - Store: publish via total-recall deploy-store <id>`
81
+
46
82
  function resolvePluginBranding(p: { id: string; branding?: { icon?: string | null; color?: string | null; badge?: string | null } }) {
47
83
  const custom = p.branding || {}
48
84
  return {
@@ -334,6 +370,17 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
334
370
  const [output, setOutput] = useState<{ text: string; ok: boolean } | null>(null)
335
371
  const [readme, setReadme] = useState("")
336
372
 
373
+ // Agent Plugin Recipe & Workspace State
374
+ const [recipeOpen, setRecipeOpen] = useState(false)
375
+ const [viewMode, setViewMode] = useState<"workspace" | "all">("workspace")
376
+ const [workspaceActiveIds, setWorkspaceActiveIds] = useState<Set<string>>(() => {
377
+ try {
378
+ const stored = localStorage.getItem("tr_workspace_plugins")
379
+ if (stored) return new Set(JSON.parse(stored))
380
+ } catch {}
381
+ return new Set<string>()
382
+ })
383
+
337
384
  // Branding Customization Modal state
338
385
  const [brandingOpen, setBrandingOpen] = useState(false)
339
386
  const [brandingPlugin, setBrandingPlugin] = useState<PluginInfo | null>(null)
@@ -420,6 +467,52 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
420
467
  return [p.id, p.name, p.description, ...p.use_cases].some((v) => v.toLowerCase().includes(q))
421
468
  }, [query, useCase])
422
469
 
470
+ const isWorkspaceCard = useCallback((p: { id: string; scope?: string }) => {
471
+ if (p.scope === "project") return true
472
+ if (DEFAULT_CORE_PLUGINS.has(p.id)) return true
473
+ if (workspaceActiveIds.has(p.id)) return true
474
+ return false
475
+ }, [workspaceActiveIds])
476
+
477
+ const addToWorkspace = async (p: { id: string; name: string }) => {
478
+ setBusy(p.id)
479
+ try {
480
+ const next = new Set(workspaceActiveIds)
481
+ next.add(p.id)
482
+ setWorkspaceActiveIds(next)
483
+ try { localStorage.setItem("tr_workspace_plugins", JSON.stringify([...next])) } catch {}
484
+ await setPluginActivation(p.id, true)
485
+ setAlert({ type: "success", message: `Added ${p.name} to workspace dashboard` })
486
+ await loadLocal()
487
+ } catch (err: any) {
488
+ setAlert({ type: "error", message: err?.message || `Could not activate ${p.name}` })
489
+ } finally {
490
+ setBusy(null)
491
+ }
492
+ }
493
+
494
+ const removeFromWorkspace = async (p: { id: string; name: string }) => {
495
+ setBusy(p.id)
496
+ try {
497
+ const next = new Set(workspaceActiveIds)
498
+ next.delete(p.id)
499
+ setWorkspaceActiveIds(next)
500
+ try { localStorage.setItem("tr_workspace_plugins", JSON.stringify([...next])) } catch {}
501
+ await setPluginActivation(p.id, false)
502
+ setAlert({ type: "success", message: `Moved ${p.name} from workspace to Plugin Store` })
503
+ await loadLocal()
504
+ } catch (err: any) {
505
+ setAlert({ type: "error", message: err?.message || `Could not deactivate ${p.name}` })
506
+ } finally {
507
+ setBusy(null)
508
+ }
509
+ }
510
+
511
+ const copyAgentPrompt = () => {
512
+ void navigator.clipboard?.writeText(AGENT_RECIPE_PROMPT)
513
+ setAlert({ type: "success", message: "Copied Agent Plugin Recipe prompt to clipboard" })
514
+ }
515
+
423
516
  const doInstall = async (source: string, opts: { link?: boolean; global?: boolean } = {}) => {
424
517
  setBusy(source)
425
518
  setAlert(null)
@@ -572,7 +665,68 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
572
665
  }
573
666
 
574
667
  const filteredInstalled = installed.filter(matches)
575
- const filteredBundled = bundled.filter(matches)
668
+ const myCards = filteredInstalled.filter(isWorkspaceCard)
669
+ const shownInstalled = viewMode === "workspace" ? myCards : filteredInstalled
670
+
671
+ const storeCatalog = useMemo(() => {
672
+ const map = new Map<string, {
673
+ id: string;
674
+ name: string;
675
+ version: string;
676
+ description: string;
677
+ use_cases: string[];
678
+ installed: boolean;
679
+ inWorkspace: boolean;
680
+ sourcePlugin?: PluginInfo;
681
+ branding?: { icon?: string | null; color?: string | null; badge?: string | null };
682
+ cli?: { command: string | null; subcommands?: Array<{ name: string; description?: string }> } | null;
683
+ tasks?: any[];
684
+ secrets?: any[];
685
+ }>()
686
+
687
+ // Add bundled plugins
688
+ for (const b of bundled) {
689
+ const inst = installed.find((i) => i.id === b.id)
690
+ map.set(b.id, {
691
+ id: b.id,
692
+ name: b.name,
693
+ version: b.version,
694
+ description: b.description,
695
+ use_cases: b.use_cases || [],
696
+ installed: b.installed || !!inst,
697
+ inWorkspace: inst ? isWorkspaceCard(inst) : false,
698
+ sourcePlugin: inst,
699
+ branding: b.branding || (inst ? inst.branding : undefined),
700
+ cli: b.cli || (inst ? inst.cli : null),
701
+ tasks: b.tasks || (inst ? inst.tasks : []),
702
+ secrets: inst?.secrets || []
703
+ })
704
+ }
705
+
706
+ // Add any installed plugins that aren't in bundled
707
+ for (const inst of installed) {
708
+ if (!map.has(inst.id)) {
709
+ map.set(inst.id, {
710
+ id: inst.id,
711
+ name: inst.name,
712
+ version: inst.version,
713
+ description: inst.description,
714
+ use_cases: inst.use_cases || [],
715
+ installed: true,
716
+ inWorkspace: isWorkspaceCard(inst),
717
+ sourcePlugin: inst,
718
+ branding: inst.branding,
719
+ cli: inst.cli,
720
+ tasks: inst.tasks,
721
+ secrets: inst.secrets || []
722
+ })
723
+ }
724
+ }
725
+
726
+ return Array.from(map.values())
727
+ }, [bundled, installed, isWorkspaceCard])
728
+
729
+ const filteredStoreCatalog = storeCatalog.filter(matches)
576
730
 
577
731
  const tabButton = (id: Tab, label: string, count?: number) => (
578
732
  <button
@@ -583,7 +737,7 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
583
737
  onClick={() => setTab(id)}
584
738
  >
585
739
  {label}
586
- {count !== undefined && <span style={{ opacity: 0.75, fontWeight: 500 }}>{count}</span>}
740
+ {count !== undefined && <span style={{ opacity: 0.75, fontWeight: 500, marginLeft: "4px" }}>{count}</span>}
587
741
  </button>
588
742
  )
589
743
 
@@ -593,13 +747,18 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
593
747
  <div>
594
748
  <h1>Plugins</h1>
595
749
  <p>
596
- Plugins deliver modular capabilities into Total Recall and SSSS apps — memory categories, agent context, commands, and design-token UI.
750
+ Modular capability system for Total Recall and SSSS apps — memory categories, agent context, commands, and design-token UI.
597
751
  Customizable branding, locked release status, and direct store deployment are managed on each card.
598
752
  </p>
599
753
  </div>
600
- <button className="btn btn-primary" onClick={() => { setInstallOpen(true); setAlert(null) }}>
601
- Install from source…
602
- </button>
754
+ <div style={{ display: "flex", gap: "8px" }}>
755
+ <button className="btn btn-ghost" onClick={() => setRecipeOpen(true)}>
756
+ Agent Recipe
757
+ </button>
758
+ <button className="btn btn-primary" onClick={() => { setInstallOpen(true); setAlert(null) }}>
759
+ Install from source…
760
+ </button>
761
+ </div>
603
762
  </div>
604
763
 
605
764
  {alert && (
@@ -612,8 +771,8 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
612
771
 
613
772
  <div style={{ display: "flex", justifyContent: "space-between", gap: "12px", flexWrap: "wrap", marginBottom: "20px" }}>
614
773
  <div role="tablist" style={{ display: "flex", gap: "8px" }}>
615
- {tabButton("installed", "Installed", installed.length)}
616
- {tabButton("bundled", "Bundled", bundled.length)}
774
+ {tabButton("installed", "My Cards", myCards.length)}
775
+ {tabButton("bundled", "Plugin Store (Bundled)", filteredStoreCatalog.length)}
617
776
  {tabButton("mesh", "On the mesh")}
618
777
  </div>
619
778
  <div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
@@ -627,96 +786,311 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
627
786
  </div>
628
787
 
629
788
  {tab === "installed" && (
630
- loading ? <div style={muted}>Loading…</div> :
631
- filteredInstalled.length === 0 ? (
632
- <Empty title={installed.length ? "No installed plugins match" : "No plugins installed"}>
633
- {!installed.length && <>Start with a <button className="btn btn-ghost btn-sm" onClick={() => setTab("bundled")}>bundled plugin</button> or see what your peers share.</>}
634
- </Empty>
635
- ) : (
636
- <div className="card-grid" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(min(100%, 360px), 1fr))", gap: "22px" }}>
637
- {filteredInstalled.map((p) => {
638
- return (
639
- <div
640
- key={`${p.scope}:${p.id}`}
641
- onContextMenu={(e) => handleContextMenu(p, e)}
642
- onTouchStart={(e) => handleTouchStart(p, e)}
643
- onTouchEnd={cancelLongPress}
644
- style={{ display: "flex", flexDirection: "column", gap: "8px" }}
645
- >
646
- <CardEngine plugin={p} />
647
- <div style={{ display: "flex", gap: "6px", alignItems: "center", padding: "0 4px", fontSize: "11px", color: "var(--text-tertiary)" }}>
648
- <span className="badge">{p.scope}</span>
649
- <span className="badge">{sourceLabel(p)}</span>
650
- {p.shared && <span className="badge badge-accent">shared</span>}
651
- {!p.valid && <span className="badge badge-error">invalid manifest</span>}
652
- {p.modified_since_install && <span className="badge badge-warning">changed since install</span>}
653
- </div>
789
+ loading ? <div style={muted}>Loading…</div> : (
790
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
791
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: "12px" }}>
792
+ <div>
793
+ <div style={{ fontWeight: 600, fontSize: "15px" }}>
794
+ {viewMode === "workspace" ? "Active Workspace Cards" : "All Installed Plugins"}
654
795
  </div>
655
- )
656
- })}
796
+ <div style={muted}>
797
+ {viewMode === "workspace"
798
+ ? "Core runtime foundation and activated repository cards. Optional tools live in the Plugin Store."
799
+ : "All plugin packages installed on this host."}
800
+ </div>
801
+ </div>
802
+ <div style={{ display: "flex", gap: "6px" }}>
803
+ <button
804
+ type="button"
805
+ className={`btn btn-xs ${viewMode === "workspace" ? "btn-primary" : "btn-ghost"}`}
806
+ onClick={() => setViewMode("workspace")}
807
+ >
808
+ Workspace ({myCards.length})
809
+ </button>
810
+ <button
811
+ type="button"
812
+ className={`btn btn-xs ${viewMode === "all" ? "btn-primary" : "btn-ghost"}`}
813
+ onClick={() => setViewMode("all")}
814
+ >
815
+ All Installed ({installed.length})
816
+ </button>
817
+ </div>
818
+ </div>
819
+
820
+ {shownInstalled.length === 0 ? (
821
+ <Empty title={viewMode === "workspace" ? "No workspace cards active" : "No plugins installed"}>
822
+ {viewMode === "workspace" ? (
823
+ <>
824
+ Explore the <button type="button" className="btn btn-ghost btn-sm" onClick={() => setTab("bundled")}>Plugin Store</button> to add capabilities or ask your agent to author one.
825
+ </>
826
+ ) : (
827
+ <>Start with a <button type="button" className="btn btn-ghost btn-sm" onClick={() => setTab("bundled")}>bundled plugin</button> or see what your peers share.</>
828
+ )}
829
+ </Empty>
830
+ ) : (
831
+ <div className="card-grid" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(min(100%, 360px), 1fr))", gap: "22px" }}>
832
+ {shownInstalled.map((p) => {
833
+ return (
834
+ <div
835
+ key={`${p.scope}:${p.id}`}
836
+ onContextMenu={(e) => handleContextMenu(p, e)}
837
+ onTouchStart={(e) => handleTouchStart(p, e)}
838
+ onTouchEnd={cancelLongPress}
839
+ style={{ display: "flex", flexDirection: "column", gap: "8px" }}
840
+ >
841
+ <CardEngine plugin={p} />
842
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "0 4px", fontSize: "11px", color: "var(--text-tertiary)", flexWrap: "wrap", gap: "6px" }}>
843
+ <div style={{ display: "flex", gap: "6px", alignItems: "center", flexWrap: "wrap" }}>
844
+ <span className="badge">{p.scope}</span>
845
+ <span className="badge">{sourceLabel(p)}</span>
846
+ {p.shared && <span className="badge badge-accent">shared</span>}
847
+ {!p.valid && <span className="badge badge-error">invalid manifest</span>}
848
+ {p.modified_since_install && <span className="badge badge-warning">changed since install</span>}
849
+ </div>
850
+ {!DEFAULT_CORE_PLUGINS.has(p.id) && (
851
+ <button
852
+ type="button"
853
+ className="btn btn-ghost btn-xs"
854
+ onClick={() => removeFromWorkspace(p)}
855
+ style={{ fontSize: "11px", color: "var(--text-secondary)" }}
856
+ title="Move back to Plugin Store"
857
+ >
858
+ Remove from Workspace
859
+ </button>
860
+ )}
861
+ </div>
862
+ </div>
863
+ )
864
+ })}
865
+ </div>
866
+ )}
657
867
  </div>
658
868
  )
659
869
  )}
660
870
 
661
871
  {tab === "bundled" && (
662
- loading ? <div style={muted}>Loading…</div> :
663
- filteredBundled.length === 0 ? <Empty title="No bundled plugins match" /> : (
664
- <>
665
- <p style={{ ...muted, marginBottom: "16px" }}>Shipped with this version of Total Recall, installable on any node.</p>
666
- <div className="card-grid">
667
- {filteredBundled.map((p) => {
668
- const branding = resolvePluginBranding(p)
669
- return (
670
- <div key={p.id} className="card" style={{ display: "flex", flexDirection: "column", gap: "10px", borderTop: `3px solid ${branding.color}` }}>
671
- <div style={{ display: "flex", justifyContent: "space-between", gap: "8px", alignItems: "flex-start" }}>
672
- <div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
673
- <div
674
- style={{
675
- width: "32px",
676
- height: "32px",
677
- borderRadius: "6px",
678
- background: `${branding.color}20`,
679
- border: `1px solid ${branding.color}40`,
680
- display: "flex",
681
- alignItems: "center",
682
- justifyContent: "center"
683
- }}
684
- >
685
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={branding.color} strokeWidth="2">
686
- <rect x="3" y="3" width="18" height="18" rx="4" />
687
- <circle cx="12" cy="12" r="3" fill={branding.color} />
688
- </svg>
872
+ loading ? <div style={muted}>Loading…</div> : (
873
+ <div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
874
+ {/* Agent Plugin Recipe Hero Banner */}
875
+ <div className="card" style={{
876
+ background: "linear-gradient(135deg, rgba(30, 41, 59, 0.7) 0%, rgba(15, 23, 42, 0.85) 100%)",
877
+ border: "1px solid rgba(99, 102, 241, 0.3)",
878
+ padding: "20px",
879
+ display: "flex",
880
+ flexDirection: "column",
881
+ gap: "14px"
882
+ }}>
883
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: "16px", flexWrap: "wrap" }}>
884
+ <div>
885
+ <div style={{ display: "flex", alignItems: "center", gap: "8px", marginBottom: "4px" }}>
886
+ <span className="badge" style={{ background: "rgba(99, 102, 241, 0.2)", color: "#818cf8", borderColor: "rgba(99, 102, 241, 0.4)", fontSize: "11px" }}>
887
+ AGENT RECIPE ARCHITECTURE
888
+ </span>
889
+ <span style={{ fontSize: "11px", color: "var(--text-tertiary)" }}>
890
+ Declarative Recipes · Private or Store
891
+ </span>
892
+ </div>
893
+ <h2 style={{ fontSize: "18px", fontWeight: 700, margin: 0, color: "#f8fafc" }}>
894
+ Build Bespoke Plugins with Your Coding Agent
895
+ </h2>
896
+ <p style={{ ...muted, margin: "6px 0 0 0", maxWidth: "640px" }}>
897
+ Total Recall plugins are not generated by closed system templates; your contextual coding agent (Antigravity, Claude, Codex, DSH) builds them from a declarative Recipe. Run them privately in your workspace or publish them to the Plugin Store.
898
+ </p>
899
+ </div>
900
+ <div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
901
+ <button
902
+ type="button"
903
+ className="btn btn-primary btn-sm"
904
+ onClick={() => setRecipeOpen(true)}
905
+ >
906
+ View Agent Recipe
907
+ </button>
908
+ <button
909
+ type="button"
910
+ className="btn btn-ghost btn-sm"
911
+ onClick={copyAgentPrompt}
912
+ >
913
+ Copy Agent Prompt
914
+ </button>
915
+ <button
916
+ type="button"
917
+ className="btn btn-ghost btn-sm"
918
+ onClick={() => { setInstallOpen(true); setAlert(null) }}
919
+ >
920
+ Install from source…
921
+ </button>
922
+ </div>
923
+ </div>
924
+ </div>
925
+
926
+ {filteredStoreCatalog.length === 0 ? (
927
+ <Empty title="No plugins match in store" />
928
+ ) : (
929
+ <div className="card-grid" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(min(100%, 340px), 1fr))", gap: "16px" }}>
930
+ {filteredStoreCatalog.map((item) => {
931
+ const branding = resolvePluginBranding(item)
932
+ return (
933
+ <div
934
+ key={item.id}
935
+ className="card"
936
+ style={{
937
+ display: "flex",
938
+ flexDirection: "column",
939
+ gap: "10px",
940
+ borderTop: `3px solid ${branding.color}`,
941
+ background: "var(--bg-secondary)"
942
+ }}
943
+ >
944
+ <div style={{ display: "flex", justifyContent: "space-between", gap: "8px", alignItems: "flex-start" }}>
945
+ <div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
946
+ <div
947
+ style={{
948
+ width: "34px",
949
+ height: "34px",
950
+ borderRadius: "8px",
951
+ background: `${branding.color}20`,
952
+ border: `1px solid ${branding.color}40`,
953
+ display: "flex",
954
+ alignItems: "center",
955
+ justifyContent: "center",
956
+ fontWeight: 700,
957
+ fontSize: "12px",
958
+ color: branding.color
959
+ }}
960
+ >
961
+ {branding.icon || item.name.slice(0, 2).toUpperCase()}
962
+ </div>
963
+ <div>
964
+ <div style={{ fontWeight: 600, fontSize: "14px", lineHeight: 1.2 }}>{item.name}</div>
965
+ <div style={{ ...mono, color: "var(--text-tertiary)", fontSize: "11px" }}>
966
+ {item.id} · v{item.version}
967
+ </div>
968
+ </div>
689
969
  </div>
690
- <div>
691
- <span style={{ fontWeight: 600, fontSize: "15px" }}>{p.name}</span>
692
- <div style={{ ...mono, color: "var(--text-tertiary)" }}>v{p.version}</div>
970
+ <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "4px" }}>
971
+ <span className="badge" style={{ background: `${branding.color}15`, color: branding.color, borderColor: `${branding.color}35`, fontSize: "10px" }}>
972
+ {branding.badge}
973
+ </span>
974
+ {item.inWorkspace && (
975
+ <span className="badge badge-success" style={{ fontSize: "10px" }}>
976
+ ✓ In Workspace
977
+ </span>
978
+ )}
693
979
  </div>
694
980
  </div>
695
- <span className="badge" style={{ background: `${branding.color}15`, color: branding.color }}>{branding.badge}</span>
696
- </div>
697
- <div style={muted}>{p.description}</div>
698
- <UseCaseChips useCases={p.use_cases} />
699
- <div style={{ ...muted, fontSize: "12px" }}>
700
- {[
701
- p.cli && `command: total-recall ${p.cli.command}`,
702
- p.tasks.length > 0 && `${p.tasks.length} scheduled task${p.tasks.length > 1 ? "s" : ""}`,
703
- p.categories.length > 0 && `${p.categories.length} memory categor${p.categories.length > 1 ? "ies" : "y"}`
704
- ].filter(Boolean).join(" · ")}
705
- </div>
706
- <div style={{ marginTop: "auto", display: "flex", gap: "8px" }}>
707
- {p.installed ? (
708
- <span className="badge badge-success">installed</span>
709
- ) : (
710
- <button className="btn btn-primary btn-sm" disabled={busy === p.id} onClick={() => doInstall(p.id)}>
711
- {busy === p.id ? "Installing…" : "Install"}
981
+
982
+ <div style={{ ...muted, fontSize: "12px", minHeight: "36px" }}>
983
+ {item.description}
984
+ </div>
985
+
986
+ <div style={{ display: "flex", gap: "4px", flexWrap: "wrap", fontSize: "10px" }}>
987
+ {item.cli && (
988
+ <span className="badge" style={{ fontFamily: "var(--font-mono)" }}>
989
+ CLI ({item.cli.subcommands?.length || 1})
990
+ </span>
991
+ )}
992
+ {item.tasks && item.tasks.length > 0 && (
993
+ <span className="badge" style={{ fontFamily: "var(--font-mono)" }}>
994
+ {item.tasks.length} {item.tasks.length === 1 ? "task" : "tasks"}
995
+ </span>
996
+ )}
997
+ {item.secrets && item.secrets.length > 0 && (
998
+ <span className="badge" style={{ fontFamily: "var(--font-mono)" }}>
999
+ {item.secrets.length} {item.secrets.length === 1 ? "secret" : "secrets"}
1000
+ </span>
1001
+ )}
1002
+ {DEFAULT_CORE_PLUGINS.has(item.id) && (
1003
+ <span className="badge" style={{ background: "rgba(59, 130, 246, 0.15)", color: "#60a5fa" }}>
1004
+ Core Bundle
1005
+ </span>
1006
+ )}
1007
+ </div>
1008
+
1009
+ <UseCaseChips useCases={item.use_cases} />
1010
+
1011
+ <div style={{ marginTop: "auto", display: "flex", justifyContent: "space-between", alignItems: "center", gap: "8px", paddingTop: "8px", borderTop: "1px solid var(--border)" }}>
1012
+ <button
1013
+ type="button"
1014
+ className="btn btn-ghost btn-sm"
1015
+ onClick={() => {
1016
+ if (item.sourcePlugin) {
1017
+ openDetail(item.sourcePlugin, "preview")
1018
+ } else {
1019
+ openDetail({
1020
+ id: item.id,
1021
+ name: item.name,
1022
+ version: item.version,
1023
+ description: item.description,
1024
+ author: null,
1025
+ license: null,
1026
+ homepage: null,
1027
+ use_cases: item.use_cases,
1028
+ valid: true,
1029
+ errors: [],
1030
+ scope: "global",
1031
+ linked: false,
1032
+ dir: "",
1033
+ shared: false,
1034
+ mesh_shared: false,
1035
+ share_url: null,
1036
+ source: { kind: "bundled", ref: item.id },
1037
+ installed_at: null,
1038
+ sha256: null,
1039
+ installed_sha256: null,
1040
+ modified_since_install: false,
1041
+ file_count: null,
1042
+ size_bytes: null,
1043
+ categories: [],
1044
+ tasks: item.tasks || [],
1045
+ openwiki_hubs: [],
1046
+ cli: item.cli as any,
1047
+ has_generator: false,
1048
+ branding: item.branding
1049
+ }, "preview")
1050
+ }
1051
+ }}
1052
+ >
1053
+ Preview
712
1054
  </button>
713
- )}
1055
+
1056
+ <div style={{ display: "flex", gap: "6px" }}>
1057
+ {!item.installed ? (
1058
+ <button
1059
+ type="button"
1060
+ className="btn btn-primary btn-sm"
1061
+ disabled={busy === item.id}
1062
+ onClick={() => doInstall(item.id)}
1063
+ >
1064
+ {busy === item.id ? "Installing…" : "Install"}
1065
+ </button>
1066
+ ) : item.inWorkspace ? (
1067
+ <button
1068
+ type="button"
1069
+ className="btn btn-ghost btn-sm"
1070
+ disabled={busy === item.id}
1071
+ onClick={() => removeFromWorkspace(item.sourcePlugin || item)}
1072
+ style={{ fontSize: "11px" }}
1073
+ >
1074
+ Remove from Workspace
1075
+ </button>
1076
+ ) : (
1077
+ <button
1078
+ type="button"
1079
+ className="btn btn-primary btn-sm"
1080
+ disabled={busy === item.id}
1081
+ onClick={() => addToWorkspace(item.sourcePlugin || item)}
1082
+ >
1083
+ {busy === item.id ? "Adding…" : "+ Add to Workspace"}
1084
+ </button>
1085
+ )}
1086
+ </div>
1087
+ </div>
714
1088
  </div>
715
- </div>
716
- )
717
- })}
718
- </div>
719
- </>
1089
+ )
1090
+ })}
1091
+ </div>
1092
+ )}
1093
+ </div>
720
1094
  )
721
1095
  )}
722
1096
 
@@ -909,6 +1283,77 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
909
1283
  </div>
910
1284
  )}
911
1285
 
1286
+ {/* Agent Plugin Recipe Modal */}
1287
+ {recipeOpen && (
1288
+ <div
1289
+ role="dialog"
1290
+ aria-modal="true"
1291
+ aria-label="Agent Plugin Recipe"
1292
+ onClick={() => setRecipeOpen(false)}
1293
+ style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.65)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000, padding: "16px" }}
1294
+ >
1295
+ <div
1296
+ className="card"
1297
+ onClick={(e) => e.stopPropagation()}
1298
+ style={{ width: "100%", maxWidth: "680px", maxHeight: "90vh", overflowY: "auto", display: "flex", flexDirection: "column", gap: "16px" }}
1299
+ >
1300
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
1301
+ <div>
1302
+ <h3 style={{ margin: 0, fontSize: "17px", fontWeight: 700 }}>Agent Plugin Recipe</h3>
1303
+ <div style={{ ...muted, fontSize: "12px" }}>How your coding agent authors private or store plugins</div>
1304
+ </div>
1305
+ <button className="btn btn-ghost btn-sm" onClick={() => setRecipeOpen(false)}>✕</button>
1306
+ </div>
1307
+
1308
+ <div style={{ display: "flex", flexDirection: "column", gap: "12px", fontSize: "13px" }}>
1309
+ <div style={{ background: "var(--bg-tertiary)", padding: "12px", borderRadius: "8px", border: "1px solid var(--border)" }}>
1310
+ <div style={{ fontWeight: 600, marginBottom: "4px" }}>1. Command your agent</div>
1311
+ <div style={muted}>
1312
+ Copy and send this instruction to your coding agent (Antigravity, Claude, Codex, DSH):
1313
+ </div>
1314
+ <pre style={{ ...mono, background: "rgba(0,0,0,0.4)", padding: "10px", borderRadius: "6px", marginTop: "8px", whiteSpace: "pre-wrap", overflowX: "auto", fontSize: "11px", color: "#a5b4fc" }}>
1315
+ {AGENT_RECIPE_PROMPT}
1316
+ </pre>
1317
+ <div style={{ display: "flex", justifyContent: "flex-end", marginTop: "8px" }}>
1318
+ <button type="button" className="btn btn-primary btn-sm" onClick={() => copyAgentPrompt()}>
1319
+ Copy Agent Prompt
1320
+ </button>
1321
+ </div>
1322
+ </div>
1323
+
1324
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px" }}>
1325
+ <div style={{ background: "var(--bg-tertiary)", padding: "12px", borderRadius: "8px", border: "1px solid var(--border)" }}>
1326
+ <div style={{ fontWeight: 600, color: "#38bdf8", marginBottom: "4px" }}>🔒 Private Plugins</div>
1327
+ <div style={{ ...muted, fontSize: "12px", lineHeight: 1.4 }}>
1328
+ Store directly inside <code style={mono}>.agent/skills/total-recall/plugins/&lt;id&gt;</code> or link locally. Never shared outside this machine or project. Zero cloud exposure.
1329
+ </div>
1330
+ </div>
1331
+
1332
+ <div style={{ background: "var(--bg-tertiary)", padding: "12px", borderRadius: "8px", border: "1px solid var(--border)" }}>
1333
+ <div style={{ fontWeight: 600, color: "#a855f7", marginBottom: "4px" }}>🌐 Plugin Store Distribution</div>
1334
+ <div style={{ ...muted, fontSize: "12px", lineHeight: 1.4 }}>
1335
+ Deploy to the store via <code style={mono}>total-recall deploy-store &lt;id&gt;</code>. Cryptographically signed and distributed across the mesh for team consumption.
1336
+ </div>
1337
+ </div>
1338
+ </div>
1339
+
1340
+ <div style={{ background: "rgba(16, 185, 129, 0.08)", padding: "12px", borderRadius: "8px", border: "1px solid rgba(16, 185, 129, 0.2)" }}>
1341
+ <div style={{ fontWeight: 600, color: "#10b981", marginBottom: "4px" }}>Quality Invariants Enforced</div>
1342
+ <ul style={{ margin: "4px 0 0 16px", padding: 0, fontSize: "12px", color: "var(--text-secondary)" }}>
1343
+ <li>Guaranteed Title, Brand Glyph, Version, and Settings drawer trigger on every card</li>
1344
+ <li>Authentic domain tools only — zero fake audio equalizers or fake skins</li>
1345
+ <li>Touch-first ergonomics with minimum 44px tap targets</li>
1346
+ </ul>
1347
+ </div>
1348
+ </div>
1349
+
1350
+ <div style={{ display: "flex", justifyContent: "flex-end", marginTop: "8px" }}>
1351
+ <button type="button" className="btn btn-ghost" onClick={() => setRecipeOpen(false)}>Close</button>
1352
+ </div>
1353
+ </div>
1354
+ </div>
1355
+ )}
1356
+
912
1357
  {/* Plugin Detail & Interactive Preview Drawer */}
913
1358
  {detail && (
914
1359
  <div role="dialog" aria-modal="true" aria-label={detail.name} onClick={closeDetail}