total-recall-brain 3.45.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 (362) hide show
  1. package/bin/total-recall.mjs +4 -2
  2. package/frontend/dist/assets/index-BRYE1jmv.css +1 -0
  3. package/frontend/dist/assets/index-D1goIPjC.js +5598 -0
  4. package/frontend/dist/index.html +2 -2
  5. package/frontend/dist/sw.js +5 -4
  6. package/frontend/src/App.tsx +183 -236
  7. package/frontend/src/api/auth-inventory.ts +62 -0
  8. package/frontend/src/api/memory.ts +13 -8
  9. package/frontend/src/api/plugin-credentials.ts +23 -0
  10. package/frontend/src/api/plugins.ts +37 -53
  11. package/frontend/src/api/workspace-drawers.ts +97 -0
  12. package/frontend/src/components/BrainSelector.tsx +9 -3
  13. package/frontend/src/components/plugins/PluginPanel.tsx +83 -42
  14. package/frontend/src/components/plugins/PluginSettings.tsx +10 -20
  15. package/frontend/src/components/plugins/PluginWorkspace.tsx +367 -0
  16. package/frontend/src/components/plugins/ScopedPluginCommands.tsx +142 -0
  17. package/frontend/src/components/plugins/plugin-frame.ts +44 -33
  18. package/frontend/src/components/plugins/scoped-plugin-operations.ts +28 -0
  19. package/frontend/src/components/workspace/DrawerChat.tsx +110 -0
  20. package/frontend/src/components/workspace/DrawerLayout.tsx +196 -0
  21. package/frontend/src/components/workspace/DrawerMarkup.tsx +49 -0
  22. package/frontend/src/components/workspace/PluginChunkLibrary.tsx +92 -0
  23. package/frontend/src/components/workspace/PluginHome.tsx +76 -0
  24. package/frontend/src/components/workspace/WorkspaceDrawers.tsx +96 -0
  25. package/frontend/src/components/workspace/WorkspacePage.tsx +95 -0
  26. package/frontend/src/components/workspace/usePluginDesign.ts +38 -0
  27. package/frontend/src/index.css +409 -136
  28. package/frontend/src/pages/HelpPage.tsx +15 -46
  29. package/frontend/src/pages/MemoryPage.tsx +19 -36
  30. package/frontend/src/pages/SettingsPage.tsx +1 -1
  31. package/metadata.plugin.schema.json +53 -227
  32. package/package.json +1 -1
  33. package/scaffold/.agent/skills/plugin/SKILL.md +13 -11
  34. package/scaffold/.agent/skills/plugin/evals/README.md +14 -21
  35. package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +15 -18
  36. package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +19 -24
  37. package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +12 -20
  38. package/scaffold/.agent/skills/plugin/evals/cases/04-home-collects-credential.mjs +35 -0
  39. package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +8 -28
  40. package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +85 -47
  41. package/scaffold/.agent/skills/plugin/evals/run.mjs +17 -18
  42. package/scaffold/.agent/skills/plugin/prompts/05-credentials.md +4 -4
  43. package/scaffold/.agent/skills/plugin/prompts/07-scaffold.md +2 -2
  44. package/scaffold/.agent/skills/plugin/prompts/08-verify.md +4 -4
  45. package/scaffold/.agent/skills/plugin/references/anti-patterns.md +2 -2
  46. package/scaffold/.agent/skills/plugin/references/generative-home.md +38 -0
  47. package/scaffold/.agent/skills/plugin/references/scaffold.md +5 -5
  48. package/scaffold/.agent/skills/plugin/references/surfaces.md +13 -4
  49. package/scaffold/.agent/skills/plugin/scripts/record.mjs +75 -153
  50. package/scaffold/.agent/skills/plugin/subagents/plugin-reviewer.md +4 -4
  51. package/scaffold/.agent/skills/total-recall/plugins/auth/DESIGN.md +76 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +3 -1
  53. package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +20 -15
  54. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +6 -6
  55. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +2 -1
  56. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +13 -51
  57. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/access-overview.md +17 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/agent-login.md +20 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/baseline-audit.md +22 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-repository-binding.md +17 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-store-maintenance.md +17 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-password.md +18 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-sessions.md +18 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/endpoint-probe.md +23 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/google-sign-in.md +21 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/mesh-machines.md +16 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/passkey-enrollment.md +21 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/registered-brains.md +16 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/scoped-access-keys.md +17 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/secret-inventory.md +25 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/signing-key-rotation.md +22 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/home.js +375 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/automations/{ui/DESIGN.md → DESIGN.md} +32 -21
  74. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +17 -1
  75. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +10 -1
  76. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +6 -6
  77. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +4 -3
  78. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +11 -27
  79. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -2
  80. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/control-ledger.md +42 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/local-inventory-triage.md +17 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/pending-control.md +39 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/run-evidence.md +41 -0
  84. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/schedule-vs-run-evidence.md +17 -0
  85. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/source-coverage.md +41 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/{card.js → home.js} +32 -27
  87. package/scaffold/.agent/skills/total-recall/plugins/code-quality/DESIGN.md +53 -0
  88. package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +23 -0
  89. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +4 -2
  90. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +4 -2
  91. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +6 -6
  92. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +2 -1
  93. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +40 -40
  94. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/config.md +15 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/detect.md +15 -0
  96. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/gate.md +15 -0
  97. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/init.md +15 -0
  98. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/report.md +17 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/run.md +17 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/snapshot.md +15 -0
  101. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +55 -23
  102. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/home.mjs +214 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/DESIGN.md +146 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +6 -6
  105. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +2 -1
  106. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +51 -34
  107. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +1 -1
  108. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/create.md +35 -0
  109. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/history.md +35 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/library.md +49 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/list.md +42 -0
  112. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/promote.md +40 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/read.md +25 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/remove.md +26 -0
  115. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/rollback.md +26 -0
  116. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/show.md +53 -0
  117. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/status.md +40 -0
  118. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test-path.md +38 -0
  119. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test.md +46 -0
  120. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/update.md +32 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/home.js +533 -0
  122. package/scaffold/.agent/skills/total-recall/plugins/embeddings/DESIGN.md +110 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +6 -6
  124. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +2 -1
  125. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +13 -10
  126. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/benchmark.md +49 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/coverage.md +53 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/models.md +43 -0
  129. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/pull.md +41 -0
  130. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/status.md +59 -0
  131. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/home.js +353 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/git/DESIGN.md +31 -0
  133. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +25 -1
  134. package/scaffold/.agent/skills/total-recall/plugins/git/cli.mjs +5 -1
  135. package/scaffold/.agent/skills/total-recall/plugins/git/package-lock.json +6 -6
  136. package/scaffold/.agent/skills/total-recall/plugins/git/package.json +2 -1
  137. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +17 -27
  138. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -6
  139. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +24 -6
  140. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +33 -7
  141. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +29 -5
  142. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/unmerged.mjs +182 -0
  143. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/change-comparison.md +27 -0
  144. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/commit-identity.md +22 -0
  145. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/create-worktree.md +25 -0
  146. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/registered-worktrees.md +22 -0
  147. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/remove-worktree.md +23 -0
  148. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/repository-ledger.md +25 -0
  149. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/unmerged-work.md +22 -0
  150. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/upstream-divergence.md +17 -0
  151. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-conflict-triage.md +17 -0
  152. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-terminal.md +22 -0
  153. package/scaffold/.agent/skills/total-recall/plugins/git/ui/home.js +273 -0
  154. package/scaffold/.agent/skills/total-recall/plugins/mesh/DESIGN.md +35 -0
  155. package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +4 -5
  156. package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +9 -3
  157. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +6 -6
  158. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +2 -1
  159. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +37 -39
  160. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +5 -3
  161. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/active-device-dispatch.md +22 -0
  162. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-probe.md +25 -0
  163. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-server.md +22 -0
  164. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/credential-readiness.md +23 -0
  165. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/elected-leader.md +22 -0
  166. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/enrollment-key.md +22 -0
  167. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/file-receipt.md +25 -0
  168. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-execution.md +23 -0
  169. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-presence.md +23 -0
  170. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/interactive-ssh.md +22 -0
  171. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/local-enrollment.md +23 -0
  172. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/mesh-policy.md +23 -0
  173. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/network-atlas.md +25 -0
  174. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-roster.md +26 -0
  175. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-selection-proof.md +17 -0
  176. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/owner-observations.md +23 -0
  177. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/peer-path.md +23 -0
  178. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/pinned-leader.md +24 -0
  179. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/reachability-evidence-ladder.md +17 -0
  180. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/recorded-login.md +23 -0
  181. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/remote-execution.md +22 -0
  182. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/secret-grants.md +26 -0
  183. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/{card.js → home.js} +93 -22
  184. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/DESIGN.md +86 -0
  185. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +19 -0
  186. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +5 -1
  187. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +6 -6
  188. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -1
  189. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +43 -22
  190. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/chat.mjs +80 -0
  191. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/adapter-docs-freshness.md +16 -0
  192. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/chat-handoff.md +26 -0
  193. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/fleet-maintenance.md +21 -0
  194. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/harness-inventory.md +21 -0
  195. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-access.md +18 -0
  196. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-usage-verification.md +15 -0
  197. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/independent-review.md +17 -0
  198. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/managed-execution.md +20 -0
  199. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/official-documentation.md +18 -0
  200. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/quota-recovery.md +21 -0
  201. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/review-routing-decision.md +16 -0
  202. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/route-work.md +18 -0
  203. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/routing-evidence.md +18 -0
  204. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/run-ledger.md +18 -0
  205. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/home.js +198 -0
  206. package/scaffold/.agent/skills/total-recall/plugins/openwiki/DESIGN.md +75 -0
  207. package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +2 -0
  208. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +17 -14
  209. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +6 -6
  210. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +2 -1
  211. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +10 -44
  212. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +5 -4
  213. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +3 -4
  214. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +23 -18
  215. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +8 -3
  216. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +6 -3
  217. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +3 -5
  218. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +21 -47
  219. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +17 -6
  220. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/auto-refresh.md +18 -0
  221. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/bootstrap-pages.md +18 -0
  222. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generate-wiki.md +21 -0
  223. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generator-settings.md +18 -0
  224. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/host-probe.md +18 -0
  225. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/ingest-preview.md +18 -0
  226. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-hygiene.md +21 -0
  227. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-topology.md +19 -0
  228. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/passage-search.md +19 -0
  229. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/propagation-plan.md +18 -0
  230. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/provider-credentials.md +18 -0
  231. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/refresh-report.md +18 -0
  232. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/task-evidence.md +22 -0
  233. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/upstream-record.md +18 -0
  234. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/watch-upstream.md +18 -0
  235. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/wiki-atlas.md +19 -0
  236. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +261 -513
  237. package/scaffold/.agent/skills/total-recall/plugins/security/DESIGN.md +84 -0
  238. package/scaffold/.agent/skills/total-recall/plugins/security/README.md +4 -2
  239. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -19
  240. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +6 -6
  241. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +2 -3
  242. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +19 -52
  243. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/device-evidence.md +21 -0
  244. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/evidence-desk.md +18 -0
  245. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/guided-device-review.md +17 -0
  246. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/host-and-network.md +21 -0
  247. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/https-headers.md +17 -0
  248. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/mesh-coverage.md +18 -0
  249. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/private-security-tools.md +16 -0
  250. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/single-origin-header-check.md +17 -0
  251. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/source-review.md +21 -0
  252. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/ssh-permission-plan.md +17 -0
  253. package/scaffold/.agent/skills/total-recall/plugins/security/ui/home.js +283 -0
  254. package/scaffold/.agent/skills/total-recall/plugins/ssss/DESIGN.md +93 -0
  255. package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +2 -2
  256. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -1
  257. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +5 -3
  258. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/conformance.md +26 -0
  259. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/registry.md +37 -0
  260. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/repair.md +42 -0
  261. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/scaffold.md +33 -0
  262. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/status.md +43 -0
  263. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validate.md +34 -0
  264. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validation-diagnostics.md +17 -0
  265. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/workspace-registry-preflight.md +17 -0
  266. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/home.js +348 -0
  267. package/scaffold/.agent/skills/total-recall/references/operations.md +2 -0
  268. package/src/cli/api.mjs +3 -3
  269. package/src/cli/brief.mjs +3 -0
  270. package/src/cli/config.mjs +16 -0
  271. package/src/cli/connect.mjs +2 -7
  272. package/src/cli/plugin/chunks.mjs +145 -0
  273. package/src/cli/plugin/create.mjs +24 -52
  274. package/src/cli/plugin/index.mjs +20 -9
  275. package/src/cli/setup.mjs +2 -9
  276. package/src/cli/start.mjs +1 -0
  277. package/src/cli/startup.mjs +18 -0
  278. package/src/core/app-deploy/design-tokens.mjs +14 -9
  279. package/src/core/app-deploy/ui-elements.mjs +1 -1
  280. package/src/core/brain-registry.mjs +5 -0
  281. package/src/core/brain-updates.mjs +5 -1
  282. package/src/core/daemon-control.mjs +5 -1
  283. package/src/core/dashboard-bundle.mjs +261 -0
  284. package/src/core/ensure-dependencies.mjs +19 -15
  285. package/src/core/memory-brain.mjs +30 -8
  286. package/src/core/plugin-activation.mjs +63 -20
  287. package/src/core/plugin-contracts/{card.mjs → manifest.mjs} +23 -57
  288. package/src/core/plugin-contracts/startup-check.mjs +49 -0
  289. package/src/core/plugin-contracts/ui.mjs +9 -3
  290. package/src/core/plugin-distribution.mjs +6 -6
  291. package/src/core/plugin-lifecycle.mjs +58 -0
  292. package/src/core/plugin-loader.mjs +9 -5
  293. package/src/core/plugin-runner.mjs +21 -10
  294. package/src/core/plugin-startup-checks.mjs +44 -0
  295. package/src/core/plugin-state.mjs +19 -5
  296. package/src/core/plugin-store.mjs +30 -11
  297. package/src/core/project-id.mjs +15 -5
  298. package/src/core/required-plugins.mjs +31 -6
  299. package/src/core/schema.mjs +2 -0
  300. package/src/core/ssss-operation-service.mjs +1 -0
  301. package/src/core/startup-health.mjs +28 -3
  302. package/src/core/testing/plugin-fixture.mjs +22 -21
  303. package/src/core/throttled-fetch.mjs +25 -18
  304. package/src/core/ui-chunk-choice.mjs +77 -0
  305. package/src/core/ui-chunk.registry.json +23 -0
  306. package/src/core/ui-chunks.mjs +434 -0
  307. package/src/core/vault-cache.mjs +58 -59
  308. package/src/core/workspace-compositions.mjs +151 -0
  309. package/src/core/workspace-drawers.mjs +79 -0
  310. package/src/server/api.mjs +4 -27
  311. package/src/server/index.mjs +28 -21
  312. package/src/server/memory-events.mjs +31 -0
  313. package/src/server/plugin-brain-targets.mjs +89 -0
  314. package/src/server/rest.mjs +2 -0
  315. package/src/server/route-inventory.mjs +3 -2
  316. package/src/server/route-manifest.json +52 -4
  317. package/src/server/routes/_shared.mjs +7 -14
  318. package/src/server/routes/brains.mjs +29 -44
  319. package/src/server/routes/dashboard.mjs +26 -0
  320. package/src/server/routes/plugins.mjs +154 -45
  321. package/src/server/routes/ui-chunks.mjs +192 -0
  322. package/frontend/dist/assets/index-DACh1dhx.js +0 -5613
  323. package/frontend/dist/assets/index-DtwsDyFM.css +0 -1
  324. package/frontend/src/components/plugins/card/CardEngine.tsx +0 -108
  325. package/frontend/src/components/plugins/card/CardScenes.tsx +0 -103
  326. package/frontend/src/components/plugins/card/ExecutionPicker.tsx +0 -64
  327. package/frontend/src/components/plugins/card/args.ts +0 -28
  328. package/frontend/src/components/plugins/card/card.css +0 -32
  329. package/frontend/src/components/plugins/card/cli-line.ts +0 -13
  330. package/frontend/src/components/plugins/card/contract.ts +0 -91
  331. package/frontend/src/components/plugins/card/format.ts +0 -49
  332. package/frontend/src/components/plugins/card/runner-context.ts +0 -11
  333. package/frontend/src/components/plugins/card/runner.tsx +0 -43
  334. package/frontend/src/components/plugins/card/theme.ts +0 -35
  335. package/frontend/src/components/plugins/card/use-source.ts +0 -22
  336. package/frontend/src/components/plugins/card/widgets.tsx +0 -94
  337. package/frontend/src/components/plugins/workspace/WorkspaceHost.tsx +0 -23
  338. package/frontend/src/pages/PluginsPage.tsx +0 -1719
  339. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +0 -47
  340. package/scaffold/.agent/skills/plugin/prompts/06-card.md +0 -46
  341. package/scaffold/.agent/skills/plugin/references/card.md +0 -90
  342. package/scaffold/.agent/skills/plugin/subagents/card-designer.md +0 -60
  343. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +0 -67
  344. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +0 -1179
  345. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +0 -32
  346. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +0 -588
  347. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +0 -575
  348. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +0 -32
  349. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +0 -482
  350. package/scaffold/.agent/skills/total-recall/plugins/git/ui/DESIGN.md +0 -100
  351. package/scaffold/.agent/skills/total-recall/plugins/git/ui/card.js +0 -637
  352. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/DESIGN.md +0 -217
  353. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +0 -32
  354. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +0 -1276
  355. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +0 -45
  356. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +0 -1401
  357. package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +0 -4
  358. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +0 -41
  359. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +0 -396
  360. package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +0 -98
  361. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/DESIGN.md +0 -15
  362. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/card.js +0 -182
