total-recall-brain 3.45.0 → 3.46.1

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 (363) hide show
  1. package/bin/total-recall.mjs +7 -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/default-plugin-refresh.mjs +39 -0
  285. package/src/core/ensure-dependencies.mjs +19 -15
  286. package/src/core/memory-brain.mjs +30 -8
  287. package/src/core/plugin-activation.mjs +63 -20
  288. package/src/core/plugin-contracts/{card.mjs → manifest.mjs} +23 -57
  289. package/src/core/plugin-contracts/startup-check.mjs +49 -0
  290. package/src/core/plugin-contracts/ui.mjs +9 -3
  291. package/src/core/plugin-distribution.mjs +6 -6
  292. package/src/core/plugin-lifecycle.mjs +58 -0
  293. package/src/core/plugin-loader.mjs +9 -5
  294. package/src/core/plugin-runner.mjs +21 -10
  295. package/src/core/plugin-startup-checks.mjs +44 -0
  296. package/src/core/plugin-state.mjs +19 -5
  297. package/src/core/plugin-store.mjs +30 -11
  298. package/src/core/project-id.mjs +15 -5
  299. package/src/core/required-plugins.mjs +31 -6
  300. package/src/core/schema.mjs +2 -0
  301. package/src/core/ssss-operation-service.mjs +1 -0
  302. package/src/core/startup-health.mjs +28 -3
  303. package/src/core/testing/plugin-fixture.mjs +22 -21
  304. package/src/core/throttled-fetch.mjs +25 -18
  305. package/src/core/ui-chunk-choice.mjs +77 -0
  306. package/src/core/ui-chunk.registry.json +23 -0
  307. package/src/core/ui-chunks.mjs +434 -0
  308. package/src/core/vault-cache.mjs +58 -59
  309. package/src/core/workspace-compositions.mjs +151 -0
  310. package/src/core/workspace-drawers.mjs +79 -0
  311. package/src/server/api.mjs +4 -27
  312. package/src/server/index.mjs +28 -21
  313. package/src/server/memory-events.mjs +31 -0
  314. package/src/server/plugin-brain-targets.mjs +89 -0
  315. package/src/server/rest.mjs +2 -0
  316. package/src/server/route-inventory.mjs +3 -2
  317. package/src/server/route-manifest.json +52 -4
  318. package/src/server/routes/_shared.mjs +7 -14
  319. package/src/server/routes/brains.mjs +29 -44
  320. package/src/server/routes/dashboard.mjs +26 -0
  321. package/src/server/routes/plugins.mjs +154 -45
  322. package/src/server/routes/ui-chunks.mjs +192 -0
  323. package/frontend/dist/assets/index-DACh1dhx.js +0 -5613
  324. package/frontend/dist/assets/index-DtwsDyFM.css +0 -1
  325. package/frontend/src/components/plugins/card/CardEngine.tsx +0 -108
  326. package/frontend/src/components/plugins/card/CardScenes.tsx +0 -103
  327. package/frontend/src/components/plugins/card/ExecutionPicker.tsx +0 -64
  328. package/frontend/src/components/plugins/card/args.ts +0 -28
  329. package/frontend/src/components/plugins/card/card.css +0 -32
  330. package/frontend/src/components/plugins/card/cli-line.ts +0 -13
  331. package/frontend/src/components/plugins/card/contract.ts +0 -91
  332. package/frontend/src/components/plugins/card/format.ts +0 -49
  333. package/frontend/src/components/plugins/card/runner-context.ts +0 -11
  334. package/frontend/src/components/plugins/card/runner.tsx +0 -43
  335. package/frontend/src/components/plugins/card/theme.ts +0 -35
  336. package/frontend/src/components/plugins/card/use-source.ts +0 -22
  337. package/frontend/src/components/plugins/card/widgets.tsx +0 -94
  338. package/frontend/src/components/plugins/workspace/WorkspaceHost.tsx +0 -23
  339. package/frontend/src/pages/PluginsPage.tsx +0 -1719
  340. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +0 -47
  341. package/scaffold/.agent/skills/plugin/prompts/06-card.md +0 -46
  342. package/scaffold/.agent/skills/plugin/references/card.md +0 -90
  343. package/scaffold/.agent/skills/plugin/subagents/card-designer.md +0 -60
  344. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +0 -67
  345. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +0 -1179
  346. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +0 -32
  347. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +0 -588
  348. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +0 -575
  349. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +0 -32
  350. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +0 -482
  351. package/scaffold/.agent/skills/total-recall/plugins/git/ui/DESIGN.md +0 -100
  352. package/scaffold/.agent/skills/total-recall/plugins/git/ui/card.js +0 -637
  353. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/DESIGN.md +0 -217
  354. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +0 -32
  355. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +0 -1276
  356. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +0 -45
  357. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +0 -1401
  358. package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +0 -4
  359. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +0 -41
  360. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +0 -396
  361. package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +0 -98
  362. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/DESIGN.md +0 -15
  363. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/card.js +0 -182
