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
@@ -0,0 +1,261 @@
1
+ /**
2
+ * The dashboard bundle a source checkout serves, kept as new as its sources.
3
+ *
4
+ * The server used to build `frontend/dist` only when `index.html` was missing,
5
+ * and auto-pull rebuilt it only when a pull from origin changed `frontend/`.
6
+ * A local merge or commit refreshed nothing, so the served dashboard silently
7
+ * stayed on an old build: the plugin workspace drawers committed on
8
+ * 2026-11-08 never appeared at localhost:3000 because the bundle there was
9
+ * built on 2026-11-09 from code without them, and nothing ever looked again.
10
+ *
11
+ * Here "stale" has one definition, shared with the publish gate
12
+ * (`scripts/check-dist-freshness.mjs`): a source file newer than the newest
13
+ * bundle file. The server checks on start and on an interval, rebuilds in the
14
+ * background with the frontend's own build script, and keeps serving the
15
+ * current bundle until the new one is complete. It never installs
16
+ * dependencies: missing build tools are reported, not repaired behind the
17
+ * operator's back. An npm tarball gives every file the same mtime, so an
18
+ * installed package is never stale and never builds.
19
+ */
20
+ import { spawn } from 'node:child_process';
21
+ import fs from 'node:fs';
22
+ import { readdir, stat } from 'node:fs/promises';
23
+ import path from 'node:path';
24
+
25
+ const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist']);
26
+
27
+ /** Sources must be untouched this long before a rebuild, so a half-written edit is not built. */
28
+ export const QUIET_MS = 60_000;
29
+ export const CHECK_INTERVAL_MS = 5 * 60_000;
30
+ export const BUILD_TIMEOUT_MS = 10 * 60_000;
31
+ const OUTPUT_TAIL_BYTES = 4096;
32
+
33
+ /**
34
+ * Newest mtime under a file or directory, and the path carrying it.
35
+ * Missing paths contribute nothing rather than failing: vite.config.ts and
36
+ * friends are allowed not to exist.
37
+ */
38
+ export async function newestUnder(target) {
39
+ let info;
40
+ try {
41
+ info = await stat(target);
42
+ } catch {
43
+ return null;
44
+ }
45
+
46
+ if (info.isFile()) return { path: target, mtimeMs: info.mtimeMs };
47
+
48
+ let best = null;
49
+ const entries = await readdir(target, { withFileTypes: true });
50
+ for (const entry of entries) {
51
+ if (entry.isDirectory() && IGNORED_DIRS.has(entry.name)) continue;
52
+ const found = await newestUnder(path.join(target, entry.name));
53
+ if (found && (!best || found.mtimeMs > best.mtimeMs)) best = found;
54
+ }
55
+ return best;
56
+ }
57
+
58
+ /**
59
+ * Whether the built bundle can be trusted to represent the sources.
60
+ * Returns { ok, reason, bundle, source }; never throws, never prints, so the
61
+ * decision can be tested apart from how it is reported.
62
+ */
63
+ export async function checkDistFreshness({ distDir, sourcePaths }) {
64
+ const bundle = await newestUnder(distDir);
65
+ if (!bundle) return { ok: false, reason: 'missing-bundle', bundle: null, source: null };
66
+
67
+ let source = null;
68
+ for (const candidate of sourcePaths) {
69
+ const found = await newestUnder(candidate);
70
+ if (found && (!source || found.mtimeMs > source.mtimeMs)) source = found;
71
+ }
72
+
73
+ if (source && source.mtimeMs > bundle.mtimeMs) {
74
+ return { ok: false, reason: 'stale-bundle', bundle, source };
75
+ }
76
+ return { ok: true, reason: 'current', bundle, source };
77
+ }
78
+
79
+ /** Every input whose change changes the built dashboard. */
80
+ export function dashboardTargets(root) {
81
+ const frontend = path.join(root, 'frontend');
82
+ let tsconfigs = [];
83
+ try {
84
+ tsconfigs = fs.readdirSync(frontend).filter(name => /^tsconfig.*\.json$/.test(name)).map(name => path.join(frontend, name));
85
+ } catch { /* no frontend directory: nothing to compare */ }
86
+ return {
87
+ distDir: path.join(frontend, 'dist'),
88
+ sourcePaths: [
89
+ path.join(frontend, 'src'),
90
+ path.join(frontend, 'public'),
91
+ path.join(frontend, 'index.html'),
92
+ path.join(frontend, 'vite.config.ts'),
93
+ path.join(frontend, 'package.json'),
94
+ ...tsconfigs,
95
+ ],
96
+ };
97
+ }
98
+
99
+ /** The build script needs vite and the TypeScript compiler, hoisted to the root or local to the frontend. */
100
+ export function buildToolsPresent(root) {
101
+ const frontend = path.join(root, 'frontend');
102
+ const has = rel => [root, frontend].some(dir => fs.existsSync(path.join(dir, 'node_modules', rel)));
103
+ return fs.existsSync(path.join(frontend, 'package.json')) && has(path.join('vite', 'bin', 'vite.js')) && has(path.join('typescript', 'bin', 'tsc'));
104
+ }
105
+
106
+ /** npm installed beside the running node (Homebrew, nvm, system installs), else from PATH. */
107
+ function npmBinary() {
108
+ const beside = path.join(path.dirname(process.execPath), process.platform === 'win32' ? 'npm.cmd' : 'npm');
109
+ return fs.existsSync(beside) ? beside : 'npm';
110
+ }
111
+
112
+ /**
113
+ * Run the frontend's own `build` script into `outDir`. npm appends the
114
+ * arguments after `--` to the script, so they reach `vite build`.
115
+ * @returns {Promise<{ code: number|null, output: string, timedOut: boolean }>}
116
+ */
117
+ export function runFrontendBuild({ frontendDir, outDir, timeoutMs = BUILD_TIMEOUT_MS }) {
118
+ return new Promise(resolve => {
119
+ let output = '';
120
+ let timedOut = false;
121
+ const keep = chunk => { output = (output + chunk).slice(-OUTPUT_TAIL_BYTES); };
122
+ const env = { ...process.env, PATH: [path.dirname(process.execPath), process.env.PATH].filter(Boolean).join(path.delimiter) };
123
+ let child;
124
+ try {
125
+ child = spawn(npmBinary(), ['run', 'build', '--', '--outDir', outDir, '--emptyOutDir'], {
126
+ cwd: frontendDir, env, stdio: ['ignore', 'pipe', 'pipe'], shell: process.platform === 'win32', detached: process.platform !== 'win32',
127
+ });
128
+ } catch (error) {
129
+ resolve({ code: null, output: error.message, timedOut: false });
130
+ return;
131
+ }
132
+ child.stdout.on('data', keep);
133
+ child.stderr.on('data', keep);
134
+ const timer = setTimeout(() => {
135
+ timedOut = true;
136
+ // The build runs in its own process group: end tsc and vite with npm.
137
+ try { process.kill(-child.pid, 'SIGTERM'); } catch { child.kill('SIGTERM'); }
138
+ }, timeoutMs);
139
+ timer.unref?.();
140
+ child.on('error', error => { clearTimeout(timer); resolve({ code: null, output: `${output}\n${error.message}`.trim(), timedOut }); });
141
+ child.on('close', code => { clearTimeout(timer); resolve({ code, output, timedOut }); });
142
+ });
143
+ }
144
+
145
+ function processAlive(pid) {
146
+ if (!Number.isInteger(pid) || pid <= 0) return false;
147
+ try { process.kill(pid, 0); return true; } catch (error) { return error.code === 'EPERM'; }
148
+ }
149
+
150
+ /**
151
+ * Hold the build lock, or report who holds it. A lock left by a process that
152
+ * no longer exists is reclaimed once.
153
+ */
154
+ function acquireLock(file) {
155
+ for (let attempt = 0; attempt < 2; attempt++) {
156
+ try {
157
+ const fd = fs.openSync(file, 'wx', 0o600);
158
+ fs.writeSync(fd, `${process.pid}\n`);
159
+ fs.closeSync(fd);
160
+ return { held: true, release: () => { try { if (Number(fs.readFileSync(file, 'utf8').trim()) === process.pid) fs.unlinkSync(file); } catch { /* already gone */ } } };
161
+ } catch (error) {
162
+ if (error.code !== 'EEXIST') throw error;
163
+ const owner = Number(String(fs.readFileSync(file, 'utf8')).trim());
164
+ if (processAlive(owner)) return { held: false, owner };
165
+ fs.rmSync(file, { force: true });
166
+ }
167
+ }
168
+ return { held: false, owner: null };
169
+ }
170
+
171
+ /**
172
+ * Build the dashboard beside the served one and swap it in. The served bundle
173
+ * stays in place until the new one is complete; a failed build leaves it
174
+ * untouched.
175
+ * @returns {Promise<{ ok: boolean, status: string, reason?: string, durationMs?: number, output?: string }>}
176
+ */
177
+ export async function rebuildDashboard({ root, run = runFrontendBuild, timeoutMs = BUILD_TIMEOUT_MS } = {}) {
178
+ const frontendDir = path.join(root, 'frontend');
179
+ const dist = path.join(frontendDir, 'dist');
180
+ const next = path.join(frontendDir, 'dist.next');
181
+ const prev = path.join(frontendDir, 'dist.prev');
182
+ const lock = acquireLock(path.join(frontendDir, '.dist-build.lock'));
183
+ if (!lock.held) return { ok: false, status: 'building-elsewhere', reason: `another process (pid ${lock.owner ?? 'unknown'}) is building the dashboard` };
184
+ const started = Date.now();
185
+ try {
186
+ fs.rmSync(next, { recursive: true, force: true });
187
+ const result = await run({ frontendDir, outDir: next, timeoutMs });
188
+ const built = result.code === 0 && !result.timedOut && fs.existsSync(path.join(next, 'index.html'));
189
+ if (!built) {
190
+ fs.rmSync(next, { recursive: true, force: true });
191
+ const reason = result.timedOut ? `build timed out after ${Math.round(timeoutMs / 1000)} s`
192
+ : result.code === 0 ? 'build finished without index.html' : `build exited ${result.code ?? 'without a status'}`;
193
+ return { ok: false, status: 'failed', reason, durationMs: Date.now() - started, output: result.output };
194
+ }
195
+ // Two renames: the served directory is absent only between them.
196
+ fs.rmSync(prev, { recursive: true, force: true });
197
+ const hadDist = fs.existsSync(dist);
198
+ if (hadDist) fs.renameSync(dist, prev);
199
+ try {
200
+ fs.renameSync(next, dist);
201
+ } catch (error) {
202
+ if (hadDist) fs.renameSync(prev, dist);
203
+ throw error;
204
+ }
205
+ fs.rmSync(prev, { recursive: true, force: true });
206
+ return { ok: true, status: 'built', durationMs: Date.now() - started };
207
+ } finally {
208
+ lock.release();
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Check once and rebuild when needed. Returns the state to report and, when
214
+ * the sources are still being edited, how long to wait before checking again.
215
+ */
216
+ export async function refreshDashboard({ root, now = Date.now, quietMs = QUIET_MS, rebuild = rebuildDashboard, toolsPresent = buildToolsPresent } = {}) {
217
+ const freshness = await checkDistFreshness(dashboardTargets(root));
218
+ if (freshness.ok) return { status: 'current', bundleBuiltAt: new Date(freshness.bundle.mtimeMs).toISOString() };
219
+ const state = freshness.reason === 'missing-bundle' ? 'missing' : 'stale';
220
+ if (!toolsPresent(root)) {
221
+ return { status: state, reason: `the build tools are not installed; run npm ci in ${root}`, source: freshness.source?.path ?? null };
222
+ }
223
+ if (freshness.source && now() - freshness.source.mtimeMs < quietMs) {
224
+ return { status: state, reason: 'sources are still changing', source: freshness.source.path, retryInMs: quietMs - (now() - freshness.source.mtimeMs) + 1000 };
225
+ }
226
+ const build = await rebuild({ root });
227
+ if (build.ok) return { status: 'current', rebuilt: true, durationMs: build.durationMs, was: state };
228
+ return { status: state, reason: build.reason, build };
229
+ }
230
+
231
+ /**
232
+ * Keep a serving checkout's bundle current: check now, after the sources go
233
+ * quiet, and every `intervalMs`. `report` receives each state change.
234
+ * @returns {{ state: () => object, check: () => Promise<object>, stop: () => void }}
235
+ */
236
+ export function watchDashboardBundle({ root, report = () => {}, intervalMs = CHECK_INTERVAL_MS, refresh = refreshDashboard } = {}) {
237
+ let current = { status: 'unchecked' };
238
+ let running = null;
239
+ let retry = null;
240
+ let lastKey = '';
241
+ const check = () => {
242
+ if (running) return running;
243
+ running = (async () => {
244
+ try {
245
+ current = { ...await refresh({ root }), checkedAt: new Date().toISOString() };
246
+ } catch (error) {
247
+ current = { status: 'error', reason: error.message, checkedAt: new Date().toISOString() };
248
+ }
249
+ const key = `${current.status}|${current.reason ?? ''}|${current.rebuilt ?? ''}`;
250
+ if (key !== lastKey || current.rebuilt) report(current);
251
+ lastKey = key;
252
+ clearTimeout(retry);
253
+ if (current.retryInMs) { retry = setTimeout(check, current.retryInMs); retry.unref?.(); }
254
+ return current;
255
+ })().finally(() => { running = null; });
256
+ return running;
257
+ };
258
+ const interval = setInterval(check, intervalMs);
259
+ interval.unref?.();
260
+ return { state: () => current, check, stop: () => { clearInterval(interval); clearTimeout(retry); } };
261
+ }
@@ -22,25 +22,29 @@ const RUNNING_PACKAGE_ROOT = (() => {
22
22
  })();
23
23
 
24
24
  /**
25
- * npm records the tree it installed in node_modules/.package-lock.json. The tree matches the
26
- * lockfile when every installed package has the locked version and every required locked package
27
- * is installed (optional packages may be absent on this platform; workspace links are not copies).
25
+ * The tree matches the lockfile when every locked package is on disk at its locked version
26
+ * (optional packages may be absent on this platform; workspace links are not copies; an absent
27
+ * peer is npm's call). It reads each package's own package.json, never npm's record of the tree
28
+ * (node_modules/.package-lock.json): `npm install --package-lock-only`, which the brain updater
29
+ * runs, rewrites that record to the new versions without installing them. On 2026-11-12 21 linked
30
+ * plugins held @ssss/cli 0.14.4 under a record and lock that both said 0.14.5, so every check that
31
+ * trusted the record, and npm itself, reported the stale tree as current.
28
32
  */
29
33
  export function npmTreeMatchesLock(root) {
30
- let lock, installed;
31
- try {
32
- lock = JSON.parse(fs.readFileSync(path.join(root, 'package-lock.json'), 'utf8')).packages;
33
- installed = JSON.parse(fs.readFileSync(path.join(root, 'node_modules', '.package-lock.json'), 'utf8')).packages;
34
- } catch { return false; }
35
- if (!lock || !installed) return false;
36
- for (const [key, entry] of Object.entries(installed)) {
37
- if (!key || entry.link) continue;
38
- if (!lock[key] || lock[key].version !== entry.version) return false;
39
- }
34
+ let lock;
35
+ try { lock = JSON.parse(fs.readFileSync(path.join(root, 'package-lock.json'), 'utf8')).packages; }
36
+ catch { return false; }
37
+ if (!lock || !fs.existsSync(path.join(root, 'node_modules'))) return false;
40
38
  for (const [key, entry] of Object.entries(lock)) {
41
39
  if (!key.startsWith('node_modules/') && !key.includes('/node_modules/')) continue;
42
- if (entry.optional || entry.link || entry.peer) continue;
43
- if (!installed[key]) return false;
40
+ if (entry.optional || entry.link) continue;
41
+ let version;
42
+ try { version = JSON.parse(fs.readFileSync(path.join(root, key, 'package.json'), 'utf8')).version; }
43
+ catch {
44
+ if (entry.peer) continue;
45
+ return false;
46
+ }
47
+ if (entry.version && version !== entry.version) return false;
44
48
  }
45
49
  return true;
46
50
  }
@@ -5,6 +5,7 @@ import { writeFileSecure } from './secure-file.mjs';
5
5
  import { ensureProjectId } from './project-id.mjs';
6
6
 
7
7
  function ensureDir(dir) { fs.mkdirSync(dir, { recursive: true }); }
8
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
8
9
 
9
10
  export const VAULT_CATEGORIES = Object.freeze([
10
11
  'invariants',
@@ -18,17 +19,33 @@ export const VAULT_CATEGORIES = Object.freeze([
18
19
  'lore',
19
20
  ]);
20
21
 
22
+ function readBrainConfig(file) {
23
+ let current;
24
+ try { current = JSON.parse(fs.readFileSync(file, 'utf8')); }
25
+ catch (error) {
26
+ if (error.code === 'ENOENT') return {};
27
+ throw error;
28
+ }
29
+ if (!current || typeof current !== 'object' || Array.isArray(current)) {
30
+ throw new Error('Existing brain identity must be an object');
31
+ }
32
+ return current;
33
+ }
34
+
35
+ /** Update connection settings without replacing the existing brain identity. */
36
+ export function writeBrainConnection(brainDir, fields) {
37
+ const file = path.join(brainDir, 'config', 'brain.json');
38
+ const current = readBrainConfig(file);
39
+ ensureDir(path.dirname(file));
40
+ writeFileSecure(file, JSON.stringify({ ...current, ...fields }, null, 2), { encoding: 'utf8', mode: 0o600 });
41
+ return file;
42
+ }
43
+
21
44
  export function writeBrainIdentity(brainDir, { name, role = 'project', tags = [] } = {}) {
22
45
  const configDir = path.join(brainDir, 'config');
23
46
  ensureDir(configDir);
24
47
  const brainJsonPath = path.join(configDir, 'brain.json');
25
- let current = {};
26
- if (fs.existsSync(brainJsonPath)) {
27
- current = JSON.parse(fs.readFileSync(brainJsonPath, 'utf8'));
28
- if (!current || typeof current !== 'object' || Array.isArray(current)) {
29
- throw new Error('Existing brain identity must be an object');
30
- }
31
- }
48
+ const current = readBrainConfig(brainJsonPath);
32
49
  const next = {
33
50
  ...current,
34
51
  name: name || current.name || path.basename(path.resolve(brainDir, '../../..')),
@@ -54,12 +71,17 @@ export function registerProjectBrain(globalBrainDir, entry) {
54
71
 
55
72
  const abs = path.resolve(entry.path);
56
73
  const idx = list.findIndex((p) => path.resolve(p.path || '') === abs);
74
+ const existing = list[idx];
75
+ const candidateId = existing?.project_id;
76
+ const fallbackId = typeof candidateId === 'string' && UUID.test(candidateId) &&
77
+ existing.brainDir === entry.brainDir && list.filter(record => record.project_id === candidateId).length === 1
78
+ ? candidateId : undefined;
57
79
  const now = new Date().toISOString();
58
80
  const row = {
59
81
  name: entry.name,
60
82
  path: abs,
61
83
  brainDir: entry.brainDir,
62
- project_id: ensureProjectId(entry.brainDir).project_id,
84
+ project_id: ensureProjectId(entry.brainDir, { fallbackId }).project_id,
63
85
  full_brain: true,
64
86
  layer: 'project',
65
87
  tags: entry.tags || ['project-brain'],
@@ -1,7 +1,10 @@
1
1
  /** Host activation policy; never pretends a provider or external daemon is healthy. */
2
- import { getPlugin } from './plugin-loader.mjs';
3
- import { patchPluginRecord, patchProjectActivationRecord, emit, vaultFor, recompileAfterPluginMutation } from './plugin-store.mjs';
4
- import { pluginActivation } from './plugin-state.mjs';
2
+ import path from 'node:path';
3
+ import { getPlugin, globalPluginsDir, projectPluginsDir, vaultForPluginsDir } from './plugin-loader.mjs';
4
+ import { patchPluginRecord, patchProjectActivationRecord, recordPath, emit, vaultFor, recompileAfterPluginMutation } from './plugin-store.mjs';
5
+ import { patchVfsDocument } from './ssss-operation-service.mjs';
6
+ import { pluginActivation, readActivationRecord, usesSharedPluginPolicy } from './plugin-state.mjs';
7
+ import { moveRepositoryPluginState, withRepositoryPluginLifecycle } from './plugin-lifecycle.mjs';
5
8
  import { REQUIRED_INSTALLATION_PLUGIN_IDS } from './default-plugins.mjs';
6
9
  export { pluginActivation };
7
10
 
@@ -14,38 +17,78 @@ export { pluginActivation };
14
17
  * following the shared setting (`clear: true`).
15
18
  */
16
19
  export async function setPluginActive(id, enabled, { projectRoot = process.cwd(), scope = 'project', clear = false } = {}) {
17
- if (enabled === false && REQUIRED_INSTALLATION_PLUGIN_IDS.includes(id)) {
20
+ const onUse = enabled === 'on-use';
21
+ if (onUse && (scope !== 'project' || !projectRoot || projectPluginsDir(projectRoot) === globalPluginsDir() || clear)) {
22
+ throw new Error('On use is available for individual repository brains.');
23
+ }
24
+ if ((enabled === false || onUse) && REQUIRED_INSTALLATION_PLUGIN_IDS.includes(id)) {
18
25
  throw new Error('Required plugin ' + id + ' cannot be disabled');
19
26
  }
20
27
  if (clear === true) {
21
28
  const plugin = getPlugin(id, projectRoot);
22
29
  if (!plugin?.valid) throw new Error(`Plugin ${id} is missing or invalid`);
23
- await patchProjectActivationRecord(id, projectRoot, null);
24
- await emit(vaultFor(plugin), id, 'activation-cleared', { projectRoot });
30
+ await withRepositoryPluginLifecycle(projectRoot, id, async brain => {
31
+ if (!usesSharedPluginPolicy(plugin) || pluginActivation(plugin, { projectRoot: null }).enabled) {
32
+ moveRepositoryPluginState(brain, id, 'restore');
33
+ }
34
+ await patchProjectActivationRecord(id, projectRoot, null);
35
+ await emit(vaultForPluginsDir(projectPluginsDir(projectRoot)), id, 'activation-cleared', {});
36
+ });
25
37
  const compilation = await recompileAfterPluginMutation(projectRoot, false);
26
38
  return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: 'shared', compilation };
27
39
  }
28
- if (typeof enabled !== 'boolean') throw new Error('Plugin activation requires a boolean');
40
+ if (onUse) enabled = false;
41
+ if (typeof enabled !== 'boolean') throw new Error('Plugin activation requires a boolean or On use');
29
42
  const plugin = getPlugin(id, projectRoot);
30
43
  if (!plugin?.valid) throw new Error(`Plugin ${id} is missing or invalid`);
31
44
  if (scope === 'shared') {
32
- if (plugin.scope !== 'global') throw new Error(`Plugin ${id} is a ${plugin.scope} copy without a shared setting to change`);
33
- await patchPluginRecord(plugin, { runtime_enabled: enabled });
45
+ if (!usesSharedPluginPolicy(plugin)) throw new Error(`Plugin ${id} is a ${plugin.scope} copy without a shared setting to change`);
46
+ await patchPluginRecord(plugin, { runtime_enabled: enabled, runtime_on_use: false });
34
47
  await emit(vaultFor(plugin), id, enabled ? 'activated' : 'deactivated', { enabled, scope: 'shared' });
35
48
  const compilation = await recompileAfterPluginMutation(projectRoot, true);
36
49
  return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: 'shared', compilation };
37
50
  }
38
51
  // Repository scope. A repository-owned copy already is that repository's setting.
39
- if (plugin.scope !== 'global') {
40
- await patchPluginRecord(plugin, { runtime_enabled: enabled });
41
- await emit(vaultFor(plugin), id, enabled ? 'activated' : 'deactivated', { enabled, scope: plugin.scope });
42
- // A project copy compiles the project's own surface and catalog. Only the
43
- // shared (scaffold) layer also rebuilds every registered project.
44
- const compilation = await recompileAfterPluginMutation(projectRoot, plugin.scope === 'scaffold');
45
- return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: plugin.scope, compilation };
46
- }
47
- await patchProjectActivationRecord(id, projectRoot, enabled, { manifest: plugin.manifest });
48
- await emit(vaultFor(plugin), id, enabled ? 'activated' : 'deactivated', { enabled, projectRoot, scope: 'project' });
52
+ const event = onUse ? 'activation-on-use' : enabled ? 'activated' : 'deactivated';
53
+ const shared = usesSharedPluginPolicy(plugin);
54
+ const resultScope = shared ? 'project' : plugin.scope;
55
+ const vaultRoot = shared ? vaultForPluginsDir(projectPluginsDir(projectRoot)) : vaultFor(plugin);
56
+ const cleanup = await withRepositoryPluginLifecycle(projectRoot, id, async brain => {
57
+ if (onUse && !brain) throw new Error('On use is available for individual repository brains.');
58
+ let result = enabled ? moveRepositoryPluginState(brain, id, 'restore') : undefined;
59
+ if (shared) await patchProjectActivationRecord(id, projectRoot, enabled, { manifest: plugin.manifest, onUse });
60
+ else await patchPluginRecord(plugin, { runtime_enabled: enabled, runtime_on_use: onUse });
61
+ if (!enabled && !onUse) {
62
+ try { result = moveRepositoryPluginState(brain, id, 'archive'); }
63
+ catch (error) {
64
+ result = { ok: false, state: 'pending', error: error.code ? `Could not archive repository plugin state (${error.code}).` : error.message };
65
+ }
66
+ }
67
+ await emit(vaultRoot, id, event, { enabled, scope: resultScope, ...(result ? { cleanup: result.state } : {}) });
68
+ return result;
69
+ });
70
+ const compilation = await recompileAfterPluginMutation(projectRoot, false);
71
+ return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: resultScope, compilation, ...(cleanup ? { cleanup } : {}) };
72
+ }
73
+
74
+ /** Claim one armed project policy through the SSSS compare-and-set writer. */
75
+ export async function activatePluginOnUse(id, { projectRoot = process.cwd() } = {}) {
76
+ if (!projectRoot || projectPluginsDir(projectRoot) === globalPluginsDir()) return null;
77
+ const plugin = getPlugin(id, projectRoot);
78
+ if (!plugin?.valid || pluginActivation(plugin, { projectRoot }).mode !== 'on-use') return null;
79
+ const vaultRoot = usesSharedPluginPolicy(plugin) ? vaultForPluginsDir(projectPluginsDir(projectRoot)) : vaultFor(plugin);
80
+ const cleanup = await withRepositoryPluginLifecycle(projectRoot, id, async brain => {
81
+ if (!brain) return null;
82
+ const current = readActivationRecord(path.join(vaultRoot, recordPath(id)), id, { withHash: true });
83
+ if (current?.record.runtime_on_use !== true) return null;
84
+ const result = moveRepositoryPluginState(brain, id, 'restore');
85
+ await patchVfsDocument(recordPath(id), { runtime_enabled: true, runtime_on_use: false, timestamp: new Date().toISOString() }, {
86
+ vaultRoot, expectedBeforeHash: current.hash, intent: 'Activate plugin on deliberate use',
87
+ });
88
+ await emit(vaultRoot, id, 'activated-on-use', { enabled: true, scope: 'project', cleanup: result.state });
89
+ return result;
90
+ });
91
+ if (!cleanup) return null;
49
92
  const compilation = await recompileAfterPluginMutation(projectRoot, false);
50
- return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: 'project', compilation };
93
+ return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: 'project', compilation, cleanup };
51
94
  }
@@ -1,15 +1,4 @@
1
- /**
2
- * Functional card contract. A plugin's card face is its own bespoke Web
3
- * Component, declared as a `ui.elements[]` entry of kind `card`. There is no
4
- * template: no shared layout, no widget vocabulary, no allowlist of shapes.
5
- * Core validates that the face exists and that the commands behind it are
6
- * declared; it never dictates how the face is arranged.
7
- *
8
- * A plugin carries exactly one face and AS MANY secondary screens as it needs,
9
- * each declared `kind: 'panel'`. Screens are the plugin's own design: it names
10
- * them, orders them and decides what is on them. The host imposes no count and
11
- * no screen set, and never derives a screen from the command list.
12
- */
1
+ /** Cross-field validation for plugin manifests and their command contracts. */
13
2
  const plain = (v) => v && typeof v === 'object' && !Array.isArray(v);
14
3
  const COMMAND = /^[a-z][a-z0-9-]{0,63}$/;
15
4
  const ARG_NAME = /^[a-z][a-zA-Z0-9]{0,63}$/;
@@ -19,11 +8,10 @@ export const COMMAND_EFFECTS = ['read', 'write', 'destructive', 'send'];
19
8
  export const OUTPUT_KINDS = ['json', 'text'];
20
9
  export const TIERS = ['core', 'official', 'personal', 'community'];
21
10
  export const LICENSE_CLASSES = ['open', 'closed'];
22
- const STATUS_STATES = ['ok', 'warn', 'error', 'unknown'];
23
- export const CARD_FONTS = ['system', 'inter', 'ibm-plex', 'space-grotesk', 'jetbrains-mono', 'dm-sans', 'outfit', 'fraunces', 'newsreader', 'chivo-mono'];
24
- export const CARD_DENSITIES = ['compact', 'comfortable', 'spacious'];
25
- export const CARD_RADII = ['none', 'sm', 'md', 'lg', 'pill'];
26
- export const CARD_HEADERS = ['plain', 'ledger-rule', 'band', 'tab-strip', 'badge-left'];
11
+ const BRAND_FONTS = ['system', 'inter', 'ibm-plex', 'space-grotesk', 'jetbrains-mono', 'dm-sans', 'outfit', 'fraunces', 'newsreader', 'chivo-mono'];
12
+ const BRAND_DENSITIES = ['compact', 'comfortable', 'spacious'];
13
+ const BRAND_RADII = ['none', 'sm', 'md', 'lg', 'pill'];
14
+ const BRAND_HEADERS = ['plain', 'ledger-rule', 'band', 'tab-strip', 'badge-left'];
27
15
  export const WORKSPACE_PERMISSIONS = ['microphone', 'camera', 'notifications'];
28
16
  const HEX = /^#[0-9a-fA-F]{6}$/;
29
17
 
@@ -41,7 +29,7 @@ function validateArg(arg, where, errors) {
41
29
  if (arg.secret === true && arg.type !== 'string') errors.push(`${where}: a secret stdin argument must be a string`);
42
30
  }
43
31
 
44
- export function validateCardContract(manifest) {
32
+ export function validateManifestContract(manifest) {
45
33
  const errors = [];
46
34
  const warnings = [];
47
35
  const subs = manifest.cli?.subcommands;
@@ -55,6 +43,13 @@ export function validateCardContract(manifest) {
55
43
  else names.add(sub.name);
56
44
  if (sub.effect !== undefined && !COMMAND_EFFECTS.includes(sub.effect)) errors.push(`${where}.effect must be one of: ${COMMAND_EFFECTS.join(', ')}`);
57
45
  if (sub.output !== undefined && !OUTPUT_KINDS.includes(sub.output)) errors.push(`${where}.output must be one of: ${OUTPUT_KINDS.join(', ')}`);
46
+ const terminalFlags = sub.terminal_only_args;
47
+ if (sub.terminal_only !== undefined && typeof sub.terminal_only !== 'boolean') errors.push(`${where}.terminal_only must be a boolean`);
48
+ if (terminalFlags !== undefined && (!Array.isArray(terminalFlags) ||
49
+ terminalFlags.some(flag => typeof flag !== 'string' || !FLAG.test(flag)) ||
50
+ new Set(terminalFlags).size !== terminalFlags.length)) {
51
+ errors.push(`${where}.terminal_only_args must contain unique long flags`);
52
+ }
58
53
  // The runner refuses anything outside this range, so a manifest that
59
54
  // declares it must fail validation rather than every call at run time.
60
55
  if (sub.timeout_ms !== undefined && (!Number.isInteger(sub.timeout_ms) || sub.timeout_ms < 1 || sub.timeout_ms > 600000)) errors.push(`${where}.timeout_ms must be an integer from 1 to 600000`);
@@ -91,7 +86,7 @@ export function validateCardContract(manifest) {
91
86
  if (!LICENSE_CLASSES.includes(manifest.distribution.license)) errors.push(`'distribution.license' must be one of: ${LICENSE_CLASSES.join(', ')}`);
92
87
  }
93
88
  }
94
- validateCardFace(manifest, errors, warnings);
89
+ validatePresentation(manifest, errors, warnings);
95
90
  validateBranding(manifest.branding, errors, warnings);
96
91
  validateWorkspace(manifest.workspace, errors);
97
92
  if (Array.isArray(manifest.secrets)) {
@@ -99,30 +94,15 @@ export function validateCardContract(manifest) {
99
94
  if (!plain(s) || typeof s.key !== 'string' || !/^[A-Z][A-Z0-9_]{0,63}$/.test(s.key)) errors.push(`secrets[${i}].key must be an uppercase identifier`);
100
95
  });
101
96
  }
102
- if (plain(manifest.card_face) && Array.isArray(manifest.card_face.metrics)) {
103
- warnings.push("card_face.metrics are fixed text and cannot reflect real state; declare a 'status' command instead");
104
- }
105
- return { errors, warnings, states: STATUS_STATES };
97
+ return { errors, warnings };
106
98
  }
107
99
 
108
- /**
109
- * Every plugin has a card face: bespoke owner code, declared once in
110
- * ui.elements[]. A plugin without one is incomplete, and a plugin that declares
111
- * a shared template is invalid.
112
- *
113
- * The *scaffold* emitted by `plugin create` deliberately contains no card: a
114
- * face is authored by hand once the plugin's function is understood. A scaffold
115
- * declares `scaffold: true`, so it installs while the author works and reports
116
- * its missing face as a warning rather than an error. Any other plugin without
117
- * a face is an error, because nothing else explains why it is missing.
118
- */
119
- function validateCardFace(manifest, errors, warnings) {
120
- if (manifest.card_layout !== undefined) {
121
- errors.push("'card_layout' is not supported: a card face is bespoke owner code in ui.elements[], never a shared template");
100
+ function validatePresentation(manifest, errors, warnings) {
101
+ for (const field of ['card', 'card_face', 'card_layout']) {
102
+ if (manifest[field] !== undefined) errors.push(`'${field}' is not supported: declare a native ui.home instead`);
122
103
  }
123
104
  const elements = manifest.ui?.elements;
124
- // A brick is distributed as capability code alone. It carries no UI, so it
125
- // has no face; every other plugin still requires exactly one.
105
+ // A brick is distributed as capability code alone and has no UI.
126
106
  if (manifest.brick === true) {
127
107
  validateCapability(manifest, errors);
128
108
  if (manifest.ui !== undefined && Array.isArray(elements) && elements.length > 0) {
@@ -131,23 +111,9 @@ function validateCardFace(manifest, errors, warnings) {
131
111
  return;
132
112
  }
133
113
  if (manifest.capability !== undefined) validateCapability(manifest, errors);
134
- if (!Array.isArray(elements) || elements.length === 0) {
135
- // A scaffold is a freshly created plugin whose face has not been authored
136
- // yet. It must be installable so the author can iterate, but it is not a
137
- // shippable plugin, so the missing face is reported and stays visible.
138
- if (manifest.scaffold === true) {
139
- warnings.push("'ui.elements' declares no card face yet: author ui/card.js and ui/DESIGN.md by hand for this plugin's own function, then declare the face in ui.elements[]");
140
- return;
141
- }
142
- errors.push("'ui.elements' must declare the plugin's card face; every plugin has one, authored by hand");
143
- return;
144
- }
145
- // One face, and as many secondary screens as the plugin needs. Cardinality is
146
- // checked for `card` alone, so panels may be declared in any number and on
147
- // either side of the face.
148
- const faces = elements.filter((element) => plain(element) && element.kind === 'card');
149
- if (faces.length === 0) errors.push("'ui.elements' must declare an element of kind 'card'");
150
- if (faces.length > 1) errors.push("'ui.elements' must declare exactly one card face; further screens use kind 'panel'");
114
+ if (manifest.ui?.home !== undefined) return;
115
+ if (manifest.scaffold === true) warnings.push("Scaffold needs an owner-authored 'ui.home' and DESIGN.md before shipping");
116
+ else errors.push("A plugin must declare a native 'ui.home'");
151
117
  }
152
118
 
153
119
  /**
@@ -188,7 +154,7 @@ function validateBranding(branding, errors, warnings) {
188
154
  if (branding.icon !== undefined) {
189
155
  warnings.push('branding.icon is deprecated: show the real logo with branding.logo instead of an emoji or generic icon');
190
156
  }
191
- const enums = { font: CARD_FONTS, density: CARD_DENSITIES, radius: CARD_RADII, header: CARD_HEADERS };
157
+ const enums = { font: BRAND_FONTS, density: BRAND_DENSITIES, radius: BRAND_RADII, header: BRAND_HEADERS };
192
158
  for (const [key, allowed] of Object.entries(enums)) {
193
159
  const value = branding[key];
194
160
  if (value === undefined || allowed.includes(value)) continue;