total-recall-brain 3.44.0 → 3.46.0

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 (502) hide show
  1. package/bin/total-recall.mjs +10 -6
  2. package/docs/ARCHITECTURE.md +10 -7
  3. package/docs/reference/cli-reference.md +13 -8
  4. package/frontend/dist/assets/index-BRYE1jmv.css +1 -0
  5. package/frontend/dist/assets/index-D1goIPjC.js +5598 -0
  6. package/frontend/dist/index.html +2 -2
  7. package/frontend/dist/sw.js +5 -4
  8. package/frontend/src/App.tsx +185 -238
  9. package/frontend/src/api/auth-inventory.ts +62 -0
  10. package/frontend/src/api/memory.ts +13 -8
  11. package/frontend/src/api/plugin-credentials.ts +23 -0
  12. package/frontend/src/api/plugins.ts +37 -53
  13. package/frontend/src/api/workspace-drawers.ts +97 -0
  14. package/frontend/src/components/BrainSelector.tsx +9 -3
  15. package/frontend/src/components/plugins/PluginPanel.tsx +83 -42
  16. package/frontend/src/components/plugins/PluginSettings.tsx +10 -20
  17. package/frontend/src/components/plugins/PluginWorkspace.tsx +367 -0
  18. package/frontend/src/components/plugins/ScopedPluginCommands.tsx +142 -0
  19. package/frontend/src/components/plugins/plugin-frame.ts +44 -33
  20. package/frontend/src/components/plugins/scoped-plugin-operations.ts +28 -0
  21. package/frontend/src/components/workspace/DrawerChat.tsx +110 -0
  22. package/frontend/src/components/workspace/DrawerLayout.tsx +196 -0
  23. package/frontend/src/components/workspace/DrawerMarkup.tsx +49 -0
  24. package/frontend/src/components/workspace/PluginChunkLibrary.tsx +92 -0
  25. package/frontend/src/components/workspace/PluginHome.tsx +76 -0
  26. package/frontend/src/components/workspace/WorkspaceDrawers.tsx +96 -0
  27. package/frontend/src/components/workspace/WorkspacePage.tsx +95 -0
  28. package/frontend/src/components/workspace/usePluginDesign.ts +38 -0
  29. package/frontend/src/index.css +409 -136
  30. package/frontend/src/pages/HelpPage.tsx +15 -46
  31. package/frontend/src/pages/MemoryPage.tsx +19 -36
  32. package/frontend/src/pages/SettingsPage.tsx +1 -1
  33. package/metadata.plugin.schema.json +53 -227
  34. package/package.json +1 -1
  35. package/scaffold/.agent/skills/plugin/SKILL.md +13 -11
  36. package/scaffold/.agent/skills/plugin/evals/README.md +14 -21
  37. package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +15 -18
  38. package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +19 -24
  39. package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +12 -20
  40. package/scaffold/.agent/skills/plugin/evals/cases/04-home-collects-credential.mjs +35 -0
  41. package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +8 -28
  42. package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +85 -47
  43. package/scaffold/.agent/skills/plugin/evals/run.mjs +17 -18
  44. package/scaffold/.agent/skills/plugin/prompts/05-credentials.md +4 -4
  45. package/scaffold/.agent/skills/plugin/prompts/07-scaffold.md +2 -2
  46. package/scaffold/.agent/skills/plugin/prompts/08-verify.md +4 -4
  47. package/scaffold/.agent/skills/plugin/references/anti-patterns.md +2 -2
  48. package/scaffold/.agent/skills/plugin/references/generative-home.md +38 -0
  49. package/scaffold/.agent/skills/plugin/references/scaffold.md +5 -5
  50. package/scaffold/.agent/skills/plugin/references/surfaces.md +13 -4
  51. package/scaffold/.agent/skills/plugin/scripts/record.mjs +75 -153
  52. package/scaffold/.agent/skills/plugin/subagents/plugin-reviewer.md +4 -4
  53. package/scaffold/.agent/skills/total-recall/plugins/auth/DESIGN.md +76 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +3 -1
  55. package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +20 -15
  56. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
  57. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +2 -1
  58. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +13 -51
  59. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
  60. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
  61. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/access-overview.md +17 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/agent-login.md +20 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/baseline-audit.md +22 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-repository-binding.md +17 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-store-maintenance.md +17 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-password.md +18 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-sessions.md +18 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/endpoint-probe.md +23 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/google-sign-in.md +21 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/mesh-machines.md +16 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/passkey-enrollment.md +21 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/registered-brains.md +16 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/scoped-access-keys.md +17 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/secret-inventory.md +25 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/signing-key-rotation.md +22 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/home.js +375 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/automations/DESIGN.md +53 -0
  79. package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +247 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +130 -0
  84. package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
  85. package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
  87. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +36 -0
  88. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +151 -0
  89. package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
  90. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
  91. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/control-ledger.md +42 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/local-inventory-triage.md +17 -0
  93. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/pending-control.md +39 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/run-evidence.md +41 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/schedule-vs-run-evidence.md +17 -0
  96. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/source-coverage.md +41 -0
  97. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/home.js +278 -0
  98. package/scaffold/.agent/skills/total-recall/plugins/code-quality/DESIGN.md +53 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +23 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +5 -3
  101. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +61 -7
  102. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
  103. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
  104. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
  105. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +2 -1
  106. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +40 -40
  107. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/config.md +15 -0
  108. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/detect.md +15 -0
  109. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/gate.md +15 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/init.md +15 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/report.md +17 -0
  112. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/run.md +17 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/snapshot.md +15 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +55 -23
  115. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/home.mjs +214 -0
  116. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/DESIGN.md +146 -0
  117. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
  118. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +2 -1
  119. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +51 -34
  120. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +1 -1
  121. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/create.md +35 -0
  122. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/history.md +35 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/library.md +49 -0
  124. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/list.md +42 -0
  125. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/promote.md +40 -0
  126. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/read.md +25 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/remove.md +26 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/rollback.md +26 -0
  129. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/show.md +53 -0
  130. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/status.md +40 -0
  131. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test-path.md +38 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test.md +46 -0
  133. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/update.md +32 -0
  134. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/home.js +533 -0
  135. package/scaffold/.agent/skills/total-recall/plugins/embeddings/DESIGN.md +110 -0
  136. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
  137. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +2 -1
  138. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +13 -10
  139. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/benchmark.md +49 -0
  140. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/coverage.md +53 -0
  141. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/models.md +43 -0
  142. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/pull.md +41 -0
  143. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/status.md +59 -0
  144. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/home.js +353 -0
  145. package/scaffold/.agent/skills/total-recall/plugins/git/DESIGN.md +31 -0
  146. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +136 -0
  147. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +140 -24
  148. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +9 -9
  149. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +4 -2
  150. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +151 -0
  151. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
  152. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
  153. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
  154. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
  155. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
  156. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
  157. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
  158. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
  159. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +54 -0
  160. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
  161. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +174 -0
  162. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
  163. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
  164. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
  165. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
  166. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
  167. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
  168. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +92 -0
  169. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +156 -0
  170. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/unmerged.mjs +182 -0
  171. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
  172. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/change-comparison.md +27 -0
  173. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/commit-identity.md +22 -0
  174. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/create-worktree.md +25 -0
  175. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/registered-worktrees.md +22 -0
  176. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/remove-worktree.md +23 -0
  177. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/repository-ledger.md +25 -0
  178. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/unmerged-work.md +22 -0
  179. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/upstream-divergence.md +17 -0
  180. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-conflict-triage.md +17 -0
  181. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-terminal.md +22 -0
  182. package/scaffold/.agent/skills/total-recall/plugins/git/ui/home.js +273 -0
  183. package/scaffold/.agent/skills/total-recall/plugins/mesh/DESIGN.md +35 -0
  184. package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +4 -5
  185. package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +9 -3
  186. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
  187. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +2 -1
  188. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +37 -39
  189. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +5 -3
  190. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/active-device-dispatch.md +22 -0
  191. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-probe.md +25 -0
  192. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-server.md +22 -0
  193. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/credential-readiness.md +23 -0
  194. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/elected-leader.md +22 -0
  195. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/enrollment-key.md +22 -0
  196. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/file-receipt.md +25 -0
  197. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-execution.md +23 -0
  198. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-presence.md +23 -0
  199. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/interactive-ssh.md +22 -0
  200. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/local-enrollment.md +23 -0
  201. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/mesh-policy.md +23 -0
  202. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/network-atlas.md +25 -0
  203. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-roster.md +26 -0
  204. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-selection-proof.md +17 -0
  205. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/owner-observations.md +23 -0
  206. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/peer-path.md +23 -0
  207. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/pinned-leader.md +24 -0
  208. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/reachability-evidence-ladder.md +17 -0
  209. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/recorded-login.md +23 -0
  210. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/remote-execution.md +22 -0
  211. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/secret-grants.md +26 -0
  212. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/{card.js → home.js} +93 -22
  213. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/DESIGN.md +86 -0
  214. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +44 -9
  215. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
  216. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
  217. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +128 -24
  218. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
  219. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +3 -2
  220. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +188 -34
  221. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
  222. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
  223. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
  224. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
  225. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/chat.mjs +80 -0
  226. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
  227. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
  228. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
  229. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
  230. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
  231. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
  232. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
  233. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
  234. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
  235. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
  236. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
  237. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
  238. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
  239. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
  240. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
  241. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
  242. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
  243. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
  244. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
  245. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
  246. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
  247. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
  248. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
  249. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
  250. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
  251. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
  252. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
  253. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
  254. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
  255. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
  256. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
  257. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
  258. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/adapter-docs-freshness.md +16 -0
  259. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/chat-handoff.md +26 -0
  260. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/fleet-maintenance.md +21 -0
  261. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/harness-inventory.md +21 -0
  262. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-access.md +18 -0
  263. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-usage-verification.md +15 -0
  264. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/independent-review.md +17 -0
  265. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/managed-execution.md +20 -0
  266. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/official-documentation.md +18 -0
  267. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/quota-recovery.md +21 -0
  268. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/review-routing-decision.md +16 -0
  269. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/route-work.md +18 -0
  270. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/routing-evidence.md +18 -0
  271. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/run-ledger.md +18 -0
  272. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/home.js +198 -0
  273. package/scaffold/.agent/skills/total-recall/plugins/openwiki/DESIGN.md +75 -0
  274. package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +2 -0
  275. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +64 -32
  276. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
  277. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +2 -1
  278. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +10 -44
  279. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +5 -4
  280. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +3 -4
  281. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +23 -18
  282. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +8 -3
  283. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
  284. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +123 -113
  285. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +3 -5
  286. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +21 -47
  287. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +17 -6
  288. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/auto-refresh.md +18 -0
  289. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/bootstrap-pages.md +18 -0
  290. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generate-wiki.md +21 -0
  291. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generator-settings.md +18 -0
  292. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/host-probe.md +18 -0
  293. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/ingest-preview.md +18 -0
  294. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-hygiene.md +21 -0
  295. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-topology.md +19 -0
  296. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/passage-search.md +19 -0
  297. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/propagation-plan.md +18 -0
  298. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/provider-credentials.md +18 -0
  299. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/refresh-report.md +18 -0
  300. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/task-evidence.md +22 -0
  301. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/upstream-record.md +18 -0
  302. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/watch-upstream.md +18 -0
  303. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/wiki-atlas.md +19 -0
  304. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +261 -513
  305. package/scaffold/.agent/skills/total-recall/plugins/security/DESIGN.md +84 -0
  306. package/scaffold/.agent/skills/total-recall/plugins/security/README.md +4 -2
  307. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -19
  308. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
  309. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +2 -3
  310. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +19 -52
  311. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/device-evidence.md +21 -0
  312. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/evidence-desk.md +18 -0
  313. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/guided-device-review.md +17 -0
  314. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/host-and-network.md +21 -0
  315. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/https-headers.md +17 -0
  316. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/mesh-coverage.md +18 -0
  317. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/private-security-tools.md +16 -0
  318. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/single-origin-header-check.md +17 -0
  319. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/source-review.md +21 -0
  320. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/ssh-permission-plan.md +17 -0
  321. package/scaffold/.agent/skills/total-recall/plugins/security/ui/home.js +283 -0
  322. package/scaffold/.agent/skills/total-recall/plugins/ssss/DESIGN.md +93 -0
  323. package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +2 -2
  324. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -1
  325. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +5 -3
  326. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/conformance.md +26 -0
  327. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/registry.md +37 -0
  328. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/repair.md +42 -0
  329. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/scaffold.md +33 -0
  330. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/status.md +43 -0
  331. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validate.md +34 -0
  332. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validation-diagnostics.md +17 -0
  333. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/workspace-registry-preflight.md +17 -0
  334. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/home.js +348 -0
  335. package/scaffold/.agent/skills/total-recall/references/operations.md +3 -1
  336. package/src/cli/agent.mjs +1 -1
  337. package/src/cli/api.mjs +3 -3
  338. package/src/cli/brief.mjs +3 -0
  339. package/src/cli/command.mjs +41 -2
  340. package/src/cli/config.mjs +16 -0
  341. package/src/cli/connect.mjs +2 -7
  342. package/src/cli/dream.mjs +93 -69
  343. package/src/cli/harness.mjs +4 -4
  344. package/src/cli/help.mjs +1 -1
  345. package/src/cli/init.mjs +0 -25
  346. package/src/cli/lessons.mjs +51 -0
  347. package/src/cli/memory-plugin.mjs +1 -1
  348. package/src/cli/plugin/chunks.mjs +145 -0
  349. package/src/cli/plugin/copies.mjs +64 -14
  350. package/src/cli/plugin/create.mjs +30 -59
  351. package/src/cli/plugin/index.mjs +31 -16
  352. package/src/cli/plugin/install.mjs +10 -15
  353. package/src/cli/plugin/remove.mjs +1 -1
  354. package/src/cli/plugin/search.mjs +2 -2
  355. package/src/cli/preflight.mjs +19 -6
  356. package/src/cli/proposals.mjs +1 -1
  357. package/src/cli/recall.mjs +66 -23
  358. package/src/cli/remember.mjs +3 -14
  359. package/src/cli/server.mjs +18 -0
  360. package/src/cli/setup.mjs +2 -9
  361. package/src/cli/start.mjs +1 -0
  362. package/src/cli/startup.mjs +19 -0
  363. package/src/cli/task.mjs +9 -2
  364. package/src/cli/update.mjs +6 -2
  365. package/src/core/access-ledger.mjs +78 -0
  366. package/src/core/agent-manager.mjs +2 -2
  367. package/src/core/app-deploy/design-tokens.mjs +14 -9
  368. package/src/core/app-deploy/ui-elements.mjs +1 -1
  369. package/src/core/brain-locate.mjs +15 -0
  370. package/src/core/brain-registry.mjs +5 -0
  371. package/src/core/brain-updates.mjs +35 -11
  372. package/src/core/claude-hooks.mjs +3 -1
  373. package/src/core/cleanup-task.mjs +15 -0
  374. package/src/core/cli-commands.mjs +2 -0
  375. package/src/core/cli-maintenance.mjs +4 -1
  376. package/src/core/cli-semantic-index.mjs +23 -3
  377. package/src/core/context-compiler.mjs +20 -0
  378. package/src/core/daemon-control.mjs +5 -1
  379. package/src/core/daemon-loop.mjs +39 -62
  380. package/src/core/dashboard-bundle.mjs +261 -0
  381. package/src/core/default-plugins.mjs +1 -1
  382. package/src/core/dream.mjs +33 -105
  383. package/src/core/ensure-dependencies.mjs +152 -0
  384. package/src/core/fast-recall.mjs +2 -2
  385. package/src/core/field-evaluation.mjs +23 -0
  386. package/src/core/gate-mode.mjs +1 -1
  387. package/src/core/harness-hooks.mjs +14 -2
  388. package/src/core/lesson-hooks.mjs +74 -0
  389. package/src/core/lesson-memory.mjs +24 -0
  390. package/src/core/lesson-sessions.mjs +52 -0
  391. package/src/core/lessons.mjs +96 -0
  392. package/src/core/memory-brain.mjs +30 -8
  393. package/src/core/meta-harness.mjs +2 -2
  394. package/src/core/optimizer.mjs +66 -165
  395. package/src/core/plugin-activation.mjs +63 -20
  396. package/src/core/plugin-contracts/{card.mjs → manifest.mjs} +26 -57
  397. package/src/core/plugin-contracts/startup-check.mjs +49 -0
  398. package/src/core/plugin-contracts/ui.mjs +9 -3
  399. package/src/core/plugin-distribution.mjs +6 -6
  400. package/src/core/plugin-lifecycle.mjs +58 -0
  401. package/src/core/plugin-loader.mjs +21 -8
  402. package/src/core/plugin-runner.mjs +21 -10
  403. package/src/core/plugin-runtime.mjs +40 -1
  404. package/src/core/plugin-startup-checks.mjs +44 -0
  405. package/src/core/plugin-state.mjs +19 -5
  406. package/src/core/plugin-store.mjs +64 -33
  407. package/src/core/post-mortem.mjs +52 -36
  408. package/src/core/preflight-gate.mjs +23 -2
  409. package/src/core/project-id.mjs +15 -5
  410. package/src/core/proposal-applier.mjs +31 -2
  411. package/src/core/repo-sync.mjs +4 -6
  412. package/src/core/required-plugins.mjs +57 -10
  413. package/src/core/rule-length.mjs +23 -0
  414. package/src/core/runtime.mjs +1 -1
  415. package/src/core/scheduler.mjs +35 -43
  416. package/src/core/schema.mjs +3 -2
  417. package/src/core/search.mjs +4 -3
  418. package/src/core/server-service.mjs +148 -0
  419. package/src/core/session-watcher.mjs +19 -1
  420. package/src/core/skill-gap.mjs +16 -0
  421. package/src/core/ssss-kernel-bridge.mjs +32 -4
  422. package/src/core/ssss-operation-service.mjs +1 -0
  423. package/src/core/startup-health.mjs +34 -4
  424. package/src/core/surface.mjs +65 -54
  425. package/src/core/task-executors.mjs +54 -106
  426. package/src/core/testing/plugin-fixture.mjs +22 -21
  427. package/src/core/throttled-fetch.mjs +25 -18
  428. package/src/core/ui-chunk-choice.mjs +77 -0
  429. package/src/core/ui-chunk.registry.json +23 -0
  430. package/src/core/ui-chunks.mjs +434 -0
  431. package/src/core/validated-write.mjs +1 -0
  432. package/src/core/vault-cache.mjs +58 -59
  433. package/src/core/vector-field.mjs +134 -192
  434. package/src/core/workspace-compositions.mjs +151 -0
  435. package/src/core/workspace-drawers.mjs +79 -0
  436. package/src/server/api.mjs +4 -27
  437. package/src/server/index.mjs +28 -21
  438. package/src/server/invoke-core-child.mjs +24 -0
  439. package/src/server/invoke-core-operations.mjs +102 -0
  440. package/src/server/memory-events.mjs +31 -0
  441. package/src/server/plugin-brain-targets.mjs +89 -0
  442. package/src/server/rest.mjs +2 -0
  443. package/src/server/route-inventory.mjs +3 -2
  444. package/src/server/route-manifest.json +47 -55
  445. package/src/server/routes/_shared.mjs +7 -14
  446. package/src/server/routes/brains.mjs +29 -62
  447. package/src/server/routes/context.mjs +0 -41
  448. package/src/server/routes/dashboard.mjs +26 -0
  449. package/src/server/routes/invoke.mjs +26 -1
  450. package/src/server/routes/plugins.mjs +154 -45
  451. package/src/server/routes/ssss.mjs +0 -169
  452. package/src/server/routes/system.mjs +0 -15
  453. package/src/server/routes/tasks.mjs +0 -26
  454. package/src/server/routes/ui-chunks.mjs +192 -0
  455. package/src/server/routes/vault.mjs +2 -23
  456. package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
  457. package/frontend/dist/assets/index-DtwsDyFM.css +0 -1
  458. package/frontend/src/components/plugins/card/CardEngine.tsx +0 -108
  459. package/frontend/src/components/plugins/card/CardScenes.tsx +0 -103
  460. package/frontend/src/components/plugins/card/ExecutionPicker.tsx +0 -64
  461. package/frontend/src/components/plugins/card/args.ts +0 -28
  462. package/frontend/src/components/plugins/card/card.css +0 -32
  463. package/frontend/src/components/plugins/card/cli-line.ts +0 -13
  464. package/frontend/src/components/plugins/card/contract.ts +0 -91
  465. package/frontend/src/components/plugins/card/format.ts +0 -49
  466. package/frontend/src/components/plugins/card/runner-context.ts +0 -11
  467. package/frontend/src/components/plugins/card/runner.tsx +0 -43
  468. package/frontend/src/components/plugins/card/theme.ts +0 -35
  469. package/frontend/src/components/plugins/card/use-source.ts +0 -22
  470. package/frontend/src/components/plugins/card/widgets.tsx +0 -94
  471. package/frontend/src/components/plugins/workspace/WorkspaceHost.tsx +0 -23
  472. package/frontend/src/pages/PluginsPage.tsx +0 -1719
  473. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +0 -47
  474. package/scaffold/.agent/skills/plugin/prompts/06-card.md +0 -46
  475. package/scaffold/.agent/skills/plugin/references/card.md +0 -90
  476. package/scaffold/.agent/skills/plugin/subagents/card-designer.md +0 -60
  477. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +0 -67
  478. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +0 -1179
  479. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +0 -32
  480. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +0 -588
  481. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +0 -575
  482. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +0 -32
  483. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +0 -482
  484. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
  485. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
  486. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +0 -100
  487. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +0 -637
  488. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/DESIGN.md +0 -217
  489. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +0 -32
  490. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +0 -1276
  491. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +0 -45
  492. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +0 -1401
  493. package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +0 -4
  494. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +0 -41
  495. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +0 -396
  496. package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +0 -98
  497. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/DESIGN.md +0 -15
  498. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/card.js +0 -182
  499. package/src/core/append-log.mjs +0 -131
  500. package/src/core/plugin-tasks.mjs +0 -173
  501. package/templates/onboarding-interview.md +0 -82
  502. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