@@ -1 +0,0 @@
1
- :root{--bg-primary:#070b14;--bg-secondary:#0c1220;--bg-tertiary:#121a2b;--bg-elevated:#172033;--bg-hover:#1c2740;--bg-active:#243352;--accent:#3b82f6;--accent-deep:#1e3a8a;--accent-glow:#3b82f647;--accent-hover:#60a5fa;--accent-muted:#3b82f624;--accent-soft:#93c5fd1f;--purple-primary:var(--accent);--purple-border:var(--border-accent);--success:#34d399;--success-muted:#34d3991f;--warning:#fbbf24;--warning-muted:#fbbf241f;--error:#f87171;--error-muted:#f871711f;--text-primary:#f1f5f9;--text-secondary:#94a3b8;--text-tertiary:#64748b;--text-inverse:#070b14;--border:#94a3b81a;--border-hover:#94a3b838;--border-accent:#3b82f659;--glass:#0c1220b8;--glass-border:#94a3b81f;--mesh-1:#1e3a8a59;--mesh-2:#3b82f61f;--mesh-3:#0f172ae6;--font-heading:"Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-sans:"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono:"JetBrains Mono", "SF Mono", ui-monospace, monospace;--sidebar-width:252px;--radius-sm:8px;--radius-md:12px;--radius-lg:18px;--radius-xl:24px;--shadow-sm:0 1px 2px #00000059;--shadow-md:0 8px 24px #00000059;--shadow-lg:0 24px 64px #00000073;--shadow-glow:0 0 40px #3b82f62e;--transition-fast:.14s cubic-bezier(.23, 1, .32, 1);--transition-normal:.22s cubic-bezier(.23, 1, .32, 1)}*,:before,:after{box-sizing:border-box;margin:0;padding:0}html,body,#root{background:var(--bg-primary);height:100%;color:var(--text-primary);font-family:var(--font-sans);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizelegibility;font-size:14px;line-height:1.6}body{background:radial-gradient(1200px 600px at 10% -10%, var(--mesh-1), transparent 55%), radial-gradient(900px 500px at 100% 0%, var(--mesh-2), transparent 50%), radial-gradient(800px 400px at 50% 100%, #1e3a8a33, transparent 60%), var(--bg-primary)}a{color:var(--accent);text-decoration:none}a:hover{color:var(--accent-hover)}h1,h2,h3,h4,h5,h6{font-family:var(--font-heading);letter-spacing:-.025em;color:var(--text-primary);font-weight:700}button{font-family:var(--font-sans);cursor:pointer;border:none;outline:none}code,pre{font-family:var(--font-mono)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--bg-hover);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text-tertiary)}.app-layout{height:100vh;display:flex;position:relative;overflow:hidden}.sidebar{width:var(--sidebar-width);min-width:var(--sidebar-width);background:var(--glass);-webkit-backdrop-filter:blur(20px)saturate(1.4);border-right:1px solid var(--glass-border);transition:transform var(--transition-normal);z-index:100;flex-direction:column;padding:14px 0 12px;display:flex;box-shadow:4px 0 40px #00000040}.sidebar-brand{border-bottom:1px solid var(--border);flex-direction:column;gap:10px;margin-bottom:10px;padding:4px 16px 18px;display:flex}.sidebar-brand-row{align-items:center;gap:12px;display:flex}.sidebar-brand h1{color:var(--text-primary);letter-spacing:-.03em;font-size:15px;font-weight:700;line-height:1.2}.sidebar-brand span{color:var(--text-tertiary);letter-spacing:.02em;font-size:11px;font-weight:500}.sidebar-brand-lockup{background:linear-gradient(#f8fafc 0%,#e8eef9 100%);border-radius:12px;width:100%;padding:10px 12px;overflow:hidden;box-shadow:inset 0 1px #ffffffb3,0 8px 20px #0f172a59,0 0 0 1px #94a3b833}.sidebar-brand-lockup img{object-fit:contain;width:100%;height:auto;display:block}.sidebar-nav{scrollbar-width:thin;flex-direction:column;flex:1;gap:2px;padding:0 10px;display:flex;overflow-y:auto}.nav-section-label{text-transform:uppercase;letter-spacing:.08em;color:var(--text-tertiary);opacity:.85;padding:12px 12px 6px;font-size:10px;font-weight:600}.nav-link{color:var(--text-secondary);transition:all var(--transition-fast);border:1px solid #0000;border-radius:10px;align-items:center;gap:10px;padding:9px 12px;font-size:13px;font-weight:500;text-decoration:none;display:flex;position:relative}.nav-link:hover{color:var(--text-primary);background:#94a3b814}.nav-link.active{color:#dbeafe;border-color:var(--border-accent);background:linear-gradient(135deg,#3b82f62e,#1e3a8a38);box-shadow:0 0 0 1px #3b82f614,inset 0 1px #ffffff0a}.nav-link.active:before{content:"";background:linear-gradient(#60a5fa,#2563eb);border-radius:0 3px 3px 0;width:3px;position:absolute;top:20%;bottom:20%;left:0;box-shadow:0 0 12px #3b82f699}.nav-link svg{opacity:.72;flex-shrink:0;width:18px;height:18px}.nav-link.active svg{opacity:1;color:#93c5fd}.nav-link:hover svg{opacity:.95}.sidebar-footer{border-top:1px solid var(--border);background:linear-gradient(#0000,#0f172a66);margin-top:8px;padding:12px 14px}.main-content{background:0 0;flex-direction:column;flex:1;min-width:0;display:flex;overflow:hidden}.page{flex:1;padding:28px 32px 40px;overflow-y:auto}.page-header{margin-bottom:28px}.page-header h1{letter-spacing:-.03em;background:linear-gradient(135deg,#f8fafc 0%,#93c5fd 100%);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text;font-size:24px;font-weight:700}.page-header p{color:var(--text-secondary);max-width:56ch;margin-top:6px;font-size:13.5px}.card{-webkit-backdrop-filter:blur(16px);border-radius:var(--radius-md);transition:all var(--transition-normal);background:#121a2ba6;border:1px solid #94a3b81f;padding:22px;box-shadow:0 4px 20px #00000040}.card:hover{background:#1c2740bf;border-color:#60a5fa59;transform:translateY(-1px);box-shadow:0 8px 30px #00000059}.card-grid{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px;display:grid}.btn{border-radius:var(--radius-md);min-height:44px;transition:all var(--transition-fast);letter-spacing:-.01em;cursor:pointer;border:1px solid #0000;justify-content:center;align-items:center;gap:8px;padding:0 18px;font-size:13.5px;font-weight:600;display:inline-flex}.btn-primary{color:#fff;background:linear-gradient(135deg,#3b82f6 0%,#2563eb 55%,#1d4ed8 100%);border-color:#ffffff1a;box-shadow:inset 0 1px #fff3,0 4px 14px #2563eb59}.btn-primary:hover{box-shadow:0 0 24px var(--accent-glow), 0 6px 20px #2563eb73;background:linear-gradient(135deg,#60a5fa 0%,#3b82f6 50%,#2563eb 100%);transform:translateY(-1px)}.btn-ghost{color:var(--text-secondary);background:#94a3b814;border:1px solid #94a3b824}.btn-ghost:hover{color:var(--text-primary);background:#94a3b829;border-color:#94a3b847}.btn-sm{border-radius:var(--radius-sm);min-height:36px;padding:0 12px;font-size:12px}.alert{border-radius:var(--radius-sm);border:1px solid #0000;padding:12px 16px;font-size:13px;font-weight:500}.alert-error{background:var(--error-muted);color:var(--error);border-color:#f8717140}.alert-success{background:var(--success-muted);color:var(--success);border-color:#34d39940}.alert-warning{background:var(--warning-muted);color:var(--warning);border-color:#fbbf2440}.input,.select{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text-primary);font-family:var(--font-sans);transition:border-color var(--transition-fast), box-shadow var(--transition-fast);outline:none;padding:9px 12px;font-size:13px}.input:focus,.select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-muted)}.input::placeholder{color:var(--text-tertiary)}.select{cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");background-position:right 10px center;background-repeat:no-repeat;padding-right:32px}.data-table{border-collapse:collapse;width:100%}.data-table th{text-align:left;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border);background:var(--bg-secondary);padding:12px 16px;font-size:12px;font-weight:500}.data-table td{border-bottom:1px solid var(--border);color:var(--text-primary);padding:14px 16px;font-size:13px}.data-table tbody tr{transition:background var(--transition-fast)}.data-table tbody tr:hover{background:var(--bg-hover)}.data-table tr.is-leader td{background:var(--accent-soft)}.page-container{max-width:1200px;margin:0 auto;padding:32px}.page-header{justify-content:space-between;align-items:center;margin-bottom:32px;display:flex}.page-header h1{color:var(--text-primary);font-size:24px;font-weight:600}.badge{letter-spacing:.03em;border-radius:4px;align-items:center;padding:3px 8px;font-size:11px;font-weight:600;display:inline-flex}.badge-online,.badge-active{background:var(--success-muted);color:var(--success)}.badge-offline,.badge-inactive{background:var(--error-muted);color:var(--error)}.badge-leader{color:var(--accent-hover);background:#3b82f626}.badge-follower{background:var(--bg-hover);color:var(--text-secondary)}.surface-premium{border:1px solid var(--glass-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-md), inset 0 1px 0 #ffffff0a;background:linear-gradient(165deg,#172033eb,#0c1220f5)}.login-shell{justify-content:center;align-items:center;min-height:100vh;padding:24px;display:flex;position:relative;overflow:hidden}.login-shell:before{content:"";pointer-events:none;background:radial-gradient(50% 40% at 20% 20%,#2563eb47,#0000 55%),radial-gradient(45% 35% at 80% 70%,#1e3a8a59,#0000 50%),radial-gradient(30% 25%,#60a5fa14,#0000 60%);animation:18s ease-in-out infinite alternate meshDrift;position:absolute;inset:-20%}@keyframes meshDrift{0%{transform:translate(0,0)scale(1)}to{transform:translate(-2%,2%)scale(1.05)}}.login-card{z-index:1;border-radius:var(--radius-xl);-webkit-backdrop-filter:blur(24px);backdrop-filter:blur(24px);background:linear-gradient(165deg,#121a2beb,#080c16f5);border:1px solid #94a3b824;flex-direction:column;gap:28px;width:min(400px,100%);padding:40px 36px;display:flex;position:relative;box-shadow:0 0 0 1px #3b82f614,0 32px 80px #0000008c,0 0 80px #2563eb1f}.onboarding-shell{max-width:680px;margin:0 auto;padding:36px 22px 72px}.onboarding-progress{background:#94a3b81a;border-radius:99px;height:6px;overflow:hidden;box-shadow:inset 0 1px 2px #0000004d}.onboarding-progress>div{background:linear-gradient(90deg,#1e40af,#3b82f6,#93c5fd);border-radius:99px;height:100%;transition:width .3s cubic-bezier(.23,1,.32,1);box-shadow:0 0 16px #3b82f680}.loop-card{border:1px solid var(--border-accent);border-radius:var(--radius-md);font-family:var(--font-mono);color:#cbd5e1;background:linear-gradient(145deg,#1e3a8a40,#0c1220cc);padding:18px 20px;font-size:12.5px;line-height:1.85;box-shadow:inset 0 1px #ffffff0a}.badge{text-transform:uppercase;letter-spacing:.04em;border-radius:99px;align-items:center;gap:4px;padding:3px 8px;font-size:11px;font-weight:600;display:inline-flex}.badge-success{background:var(--success-muted);color:var(--success)}.badge-warning{background:var(--warning-muted);color:var(--warning)}.badge-error{background:var(--error-muted);color:var(--error)}.badge-accent{background:var(--accent-muted);color:var(--accent)}.chat-container{flex-direction:column;flex:1;height:calc(100vh - 1px);display:flex;overflow:hidden}.chat-messages{flex-direction:column;flex:1;gap:16px;padding:24px 32px;display:flex;overflow-y:auto}.chat-empty{color:var(--text-tertiary);flex-direction:column;flex:1;justify-content:center;align-items:center;gap:12px;display:flex}.chat-empty svg{opacity:.3;width:48px;height:48px}.chat-empty h2{color:var(--text-secondary);font-size:18px;font-weight:600}.chat-empty p{font-size:13px}.message{border-radius:var(--radius-md);white-space:pre-wrap;word-break:break-word;max-width:760px;padding:16px 20px;font-size:14px;line-height:1.65;animation:.18s cubic-bezier(.16,1,.3,1) msgIn;position:relative}@keyframes msgIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.message-user{color:#f8fafc;-webkit-backdrop-filter:blur(12px);background:#1e3a8a47;border:1px solid #3b82f659;border-bottom-right-radius:4px;align-self:flex-end;box-shadow:0 4px 18px #00000040}.message-assistant{-webkit-backdrop-filter:blur(12px);color:var(--text-primary);background:#121a2bb8;border:1px solid #94a3b81f;border-bottom-left-radius:4px;align-self:flex-start;box-shadow:0 6px 24px #0000004d}.message-assistant code{background:#070b14bf;border:1px solid #94a3b81a;border-radius:4px;padding:2px 6px;font-size:12.5px}.chat-input-bar{-webkit-backdrop-filter:blur(20px);background:#0c1220d9;border-top:1px solid #94a3b81a;padding:16px 32px 20px}.chat-input-wrapper{align-items:flex-end;gap:10px;max-width:820px;margin:0 auto;display:flex}.chat-input-wrapper textarea{resize:none;border-radius:var(--radius-md);color:var(--text-primary);font-family:var(--font-sans);min-height:44px;max-height:160px;transition:all var(--transition-fast);background:#121a2ba6;border:1px solid #94a3b824;flex:1;padding:12px 16px;font-size:14px;line-height:1.5}.chat-input-wrapper textarea:focus{background:#121a2be6;border-color:#3b82f680;outline:none;box-shadow:0 0 0 3px #3b82f629}.chat-input-wrapper textarea::placeholder{color:var(--text-tertiary)}.chat-send-btn{border-radius:var(--radius-md);color:#fff;width:44px;height:44px;transition:all var(--transition-fast);background:linear-gradient(135deg,#3b82f6,#1d4ed8);flex-shrink:0;justify-content:center;align-items:center;display:flex;box-shadow:0 4px 14px #2563eb59}.chat-send-btn:hover{box-shadow:0 0 24px var(--accent-glow);background:linear-gradient(135deg,#60a5fa,#2563eb);transform:translateY(-1px)}.chat-send-btn:disabled{opacity:.35;cursor:not-allowed;box-shadow:none;transform:none}.chat-send-btn svg{width:18px;height:18px}.memory-layout{grid-template-columns:240px 1fr;gap:20px;height:calc(100vh - 120px);display:grid}.memory-sidebar{-webkit-backdrop-filter:blur(16px);border-radius:var(--radius-md);background:#0c1220b8;border:1px solid #94a3b81f;padding:14px;overflow-y:auto;box-shadow:0 4px 20px #00000040}.memory-sidebar h3{text-transform:uppercase;letter-spacing:.08em;color:var(--text-tertiary);padding:6px 8px 10px;font-size:11px;font-weight:600}.category-btn{border-radius:var(--radius-sm);width:100%;color:var(--text-secondary);text-align:left;transition:all var(--transition-fast);background:0 0;border:1px solid #0000;justify-content:space-between;align-items:center;padding:9px 12px;font-size:13px;font-weight:500;display:flex}.category-btn:hover{color:var(--text-primary);background:#94a3b814}.category-btn.active{color:#93c5fd;background:linear-gradient(135deg,#3b82f633,#1e3a8a40);border-color:#3b82f659;font-weight:600}.category-count{color:var(--text-secondary);background:#94a3b81f;border-radius:99px;padding:2px 7px;font-size:11px;font-weight:600}.memory-main{flex-direction:column;gap:14px;display:flex;overflow:hidden}.memory-search{gap:10px;display:flex}.memory-search input{-webkit-backdrop-filter:blur(16px);border-radius:var(--radius-md);color:var(--text-primary);font-size:13.5px;font-family:var(--font-sans);transition:all var(--transition-fast);background:#0c1220b8;border:1px solid #94a3b824;flex:1;padding:12px 16px}.memory-search input:focus{background:#121a2be6;border-color:#3b82f680;outline:none;box-shadow:0 0 0 3px #3b82f629}.memory-search input::placeholder{color:var(--text-tertiary)}.memory-list{flex-direction:column;flex:1;gap:10px;display:flex;overflow-y:auto}.memory-node-card{border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-fast);background:#121a2b99;border:1px solid #94a3b81f;padding:16px 18px;box-shadow:0 2px 8px #0003}.memory-node-card:hover{background:#1c2740b3;border-color:#60a5fa66;transform:translateY(-1px);box-shadow:0 4px 16px #0000004d}.memory-node-card.selected{background:#1e3a8a40;border-color:#3b82f68c;box-shadow:0 0 0 1px #3b82f64d,0 4px 20px #00000059}.memory-node-card h4{color:#f8fafc;margin-bottom:6px;font-size:14px;font-weight:600}.memory-node-card .meta{color:var(--text-tertiary);align-items:center;gap:14px;font-size:12px;display:flex}.memory-detail{-webkit-backdrop-filter:blur(20px);border-radius:var(--radius-lg);background:#0c1220cc;border:1px solid #94a3b824;padding:24px;overflow-y:auto;box-shadow:0 8px 32px #0006}.memory-detail h2{margin-bottom:12px;font-size:18px}.memory-detail pre{background:var(--bg-primary);border:1px solid var(--border);border-radius:var(--radius-sm);white-space:pre-wrap;color:var(--text-secondary);padding:16px;font-size:12px;line-height:1.7;overflow-x:auto}.memory-detail .frontmatter{background:var(--bg-tertiary);border-radius:var(--radius-sm);grid-template-columns:auto 1fr;gap:4px 16px;margin-bottom:16px;padding:12px 16px;font-size:13px;display:grid}.frontmatter dt{color:var(--text-tertiary);font-weight:500}.frontmatter dd{color:var(--text-primary)}.health-grid{grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-bottom:24px;display:grid}.stat-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);text-align:center;transition:border-color var(--transition-normal);padding:20px}.stat-card:hover{border-color:var(--border-hover)}.stat-card .value{letter-spacing:-.02em;font-size:28px;font-weight:700}.stat-card .label{color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.04em;margin-top:4px;font-size:12px}.stat-card.healthy .value{color:var(--success)}.stat-card.warning .value{color:var(--warning)}.stat-card.error .value{color:var(--error)}.health-log{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px}.health-log h3{margin-bottom:12px;font-size:14px;font-weight:600}.health-log table{border-collapse:collapse;width:100%;font-size:13px}.health-log th{text-align:left;color:var(--text-tertiary);border-bottom:1px solid var(--border);padding:8px 12px;font-weight:500}.health-log td{border-bottom:1px solid var(--border);color:var(--text-secondary);padding:8px 12px}.sandbox-layout{grid-template-rows:1fr auto 1fr;gap:0;height:calc(100vh - 120px);display:grid}.sandbox-editor{flex-direction:column;display:flex;overflow:hidden}.sandbox-editor-header{background:var(--bg-tertiary);border:1px solid var(--border);border-radius:var(--radius-md) var(--radius-md) 0 0;color:var(--text-tertiary);justify-content:space-between;align-items:center;padding:8px 12px;font-size:12px;display:flex}.sandbox-editor textarea{resize:none;background:var(--bg-secondary);border:1px solid var(--border);border-radius:0 0 var(--radius-md) var(--radius-md);width:100%;color:var(--text-primary);font-family:var(--font-mono);tab-size:2;border-top:none;flex:1;padding:16px;font-size:13px;line-height:1.6}.sandbox-editor textarea:focus{border-color:var(--accent);outline:none}.sandbox-toolbar{justify-content:center;align-items:center;padding:8px;display:flex}.sandbox-output{background:var(--bg-primary);border:1px solid var(--border);border-radius:var(--radius-md);font-family:var(--font-mono);color:var(--success);white-space:pre-wrap;padding:16px;font-size:13px;line-height:1.6;overflow-y:auto}.sandbox-output.error{color:var(--error)}.pulse{background:var(--success);border-radius:50%;width:8px;height:8px;animation:2s infinite pulseAnim;display:inline-block}@keyframes pulseAnim{0%,to{box-shadow:0 0 #00cec966}50%{box-shadow:0 0 0 6px #00cec900}}.skeleton{background:linear-gradient(90deg, var(--bg-tertiary) 25%, var(--bg-hover) 50%, var(--bg-tertiary) 75%);border-radius:var(--radius-sm);background-size:200% 100%;animation:1.5s infinite shimmer}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@media (width<=768px){.sidebar{position:fixed;top:0;bottom:0;left:0;transform:translate(-100%);box-shadow:0 0 40px #00000080}.sidebar.open{transform:translate(0)}.memory-layout{grid-template-columns:1fr;height:auto}.page{padding:16px}.btn,.nav-link,.category-btn{touch-action:manipulation;min-height:44px}.chat-input-wrapper textarea,.memory-search input,.sandbox-editor textarea{font-size:16px}.chat-input-bar{padding:12px 16px 16px}}.chat-layout{width:100%;height:100%;display:flex;overflow:hidden}.chat-threads-sidebar{-webkit-backdrop-filter:blur(16px);background:#0c1220b3;border-right:1px solid #94a3b81a;flex-direction:column;flex-shrink:0;width:260px;height:100%;display:flex;overflow:hidden}.sidebar-header{border-bottom:1px solid #94a3b81a;padding:14px 16px}.new-chat-btn{color:#93c5fd;border-radius:var(--radius-sm);width:100%;transition:all var(--transition-fast);cursor:pointer;background:linear-gradient(135deg,#3b82f638,#1e3a8a47);border:1px solid #3b82f659;justify-content:center;align-items:center;gap:8px;padding:10px 16px;font-size:13px;font-weight:600;display:flex}.new-chat-btn:hover{color:#fff;box-shadow:0 0 16px var(--accent-glow);background:linear-gradient(135deg,#3b82f659,#1e3a8a73);border-color:#60a5fa80}.thread-list{flex-direction:column;flex:1;gap:6px;padding:16px 12px;display:flex;overflow-y:auto}.thread-item{border-radius:var(--radius-sm);cursor:pointer;transition:all var(--transition-fast);border:1px solid #0000;justify-content:space-between;align-items:center;padding:10px 12px;display:flex;position:relative}.thread-item:hover{background:#94a3b814}.thread-item.active{background:#3b82f61f;border-color:#3b82f64d}.thread-info{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.thread-title{color:var(--text-primary);white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:500;overflow:hidden}.thread-meta{color:var(--text-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}.thread-delete-btn{color:var(--text-tertiary);cursor:pointer;opacity:0;transition:all var(--transition-fast);background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;padding:4px;display:flex}.thread-item:hover .thread-delete-btn{opacity:1}.thread-delete-btn:hover{color:var(--error);background:var(--error-muted)}.chat-suggestions-container{width:100%;max-width:900px;margin-top:32px;animation:.4s ease-out fadeIn}.chat-suggestions-container h3{text-transform:uppercase;letter-spacing:.08em;color:var(--text-tertiary);text-align:center;margin-bottom:16px;font-size:12px}.chat-suggestions-deck{grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px;width:100%;display:grid}.suggestion-card{-webkit-backdrop-filter:blur(12px);border-radius:var(--radius-md);cursor:pointer;text-align:left;transition:all var(--transition-normal);background:#12121a73;border:1px solid #ffffff0d;gap:14px;padding:16px;display:flex;position:relative;overflow:hidden}.suggestion-card:before{content:"";opacity:.8;width:4px;height:100%;transition:width var(--transition-fast) ease;position:absolute;top:0;left:0}.suggestion-card.card-fact:before{background:var(--success)}.suggestion-card.card-concept:before{background:var(--accent)}.suggestion-card.card-question:before{background:var(--warning)}.suggestion-card:hover{background:#1a1a2699;border-color:#ffffff1f;transform:translateY(-2px);box-shadow:0 8px 24px #0003}.suggestion-card:hover:before{width:6px}.suggestion-card.card-fact:hover{border-color:#00cec940;box-shadow:0 8px 24px #00cec90f}.suggestion-card.card-concept:hover{border-color:#6c5ce740;box-shadow:0 8px 24px #6c5ce70f}.suggestion-card.card-question:hover{border-color:#fdcb6e40;box-shadow:0 8px 24px #fdcb6e0f}.suggestion-card-icon{-webkit-user-select:none;user-select:none;align-items:flex-start;padding-top:2px;font-size:22px;display:flex}.suggestion-card-content{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.suggestion-card-content h4{color:var(--text-primary);font-size:13.5px;font-weight:600;line-height:1.4}.suggestion-card-content p{color:var(--text-secondary);font-size:12.5px;line-height:1.5}.suggestion-card-nodes{flex-wrap:wrap;gap:6px;margin-top:8px;display:flex}.suggestion-node-pill{background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-secondary);white-space:nowrap;text-overflow:ellipsis;border-radius:4px;max-width:140px;padding:2px 6px;font-size:10px;font-weight:500;overflow:hidden}.grounding-pills{border:1px solid var(--border);border-radius:var(--radius-md) var(--radius-md) 0 0;background:#12121a66;border-bottom:none;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 12px;display:flex}.grounding-pill{background:var(--accent-muted);color:var(--text-primary);transition:all var(--transition-fast);border:1px solid #6c5ce74d;border-radius:99px;align-items:center;gap:6px;padding:4px 10px;font-size:12px;font-weight:500;display:inline-flex}.grounding-pill:hover{background:#6c5ce733;border-color:#6c5ce780}.grounding-pill svg{color:var(--accent);flex-shrink:0}.grounding-pill-title{text-overflow:ellipsis;white-space:nowrap;max-width:150px;overflow:hidden}.grounding-pill-remove{color:var(--text-tertiary);cursor:pointer;transition:color var(--transition-fast);background:0 0;border:none;justify-content:center;align-items:center;padding:0 2px;font-size:14px;line-height:1;display:inline-flex}.grounding-pill-remove:hover{color:var(--error)}.grounding-count-badge{background:var(--accent);color:#fff;border:1.5px solid var(--bg-secondary);min-width:16px;height:16px;box-shadow:0 0 8px var(--accent-glow);border-radius:50%;justify-content:center;align-items:center;font-size:9px;font-weight:700;animation:.2s cubic-bezier(.175,.885,.32,1.275) popIn;display:flex;position:absolute;top:-4px;right:-4px}.node-selector-popover{-webkit-backdrop-filter:blur(20px);border-radius:var(--radius-lg);z-index:50;background:#12121ad9;border:1px solid #ffffff14;flex-direction:column;width:320px;max-height:380px;animation:.25s cubic-bezier(.16,1,.3,1) slideUp;display:flex;position:absolute;bottom:calc(100% + 12px);left:0;overflow:hidden;box-shadow:0 12px 32px #0006}.node-selector-header{border-bottom:1px solid var(--border);flex-direction:column;gap:8px;padding:14px 16px 10px;display:flex}.node-selector-header h4{color:var(--text-primary);letter-spacing:.02em;font-size:13px;font-weight:600}.node-search-input{background:var(--bg-tertiary);border:1px solid var(--border);border-radius:var(--radius-sm);width:100%;color:var(--text-primary);font-size:12px;font-family:var(--font-sans);transition:all var(--transition-fast);padding:6px 10px}.node-search-input:focus{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-glow);outline:none}.node-selector-list{flex-direction:column;flex:1;gap:2px;padding:8px;display:flex;overflow-y:auto}.node-selector-empty{text-align:center;color:var(--text-tertiary);padding:24px;font-size:12px}.node-selector-item{border-radius:var(--radius-sm);cursor:pointer;transition:all var(--transition-fast);align-items:center;gap:10px;padding:8px 12px;display:flex}.node-selector-item:hover{background:var(--bg-hover)}.node-selector-item.selected{background:var(--accent-muted)}.node-selector-item-checkbox{border:1px solid var(--text-tertiary);width:16px;height:16px;color:var(--accent);transition:all var(--transition-fast);background:var(--bg-tertiary);border-radius:3px;flex-shrink:0;justify-content:center;align-items:center;font-size:11px;font-weight:700;display:flex}.node-selector-item.selected .node-selector-item-checkbox{background:var(--accent);border-color:var(--accent);color:#fff}.node-selector-item-info{flex-direction:column;flex:1;min-width:0;display:flex}.node-selector-item-title{color:var(--text-primary);white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:500;overflow:hidden}.node-selector-item-meta{color:var(--text-tertiary);text-transform:capitalize;font-size:11px}.animate-fade-in{animation:.2s ease-out fadeIn}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideUp{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes popIn{0%{opacity:0;transform:scale(.6)}to{opacity:1;transform:scale(1)}}.graph-3d-wrapper{-webkit-user-select:none;user-select:none;background:radial-gradient(circle,#0a0d16 0%,#020408 100%);width:100%;height:100%;position:relative;overflow:hidden}.node-detail-drawer{-webkit-backdrop-filter:blur(25px);border-radius:var(--radius-lg);z-index:100;width:350px;color:var(--text-primary);background:#0a0f1ebf;border:1px solid #ffffff14;flex-direction:column;display:flex;position:absolute;top:20px;bottom:20px;right:20px;overflow:hidden;box-shadow:0 16px 48px #00000080,inset 0 0 0 1px #ffffff0d}.animate-slide-in{animation:.4s cubic-bezier(.16,1,.3,1) forwards slideInRight}@keyframes slideInRight{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}.drawer-header{border-bottom:1px solid #ffffff0f;justify-content:space-between;align-items:center;padding:16px 20px;display:flex}.drawer-badge{text-transform:uppercase;letter-spacing:.05em;border-radius:4px;padding:4px 8px;font-size:10px;font-weight:700}.drawer-badge.badge-memory{color:#c084fc;background:#8b5cf626;border:1px solid #8b5cf64d}.drawer-badge.badge-thread{color:#22d3ee;background:#06b6d426;border:1px solid #06b6d44d}.drawer-badge.badge-research{color:#fbbf24;background:#f59e0b26;border:1px solid #f59e0b4d}.drawer-close-btn{color:var(--text-tertiary);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:4px;font-size:20px;transition:color .15s;display:flex}.drawer-close-btn:hover{color:#fff}.drawer-body{flex-direction:column;flex:1;gap:12px;padding:20px;display:flex;overflow-y:auto}.drawer-title{color:#fff;margin:0;font-size:18px;font-weight:700;line-height:1.35}.drawer-subtitle{color:var(--text-tertiary);margin-top:-4px;font-size:11px}.drawer-status{background:#ffffff08;border:1px solid #ffffff0a;border-radius:6px;align-items:center;gap:8px;width:fit-content;padding:6px 12px;font-size:12px;display:flex}.drawer-status .label{color:var(--text-tertiary)}.drawer-status .value{text-transform:capitalize;font-weight:600}.drawer-status .value.status-completed,.drawer-status .value.status-done,.drawer-status .value.status-active{color:#10b981}.drawer-status .value.status-in-progress{color:#f59e0b}.drawer-status .value.status-pending{color:#3b82f6}.drawer-excerpt{color:var(--text-secondary);white-space:pre-wrap;margin-top:8px;font-size:13.5px;line-height:1.6}.drawer-footer{background:#00000026;border-top:1px solid #ffffff0f;padding:16px 20px}.drawer-action-btn{border-radius:var(--radius-sm);cursor:pointer;width:100%;transition:all var(--transition-fast);border:none;justify-content:center;align-items:center;padding:10px 16px;font-size:13px;font-weight:600;display:flex}.drawer-action-btn.btn-cyan{color:#fff;background:#06b6d4}.drawer-action-btn.btn-cyan:hover{background:#0891b2;box-shadow:0 0 16px #06b6d466}.drawer-action-btn.btn-purple{color:#fff;background:#8b5cf6}.drawer-action-btn.btn-purple:hover{background:#7c3aed;box-shadow:0 0 16px #8b5cf666}@media (width<=768px){.node-detail-drawer{width:auto;height:300px;bottom:20px;left:20px}}.chat-messages:has(.graph-3d-wrapper){padding:0;overflow:hidden}.chat-header{-webkit-backdrop-filter:blur(16px);border-bottom:1px solid var(--border);z-index:40;background:#12121aeb;flex-shrink:0;justify-content:space-between;align-items:center;padding:12px 24px;display:flex;position:relative;overflow:visible}.chat-header-title{align-items:center;gap:10px;display:flex}.chat-header-title h2{color:var(--text-primary);letter-spacing:-.01em;font-size:15px;font-weight:600}.chat-header-model-selector{align-items:center;gap:8px;display:flex}.chat-header-model-selector span.selector-label{color:var(--text-tertiary);font-size:12px;font-weight:500}.model-selector-dropdown-wrapper{position:relative}.model-selector-dropdown-btn{border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text-secondary);transition:all var(--transition-fast);cursor:pointer;background:#ffffff0a;align-items:center;gap:8px;padding:6px 12px;font-size:13px;font-weight:500;display:flex}.model-selector-dropdown-btn:hover{border-color:var(--border-hover);color:var(--text-primary);background:#ffffff14}.model-selector-dropdown-btn.active{border-color:var(--accent);color:var(--text-primary);background:var(--accent-muted)}.model-selector-menu{-webkit-backdrop-filter:blur(20px);border:1px solid var(--border);border-radius:var(--radius-md);z-index:200;pointer-events:auto;background:#12121afa;flex-direction:column;gap:2px;width:240px;margin-top:6px;padding:6px;animation:.15s ease-out popoverIn;display:flex;position:absolute;top:100%;right:0;box-shadow:0 12px 32px #0000008c}@keyframes popoverIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.model-selector-item{border-radius:var(--radius-sm);cursor:pointer;transition:all var(--transition-fast);text-align:left;justify-content:space-between;align-items:center;padding:8px 12px;display:flex}.model-selector-item:hover{background:var(--bg-hover)}.model-selector-item.selected{background:var(--accent-muted)}.model-selector-item-info{flex-direction:column;gap:2px;display:flex}.model-selector-item-name{color:var(--text-primary);font-size:13px;font-weight:500}.model-selector-item.selected .model-selector-item-name{color:var(--accent)}.model-selector-item-desc{color:var(--text-tertiary);font-size:11px}.model-selector-item-badge{background:var(--bg-tertiary);color:var(--text-tertiary);text-transform:uppercase;border-radius:99px;padding:2px 6px;font-size:9px;font-weight:600}.model-selector-item.selected .model-selector-item-badge{background:var(--accent-muted);color:var(--accent)}@media (width<=768px){button,a,[role=button]{min-width:44px;min-height:44px}.chat-input-area{bottom:0;bottom:env(safe-area-inset-bottom,0);position:sticky}.app-container{padding-left:env(safe-area-inset-left,0);padding-right:env(safe-area-inset-right,0);padding-bottom:env(safe-area-inset-bottom,0)}}@media (display-mode:standalone){body{padding-top:env(safe-area-inset-top,0)}}.network-page{animation:.35s ease-out page-fade-in}@keyframes page-fade-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.stat-grid{grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:32px;display:grid}.stat-card{text-align:center;flex-direction:column;align-items:center;padding:24px 16px;display:flex}.stat-value{color:var(--text-primary);font-variant-numeric:tabular-nums;font-size:32px;font-weight:700;line-height:1.2;transition:color .3s}.stat-value.error{color:var(--error)}.stat-value.success{color:var(--success)}.stat-value.accent{color:var(--accent)}.stat-label{color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.06em;margin-top:8px;font-size:12px;font-weight:500}.network-sections{grid-template-columns:1fr 1fr;gap:24px;margin-bottom:32px;display:grid}.network-section-full{margin-bottom:32px}.section-title{color:var(--text-primary);align-items:center;gap:8px;margin-bottom:16px;font-size:15px;font-weight:600;display:flex}.blocklist-items{max-height:240px;margin:0 0 12px;padding:0;list-style:none;overflow-y:auto}.blocklist-item{border-bottom:1px solid var(--border);font-family:var(--font-mono);color:var(--text-primary);transition:background var(--transition-fast);justify-content:space-between;align-items:center;padding:8px 12px;font-size:13px;display:flex}.blocklist-item:hover{background:var(--bg-hover)}.blocklist-item:last-child{border-bottom:none}.blocklist-remove{color:var(--error);opacity:.6;transition:opacity var(--transition-fast), background var(--transition-fast);background:0 0;border-radius:4px;padding:4px 8px;font-size:12px}.blocklist-remove:hover{opacity:1;background:var(--error-muted)}.blocklist-add{gap:8px;display:flex}.blocklist-add .input{flex:1}.limits-form{flex-direction:column;gap:12px;display:flex}.limits-row{justify-content:space-between;align-items:center;gap:12px;display:flex}.limits-label{color:var(--text-secondary);flex-shrink:0;font-size:13px}.limits-input{text-align:right;width:100px}.limits-actions{justify-content:flex-end;margin-top:8px;display:flex}.audit-filters{flex-wrap:wrap;gap:12px;margin-bottom:16px;display:flex}.audit-filters .input,.audit-filters .select{flex:1;min-width:140px}.audit-status-badge{border-radius:3px;align-items:center;padding:2px 6px;font-size:11px;font-weight:600;display:inline-flex}.audit-status-badge.success{background:var(--success-muted);color:var(--success)}.audit-status-badge.error{background:var(--error-muted);color:var(--error)}.audit-status-badge.timeout{background:var(--warning-muted);color:var(--warning)}.toggle-row{align-items:center;gap:12px;margin-bottom:16px;display:flex}.toggle-switch{background:var(--bg-hover);cursor:pointer;width:44px;height:24px;transition:background var(--transition-fast);border:1px solid var(--border);border-radius:12px;flex-shrink:0;position:relative}.toggle-switch.active{background:var(--accent);border-color:var(--accent)}.toggle-switch:after{content:"";width:18px;height:18px;transition:transform var(--transition-fast);background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;box-shadow:0 1px 3px #0003}.toggle-switch.active:after{transform:translate(20px)}.toggle-label{color:var(--text-secondary);font-size:13px}.refresh-controls{align-items:center;gap:8px;display:flex}.refresh-controls .select{width:auto;padding:6px 28px 6px 10px;font-size:12px}.empty-state{text-align:center;color:var(--text-tertiary);padding:32px 16px;font-size:13px;font-style:italic}@media (width<=768px){.stat-grid,.network-sections{grid-template-columns:1fr}}.webhooks-page{animation:.35s ease-out page-fade-in}.wizard-modal{width:100%;max-width:600px}.wizard-steps{justify-content:space-between;margin-bottom:24px;display:flex;position:relative}.wizard-steps:before{content:"";z-index:0;background:#ffffff1a;height:2px;position:absolute;top:12px;left:0;right:0}.wizard-step{background:var(--bg-surface);color:var(--text-muted);z-index:1;border:2px solid #ffffff1a;border-radius:50%;justify-content:center;align-items:center;width:28px;height:28px;font-size:14px;font-weight:600;transition:all .3s;display:flex}.wizard-step.active{background:var(--primary);color:#fff;border-color:var(--primary);box-shadow:0 0 12px #6366f180}.wizard-step.completed{background:var(--success);color:#fff;border-color:var(--success)}.json-viewer{color:#a5b4fc;background:#0003;border:1px solid #ffffff0d;border-radius:8px;margin-top:8px;padding:12px;font-family:Fira Code,monospace;font-size:13px;overflow-x:auto}.json-toggle{color:var(--primary);cursor:pointer;background:0 0;border:none;margin-top:4px;padding:0;font-size:12px}.json-toggle:hover{text-decoration:underline}.webhook-stats{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:16px;margin-bottom:24px;display:grid}.stat-box{text-align:center;background:#ffffff08;border:1px solid #ffffff0d;border-radius:12px;padding:16px}.stat-box .label{color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;font-size:12px}.stat-box .value{color:var(--text-primary);font-size:24px;font-weight:700}.rotate-btn{color:var(--text-primary);cursor:pointer;background:#ffffff0d;border:1px solid #ffffff1a;border-radius:4px;padding:4px 8px;font-size:12px;transition:all .2s}.rotate-btn:hover{background:#ffffff1a}.notifications-page{animation:.35s ease-out notif-fade-in}@keyframes notif-fade-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.notif-rules-card{padding:0;overflow:hidden}.notif-rules-actions{gap:6px;display:flex}.priority-badge{text-transform:uppercase;letter-spacing:.04em;border-radius:4px;align-items:center;padding:2px 8px;font-size:11px;font-weight:600;display:inline-flex}.priority-badge.critical{background:var(--error-muted);color:var(--error)}.priority-badge.high{background:var(--warning-muted);color:var(--warning)}.priority-badge.low{background:var(--success-muted);color:var(--success)}.notif-toggle{background:var(--bg-hover);cursor:pointer;width:40px;height:22px;transition:background var(--transition-fast);border:1px solid var(--border);border-radius:11px;flex-shrink:0;position:relative}.notif-toggle.active{background:var(--accent);border-color:var(--accent)}.notif-toggle:after{content:"";width:16px;height:16px;transition:transform var(--transition-fast);background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;box-shadow:0 1px 3px #0003}.notif-toggle.active:after{transform:translate(18px)}.notif-history-header{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.notif-history-header h2{color:var(--text-primary);font-size:15px;font-weight:600}.notif-history-list{flex-direction:column;gap:10px;max-height:420px;display:flex;overflow-y:auto}.notif-history-entry{background:var(--bg-hover);border-radius:var(--radius-sm);transition:background var(--transition-fast);justify-content:space-between;align-items:flex-start;padding:14px 16px;display:flex}.notif-history-entry:hover{background:var(--bg-secondary)}.notif-entry-content{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.notif-entry-title{color:var(--text-primary);font-size:14px;font-weight:500}.notif-entry-message{color:var(--text-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:13px;overflow:hidden}.notif-entry-meta{flex-direction:column;flex-shrink:0;align-items:flex-end;gap:4px;margin-left:16px;display:flex}.notif-entry-time{color:var(--text-tertiary);white-space:nowrap;font-size:12px}.notif-status-badge{border-radius:3px;align-items:center;padding:2px 6px;font-size:11px;font-weight:600;display:inline-flex}.notif-status-badge.delivered{background:var(--success-muted);color:var(--success)}.notif-status-badge.failed{background:var(--error-muted);color:var(--error)}.notif-quiet-hours-row{align-items:center;gap:10px;display:flex}.notif-quiet-hours-label{color:var(--text-secondary);cursor:pointer;font-size:13px}.notif-empty{text-align:center;color:var(--text-tertiary);padding:32px 16px;font-size:13px;font-style:italic}@media (width<=768px){.notif-history-entry{flex-direction:column;gap:8px}.notif-entry-meta{flex-direction:row;align-items:center;margin-left:0}}
@@ -1,108 +0,0 @@
1
- import { useEffect, useMemo, useRef, useState } from 'react'
2
- import type { PluginInfo } from '../../../api/plugins'
3
- import { apiFetch, API_BASE } from '../../../api/_base'
4
- import { normalizeCard, type Card } from './contract'
5
- import { CardRunner } from './runner'
6
- import { ActivityWidget, CommandsWidget, SettingsWidget } from './widgets'
7
- import { brandStyle } from './theme'
8
- import { WorkspaceHost } from '../workspace/WorkspaceHost'
9
- import { PluginPanel } from '../PluginPanel'
10
- import './card.css'
11
- import { CardScenes } from './CardScenes'
12
-
13
- /** The provider's logo comes from the plugin through a locked-down route; <img> never runs SVG script. */
14
- function useLogo(card: Card) {
15
- const [url, setUrl] = useState('')
16
- useEffect(() => {
17
- if (!card.branding.logo) return
18
- let objectUrl = ''
19
- let disposed = false
20
- apiFetch(`${API_BASE}/api/plugins/${encodeURIComponent(card.id)}/logo`)
21
- .then(r => r.ok ? r.blob() : Promise.reject(new Error('no logo')))
22
- .then(blob => { if (!disposed) { objectUrl = URL.createObjectURL(blob); setUrl(objectUrl) } })
23
- .catch(() => { if (!disposed) setUrl('') })
24
- return () => { disposed = true; if (objectUrl) URL.revokeObjectURL(objectUrl) }
25
- }, [card.id, card.branding.logo])
26
- return url
27
- }
28
-
29
- function Header({ card, onOpenDrawer }: { card: Card; onOpenDrawer: () => void }) {
30
- const logo = useLogo(card)
31
- const { branding, distribution } = card
32
- const hasWorkspace = Boolean(card.workspace)
33
- return <header className={`card-header card-header-${branding.header}`}>
34
- <div className="card-brand">
35
- {logo ? <img className="card-logo" src={logo} alt={`${branding.provider?.name || branding.name} logo`} /> : <span className="card-logo-missing" aria-hidden="true" />}
36
- <div>
37
- <h2>{branding.name}</h2>
38
- {branding.badge && <span className="card-badge">{branding.badge}</span>}
39
- {distribution && <span className="card-badge card-badge-quiet">{distribution.tier} · {distribution.license === 'open' ? 'open source' : 'service'}</span>}
40
- </div>
41
- </div>
42
- <button type="button" className="card-button card-button-quiet" onClick={onOpenDrawer}>
43
- {hasWorkspace ? 'Open workspace' : 'Settings & usage'}
44
- </button>
45
- </header>
46
- }
47
-
48
- function Drawer({ plugin, card, onClose }: { plugin: PluginInfo; card: Card; onClose: () => void }) {
49
- const hasWorkspace = Boolean(card.workspace)
50
- const [tab, setTab] = useState<'workspace' | 'settings' | 'commands' | 'usage'>(hasWorkspace ? 'workspace' : 'settings')
51
- const dialog = useRef<HTMLElement>(null)
52
- const close = useRef(onClose)
53
- useEffect(() => { close.current = onClose }, [onClose])
54
- useEffect(() => {
55
- const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null
56
- const focusable = () => Array.from(dialog.current?.querySelectorAll<HTMLElement>('button:not(:disabled), a[href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex="0"]') || [])
57
- focusable()[0]?.focus()
58
- const key = (e: KeyboardEvent) => {
59
- if (e.key === 'Escape') { e.preventDefault(); close.current(); return }
60
- if (e.key !== 'Tab') return
61
- const targets = focusable(), first = targets[0], last = targets[targets.length - 1]
62
- if (!first) { e.preventDefault(); dialog.current?.focus(); return }
63
- if (!dialog.current?.contains(document.activeElement) || (e.shiftKey && document.activeElement === first)) { e.preventDefault(); (e.shiftKey ? last : first).focus() }
64
- else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus() }
65
- }
66
- window.addEventListener('keydown', key)
67
- return () => { window.removeEventListener('keydown', key); if (previous?.isConnected) previous.focus() }
68
- }, [])
69
- const tabs = hasWorkspace
70
- ? (['workspace', 'settings', 'commands', 'usage'] as const)
71
- : (['settings', 'commands', 'usage'] as const)
72
- return <aside ref={dialog} tabIndex={-1} className="card-drawer" role="dialog" aria-modal="true" aria-label={`${card.title} settings`}>
73
- <div className="card-drawer-head"><strong>{card.title}</strong><button type="button" className="card-link" onClick={onClose} aria-label="Close">Close</button></div>
74
- <nav className="card-tabs" role="tablist">{tabs.map(t =>
75
- <button key={t} role="tab" type="button" aria-selected={tab === t} onClick={() => setTab(t)}>
76
- {t === 'workspace' ? 'Workspace' : t === 'settings' ? 'Settings' : t === 'commands' ? 'Commands' : 'Session activity'}
77
- </button>)}</nav>
78
- <div className="card-drawer-body">
79
- {tab === 'workspace' && <WorkspaceHost plugin={plugin} card={card} />}
80
- {tab === 'settings' && <SettingsWidget plugin={plugin} />}
81
- {tab === 'commands' && <CommandsWidget />}
82
- {tab === 'usage' && <ActivityWidget />}
83
- </div>
84
- </aside>
85
- }
86
-
87
- /**
88
- * The host gives a plugin its frame, its brand tokens and its command
89
- * plumbing. The layout is the plugin's own face: CardEngine never arranges
90
- * a plugin's interface and there is no shared template to fall back to.
91
- */
92
- export function CardEngine({ plugin }: { plugin: PluginInfo }) {
93
- const card = useMemo(() => normalizeCard(plugin), [plugin])
94
- const [drawer, setDrawer] = useState(false)
95
- return <CardRunner card={card}>
96
- <section className={`card card-bespoke${card.face ? ' card-owned' : ''}`} style={brandStyle(card.branding)} data-density={card.branding.density} aria-label={`${card.title} card`}>
97
- <Header card={card} onOpenDrawer={() => setDrawer(true)} />
98
- {card.description && <p className="card-muted card-description">{card.description}</p>}
99
- <CardScenes plugin={plugin}>
100
- {card.face
101
- ? <PluginPanel plugin={plugin} elementId={card.face.id} />
102
- : <p className="card-error" role="alert">{plugin.name} has not supplied its card face.</p>}
103
- </CardScenes>
104
- {card.branding.provider && <footer className="card-footer">{card.branding.provider.url ? <a href={card.branding.provider.url} target="_blank" rel="noreferrer noopener">{card.branding.provider.name}</a> : card.branding.provider.name} is a trademark of its owner. This plugin connects to the service with your own account.</footer>}
105
- {drawer && <Drawer plugin={plugin} card={card} onClose={() => setDrawer(false)} />}
106
- </section>
107
- </CardRunner>
108
- }
@@ -1,103 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react'
2
- import type { ReactNode } from 'react'
3
- import type { PluginInfo } from '../../../api/plugins'
4
- import { API_BASE, apiFetch } from '../../../api/_base'
5
- import { useRunner } from './runner-context'
6
- import { ExecutionPicker } from './ExecutionPicker'
7
- import { sanitizePluginScreens } from '../plugin-frame'
8
- import type { PluginScreen } from '../plugin-frame'
9
-
10
- /**
11
- * The host gives a plugin a place to put its screens and a way to move between
12
- * them. Which screens exist, what they are called and what is on them is the
13
- * plugin's design: the face declares them and reports the active one. The host
14
- * never derives a screen from the command list.
15
- */
16
- export type { PluginScreen } from '../plugin-frame'
17
-
18
- export function CardScenes({ plugin, children }: { plugin: PluginInfo; children: ReactNode }) {
19
- const { card, busy } = useRunner()
20
- const [enabled, setEnabled] = useState(plugin.activation?.enabled !== false)
21
- useEffect(() => { setEnabled(plugin.activation?.enabled !== false) }, [plugin.id, plugin.activation?.enabled])
22
- const [changing, setChanging] = useState(false)
23
- const [error, setError] = useState('')
24
- const [ownerBusy, setOwnerBusy] = useState<Record<string, string>>({})
25
- const [menu, setMenu] = useState(false)
26
- const [screens, setScreens] = useState<PluginScreen[]>([])
27
- const [activeScreen, setActiveScreen] = useState('')
28
- const screensRef = useRef<PluginScreen[]>([])
29
- useEffect(() => {
30
- screensRef.current = []
31
- setScreens([]); setActiveScreen(''); setMenu(false)
32
- const activity = (event: Event) => {
33
- const detail = (event as CustomEvent).detail
34
- if (detail?.pluginId !== plugin.id) return
35
- setOwnerBusy(previous => { const next = { ...previous }; if (detail.running) next[detail.requestId] = detail.command; else delete next[detail.requestId]; return next })
36
- }
37
- const contextMenu = (event: Event) => { if ((event as CustomEvent).detail?.pluginId === plugin.id) setMenu(true) }
38
- const declared = (event: Event) => {
39
- const detail = (event as CustomEvent).detail
40
- if (detail?.pluginId !== plugin.id) return
41
- const next = sanitizePluginScreens(detail.screens)
42
- screensRef.current = next
43
- setScreens(next)
44
- setActiveScreen(previous => next.some(screen => screen.id === previous) ? previous : next[0]?.id || '')
45
- }
46
- const changed = (event: Event) => {
47
- const detail = (event as CustomEvent).detail
48
- if (detail?.pluginId === plugin.id && screensRef.current.some(screen => screen.id === detail.screen)) setActiveScreen(detail.screen)
49
- }
50
- window.addEventListener('plugin-command-activity', activity)
51
- window.addEventListener('plugin-context-menu', contextMenu)
52
- window.addEventListener('plugin-screens', declared)
53
- window.addEventListener('plugin-screen-changed', changed)
54
- return () => {
55
- window.removeEventListener('plugin-command-activity', activity); window.removeEventListener('plugin-context-menu', contextMenu)
56
- window.removeEventListener('plugin-screens', declared); window.removeEventListener('plugin-screen-changed', changed)
57
- }
58
- }, [plugin.id])
59
- const running = [...busy, ...Object.values(ownerBusy)]
60
- // The face's own screens collapse into a single mount point: the plugin keeps
61
- // every screen inside its own component and reports which one is active.
62
- /** Ask the face to show a screen it declared; it may ignore a name it does not own. */
63
- const openScreen = (name: string) => {
64
- if (!screens.some(screen => screen.id === name)) return
65
- setMenu(false)
66
- window.dispatchEvent(new CustomEvent('plugin-show-screen', { detail: { pluginId: plugin.id, screen: name } }))
67
- }
68
- const move = (delta: number) => {
69
- if (screens.length < 2) return
70
- const current = Math.max(0, screens.findIndex(screen => screen.id === activeScreen))
71
- openScreen(screens[(current + delta + screens.length) % screens.length].id)
72
- }
73
- const toggle = async () => {
74
- setChanging(true); setError('')
75
- try {
76
- const response = await apiFetch(`${API_BASE}/api/plugins/${encodeURIComponent(plugin.id)}/activation`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: !enabled }) })
77
- const result = await response.json()
78
- if (result.activation) setEnabled(result.activation.enabled)
79
- if (!response.ok || !result.success) throw new Error(result.compilation?.error || result.error || 'Activation could not be changed')
80
- } catch (err) { setError(err instanceof Error ? err.message : 'Activation could not be changed') }
81
- finally { setChanging(false) }
82
- }
83
- return <div className="card-scenes" data-state={enabled ? 'active' : 'inactive'} aria-busy={running.length > 0 || changing}
84
- onContextMenu={event => { event.preventDefault(); setMenu(true) }}
85
- onKeyDown={event => { if (event.key === 'Escape') setMenu(false); if (event.key === 'F10' && event.shiftKey) { event.preventDefault(); setMenu(true) } }}>
86
- <div className="card-row card-scene-controls">
87
- <span role="status" className="card-pill">{running.length ? `Running ${running.join(', ')}` : enabled ? 'Active' : 'Inactive'}</span>
88
- <button type="button" className="card-button" role="switch" aria-checked={enabled} aria-label={`${card.title} on or off`} onClick={() => void toggle()} disabled={changing || running.length > 0}>{changing ? 'Updating…' : enabled ? 'Turn off' : 'Turn on'}</button>
89
- <button type="button" className="card-link" aria-label="Previous screen" disabled={screens.length < 2} onClick={() => move(-1)}>←</button>
90
- <button type="button" className="card-link" aria-label="Next screen" disabled={screens.length < 2} onClick={() => move(1)}>→</button>
91
- {screens.length > 0 && <span className="card-pill" aria-label="Current screen">{screens.find(screen => screen.id === activeScreen)?.label}</span>}
92
- {screens.length > 0 && <button type="button" className="card-link" aria-haspopup="menu" aria-expanded={menu} onClick={() => setMenu(!menu)}>Screens</button>}
93
- </div>
94
- {menu && screens.length > 0 && <div role="menu" aria-label={`${card.title} declared screens`} className="card-context-menu">
95
- {screens.map(screen => <button role="menuitem" type="button" className="card-link" key={screen.id} aria-current={screen.id === activeScreen ? 'page' : undefined} onClick={() => openScreen(screen.id)}>{screen.label}</button>)}
96
- </div>}
97
- {error && <p role="alert" className="card-error">{error}</p>}
98
- <ExecutionPicker />
99
- <div className="card-scene-track" tabIndex={0} aria-label={`${card.title} interface`}>
100
- <section className="card-scene" aria-label="Interface">{children}</section>
101
- </div>
102
- </div>
103
- }
@@ -1,64 +0,0 @@
1
- import { useRef, useState } from 'react'
2
- import { apiFetch, API_BASE } from '../../../api/_base'
3
- import { useRunner } from './runner-context'
4
- import type { PluginExecution } from '../../../api/plugins'
5
-
6
- type Agent = { id: string; name: string; installed: boolean; authenticated: boolean | 'unknown'; version?: string; versionVerified?: boolean }
7
- type Node = { id: string; name: string; online: boolean; self: boolean; accessConfigured: boolean }
8
- const DEFAULT: PluginExecution = { agent: 'direct', node: 'local', permissions: { read: true, write: false, send: false, destructive: false } }
9
- const labels = { read: 'Read commands', write: 'Write commands', send: 'Send commands', destructive: 'Delete commands' }
10
- let targetCache: { at: number; data: { nodes: Node[] } } | undefined
11
-
12
- export function ExecutionPicker() {
13
- const { execution, setExecution, busy } = useRunner()
14
- const selected = execution || DEFAULT
15
- const [open, setOpen] = useState(false)
16
- const [nodes, setNodes] = useState<Node[]>([])
17
- const [agents, setAgents] = useState<Agent[]>([])
18
- const [loading, setLoading] = useState(false)
19
- const [error, setError] = useState('')
20
- const inspection = useRef(0)
21
- const loadAgents = async (node: string) => {
22
- const revision = ++inspection.current
23
- setLoading(true); setAgents([]); setError('')
24
- try {
25
- const response = await apiFetch(`${API_BASE}/api/plugins/execution-targets?node=${encodeURIComponent(node)}`)
26
- const value = await response.json()
27
- if (!response.ok || !value.success) throw new Error(value.error || 'Could not inspect this node')
28
- if (revision === inspection.current) setAgents(value.agents || [])
29
- } catch (err) { if (revision === inspection.current) setError(err instanceof Error ? err.message : 'Could not inspect this node') }
30
- finally { if (revision === inspection.current) setLoading(false) }
31
- }
32
- const show = async () => {
33
- setOpen(!open)
34
- if (open) return
35
- setLoading(true); setError('')
36
- try {
37
- if (!targetCache || Date.now() - targetCache.at > 60000) {
38
- const response = await apiFetch(`${API_BASE}/api/plugins/execution-targets`)
39
- const data = await response.json()
40
- if (!response.ok || !data.success) throw new Error(data.error || 'CLI agents is unavailable')
41
- targetCache = { at: Date.now(), data }
42
- }
43
- setNodes(targetCache.data.nodes || [])
44
- await loadAgents(selected.node)
45
- } catch (err) { setError(err instanceof Error ? err.message : 'CLI agents is unavailable'); setLoading(false) }
46
- }
47
- return <div className="card-execution">
48
- <button type="button" className="card-link" onClick={() => void show()} aria-expanded={open}>Agent, node & permissions</button>
49
- {open && <fieldset className="card-form" disabled={loading || busy.size > 0}>
50
- <legend>Command execution</legend>
51
- <label>Mesh node<select value={selected.node} onChange={event => { const node = event.target.value; setExecution({ ...selected, node, agent: 'direct', cwd: undefined }); void loadAgents(node) }}>
52
- <option value="local">This node</option>{nodes.filter(node => !node.self).map(node => <option key={node.id} value={node.id} disabled={!node.online || !node.accessConfigured}>{node.name}{!node.online ? ' · offline' : !node.accessConfigured ? ' · access needed' : ''}</option>)}
53
- </select></label>
54
- <label>CLI agent<select value={selected.agent} onChange={event => setExecution({ ...selected, agent: event.target.value, allowUnknownAuth: event.target.value !== 'direct' })}>
55
- <option value="direct">Direct CLI</option>{agents.map(agent => <option key={agent.id} value={agent.id} disabled={!agent.installed || !agent.versionVerified || agent.authenticated === false}>{agent.name}{!agent.installed ? ' · not installed' : agent.authenticated === false ? ' · sign in needed' : agent.authenticated === 'unknown' ? ' · account unchecked' : ''}</option>)}
56
- </select></label>
57
- {selected.node !== 'local' && <label>Workspace on selected node<input value={selected.cwd || ''} placeholder="/absolute/workspace" onChange={event => setExecution({ ...selected, cwd: event.target.value })} /></label>}
58
- <div className="card-permissions">{(Object.keys(labels) as Array<keyof typeof labels>).map(key => <label key={key}><input type="checkbox" role="switch" checked={selected.permissions[key]} onChange={event => setExecution({ ...selected, permissions: { ...selected.permissions, [key]: event.target.checked } })} />{labels[key]}</label>)}</div>
59
- <button type="button" className="card-link" onClick={() => void loadAgents(selected.node)}>Refresh installed agents</button>
60
- </fieldset>}
61
- {loading && <p role="status" className="card-muted">Inspecting installed agents…</p>}
62
- {error && <p role="alert" className="card-error">{error}</p>}
63
- </div>
64
- }
@@ -1,28 +0,0 @@
1
- import type { CardArg, CardCommand } from './contract'
2
-
3
- export type ArgValues = Record<string, string | number | boolean | undefined>
4
-
5
- /** Secret arguments are never put on the command line; they are managed in the drawer's Credentials. */
6
- export const visibleArgs = (command: CardCommand) => command.args.filter(arg => !arg.secret)
7
-
8
- export function missingRequired(command: CardCommand, values: ArgValues): string[] {
9
- return visibleArgs(command).filter(arg => arg.required && (values[arg.name] === undefined || values[arg.name] === '')).map(arg => arg.label)
10
- }
11
-
12
- /** Positionals first, in declared order, then flags. Returns argv for `--json` runs. */
13
- export function buildArgs(command: CardCommand, values: ArgValues, extra: string[] = []): string[] {
14
- const argv: string[] = []
15
- const flagged: string[] = []
16
- for (const arg of visibleArgs(command)) {
17
- const value = values[arg.name]
18
- if (value === undefined || value === '' || value === false) continue
19
- if (arg.positional) { argv.push(String(value)); continue }
20
- if (!arg.flag) continue
21
- if (arg.type === 'boolean') flagged.push(arg.flag)
22
- else flagged.push(arg.flag, String(value))
23
- }
24
- return [...argv, ...flagged, ...extra, ...(command.output === 'json' ? ['--json'] : [])]
25
- }
26
-
27
- export const needsConfirmation = (command: CardCommand) => command.effect === 'destructive' || command.effect === 'send'
28
- export const defaultValue = (arg: CardArg) => arg.type === 'boolean' ? false : arg.type === 'choice' && arg.options?.length && arg.required ? arg.options[0] : ''
@@ -1,32 +0,0 @@
1
- .card{--card-surface:var(--bg-secondary,#fff);--card-text:var(--text-primary,#111);--card-muted:var(--text-secondary,#555);--card-line:var(--border,#d4d4d8);position:relative;background:var(--card-surface);color:var(--card-text);border:1px solid var(--card-line);border-radius:var(--card-radius);padding:var(--card-gap);display:grid;gap:var(--card-gap);overflow:hidden}
2
- .card h2,.card h3{margin:0}.card h2{font-size:1.15rem}.card h3{font-size:.8rem;text-transform:uppercase;letter-spacing:.06em;color:var(--card-muted);margin-bottom:6px}
3
- .card-header{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}
4
- .card-brand{display:flex;align-items:center;gap:12px}.card-logo{height:36px;max-width:140px;object-fit:contain}.card-logo-missing{width:36px;height:36px;border-radius:var(--card-radius);background:var(--card-color)}
5
- .card-header-band{background:var(--card-color);color:var(--card-on-color);margin:calc(var(--card-gap)*-1) calc(var(--card-gap)*-1) 0;padding:var(--card-gap)}
6
- .card-header-ledger-rule{border-bottom:3px double var(--card-line);padding-bottom:var(--card-gap)}
7
- .card-header-tab-strip{border-bottom:2px solid var(--card-accent);padding-bottom:6px}
8
- .card-header-badge-left{flex-direction:row-reverse;justify-content:flex-end}
9
- .card-badge{display:inline-block;margin:4px 6px 0 0;padding:1px 8px;border-radius:var(--card-radius);background:var(--card-accent);color:var(--card-on-accent);font-size:.7rem;font-weight:600}
10
- .card-badge-quiet{background:transparent;color:var(--card-muted);border:1px solid var(--card-line)}
11
- .card-description{margin:0}.card-muted{color:var(--card-muted);font-size:.85rem;margin:2px 0}
12
- /* The face supplies its own layout; the host only gives it a surface. */
13
- .card-bespoke .card-face{display:block;min-width:0}
14
- .card-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.card-kv{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:8px;margin:8px 0 0}.card-kv dt{font-size:.7rem;color:var(--card-muted);text-transform:uppercase}.card-kv dd{margin:0;font-weight:600;overflow-wrap:anywhere}
15
- .card-pill{padding:1px 8px;border-radius:999px;font-size:.75rem;font-weight:600;border:1px solid currentColor}.card-pill-ok{color:#047857}.card-pill-warn{color:#b45309}.card-pill-error{color:#b91c1c}.card-pill-neutral{color:var(--card-muted)}
16
- .card-button{background:var(--card-color);color:var(--card-on-color);border:0;border-radius:var(--card-radius);padding:6px 12px;font:inherit;cursor:pointer}.card-button:disabled{opacity:.5;cursor:not-allowed}.card-button-quiet{background:transparent;color:var(--card-text);border:1px solid var(--card-line)}
17
- .card-link{background:none;border:0;color:var(--card-color);cursor:pointer;font:inherit;text-decoration:underline;padding:0}
18
- .card :focus-visible{outline:2px solid var(--card-accent);outline-offset:2px}
19
- .card-code,.card-pre{font-family:ui-monospace,monospace;font-size:.78rem;background:rgba(127,127,127,.12);border-radius:var(--card-radius);padding:4px 8px;overflow-x:auto}.card-pre{white-space:pre-wrap;max-height:260px;overflow:auto}
20
- .card-commands,.card-list{list-style:none;margin:0;padding:0;display:grid;gap:10px}.card-commands li{border-top:1px solid var(--card-line);padding-top:8px}
21
- .card-form{display:grid;gap:8px;margin-top:8px}.card-form label{display:grid;gap:2px;font-size:.8rem}.card-form input,.card-form select{font:inherit;padding:4px 6px;border:1px solid var(--card-line);border-radius:var(--card-radius);background:transparent;color:inherit}
22
- .card-scroll{overflow-x:auto}.card-table{width:100%;border-collapse:collapse;font-size:.85rem}.card-table th{text-align:left;font-size:.7rem;text-transform:uppercase;color:var(--card-muted);border-bottom:2px solid var(--card-line);padding:4px 8px}.card-table td{padding:5px 8px;border-bottom:1px solid var(--card-line)}
23
- .card-gauge{height:10px;background:rgba(127,127,127,.2);border-radius:var(--card-radius);overflow:hidden}.card-gauge span{display:block;height:100%;background:var(--card-accent)}
24
- .card-error{color:#b91c1c;margin:6px 0}.card-footer{font-size:.72rem;color:var(--card-muted)}.card-footer a{color:inherit}
25
- .card-drawer{position:absolute;top:0;right:0;bottom:0;width:min(420px,100%);background:var(--card-surface);border-left:1px solid var(--card-line);box-shadow:-8px 0 24px rgba(0,0,0,.15);display:flex;flex-direction:column;z-index:2}
26
- .card-drawer-head{display:flex;justify-content:space-between;padding:var(--card-gap);border-bottom:1px solid var(--card-line)}.card-drawer-body{padding:var(--card-gap);overflow:auto;flex:1}
27
- .card-tabs{display:flex;border-bottom:1px solid var(--card-line)}.card-tabs button{flex:1;background:none;border:0;border-bottom:3px solid transparent;padding:8px;font:inherit;color:var(--card-muted);cursor:pointer}.card-tabs button[aria-selected=true]{color:var(--card-text);border-bottom-color:var(--card-accent)}
28
- @media (prefers-reduced-motion:no-preference){.card-drawer{animation:card-slide .18s ease-out}@keyframes card-slide{from{transform:translateX(24px);opacity:0}}}
29
- .card-scene-track{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:thin;gap:var(--card-gap);overscroll-behavior-x:contain}.card-scene{flex:0 0 100%;min-width:0;scroll-snap-align:start;padding:4px}.card-scene-controls{margin-bottom:var(--card-gap)}.card-scenes[data-state=inactive] .card-scene-track{opacity:.65}.card-scenes[aria-busy=true] .card-pill{animation:card-running 1.1s ease-in-out infinite}@keyframes card-running{50%{box-shadow:0 0 0 3px color-mix(in srgb,var(--card-color) 28%,transparent)}}@media(prefers-reduced-motion:reduce){.card-scenes[aria-busy=true] .card-pill{animation:none}.card-scene-track{scroll-behavior:auto}}
30
- .card-scene-track{position:relative}.card-context-menu{display:grid;gap:8px;padding:12px;border:1px solid var(--card-line);border-radius:var(--card-radius);background:var(--card-surface);box-shadow:0 8px 24px #0002}.card-context-menu button{text-align:left}
31
- .card.card-owned{border:1px solid var(--card-line);background:var(--card-surface);border-radius:var(--card-radius);padding:var(--card-gap);box-shadow:0 1px 3px rgba(0,0,0,0.06)}.card-owned .card-link{color:var(--text-primary,#111)}.card-permissions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.card-permissions label{display:flex;align-items:center;gap:8px}.card-execution{margin-bottom:var(--card-gap)}
32
- .card-scenes,.card-scene-track{min-width:0;max-width:100%}.card-scene{box-sizing:border-box}.card-execution fieldset{min-inline-size:0;box-sizing:border-box;width:100%}.card-execution select,.card-execution input:not([type=checkbox]){min-width:0;width:100%;box-sizing:border-box}.card-permissions label{white-space:normal}
@@ -1,13 +0,0 @@
1
- import type { Card, CardCommand } from './contract'
2
-
3
- /** The exact line an agent runs; shown on the card so config and CLI never drift apart. */
4
- export function cliLine(card: Card, command: CardCommand, cliName: string): string {
5
- const parts = [`total-recall`, cliName || card.id, command.name]
6
- for (const arg of command.args) {
7
- if (arg.secret) continue // secrets go on stdin, never in the line an agent logs
8
- const token = arg.positional ? `<${arg.name}>` : `${arg.flag}${arg.type === 'boolean' ? '' : ` <${arg.name}>`}`
9
- parts.push(arg.required ? token : `[${token}]`)
10
- }
11
- if (command.output === 'json') parts.push('--json')
12
- return parts.join(' ')
13
- }
@@ -1,91 +0,0 @@
1
- import type { PluginInfo } from '../../../api/plugins'
2
-
3
- export type ArgType = 'string' | 'number' | 'boolean' | 'choice'
4
- export type Effect = 'read' | 'write' | 'destructive' | 'send'
5
-
6
- export interface CardArg { name: string; flag?: string; positional?: boolean; label: string; type: ArgType; options?: Array<string | number>; required?: boolean; secret?: boolean }
7
- export interface CardCommand { name: string; description?: string; args: CardArg[]; effect: Effect; output: 'json' | 'text'; timeoutMs?: number }
8
- export interface CardBranding {
9
- name: string; color: string; accent: string; font: string; density: 'compact' | 'comfortable' | 'spacious'
10
- radius: 'none' | 'sm' | 'md' | 'lg' | 'pill'; header: 'plain' | 'ledger-rule' | 'band' | 'tab-strip' | 'badge-left'
11
- badge?: string; logo?: string; provider?: { name: string; url?: string }
12
- }
13
- export interface CardWorkspace {
14
- component?: string;
15
- tabs?: string[];
16
- permissions?: string[];
17
- }
18
- /** The plugin's own card face. Layout belongs to the plugin, never to a shared template. */
19
- export interface CardFace { id: string; tag: string; module: string; description?: string }
20
- export interface Card {
21
- id: string; title: string; description: string; face?: CardFace
22
- commands: Map<string, CardCommand>; branding: CardBranding; status?: { command: string; refreshSeconds: number }
23
- distribution?: { tier: string; license: string }
24
- workspace?: CardWorkspace
25
- }
26
-
27
- const FONTS = ['system', 'inter', 'ibm-plex', 'space-grotesk', 'jetbrains-mono', 'dm-sans', 'outfit', 'fraunces', 'newsreader', 'chivo-mono']
28
- const HEX = /^#[0-9a-fA-F]{6}$/
29
- /** Manifest JSON is untyped input; every field is checked before it is used. */
30
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
- export type Loose = any
32
- const obj = (v: unknown): Record<string, Loose> => (v && typeof v === 'object' && !Array.isArray(v) ? v as Record<string, Loose> : {})
33
-
34
- function normalizeArg(raw: unknown): CardArg | null {
35
- const a = obj(raw)
36
- if (typeof a.name !== 'string' || typeof a.label !== 'string') return null
37
- const type = ['string', 'number', 'boolean', 'choice'].includes(a.type) ? a.type : 'string'
38
- return { name: a.name, flag: typeof a.flag === 'string' ? a.flag : undefined, positional: a.positional === true, label: a.label, type, options: Array.isArray(a.options) ? a.options : undefined, required: a.required === true, secret: a.secret === true }
39
- }
40
-
41
- export function normalizeCard(plugin: PluginInfo): Card {
42
- const manifest = obj(plugin.manifest)
43
- const commands = new Map<string, CardCommand>()
44
- for (const sub of plugin.cli?.subcommands || []) {
45
- const declared = obj(manifest.cli?.subcommands?.find((item: Loose) => item?.name === sub.name))
46
- commands.set(sub.name, {
47
- name: sub.name, description: sub.description,
48
- args: (Array.isArray(declared.args) ? declared.args : []).map(normalizeArg).filter(Boolean) as CardArg[],
49
- // A grouped command such as `config set` may write. Only explicit read declarations are safe.
50
- effect: ['read', 'write', 'destructive', 'send'].includes(declared.effect) ? declared.effect : 'write',
51
- output: declared.output === 'text' ? 'text' : 'json',
52
- timeoutMs: Number.isFinite(declared.timeout_ms) ? declared.timeout_ms : undefined,
53
- })
54
- }
55
- const b = obj(manifest.branding)
56
- const branding: CardBranding = {
57
- name: typeof b.name === 'string' ? b.name : plugin.name,
58
- color: HEX.test(b.color) ? b.color : '#475569',
59
- accent: HEX.test(b.accent) ? b.accent : HEX.test(b.color) ? b.color : '#0ea5e9',
60
- font: FONTS.includes(b.font) ? b.font : 'system',
61
- density: ['compact', 'comfortable', 'spacious'].includes(b.density) ? b.density : 'comfortable',
62
- radius: ['none', 'sm', 'md', 'lg', 'pill'].includes(b.radius) ? b.radius : 'md',
63
- header: ['plain', 'ledger-rule', 'band', 'tab-strip', 'badge-left'].includes(b.header) ? b.header : 'plain',
64
- badge: typeof b.badge === 'string' ? b.badge : undefined,
65
- logo: typeof b.logo === 'string' ? b.logo : undefined,
66
- provider: obj(b.provider).name ? { name: String(b.provider.name), url: typeof b.provider.url === 'string' ? b.provider.url : undefined } : undefined,
67
- }
68
- const status = obj(manifest.status)
69
- const statusCommand = typeof status.command === 'string' && commands.has(status.command) ? status.command : undefined
70
- // The face is the plugin's own component; there is no shared layout to fall back to.
71
- // Only `kind: 'card'` is the face. A plugin may declare any number of
72
- // `kind: 'panel'` screens, and one declared before the card is still a
73
- // secondary screen, never the face.
74
- const elements: Loose[] = Array.isArray(manifest.ui?.elements) ? manifest.ui.elements : (Array.isArray(plugin.ui?.elements) ? plugin.ui.elements : [])
75
- const declared = elements.find((element: Loose) => element && typeof element === 'object' && element.kind === 'card')
76
- const face: CardFace | undefined = declared && typeof declared.id === 'string' && typeof declared.tag === 'string' && typeof declared.module === 'string'
77
- ? { id: declared.id, tag: declared.tag, module: declared.module, description: typeof declared.description === 'string' ? declared.description : undefined }
78
- : undefined
79
- const ws = obj(manifest.workspace)
80
- const workspace: CardWorkspace | undefined = ws && (ws.component || Array.isArray(ws.tabs)) ? {
81
- component: typeof ws.component === 'string' ? ws.component : undefined,
82
- tabs: Array.isArray(ws.tabs) ? ws.tabs.filter((t: unknown) => typeof t === 'string') : undefined,
83
- permissions: Array.isArray(ws.permissions) ? ws.permissions.filter((p: unknown) => typeof p === 'string') : undefined,
84
- } : undefined
85
- return {
86
- id: plugin.id, title: branding.name, description: plugin.description, face, commands, branding,
87
- status: statusCommand ? { command: statusCommand, refreshSeconds: Number.isInteger(status.refresh_seconds) ? Math.min(86400, Math.max(5, status.refresh_seconds)) : 30 } : undefined,
88
- distribution: obj(manifest.distribution).tier ? { tier: manifest.distribution.tier, license: manifest.distribution.license } : undefined,
89
- workspace,
90
- }
91
- }
@@ -1,49 +0,0 @@
1
- import type { Loose } from './contract'
2
-
3
- /**
4
- * How a scalar is rendered. This is presentation owned by the card face that
5
- * asks for it — not a template primitive. The layout vocabulary (`CardRegion`,
6
- * `CardColumn`, `CellFormat`) that once shared these names is deleted.
7
- */
8
- export type CellFormat = 'text' | 'number' | 'money' | 'percent' | 'bytes' | 'date' | 'datetime'
9
-
10
- export type Tone = 'ok' | 'warn' | 'error' | 'neutral'
11
- const GOOD = /^(ok|healthy|active|connected|ready|running|verified|delivered|sent|passed|pass|up|online|true|complete|completed)$/i
12
- const BAD = /^(error|failed|fail|down|offline|rejected|bounced|blocked|false|expired|denied)$/i
13
- const WARN = /^(warn|warning|pending|queued|degraded|unknown|stale|paused|partial|throttled)$/i
14
- export const toneOf = (value: unknown): Tone => { const s = String(value); return GOOD.test(s) ? 'ok' : BAD.test(s) ? 'error' : WARN.test(s) ? 'warn' : 'neutral' }
15
-
16
- export function formatCell(value: unknown, format: CellFormat = 'text'): string {
17
- if (value === null || value === undefined || value === '') return '—'
18
- if (typeof value === 'object') return JSON.stringify(value)
19
- const n = Number(value)
20
- switch (format) {
21
- case 'number': return Number.isFinite(n) ? n.toLocaleString() : String(value)
22
- case 'money': return Number.isFinite(n) ? n.toLocaleString(undefined, { style: 'currency', currency: 'USD' }) : String(value)
23
- case 'percent': return Number.isFinite(n) ? `${Math.round((n <= 1 ? n * 100 : n) * 10) / 10}%` : String(value)
24
- case 'bytes': {
25
- if (!Number.isFinite(n)) return String(value)
26
- const units = ['B', 'KB', 'MB', 'GB', 'TB']; let v = n, i = 0
27
- while (v >= 1024 && i < units.length - 1) { v /= 1024; i++ }
28
- return `${i === 0 ? v : v.toFixed(1)} ${units[i]}`
29
- }
30
- case 'date': case 'datetime': {
31
- const d = new Date(String(value))
32
- if (Number.isNaN(d.getTime())) return String(value)
33
- return format === 'date' ? d.toLocaleDateString() : d.toLocaleString()
34
- }
35
- default: return String(value)
36
- }
37
- }
38
-
39
- /** Rows may arrive as a bare array or wrapped under a common key. */
40
- export function rowsOf(data: Loose): Loose[] {
41
- if (Array.isArray(data)) return data
42
- for (const key of ['rows', 'items', 'results', 'data', 'entries', 'records']) if (Array.isArray(data?.[key])) return data[key]
43
- return []
44
- }
45
-
46
- export function scalarsOf(data: Loose): Array<[string, string]> {
47
- if (!data || typeof data !== 'object' || Array.isArray(data)) return []
48
- return Object.entries(data).filter(([, v]) => v === null || ['string', 'number', 'boolean'].includes(typeof v)).map(([k, v]) => [k, v === null ? '—' : String(v)])
49
- }
@@ -1,11 +0,0 @@
1
- import { createContext, useContext } from 'react'
2
- import type { ArgValues } from './args'
3
- import type { Card, Loose } from './contract'
4
- import type { PluginExecution } from '../../../api/plugins'
5
-
6
- export interface RunResult { ok: boolean; data?: Loose; text: string; error?: string; ms: number }
7
- export interface ActivityEntry { at: string; command: string; effect: string; ok: boolean; ms: number }
8
- export interface Runner { card: Card; run: (command: string, values?: ArgValues, extra?: string[], stdin?: string) => Promise<RunResult>; activity: ActivityEntry[]; busy: Set<string>; execution: PluginExecution | null; setExecution: (value: PluginExecution | null) => void }
9
-
10
- export const RunnerContext = createContext<Runner | null>(null)
11
- export const useRunner = () => { const value = useContext(RunnerContext); if (!value) throw new Error('Card widgets must render inside a CardRunner'); return value }