@@ -1,12 +1,13 @@
1
1
  import { useEffect, useState } from 'react'
2
2
  import type { PluginInfo } from '../../api/plugins'
3
3
  import { apiFetch, API_BASE } from '../../api/_base'
4
+ import { readSavedPluginCredentials, savePluginCredential } from '../../api/plugin-credentials'
4
5
 
5
6
  interface Setting { name: string; label: string; type: 'string' | 'number' | 'boolean'; options?: Array<string | number>; required?: boolean; default?: string | number | boolean }
6
7
 
7
8
  /** Credential values are write-only. Existing values never enter browser state. */
8
9
  export function PluginSettings({ plugin }: { plugin: PluginInfo }) {
9
- const [stored, setStored] = useState<Set<string>>(new Set())
10
+ const [stored, setStored] = useState<Set<string> | null>(null)
10
11
  const [values, setValues] = useState<Record<string, string>>({})
11
12
  const [saving, setSaving] = useState('')
12
13
  const [notice, setNotice] = useState('')
@@ -16,13 +17,10 @@ export function PluginSettings({ plugin }: { plugin: PluginInfo }) {
16
17
  useEffect(() => {
17
18
  let disposed = false
18
19
  setValues({}); setNotice(''); setError('')
19
- apiFetch(`${API_BASE}/api/plugins/auth/secrets`).then(async res => {
20
- if (!res.ok) return []
21
- const entries = await res.json()
22
- if (!disposed && Array.isArray(entries)) {
23
- setStored(new Set(entries.map((entry: any) => entry.key)))
24
- }
25
- }).catch((err: any) => { if (!disposed) setError(err.message) })
20
+ setStored(null)
21
+ readSavedPluginCredentials().then(keys => {
22
+ if (!disposed) setStored(keys)
23
+ }).catch((err: Error) => { if (!disposed) setError(err.message) })
26
24
  apiFetch(`${API_BASE}/api/plugins/${encodeURIComponent(plugin.id)}/configuration`).then(async response => {
27
25
  if (!response.ok) throw new Error('Could not load plugin settings')
28
26
  const data = await response.json()
@@ -36,17 +34,9 @@ export function PluginSettings({ plugin }: { plugin: PluginInfo }) {
36
34
  async function save(key: string) {
37
35
  setSaving(key); setError(''); setNotice('')
38
36
  try {
39
- const res = await apiFetch(plugin.manifest?.runtime && (plugin.manifest.runtime as any).modules?.credentials ? `${API_BASE}/api/plugins/${encodeURIComponent(plugin.id)}/credentials` : `${API_BASE}/api/plugins/auth/secrets`, {
40
- method: 'POST',
41
- headers: { 'Content-Type': 'application/json' },
42
- body: JSON.stringify({ key, value: values[key], tags: [`plugin:${plugin.id}`] })
43
- })
44
- if (!res.ok) {
45
- const errData = await res.json().catch(() => ({}))
46
- throw new Error(errData.error || 'Failed to save secret')
47
- }
37
+ await savePluginCredential(plugin, key, values[key])
48
38
  setValues(previous => ({ ...previous, [key]: '' }))
49
- setStored(previous => new Set([...previous, key]))
39
+ setStored(await readSavedPluginCredentials().catch(() => null))
50
40
  setNotice('Credential saved. Open the plugin to verify the connection.')
51
41
  } catch (err: any) { setError(err.message) }
52
42
  finally { setSaving('') }
@@ -83,9 +73,9 @@ export function PluginSettings({ plugin }: { plugin: PluginInfo }) {
83
73
  {(plugin.secrets || []).map((secret, index) => {
84
74
  const label = `Credential ${index + 1}${secret.required ? ' (required)' : ' (optional)'}`
85
75
  return <form key={secret.key} onSubmit={event => { event.preventDefault(); void save(secret.key) }} style={{ marginBottom: 16 }}>
86
- <label style={{ display: 'block', marginBottom: 6 }}>{label} · {stored.has(secret.key) ? 'Saved' : 'Not saved'}
76
+ <label style={{ display: 'block', marginBottom: 6 }}>{label} · {stored?.has(secret.key) ? 'Saved' : stored ? 'Not saved' : 'Status unavailable'}
87
77
  <input className="input" type="password" autoComplete="new-password" value={values[secret.key] || ''}
88
- placeholder={stored.has(secret.key) ? 'Paste a replacement' : 'Paste credential'}
78
+ placeholder={stored?.has(secret.key) ? 'Paste a replacement' : 'Paste credential'}
89
79
  onChange={event => setValues(previous => ({ ...previous, [secret.key]: event.target.value }))} />
90
80
  </label>
91
81
  <button className="btn btn-sm btn-primary" disabled={!values[secret.key]?.trim() || !!saving}>{saving === secret.key ? 'Saving…' : 'Save credential'}</button>
@@ -0,0 +1,367 @@
1
+ import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react'
2
+ import { useLocation, useSearchParams } from 'react-router-dom'
3
+ import {
4
+ fetchBundledPlugins, fetchPeerPlugins, fetchPlugins, installPlugin, removePlugin,
5
+ setPluginActivation, setPluginShared, type BrainPluginActivation, type BundledPlugin,
6
+ type PeersResponse, type PluginInfo
7
+ } from '../../api/plugins'
8
+ import { PluginPanel, pluginElements } from './PluginPanel'
9
+ import { PluginSettings } from './PluginSettings'
10
+ import { PluginChunkLibrary } from '../workspace/PluginChunkLibrary'
11
+ import { PluginHome } from '../workspace/PluginHome'
12
+ import { readSavedPluginCredentials } from '../../api/plugin-credentials'
13
+ import { ScopedPluginCommands } from './ScopedPluginCommands'
14
+
15
+ type Notice = { text: string; error?: boolean }
16
+ type OpenAttempt = { pluginId: string; brainId: string; error?: string }
17
+
18
+ interface WorkspaceProps {
19
+ activeBrainId: string
20
+ renderShell: (folder: ReactNode, page: ReactNode, plugins: PluginInfo[] | null) => ReactNode
21
+ onOpenPlugin: () => void
22
+ }
23
+
24
+ function pluginOrigin(plugin: PluginInfo) {
25
+ if (plugin.core) return 'Core'
26
+ if (plugin.scope === 'scaffold' || plugin.source.kind === 'bundled') return 'Bundled'
27
+ if (plugin.source.kind === 'peer' || plugin.source.kind === 'public') return 'Shared'
28
+ return 'User'
29
+ }
30
+
31
+ export default function PluginWorkspace({ activeBrainId, renderShell, onOpenPlugin }: WorkspaceProps) {
32
+ const [params, setParams] = useSearchParams()
33
+ const { pathname } = useLocation()
34
+ const [expanded, setExpanded] = useState<boolean>()
35
+ const [inventory, setInventory] = useState<{ selection: string; plugins: PluginInfo[] }>()
36
+ const [failure, setFailure] = useState<{ selection: string; message: string }>()
37
+ const [openAttempt, setOpenAttempt] = useState<OpenAttempt>()
38
+ const [browse, setBrowse] = useState(false)
39
+ const request = useRef(0)
40
+ const currentSelection = useRef(activeBrainId)
41
+ const plugins = inventory?.selection === activeBrainId ? inventory.plugins : null
42
+ const loadError = failure?.selection === activeBrainId ? failure.message : undefined
43
+ const brainIds = activeBrainId.split(',').filter(Boolean)
44
+ const brainId = brainIds.length === 1 ? brainIds[0] : undefined
45
+ const id = pathname === '/plugins' || (pathname === '/memory' && params.get('page')) ? null : params.get('plugin')
46
+ const open = expanded ?? Boolean(id || params.has('plugins'))
47
+ const selected = id ? plugins?.find(plugin => plugin.id === id || plugin.id === `tr-plugin-${id}` || plugin.name === id) : undefined
48
+
49
+ const refresh = useCallback(async () => {
50
+ if (currentSelection.current !== activeBrainId) return
51
+ const current = ++request.current
52
+ try {
53
+ const installed = await fetchPlugins(activeBrainId)
54
+ if (request.current !== current) return
55
+ setInventory({ selection: activeBrainId, plugins: [...installed].sort((a, b) => a.name.localeCompare(b.name)) })
56
+ setFailure(undefined)
57
+ setOpenAttempt(attempt => attempt?.error ? undefined : attempt)
58
+ } catch (error) {
59
+ if (request.current !== current) return
60
+ setFailure({ selection: activeBrainId, message: error instanceof Error ? error.message : 'Could not load plugins.' })
61
+ }
62
+ }, [activeBrainId])
63
+
64
+ useEffect(() => {
65
+ currentSelection.current = activeBrainId
66
+ void refresh()
67
+ return () => { request.current++ }
68
+ }, [activeBrainId, refresh])
69
+
70
+ function select(pluginId: string | null) {
71
+ const next = new URLSearchParams(params)
72
+ if (pluginId) {
73
+ next.set('plugin', pluginId)
74
+ next.delete('page')
75
+ } else next.delete('plugin')
76
+ setParams(next, { replace: !pluginId })
77
+ }
78
+
79
+ async function openArmed(plugin: PluginInfo) {
80
+ if (!brainId?.startsWith('project:') || plugin.activation?.mode !== 'on-use') return
81
+ if (openAttempt?.brainId === brainId && openAttempt.pluginId === plugin.id && !openAttempt.error) return
82
+ const attempt = { pluginId: plugin.id, brainId }
83
+ setOpenAttempt(attempt)
84
+ const result = await setPluginActivation(plugin.id, 'use', [brainId])
85
+ const error = result.success ? undefined : result.results?.[0]?.error || result.error || 'Could not open this plugin.'
86
+ await refresh()
87
+ setOpenAttempt(current => current === attempt ? (error ? { ...attempt, error } : undefined) : current)
88
+ }
89
+
90
+ const folder = <>
91
+ <button type="button" className="nav-link plugin-folder-toggle" id="nav-plugins"
92
+ aria-expanded={open} aria-controls="plugin-folder" onClick={() => setExpanded(!open)}>
93
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true">
94
+ <path d="M3 7a2 2 0 0 1 2-2h5l2 2h7a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z" />
95
+ </svg>
96
+ Plugins <span className="plugin-folder-chevron" aria-hidden="true">{open ? '▾' : '›'}</span>
97
+ </button>
98
+ <div id="plugin-folder" hidden={!open}>
99
+ {loadError ? <div className="plugin-folder-message" role="alert">
100
+ {loadError} <button className="btn btn-ghost btn-sm" onClick={() => void refresh()}>Retry</button>
101
+ </div> : plugins === null ? <p className="plugin-folder-message" role="status">Loading plugins…</p> :
102
+ <ul className="plugin-folder-list" aria-label="Installed plugins">
103
+ {plugins.map(plugin => <li key={plugin.id}>
104
+ <button type="button" className={`nav-link plugin-folder-item ${selected?.id === plugin.id ? 'active' : ''}`}
105
+ aria-current={selected?.id === plugin.id || undefined}
106
+ onClick={() => { select(plugin.id); onOpenPlugin(); void openArmed(plugin) }}>
107
+ <span>{plugin.name}</span><small className="plugin-origin" aria-hidden="true">{pluginOrigin(plugin)}</small>
108
+ </button>
109
+ </li>)}
110
+ {!plugins.length && <li className="plugin-folder-message">No plugins installed</li>}
111
+ </ul>}
112
+ <button type="button" className="nav-link plugin-folder-item" onClick={() => { setBrowse(true); onOpenPlugin() }}>
113
+ Browse and install
114
+ </button>
115
+ </div>
116
+ </>
117
+ let page: ReactNode = null
118
+ if (selected) page = <PluginDetail key={`${activeBrainId}:${selected.id}`} plugin={selected} plugins={plugins} stale={!!loadError} brainId={brainId}
119
+ opening={openAttempt?.pluginId === selected.id && openAttempt.brainId === brainId ? openAttempt : undefined}
120
+ onUse={() => void openArmed(selected)} onClose={() => select(null)} onChange={refresh} />
121
+ else if (id && (loadError || plugins)) page = <section className="plugin-workspace-page" aria-label="Plugin unavailable">
122
+ <p role="alert">{loadError || `No installed plugin matches ${id}.`}</p>
123
+ <button type="button" className="btn btn-ghost" onClick={() => select(null)}>Close</button>
124
+ </section>
125
+
126
+ return <>
127
+ {renderShell(folder, page, loadError ? null : plugins)}
128
+ {browse && <PluginInstaller onClose={() => setBrowse(false)} onChange={refresh} />}
129
+ </>
130
+ }
131
+
132
+ function PluginDialog({ title, children, onClose }: { title: string; children: ReactNode; onClose: () => void }) {
133
+ return <div className="plugin-drawer-backdrop" onClick={onClose}>
134
+ <section className="plugin-drawer" role="dialog" aria-modal="true" aria-label={title}
135
+ onClick={event => event.stopPropagation()} onKeyDown={event => {
136
+ if (event.key === 'Escape') { event.stopPropagation(); onClose() }
137
+ }}>
138
+ <header className="plugin-drawer-header">
139
+ <h2>{title}</h2>
140
+ <button type="button" className="btn btn-ghost btn-sm" autoFocus onClick={onClose}>Close</button>
141
+ </header>
142
+ {children}
143
+ </section>
144
+ </div>
145
+ }
146
+
147
+ function PluginDetail({ plugin, plugins, brainId, stale, opening, onUse, onChange, onClose }: {
148
+ plugin: PluginInfo; plugins: PluginInfo[] | null; brainId?: string; stale: boolean; opening?: OpenAttempt;
149
+ onUse: () => void; onChange: () => Promise<void>; onClose: () => void
150
+ }) {
151
+ const panels = pluginElements(plugin).filter(element => element.kind !== 'settings')
152
+ const activation = plugin.activation
153
+ const error = !plugin.valid || activation?.state === 'error'
154
+ const enabled = activation?.enabled === true && !error
155
+ const rows = plugin.activationByBrain ?? (brainId && activation && activation.state !== 'mixed'
156
+ ? [{ ...activation, state: activation.state, brainId, name: brainId === 'global' ? 'Global Brain' : brainId.slice('project:'.length) }]
157
+ : [])
158
+ const armed = rows.find(row => row.brainId === brainId && row.mode === 'on-use')
159
+ const canManage = plugin.servingAvailable !== false
160
+ const [tab, setTab] = useState<'home' | 'interface' | 'settings' | 'commands' | 'blocks'>('home')
161
+ const [initialCommand, setInitialCommand] = useState<string>()
162
+ const [panelId, setPanelId] = useState(panels[0]?.id)
163
+ const [changing, setChanging] = useState(false)
164
+ const busy = changing || Boolean(opening && !opening.error)
165
+ const [notice, setNotice] = useState<Notice>()
166
+ const [cleanupRetries, setCleanupRetries] = useState<string[]>([])
167
+ const [uninstall, setUninstall] = useState(false)
168
+ const [savedCredentials, setSavedCredentials] = useState<Set<string> | null>(null)
169
+ const panel = panels.find(item => item.id === panelId) ?? panels[0]
170
+ function openCommand(name: string) {
171
+ setInitialCommand(name)
172
+ setTab('commands')
173
+ }
174
+ const declaredCredentials = plugin.secrets || []
175
+ const credentialCount = declaredCredentials.length
176
+ useEffect(() => {
177
+ if (!credentialCount) return
178
+ let disposed = false
179
+ void readSavedPluginCredentials().then(keys => {
180
+ if (!disposed) setSavedCredentials(keys)
181
+ }).catch(() => { if (!disposed) setSavedCredentials(null) })
182
+ return () => { disposed = true }
183
+ }, [plugin.id, credentialCount, tab])
184
+ const credentialLabel = !credentialCount ? 'Dashboard session'
185
+ : !savedCredentials ? 'Credential status unavailable'
186
+ : declaredCredentials.some(secret => secret.required && !savedCredentials.has(secret.key)) ? 'Credential required'
187
+ : declaredCredentials.some(secret => savedCredentials.has(secret.key)) ? 'Saved in Auth' : 'Optional credential'
188
+
189
+ async function change(action: BrainPluginActivation | 'share' | 'uninstall', value?: boolean | 'on-use') {
190
+ setChanging(true)
191
+ setNotice(undefined)
192
+ try {
193
+ if (typeof action === 'object') {
194
+ const result = await setPluginActivation(plugin.id, value ?? !action.enabled, [action.brainId])
195
+ const target = result.results?.find(item => item.brainId === action.brainId)
196
+ if (target?.cleanup?.ok === false) {
197
+ setCleanupRetries(current => current.includes(action.brainId) ? current : [...current, action.brainId])
198
+ } else if (result.success || target?.cleanup?.ok === true) {
199
+ setCleanupRetries(current => current.filter(id => id !== action.brainId))
200
+ }
201
+ if (!result.success) {
202
+ setNotice({ text: target?.error ? `${target.name}. ${target.error}` : result.error || 'Could not update activation.', error: true })
203
+ }
204
+ } else {
205
+ const result = action === 'share' ? await setPluginShared(plugin.id, !plugin.shared) : await removePlugin(plugin.id, plugin.scope === 'global')
206
+ if (!result.success) setNotice({ text: result.error || 'Could not update the plugin.', error: true })
207
+ if (action === 'uninstall' && result.success) onClose()
208
+ }
209
+ // The record can persist even when recompilation reports a failure.
210
+ await onChange()
211
+ } finally { setChanging(false) }
212
+ }
213
+
214
+ return <section className="plugin-workspace-page" aria-label={plugin.name}>
215
+ <header className="plugin-drawer-header">
216
+ <h1>{plugin.name}</h1>
217
+ <button type="button" className="btn btn-ghost btn-sm" onClick={onClose}>Close</button>
218
+ </header>
219
+ <p className="plugin-muted">{plugin.description}</p>
220
+ <dl className="plugin-detail-status">
221
+ <div><dt>Brain activation</dt><dd>
222
+ <span className={`badge ${error ? 'badge-error' : enabled ? 'badge-success' : ''}`}>
223
+ {stale ? 'Unknown' : activation?.state === 'mixed' ? 'Mixed' : error ? 'Error' : activation?.mode === 'on-use' ? 'On use' : enabled ? 'Active' : activation ? 'Off' : 'Unknown'}
224
+ </span>
225
+ </dd></div>
226
+ <div><dt>Authentication</dt><dd>{credentialLabel}</dd></div>
227
+ <div><dt>Provider connection</dt><dd>Unreported</dd></div>
228
+ <div><dt>Installed</dt><dd>v{plugin.version} · {pluginOrigin(plugin)} · {plugin.scope}</dd></div>
229
+ </dl>
230
+ {!!rows.length && <ul className="plugin-folder-list" aria-label="Activation by brain">
231
+ {rows.map(row => <li className="plugin-drawer-actions" key={row.brainId}>
232
+ <span>{row.name}{row.brainId === 'global' && <small className="plugin-muted"> · Shared default</small>}</span>
233
+ {row.state === 'unavailable' || row.state === 'error' ? <span>{row.state === 'error' ? 'Error' : 'Unavailable'}</span> :
234
+ row.brainId.startsWith('project:') ? <select className="select"
235
+ style={{ minHeight: 44, width: 'auto' }}
236
+ aria-label={`${plugin.name} activation for ${row.name}`} aria-busy={busy || undefined}
237
+ disabled={stale || busy || row.required} value={row.mode || (row.enabled ? 'on' : 'off')}
238
+ onChange={event => {
239
+ const mode = event.currentTarget.value
240
+ void change(row, mode === 'on-use' ? mode : mode === 'on')
241
+ }}>
242
+ <option value="off">Off</option>
243
+ <option value="on-use">On use</option>
244
+ <option value="on">On</option>
245
+ </select> :
246
+ <button type="button" role="switch" aria-checked={row.enabled}
247
+ aria-label={rows.length === 1 ? `${plugin.name} activation` : `${plugin.name} activation for ${row.name}`}
248
+ title={row.required ? 'Required plugin' : undefined} className="btn btn-ghost btn-sm"
249
+ disabled={stale || busy || row.required} onClick={() => void change(row)}>
250
+ {stale ? 'Unknown' : busy ? 'Updating…' : row.enabled ? 'On' : 'Off'}
251
+ </button>}
252
+ {!row.enabled && cleanupRetries.includes(row.brainId) && <button type="button"
253
+ className="btn btn-ghost btn-sm" disabled={stale || busy}
254
+ aria-label={`Retry cleanup for ${row.name}`} onClick={() => void change(row, false)}>Retry cleanup</button>}
255
+ </li>)}
256
+ </ul>}
257
+ {stale && <p role="alert">The installed state could not be refreshed.</p>}
258
+ {error && <p role="alert">{plugin.errors[0] || 'The activation policy could not be read.'}</p>}
259
+ {opening?.error && !enabled && !notice && <p role="alert">{opening.error}</p>}
260
+ {notice && <p role={notice.error ? 'alert' : 'status'}>{notice.text}</p>}
261
+ {!brainId ? <p className="plugin-muted">Select one brain to open this plugin’s tools.</p> : <>
262
+ <nav className="plugin-drawer-actions" aria-label={`${plugin.name} views`}>
263
+ <button className={`btn ${tab === 'home' ? 'btn-primary' : 'btn-ghost'}`} aria-pressed={tab === 'home'}
264
+ onClick={() => setTab('home')}>Home</button>
265
+ {panel && enabled && <button className={`btn ${tab === 'interface' ? 'btn-primary' : 'btn-ghost'}`} aria-pressed={tab === 'interface'}
266
+ onClick={() => setTab('interface')}>Interface</button>}
267
+ {enabled && !stale && <button className={`btn ${tab === 'blocks' ? 'btn-primary' : 'btn-ghost'}`} aria-pressed={tab === 'blocks'}
268
+ onClick={() => setTab('blocks')}>Blocks</button>}
269
+ {canManage && <button className={`btn ${tab === 'settings' ? 'btn-primary' : 'btn-ghost'}`} aria-pressed={tab === 'settings'}
270
+ onClick={() => setTab('settings')}>Settings</button>}
271
+ {plugin.cli && <button className={`btn ${tab === 'commands' ? 'btn-primary' : 'btn-ghost'}`} aria-pressed={tab === 'commands'}
272
+ onClick={() => setTab('commands')}>Commands</button>}
273
+ </nav>
274
+ {tab === 'home' && (enabled && !stale
275
+ ? <PluginHome plugin={plugin} plugins={plugins} brainId={brainId} onCommand={openCommand} />
276
+ : armed && !stale ? <div className="plugin-drawer-actions">
277
+ <span className="plugin-muted">This plugin activates when you open it.</span>
278
+ <button type="button" className="btn btn-primary" disabled={busy}
279
+ onClick={onUse}>{busy ? 'Opening…' : 'Open plugin'}</button>
280
+ </div> : <p className="plugin-muted">An active plugin is needed to open its Home tools.</p>)}
281
+ {tab === 'interface' && enabled && panel && <>
282
+ {panels.length > 1 && <label>Panel
283
+ <select className="select" value={panel.id} onChange={event => setPanelId(event.target.value)}>
284
+ {panels.map(item => <option key={item.id} value={item.id}>{item.description || item.id}</option>)}
285
+ </select>
286
+ </label>}
287
+ <PluginPanel key={panel.id} plugin={plugin} elementId={panel.id} brainId={brainId} readOnly={stale} />
288
+ </>}
289
+ {tab === 'settings' && canManage && <>
290
+ {!panel && <p className="plugin-muted">This package has no installed workspace panel.</p>}
291
+ {plugin.valid && <PluginSettings plugin={plugin} />}
292
+ <details>
293
+ <summary>Manage installation</summary>
294
+ <div className="plugin-drawer-actions">
295
+ <button className="btn btn-ghost" disabled={busy || stale || !plugin.valid} onClick={() => void change('share')}>
296
+ {plugin.shared ? 'Stop sharing' : 'Share on mesh'}
297
+ </button>
298
+ {!uninstall ? <button className="btn btn-ghost" disabled={busy || stale || activation?.required}
299
+ onClick={() => setUninstall(true)}>Uninstall…</button>
300
+ : <button className="btn btn-ghost" disabled={busy || stale} onClick={() => void change('uninstall')}>
301
+ Confirm uninstall
302
+ </button>}
303
+ </div>
304
+ </details>
305
+ </>}
306
+ {tab === 'commands' && plugin.cli && <ScopedPluginCommands key={plugin.sha256} plugin={plugin} brainId={brainId}
307
+ enabled={enabled && !stale} initialCommand={initialCommand} />}
308
+ {tab === 'blocks' && enabled && !stale && <PluginChunkLibrary key={`${plugin.id}:${brainId}`}
309
+ plugin={plugin} brainId={brainId} />}
310
+ </>}
311
+ </section>
312
+ }
313
+
314
+ function PluginInstaller({ onChange, onClose }: { onChange: () => Promise<void>; onClose: () => void }) {
315
+ const [source, setSource] = useState('')
316
+ const [bundled, setBundled] = useState<BundledPlugin[]>([])
317
+ const [peers, setPeers] = useState<PeersResponse | null>()
318
+ const [busy, setBusy] = useState(false)
319
+ const [notice, setNotice] = useState<Notice>()
320
+ useEffect(() => {
321
+ let disposed = false
322
+ void fetchBundledPlugins().then(result => { if (!disposed) setBundled(result) })
323
+ return () => { disposed = true }
324
+ }, [])
325
+
326
+ async function install() {
327
+ setBusy(true)
328
+ setNotice(undefined)
329
+ try {
330
+ const result = await installPlugin({ source: source.trim(), global: true })
331
+ setNotice({ text: result.message || result.error || (result.success ? 'Plugin installed.' : 'Install failed.'), error: !result.success })
332
+ if (result.success) {
333
+ setSource('')
334
+ setBundled(await fetchBundledPlugins())
335
+ }
336
+ await onChange()
337
+ } finally { setBusy(false) }
338
+ }
339
+
340
+ return <PluginDialog title="Browse and install plugins" onClose={onClose}>
341
+ <p className="plugin-muted">Installs into the shared plugin folder on this host.</p>
342
+ <form className="plugin-drawer-form" onSubmit={event => { event.preventDefault(); void install() }}>
343
+ <label>Plugin source<input className="input" value={source} onChange={event => setSource(event.target.value)}
344
+ placeholder="Plugin ID, folder, Git URL, or peer link" required /></label>
345
+ <button type="submit" className="btn btn-primary" disabled={busy || !source.trim()}>Install</button>
346
+ </form>
347
+ {notice && <p role={notice.error ? 'alert' : 'status'}>{notice.text}</p>}
348
+ <details><summary>Bundled plugins</summary>
349
+ <ul className="plugin-folder-list">{bundled.map(plugin => <li key={plugin.id}>
350
+ <button className="btn btn-ghost" disabled={busy || plugin.installed} onClick={() => setSource(plugin.id)}>
351
+ {plugin.name}{plugin.installed ? ' (installed)' : ''}
352
+ </button>
353
+ </li>)}</ul>
354
+ </details>
355
+ <details><summary>Mesh peers</summary>
356
+ <button className="btn btn-ghost" onClick={async () => setPeers(await fetchPeerPlugins())}>Load shared plugins</button>
357
+ {peers === null && <p role="status">Could not load mesh peers.</p>}
358
+ {peers?.peers.map(peer => <div key={peer.hostname}>
359
+ <p>{peer.hostname} · {peer.status === 'ok' ? 'online' : peer.error || peer.status}</p>
360
+ <ul className="plugin-folder-list">{peer.plugins.map(plugin => <li key={plugin.id}>
361
+ <button className="btn btn-ghost" disabled={busy || plugin.same_as_installed}
362
+ onClick={() => setSource(`peer:${peer.hostname}/${plugin.id}`)}>{plugin.name}</button>
363
+ </li>)}</ul>
364
+ </div>)}
365
+ </details>
366
+ </PluginDialog>
367
+ }
@@ -0,0 +1,142 @@
1
+ import { useRef, useState } from 'react'
2
+ import { runPluginCommand, type PluginInfo } from '../../api/plugins'
3
+ import { scopedPluginOperations, type Argument, type Command } from './scoped-plugin-operations'
4
+
5
+ type InputValues = Record<string, string | boolean>
6
+ type Run = { command: string; args: string[] }
7
+
8
+ function browserArguments(command: Command) {
9
+ return (command.args || []).filter(arg => !command.terminal_only_args?.includes(arg.flag || ''))
10
+ }
11
+
12
+ function buildArguments(command: Command, values: InputValues, extra: string): string[] {
13
+ const args: string[] = []
14
+ let omittedPosition: Argument | undefined
15
+ for (const arg of browserArguments(command)) {
16
+ const input = values[arg.name]
17
+ const value = arg.type === 'boolean' ? (input === true ? 'true' : '') : typeof input === 'string' ? input : ''
18
+ if (!value.trim()) {
19
+ if (arg.required) throw new Error(`Enter ${arg.label}.`)
20
+ if (!arg.flag) omittedPosition ??= arg
21
+ continue
22
+ }
23
+ if (arg.flag) args.push(arg.flag)
24
+ else if (omittedPosition) throw new Error(`Enter ${omittedPosition.label} before ${arg.label}.`)
25
+ if (arg.type !== 'boolean' || !arg.flag) args.push(value)
26
+ }
27
+ if (extra.trim()) {
28
+ let parsed: unknown
29
+ try { parsed = JSON.parse(extra) }
30
+ catch { throw new Error('Arguments must be a JSON array of strings.') }
31
+ if (!Array.isArray(parsed) || parsed.some(value => typeof value !== 'string')) {
32
+ throw new Error('Arguments must be a JSON array of strings.')
33
+ }
34
+ args.push(...parsed)
35
+ }
36
+ if (args.some(arg => arg.includes('\0'))) throw new Error('An argument contains an invalid character.')
37
+ if (args.some(arg => command.terminal_only_args?.includes(arg.split('=', 1)[0]))) {
38
+ throw new Error('Use the owning CLI for terminal-only arguments.')
39
+ }
40
+ return args
41
+ }
42
+
43
+ export function ScopedPluginCommands({ plugin, brainId, enabled = true, operationIds, initialCommand, label }: {
44
+ plugin: PluginInfo
45
+ brainId: string
46
+ enabled?: boolean
47
+ operationIds?: readonly string[]
48
+ initialCommand?: string
49
+ label?: string
50
+ }) {
51
+ const commands = scopedPluginOperations(plugin, operationIds)
52
+ const [selected, setSelected] = useState(initialCommand)
53
+ const command = commands.find(item => item.name === selected) ?? commands[0]
54
+ const [values, setValues] = useState<InputValues>({})
55
+ const [extra, setExtra] = useState('')
56
+ const [pending, setPending] = useState<Run>()
57
+ const [output, setOutput] = useState<{ text: string; error: boolean }>()
58
+ const [busy, setBusy] = useState(false)
59
+ const running = useRef(false)
60
+ const canRun = enabled && plugin.valid && plugin.activation?.enabled === true &&
61
+ plugin.activation.state !== 'error' && Boolean(brainId) && !brainId.includes(',')
62
+ if (!command) return null
63
+ const fields = browserArguments(command)
64
+
65
+ function clearResult() {
66
+ setPending(undefined)
67
+ setOutput(undefined)
68
+ }
69
+
70
+ function changeValue(name: string, value: string | boolean) {
71
+ setValues(current => ({ ...current, [name]: value }))
72
+ clearResult()
73
+ }
74
+
75
+ async function run(request: Run) {
76
+ if (!canRun || running.current || !commands.some(item => item.name === request.command)) return
77
+ running.current = true
78
+ setBusy(true)
79
+ clearResult()
80
+ try {
81
+ const result = await runPluginCommand(plugin.id, request.command, request.args, { brainId })
82
+ setOutput({
83
+ text: result.output || result.error || (result.success ? 'Command completed.' : 'Command failed.'),
84
+ error: !result.success,
85
+ })
86
+ } catch (error) {
87
+ setOutput({ text: error instanceof Error ? error.message : 'Command failed.', error: true })
88
+ } finally {
89
+ running.current = false
90
+ setBusy(false)
91
+ }
92
+ }
93
+
94
+ const argumentInput = <label>Arguments (JSON array)
95
+ <textarea className="input" rows={2} value={extra} placeholder='["--name", "Value with spaces"]'
96
+ onChange={event => { setExtra(event.target.value); clearResult() }} />
97
+ </label>
98
+
99
+ return <form className="plugin-drawer-form" aria-label={label || `${plugin.name} commands`}
100
+ onSubmit={event => {
101
+ event.preventDefault()
102
+ if (!canRun || running.current || pending) return
103
+ try {
104
+ const request = { command: command.name, args: buildArguments(command, values, extra) }
105
+ if (command.effect === 'read') void run(request)
106
+ else { setOutput(undefined); setPending(request) }
107
+ } catch (error) {
108
+ setOutput({ text: error instanceof Error ? error.message : 'Invalid arguments.', error: true })
109
+ }
110
+ }}>
111
+ <fieldset className="plugin-drawer-form" disabled={!canRun || busy} style={{ border: 0, margin: 0, padding: 0 }}>
112
+ <label>Command
113
+ <select className="select" value={command.name} onChange={event => {
114
+ setSelected(event.target.value); setValues({}); setExtra(''); clearResult()
115
+ }}>
116
+ {commands.map(item => <option key={item.name} value={item.name}>{item.name}</option>)}
117
+ </select>
118
+ </label>
119
+ {command.description && <p className="plugin-muted">{command.description}</p>}
120
+ {fields.map(arg => <label key={arg.name}>{arg.label}
121
+ {arg.type === 'choice' ? <select className="select" required={arg.required} value={String(values[arg.name] ?? '')}
122
+ onChange={event => changeValue(arg.name, event.target.value)}>
123
+ <option value="">Choose…</option>
124
+ {arg.options?.map(option => <option key={option} value={option}>{option}</option>)}
125
+ </select> : arg.type === 'boolean' ? <input type="checkbox" required={arg.required} checked={values[arg.name] === true}
126
+ onChange={event => changeValue(arg.name, event.target.checked)} /> :
127
+ <input className="input" type={arg.type === 'number' ? 'number' : 'text'} step={arg.type === 'number' ? 'any' : undefined}
128
+ required={arg.required} value={String(values[arg.name] ?? '')} onChange={event => changeValue(arg.name, event.target.value)} />}
129
+ </label>)}
130
+ {fields.length ? <details><summary>Additional arguments</summary>{argumentInput}</details> : argumentInput}
131
+ {pending ? <div className="drawer-stack" role="group" aria-label="Confirm command">
132
+ <p>Confirm <strong>{pending.command}</strong> in the selected brain.</p>
133
+ {pending.args.length > 0 && <pre className="plugin-command-output">{JSON.stringify(pending.args, null, 2)}</pre>}
134
+ <div className="drawer-actions">
135
+ <button type="button" className="btn btn-primary" onClick={() => void run(pending)}>Confirm and run</button>
136
+ <button type="button" className="btn btn-ghost" onClick={() => setPending(undefined)}>Cancel</button>
137
+ </div>
138
+ </div> : <button type="submit" className="btn btn-primary">{busy ? 'Running…' : 'Run command'}</button>}
139
+ </fieldset>
140
+ {output && <pre className="plugin-command-output" role={output.error ? 'alert' : 'status'}>{output.text}</pre>}
141
+ </form>
142
+ }