@@ -1,1719 +0,0 @@
1
- import { useState, useEffect, useMemo, useCallback, useRef, type CSSProperties, type ReactNode } from "react"
2
- import { useSearchParams } from "react-router-dom"
3
- import {
4
- fetchPlugins,
5
- fetchPlugin,
6
- fetchBundledPlugins,
7
- fetchPeerPlugins,
8
- installPlugin,
9
- removePlugin,
10
- setPluginShared,
11
- updatePluginBranding,
12
- setPluginLocked,
13
- deployPluginToStore,
14
- runPluginCommand,
15
- fetchPluginReadme,
16
- setPluginActivation,
17
- type PluginInfo,
18
- type BundledPlugin,
19
- type PeersResponse,
20
- type PeerNode
21
- } from "../api"
22
- import { renderMarkdown } from "../components/MarkdownUtils"
23
- import { PluginPanel, pluginElements } from "../components/plugins/PluginPanel"
24
- import { PluginSettings } from "../components/plugins/PluginSettings"
25
-
26
- interface PluginsPageProps {
27
- activeBrainId?: string | null
28
- }
29
-
30
- type Tab = "installed" | "bundled" | "mesh"
31
- type DetailTab = "preview" | "run" | "readme" | "details"
32
- type Alert = { type: "success" | "error"; message: string } | null
33
-
34
- const PEER_STATUS_LABEL: Record<PeerNode["status"], string> = {
35
- ok: "online",
36
- offline: "offline",
37
- unreachable: "unreachable",
38
- not_configured: "mesh sync not configured",
39
- unsupported: "no plugin sharing (older version)",
40
- error: "error"
41
- }
42
-
43
- const mono: CSSProperties = { fontFamily: "var(--font-mono)", fontSize: "12px" }
44
- const muted: CSSProperties = { color: "var(--text-secondary)", fontSize: "13px", lineHeight: 1.5 }
45
-
46
- const DEFAULT_CORE_PLUGINS = new Set([
47
- "auth",
48
- "mesh",
49
- "openwiki",
50
- "composable-cli",
51
- "cli-agents",
52
- "code-quality",
53
- "security",
54
- "embeddings",
55
- "git-sentinel"
56
- ])
57
-
58
- const AGENT_RECIPE_PROMPT = `You are my coding agent. Author a bespoke Total Recall plugin for [DOMAIN] following the Total Recall Plugin Recipe:
59
-
60
- 1. Scaffold:
61
- total-recall scaffold-plugin <id> --title "<Name>" --description "<Description>"
62
-
63
- 2. Manifest (plugin.json):
64
- - id: lowercase kebab-case
65
- - name: Clear Title
66
- - branding: { name, color, badge }
67
- - cli: command name and subcommands with effect (read/write)
68
- - ui: elements declaring the Web Component tag <tr-plugin-<id>-card>
69
- - secrets: declare required keys, never store plain text
70
-
71
- 3. Web Component (ui/card.js):
72
- - Register <tr-plugin-<id>-card>
73
- - Standardized layout with clean borders and >=44px touch targets
74
- - Authentic purpose-built domain tools (NO fake audio equalizers or fake skins)
75
- - Real interactive controls executing plugin subcommands via host.run(subcommand, args)
76
-
77
- 4. Distribution:
78
- - Private: keep in .agent/plugins/<id>
79
- - Mesh: share peer-to-peer via total-recall plugin share <id>`
80
-
81
- function resolvePluginBranding(p: { id: string; branding?: { icon?: string | null; color?: string | null; badge?: string | null } }) {
82
- const custom = p.branding || {}
83
- return {
84
- icon: custom.icon || "",
85
- color: custom.color || "#3b82f6",
86
- badge: custom.badge || "Plugin",
87
- }
88
- }
89
-
90
- function shortHash(sha: string | null | undefined) {
91
- return sha ? `${sha.slice(0, 12)}…` : "—"
92
- }
93
-
94
- function formatBytes(n: number | null | undefined) {
95
- if (n === null || n === undefined) return "—"
96
- if (n < 1024) return `${n} B`
97
- if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`
98
- return `${(n / (1024 * 1024)).toFixed(1)} MB`
99
- }
100
-
101
- function sourceLabel(p: PluginInfo) {
102
- switch (p.source.kind) {
103
- case "peer": return `from ${p.source.peer_hostname || "a peer"}`
104
- case "bundled": return "bundled"
105
- case "git": return "git"
106
- case "public": return "direct link"
107
- case "link": return "linked folder"
108
- default: return "local folder"
109
- }
110
- }
111
-
112
- function UseCaseChips({ useCases }: { useCases: string[] }) {
113
- if (!useCases.length) return null
114
- return (
115
- <div style={{ display: "flex", gap: "6px", flexWrap: "wrap" }}>
116
- {useCases.map((u) => (
117
- <span key={u} className="badge" style={{ fontSize: "11px" }}>{u}</span>
118
- ))}
119
- </div>
120
- )
121
- }
122
-
123
- function Fact({ label, children }: { label: string; children: ReactNode }) {
124
- return (
125
- <div style={{ display: "flex", gap: "12px", fontSize: "13px", padding: "6px 0", borderBottom: "1px solid var(--border)" }}>
126
- <span style={{ width: "132px", flexShrink: 0, color: "var(--text-tertiary)" }}>{label}</span>
127
- <span style={{ color: "var(--text-primary)", minWidth: 0, overflowWrap: "anywhere" }}>{children}</span>
128
- </div>
129
- )
130
- }
131
-
132
- function Empty({ title, children }: { title: string; children?: ReactNode }) {
133
- return (
134
- <div className="card" style={{ textAlign: "center", padding: "40px 24px" }}>
135
- <div style={{ fontWeight: 600, marginBottom: "6px" }}>{title}</div>
136
- {children && <div style={muted}>{children}</div>}
137
- </div>
138
- )
139
- }
140
-
141
- export function PluginCardCapabilities({
142
- p,
143
- onRunSubcommand,
144
- onOpenSettings,
145
- onOpenPreview
146
- }: {
147
- p: PluginInfo;
148
- onRunSubcommand: (subcommand: string) => void;
149
- onOpenSettings: () => void;
150
- onOpenPreview: () => void;
151
- }) {
152
- const [expanded, setExpanded] = useState(false)
153
- const subcommands = p.cli?.subcommands || []
154
- const tasks = p.tasks || []
155
- const secrets = p.secrets || []
156
- const uiElements = p.ui?.elements || []
157
- const categories = p.categories || []
158
- const deploy = (p.manifest?.deploy as Record<string, unknown> | undefined) || {}
159
- const accessGrants = (deploy.access_grants as string[] | undefined) || []
160
-
161
- return (
162
- <div style={{ display: "flex", flexDirection: "column", gap: "8px", marginTop: "4px" }}>
163
- {/* Quick Capability Badges */}
164
- <div style={{ display: "flex", gap: "6px", flexWrap: "wrap", fontSize: "11px" }}>
165
- {p.cli && (
166
- <span className="badge" style={{ background: "rgba(20, 184, 166, 0.15)", color: "#14b8a6", borderColor: "rgba(20, 184, 166, 0.3)" }}>
167
- [CLI] {p.cli.command || p.id} ({subcommands.length} verbs)
168
- </span>
169
- )}
170
- {tasks.length > 0 && (
171
- <span className="badge" style={{ background: "rgba(99, 102, 241, 0.15)", color: "#818cf8", borderColor: "rgba(99, 102, 241, 0.3)" }}>
172
- [CRON] {tasks.length} {tasks.length === 1 ? "task" : "tasks"}
173
- </span>
174
- )}
175
- {secrets.length > 0 && (
176
- <span className="badge" style={{ background: "rgba(245, 158, 11, 0.15)", color: "#f59e0b", borderColor: "rgba(245, 158, 11, 0.3)" }}>
177
- [KEY] {secrets.length} {secrets.length === 1 ? "secret" : "secrets"}
178
- </span>
179
- )}
180
- {uiElements.length > 0 && (
181
- <span className="badge" style={{ background: "rgba(236, 72, 153, 0.15)", color: "#f472b6", borderColor: "rgba(236, 72, 153, 0.3)" }}>
182
- [UI] {uiElements.length} {uiElements.length === 1 ? "component" : "components"}
183
- </span>
184
- )}
185
- {categories.length > 0 && (
186
- <span className="badge" style={{ background: "rgba(139, 92, 246, 0.15)", color: "#a78bfa", borderColor: "rgba(139, 92, 246, 0.3)" }}>
187
- [CAT] {categories.length} {categories.length === 1 ? "category" : "categories"}
188
- </span>
189
- )}
190
- </div>
191
-
192
- {/* Clickable Subcommand Chips */}
193
- {subcommands.length > 0 && (
194
- <div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
195
- <div style={{ fontSize: "11px", color: "var(--text-tertiary)", fontWeight: 600 }}>ACTIONS &amp; CLIS:</div>
196
- <div style={{ display: "flex", gap: "4px", flexWrap: "wrap" }}>
197
- {subcommands.slice(0, expanded ? undefined : 4).map((sub) => (
198
- <button
199
- key={sub.name}
200
- type="button"
201
- className="btn btn-ghost btn-sm"
202
- onClick={() => onRunSubcommand(sub.name)}
203
- title={sub.description || `Run ${p.cli?.command} ${sub.name}`}
204
- style={{
205
- padding: "2px 8px",
206
- fontSize: "11px",
207
- fontFamily: "var(--font-mono)",
208
- background: "var(--bg-tertiary)",
209
- border: "1px solid var(--border)",
210
- borderRadius: "4px"
211
- }}
212
- >
213
- ${p.cli?.command ? ` ${p.cli.command}` : ""} {sub.name}
214
- </button>
215
- ))}
216
- {subcommands.length > 4 && !expanded && (
217
- <button
218
- type="button"
219
- className="btn btn-ghost btn-sm"
220
- onClick={() => setExpanded(true)}
221
- style={{ padding: "2px 6px", fontSize: "10px", color: "var(--text-secondary)" }}
222
- >
223
- +{subcommands.length - 4} more
224
- </button>
225
- )}
226
- </div>
227
- </div>
228
- )}
229
-
230
- {/* Expandable Deep Capabilities Drawer */}
231
- {expanded && (
232
- <div style={{ display: "flex", flexDirection: "column", gap: "8px", marginTop: "6px", padding: "10px", background: "rgba(0,0,0,0.2)", borderRadius: "6px", border: "1px solid var(--border)" }}>
233
- {/* Workflows / Tasks */}
234
- {tasks.length > 0 && (
235
- <div>
236
- <div style={{ fontSize: "11px", fontWeight: 600, color: "var(--text-tertiary)", marginBottom: "4px" }}>SCHEDULED WORKFLOWS:</div>
237
- {tasks.map((t, idx) => (
238
- <div key={idx} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: "11px", padding: "3px 0" }}>
239
- <span style={{ fontFamily: "var(--font-mono)", color: "#818cf8" }}>{t.schedule}</span>
240
- <span style={{ color: "var(--text-secondary)", flex: 1, marginLeft: "8px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={t.intent}>
241
- {t.intent}
242
- </span>
243
- <button
244
- type="button"
245
- className="btn btn-ghost btn-sm"
246
- onClick={() => onRunSubcommand(t.command)}
247
- style={{ padding: "1px 6px", fontSize: "10px" }}
248
- >
249
- ▶ Run
250
- </button>
251
- </div>
252
- ))}
253
- </div>
254
- )}
255
-
256
- {/* Secrets & Config */}
257
- {secrets.length > 0 && (
258
- <div>
259
- <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "4px" }}>
260
- <span style={{ fontSize: "11px", fontWeight: 600, color: "var(--text-tertiary)" }}>CONFIGS &amp; CREDENTIALS:</span>
261
- <button
262
- type="button"
263
- className="btn btn-ghost btn-sm"
264
- onClick={onOpenSettings}
265
- style={{ padding: "1px 6px", fontSize: "10px" }}
266
- >
267
- Configure
268
- </button>
269
- </div>
270
- <div style={{ display: "flex", gap: "4px", flexWrap: "wrap" }}>
271
- {secrets.map((s) => (
272
- <span
273
- key={s.key}
274
- className="badge"
275
- style={{
276
- fontSize: "10px",
277
- fontFamily: "var(--font-mono)",
278
- background: s.required ? "rgba(239, 68, 68, 0.1)" : "rgba(255, 255, 255, 0.05)",
279
- color: s.required ? "#f87171" : "var(--text-secondary)",
280
- borderColor: s.required ? "rgba(239, 68, 68, 0.2)" : "var(--border)"
281
- }}
282
- title={s.description || s.key}
283
- >
284
- {s.key} {s.required ? "(required)" : "(optional)"}
285
- </span>
286
- ))}
287
- </div>
288
- </div>
289
- )}
290
-
291
- {/* Access Grants & Workspaces */}
292
- {accessGrants.length > 0 && (
293
- <div>
294
- <div style={{ fontSize: "11px", fontWeight: 600, color: "var(--text-tertiary)", marginBottom: "4px" }}>SSSS ACCESS GRANTS:</div>
295
- <div style={{ display: "flex", gap: "4px", flexWrap: "wrap" }}>
296
- {accessGrants.map((grant: string) => (
297
- <span key={grant} className="badge" style={{ fontSize: "10px", fontFamily: "var(--font-mono)" }}>
298
- {grant}
299
- </span>
300
- ))}
301
- </div>
302
- </div>
303
- )}
304
-
305
- {/* UI Custom Elements */}
306
- {uiElements.length > 0 && (
307
- <div>
308
- <div style={{ fontSize: "11px", fontWeight: 600, color: "var(--text-tertiary)", marginBottom: "4px" }}>UI CUSTOM ELEMENTS:</div>
309
- <div style={{ display: "flex", gap: "4px", flexWrap: "wrap" }}>
310
- {uiElements.map((el) => (
311
- <button
312
- key={el.id}
313
- type="button"
314
- className="btn btn-ghost btn-sm"
315
- onClick={onOpenPreview}
316
- style={{ fontSize: "10px", fontFamily: "var(--font-mono)", padding: "2px 6px" }}
317
- title={`Open <${el.tag}> (${el.kind})`}
318
- >
319
- &lt;{el.tag}&gt;
320
- </button>
321
- ))}
322
- </div>
323
- </div>
324
- )}
325
- </div>
326
- )}
327
-
328
- {/* Toggle Expand / Collapse */}
329
- <button
330
- type="button"
331
- className="btn btn-ghost btn-sm"
332
- onClick={() => setExpanded(!expanded)}
333
- style={{
334
- alignSelf: "flex-start",
335
- padding: "2px 6px",
336
- fontSize: "11px",
337
- color: "var(--text-secondary)",
338
- marginTop: "2px"
339
- }}
340
- >
341
- {expanded ? "▴ Collapse details" : "▾ Expand capabilities & configs"}
342
- </button>
343
- </div>
344
- )
345
- }
346
-
347
- export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
348
- const [searchParams, setSearchParams] = useSearchParams()
349
- const [tab, setTab] = useState<Tab>("installed")
350
- const [installed, setInstalled] = useState<PluginInfo[]>([])
351
- const [bundled, setBundled] = useState<BundledPlugin[]>([])
352
- const [peers, setPeers] = useState<PeersResponse | null>(null)
353
- const [peersLoading, setPeersLoading] = useState(false)
354
- const [loading, setLoading] = useState(true)
355
- const [query, setQuery] = useState("")
356
- const [useCase, setUseCase] = useState("")
357
- const [busy, setBusy] = useState<string | null>(null)
358
- const [alert, setAlert] = useState<Alert>(null)
359
-
360
- const [installOpen, setInstallOpen] = useState(false)
361
- const [installSource, setInstallSource] = useState("")
362
- const [installLink, setInstallLink] = useState(false)
363
- const [installGlobal, setInstallGlobal] = useState(false)
364
-
365
- const [detail, setDetail] = useState<PluginInfo | null>(null)
366
- const [detailTab, setDetailTab] = useState<DetailTab>("preview")
367
- const [subcommand, setSubcommand] = useState("")
368
- const [args, setArgs] = useState("")
369
- const [output, setOutput] = useState<{ text: string; ok: boolean } | null>(null)
370
- const [readme, setReadme] = useState("")
371
-
372
- // Agent Plugin Recipe & Workspace State
373
- const [recipeOpen, setRecipeOpen] = useState(false)
374
- const [viewMode, setViewMode] = useState<"workspace" | "all">("workspace")
375
- const [workspaceActiveIds, setWorkspaceActiveIds] = useState<Set<string>>(() => {
376
- try {
377
- const stored = localStorage.getItem("tr_workspace_plugins")
378
- if (stored) return new Set(JSON.parse(stored))
379
- } catch {}
380
- return new Set<string>()
381
- })
382
-
383
- // Branding Customization Modal state
384
- const [brandingOpen, setBrandingOpen] = useState(false)
385
- const [brandingPlugin, setBrandingPlugin] = useState<PluginInfo | null>(null)
386
- const [brandingIcon, setBrandingIcon] = useState("")
387
- const [brandingColor, setBrandingColor] = useState("#3b82f6")
388
- const [brandingBadge, setBrandingBadge] = useState("")
389
-
390
- // Touch & Context Menu state (right-click & long-press)
391
- const [contextMenu, setContextMenu] = useState<{ plugin: PluginInfo; x: number; y: number } | null>(null)
392
- const longPressTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
393
-
394
- const handleTouchStart = (p: PluginInfo, e: React.TouchEvent) => {
395
- if (e.touches.length !== 1) return
396
- const touch = e.touches[0]
397
- const clientX = touch.clientX
398
- const clientY = touch.clientY
399
- longPressTimerRef.current = setTimeout(() => {
400
- if ("vibrate" in navigator) {
401
- try { navigator.vibrate(50) } catch {}
402
- }
403
- setContextMenu({ plugin: p, x: clientX, y: clientY })
404
- }, 500)
405
- }
406
-
407
- const cancelLongPress = () => {
408
- if (longPressTimerRef.current) {
409
- clearTimeout(longPressTimerRef.current)
410
- longPressTimerRef.current = null
411
- }
412
- }
413
-
414
- const handleContextMenu = (p: PluginInfo, e: React.MouseEvent) => {
415
- e.preventDefault()
416
- e.stopPropagation()
417
- setContextMenu({ plugin: p, x: e.clientX, y: e.clientY })
418
- }
419
-
420
- useEffect(() => {
421
- const onPointerDown = () => setContextMenu(null)
422
- window.addEventListener("pointerdown", onPointerDown)
423
- return () => window.removeEventListener("pointerdown", onPointerDown)
424
- }, [])
425
-
426
- const loadLocal = useCallback(async () => {
427
- setLoading(true)
428
- try {
429
- const [i, b] = await Promise.all([fetchPlugins(), fetchBundledPlugins()])
430
- setInstalled(i)
431
- setBundled(b)
432
- } finally {
433
- setLoading(false)
434
- }
435
- }, [])
436
-
437
- const loadPeers = useCallback(async () => {
438
- setPeersLoading(true)
439
- try {
440
- setPeers(await fetchPeerPlugins())
441
- } finally {
442
- setPeersLoading(false)
443
- }
444
- }, [])
445
-
446
- useEffect(() => {
447
- loadLocal()
448
- }, [loadLocal, activeBrainId])
449
-
450
- useEffect(() => {
451
- if (tab === "mesh" && peers === null && !peersLoading) loadPeers()
452
- }, [tab, peers, peersLoading, loadPeers])
453
-
454
- const allUseCases = useMemo(() => {
455
- const set = new Set<string>()
456
- for (const p of installed) p.use_cases.forEach((u) => set.add(u))
457
- for (const p of bundled) p.use_cases.forEach((u) => set.add(u))
458
- for (const n of peers?.peers || []) n.plugins.forEach((p) => p.use_cases.forEach((u) => set.add(u)))
459
- return Array.from(set).sort()
460
- }, [installed, bundled, peers])
461
-
462
- const matches = useCallback((p: { id: string; name: string; description: string; use_cases: string[] }) => {
463
- if (useCase && !p.use_cases.includes(useCase)) return false
464
- const q = query.trim().toLowerCase()
465
- if (!q) return true
466
- return [p.id, p.name, p.description, ...p.use_cases].some((v) => v.toLowerCase().includes(q))
467
- }, [query, useCase])
468
-
469
- const isWorkspaceCard = useCallback((p: { id: string; scope?: string }) => {
470
- if (p.scope === "project") return true
471
- if (DEFAULT_CORE_PLUGINS.has(p.id)) return true
472
- if (workspaceActiveIds.has(p.id)) return true
473
- return false
474
- }, [workspaceActiveIds])
475
-
476
- const addToWorkspace = async (p: { id: string; name: string }) => {
477
- setBusy(p.id)
478
- try {
479
- const next = new Set(workspaceActiveIds)
480
- next.add(p.id)
481
- setWorkspaceActiveIds(next)
482
- try { localStorage.setItem("tr_workspace_plugins", JSON.stringify([...next])) } catch {}
483
- await setPluginActivation(p.id, true)
484
- setAlert({ type: "success", message: `Added ${p.name} to workspace dashboard` })
485
- await loadLocal()
486
- } catch (err: any) {
487
- setAlert({ type: "error", message: err?.message || `Could not activate ${p.name}` })
488
- } finally {
489
- setBusy(null)
490
- }
491
- }
492
-
493
- const removeFromWorkspace = async (p: { id: string; name: string }) => {
494
- setBusy(p.id)
495
- try {
496
- const next = new Set(workspaceActiveIds)
497
- next.delete(p.id)
498
- setWorkspaceActiveIds(next)
499
- try { localStorage.setItem("tr_workspace_plugins", JSON.stringify([...next])) } catch {}
500
- await setPluginActivation(p.id, false)
501
- setAlert({ type: "success", message: `Deactivated card for ${p.name}` })
502
- await loadLocal()
503
- } catch (err: any) {
504
- setAlert({ type: "error", message: err?.message || `Could not deactivate ${p.name}` })
505
- } finally {
506
- setBusy(null)
507
- }
508
- }
509
-
510
- const copyAgentPrompt = () => {
511
- void navigator.clipboard?.writeText(AGENT_RECIPE_PROMPT)
512
- setAlert({ type: "success", message: "Copied Agent Plugin Recipe prompt to clipboard" })
513
- }
514
-
515
- const doInstall = async (source: string, opts: { link?: boolean; global?: boolean } = {}) => {
516
- setBusy(source)
517
- setAlert(null)
518
- const res = await installPlugin({ source, ...opts })
519
- setBusy(null)
520
- if (res.success) {
521
- setAlert({ type: "success", message: res.message || `Installed ${source}` })
522
- await loadLocal()
523
- if (peers) loadPeers()
524
- return true
525
- }
526
- setAlert({ type: "error", message: res.error || `Could not install ${source}` })
527
- return false
528
- }
529
-
530
- const doRemove = async (p: PluginInfo) => {
531
- if (!window.confirm(`Remove "${p.name}" (${p.scope})?`)) return
532
- setBusy(p.id)
533
- const res = await removePlugin(p.id, p.scope === "global")
534
- setBusy(null)
535
- setAlert(res.success ? { type: "success", message: `Removed ${p.name}` } : { type: "error", message: res.error || "Remove failed" })
536
- if (res.success) {
537
- if (detail?.id === p.id) closeDetail()
538
- loadLocal()
539
- }
540
- }
541
-
542
- const doShare = async (p: PluginInfo, shared: boolean) => {
543
- setBusy(p.id)
544
- const res = await setPluginShared(p.id, shared)
545
- setBusy(null)
546
- if (!res.success) {
547
- setAlert({ type: "error", message: res.error || "Could not change sharing" })
548
- return
549
- }
550
- if (res.plugin && detail?.id === p.id) setDetail(res.plugin)
551
- setAlert({ type: "success", message: shared ? `${p.name} is shared. Its public link is in the details.` : `${p.name} is no longer shared` })
552
- loadLocal()
553
- }
554
-
555
- const doToggleLock = async (p: PluginInfo) => {
556
- setBusy(p.id)
557
- const nextLocked = !p.locked
558
- const res = await setPluginLocked(p.id, nextLocked)
559
- setBusy(null)
560
- if (!res.success) {
561
- setAlert({ type: "error", message: res.error || "Could not update lock status" })
562
- return
563
- }
564
- if (res.plugin && detail?.id === p.id) setDetail(res.plugin)
565
- setAlert({
566
- type: "success",
567
- message: nextLocked ? `${p.name} is locked against further editing.` : `${p.name} is unlocked for editing.`
568
- })
569
- loadLocal()
570
- }
571
-
572
- const doDeployStore = async (p: PluginInfo) => {
573
- setBusy(p.id)
574
- const res = await deployPluginToStore(p.id, true)
575
- setBusy(null)
576
- if (!res.success) {
577
- setAlert({ type: "error", message: res.error || "Could not deploy plugin to store" })
578
- return
579
- }
580
- if (res.plugin && detail?.id === p.id) setDetail(res.plugin)
581
- setAlert({
582
- type: "success",
583
- message: `${p.name} deployed to the plugin store and locked as complete.`
584
- })
585
- loadLocal()
586
- }
587
-
588
- const openBrandingModal = (p: PluginInfo) => {
589
- const branding = resolvePluginBranding(p)
590
- setBrandingPlugin(p)
591
- setBrandingIcon(branding.icon)
592
- setBrandingColor(branding.color)
593
- setBrandingBadge(branding.badge)
594
- setBrandingOpen(true)
595
- }
596
-
597
- const saveBranding = async () => {
598
- if (!brandingPlugin) return
599
- if (brandingPlugin.locked) {
600
- setAlert({ type: "error", message: "Plugin is locked. Unlock it before editing branding." })
601
- setBrandingOpen(false)
602
- return
603
- }
604
- setBusy(brandingPlugin.id)
605
- const res = await updatePluginBranding(brandingPlugin.id, {
606
- icon: brandingIcon.trim() || undefined,
607
- color: brandingColor.trim() || undefined,
608
- badge: brandingBadge.trim() || undefined
609
- })
610
- setBusy(null)
611
- setBrandingOpen(false)
612
- if (!res.success) {
613
- setAlert({ type: "error", message: res.error || "Could not save branding" })
614
- return
615
- }
616
- setAlert({ type: "success", message: `Updated branding for ${brandingPlugin.name}` })
617
- await loadLocal()
618
- if (detail?.id === brandingPlugin.id && res.plugin) {
619
- setDetail(res.plugin)
620
- }
621
- }
622
-
623
- const openDetail = async (p: PluginInfo, targetTab: DetailTab = "preview") => {
624
- setDetail(await fetchPlugin(p.id) || p)
625
- setDetailTab(targetTab)
626
- setSubcommand(p.cli?.subcommands?.[0]?.name || "")
627
- setArgs("")
628
- setOutput(null)
629
- setReadme("")
630
- setReadme(await fetchPluginReadme(p.id))
631
- }
632
-
633
- const closeDetail = () => {
634
- setDetail(null)
635
- if (searchParams.has("id") || searchParams.has("plugin")) {
636
- const next = new URLSearchParams(searchParams)
637
- next.delete("id")
638
- next.delete("plugin")
639
- setSearchParams(next)
640
- }
641
- }
642
-
643
- useEffect(() => {
644
- const id = searchParams.get("id") || searchParams.get("plugin")
645
- if (!id || detail?.id === id) return
646
- const match = installed.find((p) => p.id === id || p.id === `tr-plugin-${id}` || p.name === id)
647
- if (match) openDetail(match)
648
- // eslint-disable-next-line react-hooks/exhaustive-deps
649
- }, [searchParams, installed])
650
-
651
- useEffect(() => {
652
- if (!detail) return
653
- const fresh = installed.find((p) => p.id === detail.id)
654
- if (fresh && fresh !== detail) setDetail(fresh)
655
- // eslint-disable-next-line react-hooks/exhaustive-deps
656
- }, [installed])
657
-
658
- const runCommand = async () => {
659
- if (!detail) return
660
- setBusy(`run:${detail.id}`)
661
- const res = await runPluginCommand(detail.id, subcommand, args.trim() ? args.trim().split(/\s+/) : [])
662
- setBusy(null)
663
- if (res.error) setOutput({ text: res.error, ok: false })
664
- else setOutput({ text: (res.output || "").trim() || "(no output)", ok: res.success })
665
- }
666
-
667
- const filteredInstalled = installed.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)
730
-
731
- const tabButton = (id: Tab, label: string, count?: number) => (
732
- <button
733
- key={id}
734
- role="tab"
735
- aria-selected={tab === id}
736
- className={`btn btn-sm ${tab === id ? "btn-primary" : "btn-ghost"}`}
737
- onClick={() => setTab(id)}
738
- >
739
- {label}
740
- {count !== undefined && <span style={{ opacity: 0.75, fontWeight: 500, marginLeft: "4px" }}>{count}</span>}
741
- </button>
742
- )
743
-
744
- return (
745
- <div className="page" style={{ maxWidth: "100%", padding: "0 20px", margin: "0 auto", width: "100%" }}>
746
- <div className="page-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: "16px", flexWrap: "wrap" }}>
747
- <div>
748
- <h1>Plugins</h1>
749
- <p>
750
- Modular capability system for Total Recall and SSSS apps — memory categories, agent context, commands, and design-token UI.
751
- Customizable branding, locked release status, and direct store deployment are managed on each card.
752
- </p>
753
- </div>
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>
762
- </div>
763
-
764
- {alert && (
765
- <div className={`alert ${alert.type === "success" ? "alert-success" : "alert-error"}`} role="status"
766
- style={{ display: "flex", justifyContent: "space-between", gap: "12px", marginBottom: "20px" }}>
767
- <span>{alert.message}</span>
768
- <button onClick={() => setAlert(null)} aria-label="Dismiss" style={{ background: "none", border: "none", color: "inherit", cursor: "pointer" }}>×</button>
769
- </div>
770
- )}
771
-
772
- <div style={{ display: "flex", justifyContent: "space-between", gap: "12px", flexWrap: "wrap", marginBottom: "20px" }}>
773
- <div role="tablist" style={{ display: "flex", gap: "8px" }}>
774
- {tabButton("installed", "My Cards", myCards.length)}
775
- {tabButton("bundled", "Plugin Catalog", filteredStoreCatalog.length)}
776
- {tabButton("mesh", "On the mesh")}
777
- </div>
778
- <div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
779
- <input className="input" placeholder="Search plugins" value={query} onChange={(e) => setQuery(e.target.value)}
780
- aria-label="Search plugins" style={{ width: "220px" }} />
781
- <select className="select" value={useCase} onChange={(e) => setUseCase(e.target.value)} aria-label="Filter by use case" style={{ paddingRight: "32px" }}>
782
- <option value="">All use cases</option>
783
- {allUseCases.map((u) => <option key={u} value={u}>{u}</option>)}
784
- </select>
785
- </div>
786
- </div>
787
-
788
- {tab === "installed" && (
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"}
795
- </div>
796
- <div style={muted}>
797
- {viewMode === "workspace"
798
- ? "Core runtime foundation and activated repository cards. Standalone tools live in the Plugin Catalog."
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
- // Plugin faces are wide surfaces with screens to flip through, never narrow columns.
832
- <div className="card-grid" style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 720px), 1fr))", gap: "22px" }}>
833
- {shownInstalled.map((p) => {
834
- return (
835
- <div
836
- key={`${p.scope}:${p.id}`}
837
- onContextMenu={(e) => handleContextMenu(p, e)}
838
- onTouchStart={(e) => handleTouchStart(p, e)}
839
- onTouchEnd={cancelLongPress}
840
- style={{ display: "flex", flexDirection: "column", gap: "8px", minWidth: 0 }}
841
- >
842
- <PluginPanel plugin={p} />
843
- <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "0 4px", fontSize: "11px", color: "var(--text-tertiary)", flexWrap: "wrap", gap: "6px" }}>
844
- <div style={{ display: "flex", gap: "6px", alignItems: "center", flexWrap: "wrap" }}>
845
- <span className="badge">{p.scope}</span>
846
- <span className="badge">{sourceLabel(p)}</span>
847
- {p.shared && <span className="badge badge-accent">shared</span>}
848
- {!p.valid && <span className="badge badge-error">invalid manifest</span>}
849
- {p.modified_since_install && <span className="badge badge-warning">changed since install</span>}
850
- </div>
851
- <button
852
- type="button"
853
- className="btn btn-ghost btn-xs"
854
- onClick={() => openDetail(p, "details")}
855
- style={{ fontSize: "11px", color: "var(--text-secondary)" }}
856
- >
857
- Settings &amp; usage
858
- </button>
859
- {!DEFAULT_CORE_PLUGINS.has(p.id) && (
860
- <button
861
- type="button"
862
- className="btn btn-ghost btn-xs"
863
- onClick={() => removeFromWorkspace(p)}
864
- style={{ fontSize: "11px", color: "var(--text-secondary)" }}
865
- title="Move back to Plugin Store"
866
- >
867
- Remove from Workspace
868
- </button>
869
- )}
870
- </div>
871
- </div>
872
- )
873
- })}
874
- </div>
875
- )}
876
- </div>
877
- )
878
- )}
879
-
880
- {tab === "bundled" && (
881
- loading ? <div style={muted}>Loading…</div> : (
882
- <div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
883
- {/* Agent Plugin Recipe Hero Banner */}
884
- <div className="card" style={{
885
- background: "linear-gradient(135deg, rgba(30, 41, 59, 0.7) 0%, rgba(15, 23, 42, 0.85) 100%)",
886
- border: "1px solid rgba(99, 102, 241, 0.3)",
887
- padding: "20px",
888
- display: "flex",
889
- flexDirection: "column",
890
- gap: "14px"
891
- }}>
892
- <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: "16px", flexWrap: "wrap" }}>
893
- <div>
894
- <div style={{ display: "flex", alignItems: "center", gap: "8px", marginBottom: "4px" }}>
895
- <span className="badge" style={{ background: "rgba(99, 102, 241, 0.2)", color: "#818cf8", borderColor: "rgba(99, 102, 241, 0.4)", fontSize: "11px" }}>
896
- AGENT RECIPE ARCHITECTURE
897
- </span>
898
- <span style={{ fontSize: "11px", color: "var(--text-tertiary)" }}>
899
- Declarative Recipes · Private or Store
900
- </span>
901
- </div>
902
- <h2 style={{ fontSize: "18px", fontWeight: 700, margin: 0, color: "#f8fafc" }}>
903
- Build Bespoke Plugins with Your Coding Agent
904
- </h2>
905
- <p style={{ ...muted, margin: "6px 0 0 0", maxWidth: "640px" }}>
906
- 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.
907
- </p>
908
- </div>
909
- <div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
910
- <button
911
- type="button"
912
- className="btn btn-primary btn-sm"
913
- onClick={() => setRecipeOpen(true)}
914
- >
915
- View Agent Recipe
916
- </button>
917
- <button
918
- type="button"
919
- className="btn btn-ghost btn-sm"
920
- onClick={copyAgentPrompt}
921
- >
922
- Copy Agent Prompt
923
- </button>
924
- <button
925
- type="button"
926
- className="btn btn-ghost btn-sm"
927
- onClick={() => { setInstallOpen(true); setAlert(null) }}
928
- >
929
- Install from source…
930
- </button>
931
- </div>
932
- </div>
933
- </div>
934
-
935
- {filteredStoreCatalog.length === 0 ? (
936
- <Empty title="No plugins match in store" />
937
- ) : (
938
- <div className="card-grid" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(min(100%, 340px), 1fr))", gap: "16px" }}>
939
- {filteredStoreCatalog.map((item) => {
940
- const branding = resolvePluginBranding(item)
941
- return (
942
- <div
943
- key={item.id}
944
- className="card"
945
- style={{
946
- display: "flex",
947
- flexDirection: "column",
948
- gap: "10px",
949
- borderTop: `3px solid ${branding.color}`,
950
- background: "var(--bg-secondary)"
951
- }}
952
- >
953
- <div style={{ display: "flex", justifyContent: "space-between", gap: "8px", alignItems: "flex-start" }}>
954
- <div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
955
- <div
956
- style={{
957
- width: "34px",
958
- height: "34px",
959
- borderRadius: "8px",
960
- background: `${branding.color}20`,
961
- border: `1px solid ${branding.color}40`,
962
- display: "flex",
963
- alignItems: "center",
964
- justifyContent: "center",
965
- fontWeight: 700,
966
- fontSize: "12px",
967
- color: branding.color
968
- }}
969
- >
970
- {branding.icon || item.name.slice(0, 2).toUpperCase()}
971
- </div>
972
- <div>
973
- <div style={{ fontWeight: 600, fontSize: "14px", lineHeight: 1.2 }}>{item.name}</div>
974
- <div style={{ ...mono, color: "var(--text-tertiary)", fontSize: "11px" }}>
975
- {item.id} · v{item.version}
976
- </div>
977
- </div>
978
- </div>
979
- <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "4px" }}>
980
- <span className="badge" style={{ background: `${branding.color}15`, color: branding.color, borderColor: `${branding.color}35`, fontSize: "10px" }}>
981
- {branding.badge}
982
- </span>
983
- {item.inWorkspace && (
984
- <span className="badge badge-success" style={{ fontSize: "10px" }}>
985
- ✓ In Workspace
986
- </span>
987
- )}
988
- </div>
989
- </div>
990
-
991
- <div style={{ ...muted, fontSize: "12px", minHeight: "36px" }}>
992
- {item.description}
993
- </div>
994
-
995
- <div style={{ display: "flex", gap: "4px", flexWrap: "wrap", fontSize: "10px" }}>
996
- {item.cli && (
997
- <span className="badge" style={{ fontFamily: "var(--font-mono)" }}>
998
- CLI ({item.cli.subcommands?.length || 1})
999
- </span>
1000
- )}
1001
- {item.tasks && item.tasks.length > 0 && (
1002
- <span className="badge" style={{ fontFamily: "var(--font-mono)" }}>
1003
- {item.tasks.length} {item.tasks.length === 1 ? "task" : "tasks"}
1004
- </span>
1005
- )}
1006
- {item.secrets && item.secrets.length > 0 && (
1007
- <span className="badge" style={{ fontFamily: "var(--font-mono)" }}>
1008
- {item.secrets.length} {item.secrets.length === 1 ? "secret" : "secrets"}
1009
- </span>
1010
- )}
1011
- {DEFAULT_CORE_PLUGINS.has(item.id) && (
1012
- <span className="badge" style={{ background: "rgba(59, 130, 246, 0.15)", color: "#60a5fa" }}>
1013
- Core Bundle
1014
- </span>
1015
- )}
1016
- </div>
1017
-
1018
- <UseCaseChips useCases={item.use_cases} />
1019
-
1020
- <div style={{ marginTop: "auto", display: "flex", justifyContent: "space-between", alignItems: "center", gap: "8px", paddingTop: "8px", borderTop: "1px solid var(--border)" }}>
1021
- <button
1022
- type="button"
1023
- className="btn btn-ghost btn-sm"
1024
- onClick={() => {
1025
- if (item.sourcePlugin) {
1026
- openDetail(item.sourcePlugin, "preview")
1027
- } else {
1028
- openDetail({
1029
- id: item.id,
1030
- name: item.name,
1031
- version: item.version,
1032
- description: item.description,
1033
- author: null,
1034
- license: null,
1035
- homepage: null,
1036
- use_cases: item.use_cases,
1037
- valid: true,
1038
- errors: [],
1039
- scope: "global",
1040
- linked: false,
1041
- dir: "",
1042
- shared: false,
1043
- mesh_shared: false,
1044
- share_url: null,
1045
- source: { kind: "bundled", ref: item.id },
1046
- installed_at: null,
1047
- sha256: null,
1048
- installed_sha256: null,
1049
- modified_since_install: false,
1050
- file_count: null,
1051
- size_bytes: null,
1052
- categories: [],
1053
- tasks: item.tasks || [],
1054
- openwiki_hubs: [],
1055
- cli: item.cli as any,
1056
- has_generator: false,
1057
- branding: item.branding
1058
- }, "preview")
1059
- }
1060
- }}
1061
- >
1062
- Preview
1063
- </button>
1064
-
1065
- <div style={{ display: "flex", gap: "6px" }}>
1066
- {!item.installed ? (
1067
- <button
1068
- type="button"
1069
- className="btn btn-primary btn-sm"
1070
- disabled={busy === item.id}
1071
- onClick={() => doInstall(item.id)}
1072
- >
1073
- {busy === item.id ? "Installing…" : "Install"}
1074
- </button>
1075
- ) : item.inWorkspace ? (
1076
- <button
1077
- type="button"
1078
- className="btn btn-ghost btn-sm"
1079
- disabled={busy === item.id}
1080
- onClick={() => removeFromWorkspace(item.sourcePlugin || item)}
1081
- style={{ fontSize: "11px" }}
1082
- >
1083
- Remove from Workspace
1084
- </button>
1085
- ) : (
1086
- <button
1087
- type="button"
1088
- className="btn btn-primary btn-sm"
1089
- disabled={busy === item.id}
1090
- onClick={() => addToWorkspace(item.sourcePlugin || item)}
1091
- >
1092
- {busy === item.id ? "Adding…" : "+ Add to Workspace"}
1093
- </button>
1094
- )}
1095
- </div>
1096
- </div>
1097
- </div>
1098
- )
1099
- })}
1100
- </div>
1101
- )}
1102
- </div>
1103
- )
1104
- )}
1105
-
1106
- {tab === "mesh" && (
1107
- <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
1108
- <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: "12px", flexWrap: "wrap" }}>
1109
- <p style={{ ...muted, margin: 0 }}>Asked live from each node. Only plugins their owner chose to share appear here; content is hash-checked on install.</p>
1110
- <button className="btn btn-ghost btn-sm" onClick={loadPeers} disabled={peersLoading}>{peersLoading ? "Asking peers…" : "Refresh"}</button>
1111
- </div>
1112
-
1113
- {peersLoading && !peers ? <div style={muted}>Asking peers…</div> :
1114
- peers === null ? <Empty title="Could not ask the mesh">The brain server did not answer the peer query.</Empty> :
1115
- !peers.mesh.available ? (
1116
- <Empty title="This node is not on a mesh">Enroll it with <code style={mono}>npx total-recall mesh enroll</code> to share plugins between your nodes.</Empty>
1117
- ) : peers.peers.length === 0 ? <Empty title="No other nodes on the mesh" /> : (
1118
- <>
1119
- {!peers.mesh.configured && (
1120
- <div className="alert alert-warning">This node has no <code style={mono}>TR_MESH_SYNC_TOKEN</code>, so it cannot query peers. Set the same token on each node.</div>
1121
- )}
1122
- {peers.peers.map((node) => {
1123
- const shown = node.plugins.filter(matches)
1124
- return (
1125
- <div key={node.hostname} className="card">
1126
- <div style={{ display: "flex", justifyContent: "space-between", gap: "12px", alignItems: "center", marginBottom: node.plugins.length ? "12px" : 0, flexWrap: "wrap" }}>
1127
- <div>
1128
- <span style={{ fontWeight: 600 }}>{node.hostname}</span>{" "}
1129
- <span style={{ ...mono, color: "var(--text-tertiary)" }}>{node.ip}</span>
1130
- </div>
1131
- <span className={`badge ${node.status === "ok" ? "badge-online" : node.status === "offline" ? "badge-offline" : "badge-warning"}`}>
1132
- {PEER_STATUS_LABEL[node.status]}{node.status === "ok" ? ` · ${node.plugins.length} shared` : ""}
1133
- </span>
1134
- </div>
1135
- {node.error && node.status !== "ok" && <div style={{ ...muted, fontSize: "12px" }}>{node.error}</div>}
1136
- {node.status === "ok" && node.plugins.length === 0 && <div style={muted}>Shares no plugins.</div>}
1137
- {node.status === "ok" && node.plugins.length > 0 && shown.length === 0 && <div style={muted}>No shared plugins match the filter.</div>}
1138
- {shown.map((p) => {
1139
- const source = `peer:${node.hostname}/${p.id}`
1140
- return (
1141
- <div key={p.id} style={{ display: "flex", justifyContent: "space-between", gap: "16px", padding: "12px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }}>
1142
- <div style={{ minWidth: 0, flex: "1 1 320px", display: "flex", flexDirection: "column", gap: "6px" }}>
1143
- <div><span style={{ fontWeight: 600 }}>{p.name}</span> <span style={{ ...mono, color: "var(--text-tertiary)" }}>v{p.version}</span></div>
1144
- <div style={muted}>{p.description}</div>
1145
- <UseCaseChips useCases={p.use_cases} />
1146
- <div style={{ ...mono, color: "var(--text-tertiary)" }} title={p.sha256}>
1147
- sha256 {shortHash(p.sha256)} · {p.file_count} files · {formatBytes(p.size_bytes)}
1148
- </div>
1149
- </div>
1150
- <div style={{ alignSelf: "center" }}>
1151
- {p.installed
1152
- ? <span className={`badge ${p.same_as_installed ? "badge-success" : "badge-warning"}`}>
1153
- {p.same_as_installed ? "installed (same content)" : "installed (different content)"}
1154
- </span>
1155
- : <button className="btn btn-primary btn-sm" disabled={busy === source} onClick={() => doInstall(source)}>
1156
- {busy === source ? "Installing…" : "Install"}
1157
- </button>}
1158
- </div>
1159
- </div>
1160
- )
1161
- })}
1162
- </div>
1163
- )
1164
- })}
1165
- </>
1166
- )}
1167
- </div>
1168
- )}
1169
-
1170
- {installOpen && (
1171
- <div role="dialog" aria-modal="true" aria-label="Install plugin from source" onClick={() => setInstallOpen(false)}
1172
- style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.6)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000, padding: "16px" }}>
1173
- <form className="card" onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: "520px", display: "flex", flexDirection: "column", gap: "14px" }}
1174
- onSubmit={async (e) => {
1175
- e.preventDefault()
1176
- if (!installSource.trim()) return
1177
- if (await doInstall(installSource.trim(), { link: installLink, global: installGlobal })) {
1178
- setInstallOpen(false)
1179
- setInstallSource("")
1180
- }
1181
- }}>
1182
- <div style={{ fontWeight: 600, fontSize: "16px" }}>Install from source</div>
1183
- <div style={muted}>
1184
- A bundled plugin id, <code style={mono}>peer:&lt;host&gt;/&lt;id&gt;</code>, a git URL, or a folder on this machine containing <code style={mono}>plugin.json</code>.
1185
- </div>
1186
- <input className="input" autoFocus value={installSource} onChange={(e) => setInstallSource(e.target.value)}
1187
- placeholder="https://github.com/you/my-plugin.git" aria-label="Plugin source" />
1188
- <label style={{ ...muted, display: "flex", gap: "8px", alignItems: "center" }}>
1189
- <input type="checkbox" checked={installLink} onChange={(e) => setInstallLink(e.target.checked)} />
1190
- Link a local folder instead of copying it (for plugin development)
1191
- </label>
1192
- <label style={{ ...muted, display: "flex", gap: "8px", alignItems: "center" }}>
1193
- <input type="checkbox" checked={installGlobal} onChange={(e) => setInstallGlobal(e.target.checked)} />
1194
- Install for every project on this machine
1195
- </label>
1196
- <div style={{ display: "flex", justifyContent: "flex-end", gap: "8px" }}>
1197
- <button type="button" className="btn btn-ghost" onClick={() => setInstallOpen(false)}>Cancel</button>
1198
- <button type="submit" className="btn btn-primary" disabled={!installSource.trim() || busy !== null}>
1199
- {busy ? "Installing…" : "Install"}
1200
- </button>
1201
- </div>
1202
- </form>
1203
- </div>
1204
- )}
1205
-
1206
- {/* Branding Customization Modal */}
1207
- {brandingOpen && brandingPlugin && (
1208
- <div role="dialog" aria-modal="true" aria-label="Customize Plugin Branding" onClick={() => setBrandingOpen(false)}
1209
- style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.6)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000, padding: "16px" }}>
1210
- <div className="card" onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: "480px", display: "flex", flexDirection: "column", gap: "16px" }}>
1211
- <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
1212
- <div style={{ fontWeight: 700, fontSize: "16px" }}>Customize Branding: {brandingPlugin.name}</div>
1213
- <button className="btn btn-ghost btn-sm" onClick={() => setBrandingOpen(false)}>✕</button>
1214
- </div>
1215
-
1216
- {brandingPlugin.locked && (
1217
- <div className="alert alert-warning" style={{ fontSize: "12px" }}>
1218
- [LOCKED] This plugin is marked complete and locked against editing. Unlock it to change branding.
1219
- </div>
1220
- )}
1221
-
1222
- <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
1223
- <label style={{ fontSize: "12px", color: "var(--text-secondary)" }}>Wordmark Glyph / Tag</label>
1224
- <div style={{ display: "flex", gap: "8px" }}>
1225
- <input
1226
- className="input"
1227
- value={brandingIcon}
1228
- onChange={(e) => setBrandingIcon(e.target.value)}
1229
- style={{ width: "90px", textAlign: "center", fontSize: "14px", fontFamily: "var(--font-mono)" }}
1230
- disabled={brandingPlugin.locked}
1231
- />
1232
- <div style={{ display: "flex", gap: "4px", flexWrap: "wrap", alignItems: "center" }}>
1233
- {["[TEL]", "[DNS]", "[SIGN]", "[GATE]", "[CLI]", "[LAW]", "[UI]", "[SMS]", "[FIND]", "[GIT]", "[OPS]", "[WARN]", "[CORE]"].map((glyph) => (
1234
- <button
1235
- key={glyph}
1236
- type="button"
1237
- className="btn btn-ghost btn-sm"
1238
- style={{ padding: "4px 6px", fontSize: "11px", fontFamily: "var(--font-mono)" }}
1239
- disabled={brandingPlugin.locked}
1240
- onClick={() => setBrandingIcon(glyph)}
1241
- >
1242
- {glyph}
1243
- </button>
1244
- ))}
1245
- </div>
1246
- </div>
1247
- </div>
1248
-
1249
- <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
1250
- <label style={{ fontSize: "12px", color: "var(--text-secondary)" }}>Theme Color (Hex)</label>
1251
- <div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
1252
- <input
1253
- type="color"
1254
- value={brandingColor}
1255
- onChange={(e) => setBrandingColor(e.target.value)}
1256
- style={{ width: "42px", height: "38px", border: "none", cursor: "pointer", background: "none" }}
1257
- disabled={brandingPlugin.locked}
1258
- />
1259
- <input
1260
- className="input"
1261
- value={brandingColor}
1262
- onChange={(e) => setBrandingColor(e.target.value)}
1263
- style={{ flex: 1, fontFamily: "var(--font-mono)" }}
1264
- disabled={brandingPlugin.locked}
1265
- />
1266
- </div>
1267
- </div>
1268
-
1269
- <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
1270
- <label style={{ fontSize: "12px", color: "var(--text-secondary)" }}>Domain Badge Text</label>
1271
- <input
1272
- className="input"
1273
- value={brandingBadge}
1274
- onChange={(e) => setBrandingBadge(e.target.value)}
1275
- placeholder="e.g. Telephony, Quality, DNS"
1276
- disabled={brandingPlugin.locked}
1277
- />
1278
- </div>
1279
-
1280
- <div style={{ display: "flex", justifyContent: "flex-end", gap: "8px", marginTop: "8px" }}>
1281
- <button type="button" className="btn btn-ghost" onClick={() => setBrandingOpen(false)}>Cancel</button>
1282
- <button
1283
- type="button"
1284
- className="btn btn-primary"
1285
- onClick={saveBranding}
1286
- disabled={brandingPlugin.locked || busy !== null}
1287
- >
1288
- Save Branding
1289
- </button>
1290
- </div>
1291
- </div>
1292
- </div>
1293
- )}
1294
-
1295
- {/* Agent Plugin Recipe Modal */}
1296
- {recipeOpen && (
1297
- <div
1298
- role="dialog"
1299
- aria-modal="true"
1300
- aria-label="Agent Plugin Recipe"
1301
- onClick={() => setRecipeOpen(false)}
1302
- style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.65)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000, padding: "16px" }}
1303
- >
1304
- <div
1305
- className="card"
1306
- onClick={(e) => e.stopPropagation()}
1307
- style={{ width: "100%", maxWidth: "680px", maxHeight: "90vh", overflowY: "auto", display: "flex", flexDirection: "column", gap: "16px" }}
1308
- >
1309
- <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
1310
- <div>
1311
- <h3 style={{ margin: 0, fontSize: "17px", fontWeight: 700 }}>Agent Plugin Recipe</h3>
1312
- <div style={{ ...muted, fontSize: "12px" }}>How your coding agent authors private or store plugins</div>
1313
- </div>
1314
- <button className="btn btn-ghost btn-sm" onClick={() => setRecipeOpen(false)}>✕</button>
1315
- </div>
1316
-
1317
- <div style={{ display: "flex", flexDirection: "column", gap: "12px", fontSize: "13px" }}>
1318
- <div style={{ background: "var(--bg-tertiary)", padding: "12px", borderRadius: "8px", border: "1px solid var(--border)" }}>
1319
- <div style={{ fontWeight: 600, marginBottom: "4px" }}>1. Command your agent</div>
1320
- <div style={muted}>
1321
- Copy and send this instruction to your coding agent (Antigravity, Claude, Codex, DSH):
1322
- </div>
1323
- <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" }}>
1324
- {AGENT_RECIPE_PROMPT}
1325
- </pre>
1326
- <div style={{ display: "flex", justifyContent: "flex-end", marginTop: "8px" }}>
1327
- <button type="button" className="btn btn-primary btn-sm" onClick={() => copyAgentPrompt()}>
1328
- Copy Agent Prompt
1329
- </button>
1330
- </div>
1331
- </div>
1332
-
1333
- <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px" }}>
1334
- <div style={{ background: "var(--bg-tertiary)", padding: "12px", borderRadius: "8px", border: "1px solid var(--border)" }}>
1335
- <div style={{ fontWeight: 600, color: "#38bdf8", marginBottom: "4px" }}>Private Plugins</div>
1336
- <div style={{ ...muted, fontSize: "12px", lineHeight: 1.4 }}>
1337
- 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.
1338
- </div>
1339
- </div>
1340
-
1341
- <div style={{ background: "var(--bg-tertiary)", padding: "12px", borderRadius: "8px", border: "1px solid var(--border)" }}>
1342
- <div style={{ fontWeight: 600, color: "#a855f7", marginBottom: "4px" }}>Plugin Store Distribution</div>
1343
- <div style={{ ...muted, fontSize: "12px", lineHeight: 1.4 }}>
1344
- 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.
1345
- </div>
1346
- </div>
1347
- </div>
1348
-
1349
- <div style={{ background: "rgba(16, 185, 129, 0.08)", padding: "12px", borderRadius: "8px", border: "1px solid rgba(16, 185, 129, 0.2)" }}>
1350
- <div style={{ fontWeight: 600, color: "#10b981", marginBottom: "4px" }}>Quality Invariants Enforced</div>
1351
- <ul style={{ margin: "4px 0 0 16px", padding: 0, fontSize: "12px", color: "var(--text-secondary)" }}>
1352
- <li>Guaranteed Title, Brand Glyph, Version, and Settings drawer trigger on every card</li>
1353
- <li>Authentic domain tools only — zero fake audio equalizers or fake skins</li>
1354
- <li>Touch-first ergonomics with minimum 44px tap targets</li>
1355
- </ul>
1356
- </div>
1357
- </div>
1358
-
1359
- <div style={{ display: "flex", justifyContent: "flex-end", marginTop: "8px" }}>
1360
- <button type="button" className="btn btn-ghost" onClick={() => setRecipeOpen(false)}>Close</button>
1361
- </div>
1362
- </div>
1363
- </div>
1364
- )}
1365
-
1366
- {/* Plugin Detail & Interactive Preview Drawer */}
1367
- {detail && (
1368
- <div role="dialog" aria-modal="true" aria-label={detail.name} onClick={closeDetail}
1369
- style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.6)", display: "flex", justifyContent: "flex-end", zIndex: 1000 }}>
1370
- <div onClick={(e) => e.stopPropagation()}
1371
- style={{ width: "100%", maxWidth: "680px", height: "100%", overflowY: "auto", background: "var(--bg-secondary)", borderLeft: "1px solid var(--border)", padding: "24px", display: "flex", flexDirection: "column", gap: "16px" }}>
1372
-
1373
- {/* Drawer Header */}
1374
- {(() => {
1375
- const branding = resolvePluginBranding(detail)
1376
- const hasPanel = pluginElements(detail).length > 0
1377
- return (
1378
- <>
1379
- <div style={{ display: "flex", justifyContent: "space-between", gap: "12px" }}>
1380
- <div style={{ display: "flex", gap: "12px", alignItems: "center" }}>
1381
- <div
1382
- style={{
1383
- width: "44px",
1384
- height: "44px",
1385
- borderRadius: "10px",
1386
- background: `${branding.color}20`,
1387
- border: `1px solid ${branding.color}50`,
1388
- display: "flex",
1389
- alignItems: "center",
1390
- justifyContent: "center",
1391
- fontSize: "22px",
1392
- flexShrink: 0
1393
- }}
1394
- >
1395
- {branding.icon}
1396
- </div>
1397
- <div>
1398
- <div style={{ fontSize: "18px", fontWeight: 700 }}>{detail.name}</div>
1399
- <div style={{ display: "flex", gap: "8px", alignItems: "center", marginTop: "2px" }}>
1400
- <span style={{ ...mono, color: "var(--text-tertiary)" }}>{detail.id} · v{detail.version}</span>
1401
- <span className="badge" style={{ background: `${branding.color}15`, color: branding.color, borderColor: `${branding.color}40`, fontSize: "11px" }}>
1402
- {branding.badge}
1403
- </span>
1404
- {detail.locked && <span className="badge" style={{ background: "#2e1065", color: "#c084fc" }}>Locked</span>}
1405
- </div>
1406
- </div>
1407
- </div>
1408
- <button className="btn btn-ghost btn-sm" onClick={closeDetail} aria-label="Close">Close</button>
1409
- </div>
1410
-
1411
- <div style={muted}>{detail.description}</div>
1412
-
1413
- {!detail.valid && (
1414
- <div className="alert alert-error">
1415
- {detail.errors.map((e) => <div key={e}>{e}</div>)}
1416
- </div>
1417
- )}
1418
-
1419
- {/* Drawer Navigation Tabs */}
1420
- <div role="tablist" style={{ display: "flex", gap: "8px", borderBottom: "1px solid var(--border)", paddingBottom: "10px" }}>
1421
- {hasPanel && (
1422
- <button
1423
- role="tab"
1424
- aria-selected={detailTab === "preview"}
1425
- className={`btn btn-sm ${detailTab === "preview" ? "btn-primary" : "btn-ghost"}`}
1426
- onClick={() => setDetailTab("preview")}
1427
- >
1428
- Open {detail.name}
1429
- </button>
1430
- )}
1431
- {detail.cli && (
1432
- <button
1433
- role="tab"
1434
- aria-selected={detailTab === "run"}
1435
- className={`btn btn-sm ${detailTab === "run" ? "btn-primary" : "btn-ghost"}`}
1436
- onClick={() => setDetailTab("run")}
1437
- >
1438
- Run CLI
1439
- </button>
1440
- )}
1441
- <button
1442
- role="tab"
1443
- aria-selected={detailTab === "details"}
1444
- className={`btn btn-sm ${detailTab === "details" ? "btn-primary" : "btn-ghost"}`}
1445
- onClick={() => setDetailTab("details")}
1446
- >
1447
- Settings
1448
- </button>
1449
- <button
1450
- role="tab"
1451
- aria-selected={detailTab === "readme"}
1452
- className={`btn btn-sm ${detailTab === "readme" ? "btn-primary" : "btn-ghost"}`}
1453
- onClick={() => setDetailTab("readme")}
1454
- >
1455
- README
1456
- </button>
1457
- </div>
1458
-
1459
- {/* Plugin-owned interface */}
1460
- {detailTab === "preview" && (
1461
- <div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
1462
- <PluginPanel plugin={detail} />
1463
- </div>
1464
- )}
1465
-
1466
- {/* Tab 2: CLI Command Runner */}
1467
- {detailTab === "run" && detail.cli && (
1468
- <div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
1469
- <div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
1470
- <select className="select" value={subcommand} onChange={(e) => setSubcommand(e.target.value)} aria-label="Subcommand" style={{ paddingRight: "32px" }}>
1471
- {(detail.cli.subcommands.length ? detail.cli.subcommands : [{ name: "" }]).map((s) => (
1472
- <option key={s.name} value={s.name}>{s.name || "(default)"}</option>
1473
- ))}
1474
- </select>
1475
- <input className="input" value={args} onChange={(e) => setArgs(e.target.value)} placeholder="extra arguments" aria-label="Arguments" style={{ flex: 1, minWidth: "160px" }} />
1476
- <button className="btn btn-primary" onClick={runCommand} disabled={busy === `run:${detail.id}` || !detail.valid}>
1477
- {busy === `run:${detail.id}` ? "Running…" : "Run"}
1478
- </button>
1479
- </div>
1480
- <div style={{ ...muted, fontSize: "12px" }}>
1481
- {detail.cli.subcommands.find((s) => s.name === subcommand)?.description}
1482
- {" "}Same as <code style={mono}>npx total-recall {detail.cli.command} {subcommand} {args}</code>. Runs in a separate process.
1483
- </div>
1484
- {output && (
1485
- <pre style={{ ...mono, margin: 0, padding: "14px", borderRadius: "var(--radius-sm)", background: "var(--bg-primary)", border: `1px solid ${output.ok ? "var(--border)" : "var(--error)"}`, whiteSpace: "pre-wrap", maxHeight: "420px", overflow: "auto" }}>
1486
- {output.text}
1487
- </pre>
1488
- )}
1489
- </div>
1490
- )}
1491
-
1492
- {/* Tab 3: Plugin Facts & Config Controls */}
1493
- {detailTab === "details" && (
1494
- <div>
1495
- <PluginSettings plugin={detail} />
1496
- <Fact label="Scope">{detail.scope}{detail.linked ? " (linked folder)" : ""}</Fact>
1497
- <Fact label="Source">{sourceLabel(detail)} — <span style={mono}>{detail.source.ref}</span></Fact>
1498
- <Fact label="Installed">{detail.installed_at ? new Date(detail.installed_at).toLocaleString() : "no install record"}</Fact>
1499
- <Fact label="Content sha256"><span style={mono}>{detail.sha256 || "—"}</span></Fact>
1500
- {detail.modified_since_install && <Fact label="At install"><span style={mono}>{detail.installed_sha256}</span></Fact>}
1501
- <Fact label="Files">{detail.file_count ?? "—"} · {formatBytes(detail.size_bytes)}</Fact>
1502
- <Fact label="Shared">{detail.shared ? "yes" : "no"}</Fact>
1503
- <Fact label="Lock Status">{detail.locked ? "Locked (Completed)" : "Unlocked (Editable)"}</Fact>
1504
- <Fact label="Store Status">{detail.store_deployed ? "Published in Plugin Store" : "Not yet deployed to store"}</Fact>
1505
- {detail.shared && <Fact label="Public link">{detail.share_url ? <input className="input" aria-label="Public plugin share link" readOnly value={detail.share_url} onFocus={(e) => e.currentTarget.select()} /> : "Set TR_PUBLIC_BASE_URL to your public HTTPS origin to create a link."}</Fact>}
1506
- <Fact label="Use cases">{detail.use_cases.join(", ") || "—"}</Fact>
1507
- {detail.author && <Fact label="Author">{detail.author}</Fact>}
1508
- {detail.license && <Fact label="License">{detail.license}</Fact>}
1509
- <Fact label="Location"><span style={mono}>{detail.dir}</span></Fact>
1510
- <Fact label="Agent context">{detail.has_generator ? "adds a block to compiled instructions" : "—"}</Fact>
1511
- <Fact label="Memory categories">
1512
- {detail.categories.length ? detail.categories.map((c) => c.name).join(", ") : "—"}
1513
- </Fact>
1514
- <Fact label="Scheduled tasks">
1515
- {detail.tasks.length === 0 ? "—" : (
1516
- <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
1517
- {detail.tasks.map((t) => (
1518
- <div key={t.command}>
1519
- <span style={mono}>{t.schedule}</span> {t.command} — {t.intent}
1520
- <div style={{ color: "var(--text-tertiary)", fontSize: "12px" }}>last run: {t.last_run || "not yet"}</div>
1521
- </div>
1522
- ))}
1523
- </div>
1524
- )}
1525
- </Fact>
1526
-
1527
- {/* Config Actions Toolbar inside drawer */}
1528
- <div style={{ display: "flex", gap: "8px", marginTop: "16px", flexWrap: "wrap" }}>
1529
- <button
1530
- className="btn btn-ghost btn-sm"
1531
- onClick={() => openBrandingModal(detail)}
1532
- disabled={detail.locked}
1533
- >
1534
- Customize Branding
1535
- </button>
1536
- <button
1537
- className="btn btn-ghost btn-sm"
1538
- onClick={() => doToggleLock(detail)}
1539
- disabled={busy === detail.id}
1540
- >
1541
- {detail.locked ? "Unlock Editing" : "Lock When Complete"}
1542
- </button>
1543
- <button
1544
- className="btn btn-primary btn-sm"
1545
- onClick={() => doDeployStore(detail)}
1546
- disabled={busy === detail.id || !detail.valid}
1547
- >
1548
- {detail.store_deployed ? "✓ Deployed to Store" : "Deploy to Plugin Store"}
1549
- </button>
1550
- <button
1551
- className="btn btn-ghost btn-sm"
1552
- disabled={busy === detail.id || (!detail.valid && !detail.shared)}
1553
- onClick={() => doShare(detail, !detail.shared)}
1554
- >
1555
- {detail.shared ? "Stop sharing" : "Share"}
1556
- </button>
1557
- <button
1558
- className="btn btn-ghost btn-sm"
1559
- disabled={busy === detail.id}
1560
- onClick={() => doRemove(detail)}
1561
- >
1562
- Remove
1563
- </button>
1564
- </div>
1565
- </div>
1566
- )}
1567
-
1568
- {/* Tab 4: Markdown Readme */}
1569
- {detailTab === "readme" && (
1570
- <div style={{ fontSize: "14px", lineHeight: 1.6 }}>{readme ? renderMarkdown(readme) : <span style={muted}>Loading…</span>}</div>
1571
- )}
1572
- </>
1573
- )
1574
- })()}
1575
- </div>
1576
- </div>
1577
- )}
1578
-
1579
- {/* Floating Touch & Right-Click Context Menu */}
1580
- {contextMenu && (
1581
- <div
1582
- role="menu"
1583
- aria-label="Plugin actions menu"
1584
- onClick={(e) => e.stopPropagation()}
1585
- onPointerDown={(e) => e.stopPropagation()}
1586
- style={{
1587
- position: "fixed",
1588
- left: Math.max(12, Math.min(contextMenu.x, window.innerWidth - 264)),
1589
- top: Math.max(12, Math.min(contextMenu.y, window.innerHeight - 380)),
1590
- zIndex: 2000,
1591
- width: "250px",
1592
- background: "var(--bg-secondary)",
1593
- border: "1px solid var(--border)",
1594
- borderRadius: "10px",
1595
- boxShadow: "0 12px 36px rgba(0,0,0,0.55), 0 0 0 1px rgba(255,255,255,0.08)",
1596
- padding: "6px",
1597
- display: "flex",
1598
- flexDirection: "column",
1599
- gap: "2px",
1600
- backdropFilter: "blur(12px)"
1601
- }}
1602
- >
1603
- <div style={{ padding: "8px 10px", borderBottom: "1px solid var(--border)", display: "flex", alignItems: "center", gap: "8px" }}>
1604
- <div style={{ width: "8px", height: "8px", borderRadius: "50%", background: resolvePluginBranding(contextMenu.plugin).color }} />
1605
- <div style={{ minWidth: 0 }}>
1606
- <div style={{ fontWeight: 600, fontSize: "13px", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
1607
- {contextMenu.plugin.name}
1608
- </div>
1609
- <div style={{ ...mono, fontSize: "10px", color: "var(--text-tertiary)" }}>
1610
- {contextMenu.plugin.id} · v{contextMenu.plugin.version}
1611
- </div>
1612
- </div>
1613
- </div>
1614
-
1615
- <button
1616
- className="btn btn-ghost btn-sm"
1617
- style={{ justifyContent: "flex-start", width: "100%", padding: "8px 10px", fontSize: "12px", minHeight: "36px" }}
1618
- onClick={() => {
1619
- const p = contextMenu.plugin
1620
- setContextMenu(null)
1621
- openDetail(p, "preview")
1622
- }}
1623
- >
1624
- Open UI Component
1625
- </button>
1626
-
1627
- <button
1628
- className="btn btn-ghost btn-sm"
1629
- style={{ justifyContent: "flex-start", width: "100%", padding: "8px 10px", fontSize: "12px", minHeight: "36px" }}
1630
- onClick={() => {
1631
- const p = contextMenu.plugin
1632
- setContextMenu(null)
1633
- openDetail(p, "run")
1634
- }}
1635
- >
1636
- Run CLI ({contextMenu.plugin.cli?.command || contextMenu.plugin.id})
1637
- </button>
1638
-
1639
- <button
1640
- className="btn btn-ghost btn-sm"
1641
- style={{ justifyContent: "flex-start", width: "100%", padding: "8px 10px", fontSize: "12px", minHeight: "36px" }}
1642
- onClick={() => {
1643
- const p = contextMenu.plugin
1644
- setContextMenu(null)
1645
- openDetail(p, "details")
1646
- }}
1647
- >
1648
- Configure &amp; Settings
1649
- </button>
1650
-
1651
- <button
1652
- className="btn btn-ghost btn-sm"
1653
- style={{ justifyContent: "flex-start", width: "100%", padding: "8px 10px", fontSize: "12px", minHeight: "36px" }}
1654
- onClick={() => {
1655
- const p = contextMenu.plugin
1656
- setContextMenu(null)
1657
- openDetail(p, "readme")
1658
- }}
1659
- >
1660
- View Documentation
1661
- </button>
1662
-
1663
- <div style={{ height: "1px", background: "var(--border)", margin: "4px 0" }} />
1664
-
1665
- <button
1666
- className="btn btn-ghost btn-sm"
1667
- style={{ justifyContent: "flex-start", width: "100%", padding: "8px 10px", fontSize: "12px", minHeight: "36px" }}
1668
- onClick={() => {
1669
- const p = contextMenu.plugin
1670
- setContextMenu(null)
1671
- doShare(p, !p.shared)
1672
- }}
1673
- >
1674
- {contextMenu.plugin.shared ? "Stop sharing on mesh" : "Share on mesh & link"}
1675
- </button>
1676
-
1677
- <button
1678
- className="btn btn-ghost btn-sm"
1679
- style={{ justifyContent: "flex-start", width: "100%", padding: "8px 10px", fontSize: "12px", minHeight: "36px" }}
1680
- onClick={() => {
1681
- const p = contextMenu.plugin
1682
- setContextMenu(null)
1683
- doToggleLock(p)
1684
- }}
1685
- >
1686
- {contextMenu.plugin.locked ? "Unlock editing" : "Lock for release"}
1687
- </button>
1688
-
1689
- <button
1690
- className="btn btn-ghost btn-sm"
1691
- style={{ justifyContent: "flex-start", width: "100%", padding: "8px 10px", fontSize: "12px", minHeight: "36px" }}
1692
- onClick={() => {
1693
- const p = contextMenu.plugin
1694
- setContextMenu(null)
1695
- void navigator.clipboard?.writeText(`npx total-recall ${p.cli?.command || p.id}`)
1696
- setAlert({ type: "success", message: `Copied "npx total-recall ${p.cli?.command || p.id}" to clipboard` })
1697
- }}
1698
- >
1699
- Copy CLI command
1700
- </button>
1701
-
1702
- <div style={{ height: "1px", background: "var(--border)", margin: "4px 0" }} />
1703
-
1704
- <button
1705
- className="btn btn-ghost btn-sm"
1706
- style={{ justifyContent: "flex-start", width: "100%", padding: "8px 10px", fontSize: "12px", color: "var(--color-error, #f87171)", minHeight: "36px" }}
1707
- onClick={() => {
1708
- const p = contextMenu.plugin
1709
- setContextMenu(null)
1710
- doRemove(p)
1711
- }}
1712
- >
1713
- Uninstall plugin
1714
- </button>
1715
- </div>
1716
- )}
1717
- </div>
1718
- )
1719
- }