total-recall-brain 3.45.0 → 3.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/total-recall.mjs +4 -2
- package/frontend/dist/assets/index-BRYE1jmv.css +1 -0
- package/frontend/dist/assets/index-D1goIPjC.js +5598 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +5 -4
- package/frontend/src/App.tsx +183 -236
- package/frontend/src/api/auth-inventory.ts +62 -0
- package/frontend/src/api/memory.ts +13 -8
- package/frontend/src/api/plugin-credentials.ts +23 -0
- package/frontend/src/api/plugins.ts +37 -53
- package/frontend/src/api/workspace-drawers.ts +97 -0
- package/frontend/src/components/BrainSelector.tsx +9 -3
- package/frontend/src/components/plugins/PluginPanel.tsx +83 -42
- package/frontend/src/components/plugins/PluginSettings.tsx +10 -20
- package/frontend/src/components/plugins/PluginWorkspace.tsx +367 -0
- package/frontend/src/components/plugins/ScopedPluginCommands.tsx +142 -0
- package/frontend/src/components/plugins/plugin-frame.ts +44 -33
- package/frontend/src/components/plugins/scoped-plugin-operations.ts +28 -0
- package/frontend/src/components/workspace/DrawerChat.tsx +110 -0
- package/frontend/src/components/workspace/DrawerLayout.tsx +196 -0
- package/frontend/src/components/workspace/DrawerMarkup.tsx +49 -0
- package/frontend/src/components/workspace/PluginChunkLibrary.tsx +92 -0
- package/frontend/src/components/workspace/PluginHome.tsx +76 -0
- package/frontend/src/components/workspace/WorkspaceDrawers.tsx +96 -0
- package/frontend/src/components/workspace/WorkspacePage.tsx +95 -0
- package/frontend/src/components/workspace/usePluginDesign.ts +38 -0
- package/frontend/src/index.css +409 -136
- package/frontend/src/pages/HelpPage.tsx +15 -46
- package/frontend/src/pages/MemoryPage.tsx +19 -36
- package/frontend/src/pages/SettingsPage.tsx +1 -1
- package/metadata.plugin.schema.json +53 -227
- package/package.json +1 -1
- package/scaffold/.agent/skills/plugin/SKILL.md +13 -11
- package/scaffold/.agent/skills/plugin/evals/README.md +14 -21
- package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +15 -18
- package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +19 -24
- package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +12 -20
- package/scaffold/.agent/skills/plugin/evals/cases/04-home-collects-credential.mjs +35 -0
- package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +8 -28
- package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +85 -47
- package/scaffold/.agent/skills/plugin/evals/run.mjs +17 -18
- package/scaffold/.agent/skills/plugin/prompts/05-credentials.md +4 -4
- package/scaffold/.agent/skills/plugin/prompts/07-scaffold.md +2 -2
- package/scaffold/.agent/skills/plugin/prompts/08-verify.md +4 -4
- package/scaffold/.agent/skills/plugin/references/anti-patterns.md +2 -2
- package/scaffold/.agent/skills/plugin/references/generative-home.md +38 -0
- package/scaffold/.agent/skills/plugin/references/scaffold.md +5 -5
- package/scaffold/.agent/skills/plugin/references/surfaces.md +13 -4
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +75 -153
- package/scaffold/.agent/skills/plugin/subagents/plugin-reviewer.md +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/DESIGN.md +76 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +3 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +20 -15
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +13 -51
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/access-overview.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/agent-login.md +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/baseline-audit.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-repository-binding.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-store-maintenance.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-password.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-sessions.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/endpoint-probe.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/google-sign-in.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/mesh-machines.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/passkey-enrollment.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/registered-brains.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/scoped-access-keys.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/secret-inventory.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/signing-key-rotation.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/home.js +375 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/{ui/DESIGN.md → DESIGN.md} +32 -21
- package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +17 -1
- package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +10 -1
- package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +4 -3
- package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +11 -27
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/control-ledger.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/local-inventory-triage.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/pending-control.md +39 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/run-evidence.md +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/schedule-vs-run-evidence.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/source-coverage.md +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/{card.js → home.js} +32 -27
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/DESIGN.md +53 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +40 -40
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/config.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/detect.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/gate.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/init.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/report.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/run.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/snapshot.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +55 -23
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/home.mjs +214 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/DESIGN.md +146 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +51 -34
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/create.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/history.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/library.md +49 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/list.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/promote.md +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/read.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/remove.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/rollback.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/show.md +53 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/status.md +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test-path.md +38 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test.md +46 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/update.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/home.js +533 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/DESIGN.md +110 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +13 -10
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/benchmark.md +49 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/coverage.md +53 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/models.md +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/pull.md +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/status.md +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/home.js +353 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/DESIGN.md +31 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/README.md +25 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/cli.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/git/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +17 -27
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -6
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +24 -6
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +33 -7
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +29 -5
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/unmerged.mjs +182 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/change-comparison.md +27 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/commit-identity.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/create-worktree.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/registered-worktrees.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/remove-worktree.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/repository-ledger.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/unmerged-work.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/upstream-divergence.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-conflict-triage.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-terminal.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/home.js +273 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/DESIGN.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +4 -5
- package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +9 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +37 -39
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/active-device-dispatch.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-probe.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-server.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/credential-readiness.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/elected-leader.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/enrollment-key.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/file-receipt.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-execution.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-presence.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/interactive-ssh.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/local-enrollment.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/mesh-policy.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/network-atlas.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-roster.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-selection-proof.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/owner-observations.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/peer-path.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/pinned-leader.md +24 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/reachability-evidence-ladder.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/recorded-login.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/remote-execution.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/secret-grants.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/{card.js → home.js} +93 -22
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/DESIGN.md +86 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +43 -22
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/chat.mjs +80 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/adapter-docs-freshness.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/chat-handoff.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/fleet-maintenance.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/harness-inventory.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-access.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-usage-verification.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/independent-review.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/managed-execution.md +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/official-documentation.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/quota-recovery.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/review-routing-decision.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/route-work.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/routing-evidence.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/run-ledger.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/home.js +198 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/DESIGN.md +75 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +17 -14
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +10 -44
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +5 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +3 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +23 -18
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +8 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +3 -5
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +21 -47
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +17 -6
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/auto-refresh.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/bootstrap-pages.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generate-wiki.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generator-settings.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/host-probe.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/ingest-preview.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-hygiene.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-topology.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/passage-search.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/propagation-plan.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/provider-credentials.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/refresh-report.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/task-evidence.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/upstream-record.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/watch-upstream.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/wiki-atlas.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +261 -513
- package/scaffold/.agent/skills/total-recall/plugins/security/DESIGN.md +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/README.md +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -19
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +2 -3
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +19 -52
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/device-evidence.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/evidence-desk.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/guided-device-review.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/host-and-network.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/https-headers.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/mesh-coverage.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/private-security-tools.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/single-origin-header-check.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/source-review.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/ssh-permission-plan.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/home.js +283 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/DESIGN.md +93 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/conformance.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/registry.md +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/repair.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/scaffold.md +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/status.md +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validate.md +34 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validation-diagnostics.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/workspace-registry-preflight.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/home.js +348 -0
- package/scaffold/.agent/skills/total-recall/references/operations.md +2 -0
- package/src/cli/api.mjs +3 -3
- package/src/cli/brief.mjs +3 -0
- package/src/cli/config.mjs +16 -0
- package/src/cli/connect.mjs +2 -7
- package/src/cli/plugin/chunks.mjs +145 -0
- package/src/cli/plugin/create.mjs +24 -52
- package/src/cli/plugin/index.mjs +20 -9
- package/src/cli/setup.mjs +2 -9
- package/src/cli/start.mjs +1 -0
- package/src/cli/startup.mjs +18 -0
- package/src/core/app-deploy/design-tokens.mjs +14 -9
- package/src/core/app-deploy/ui-elements.mjs +1 -1
- package/src/core/brain-registry.mjs +5 -0
- package/src/core/brain-updates.mjs +5 -1
- package/src/core/daemon-control.mjs +5 -1
- package/src/core/dashboard-bundle.mjs +261 -0
- package/src/core/ensure-dependencies.mjs +19 -15
- package/src/core/memory-brain.mjs +30 -8
- package/src/core/plugin-activation.mjs +63 -20
- package/src/core/plugin-contracts/{card.mjs → manifest.mjs} +23 -57
- package/src/core/plugin-contracts/startup-check.mjs +49 -0
- package/src/core/plugin-contracts/ui.mjs +9 -3
- package/src/core/plugin-distribution.mjs +6 -6
- package/src/core/plugin-lifecycle.mjs +58 -0
- package/src/core/plugin-loader.mjs +9 -5
- package/src/core/plugin-runner.mjs +21 -10
- package/src/core/plugin-startup-checks.mjs +44 -0
- package/src/core/plugin-state.mjs +19 -5
- package/src/core/plugin-store.mjs +30 -11
- package/src/core/project-id.mjs +15 -5
- package/src/core/required-plugins.mjs +31 -6
- package/src/core/schema.mjs +2 -0
- package/src/core/ssss-operation-service.mjs +1 -0
- package/src/core/startup-health.mjs +28 -3
- package/src/core/testing/plugin-fixture.mjs +22 -21
- package/src/core/throttled-fetch.mjs +25 -18
- package/src/core/ui-chunk-choice.mjs +77 -0
- package/src/core/ui-chunk.registry.json +23 -0
- package/src/core/ui-chunks.mjs +434 -0
- package/src/core/vault-cache.mjs +58 -59
- package/src/core/workspace-compositions.mjs +151 -0
- package/src/core/workspace-drawers.mjs +79 -0
- package/src/server/api.mjs +4 -27
- package/src/server/index.mjs +28 -21
- package/src/server/memory-events.mjs +31 -0
- package/src/server/plugin-brain-targets.mjs +89 -0
- package/src/server/rest.mjs +2 -0
- package/src/server/route-inventory.mjs +3 -2
- package/src/server/route-manifest.json +52 -4
- package/src/server/routes/_shared.mjs +7 -14
- package/src/server/routes/brains.mjs +29 -44
- package/src/server/routes/dashboard.mjs +26 -0
- package/src/server/routes/plugins.mjs +154 -45
- package/src/server/routes/ui-chunks.mjs +192 -0
- package/frontend/dist/assets/index-DACh1dhx.js +0 -5613
- package/frontend/dist/assets/index-DtwsDyFM.css +0 -1
- package/frontend/src/components/plugins/card/CardEngine.tsx +0 -108
- package/frontend/src/components/plugins/card/CardScenes.tsx +0 -103
- package/frontend/src/components/plugins/card/ExecutionPicker.tsx +0 -64
- package/frontend/src/components/plugins/card/args.ts +0 -28
- package/frontend/src/components/plugins/card/card.css +0 -32
- package/frontend/src/components/plugins/card/cli-line.ts +0 -13
- package/frontend/src/components/plugins/card/contract.ts +0 -91
- package/frontend/src/components/plugins/card/format.ts +0 -49
- package/frontend/src/components/plugins/card/runner-context.ts +0 -11
- package/frontend/src/components/plugins/card/runner.tsx +0 -43
- package/frontend/src/components/plugins/card/theme.ts +0 -35
- package/frontend/src/components/plugins/card/use-source.ts +0 -22
- package/frontend/src/components/plugins/card/widgets.tsx +0 -94
- package/frontend/src/components/plugins/workspace/WorkspaceHost.tsx +0 -23
- package/frontend/src/pages/PluginsPage.tsx +0 -1719
- package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +0 -47
- package/scaffold/.agent/skills/plugin/prompts/06-card.md +0 -46
- package/scaffold/.agent/skills/plugin/references/card.md +0 -90
- package/scaffold/.agent/skills/plugin/subagents/card-designer.md +0 -60
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +0 -67
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +0 -1179
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +0 -588
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +0 -575
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +0 -482
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/DESIGN.md +0 -100
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/card.js +0 -637
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/DESIGN.md +0 -217
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +0 -1276
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +0 -45
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +0 -1401
- package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +0 -4
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +0 -41
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +0 -396
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +0 -98
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/DESIGN.md +0 -15
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/card.js +0 -182
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import type { PluginInfo } from '../../api/plugins'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const inlineJson = (value: unknown) => JSON.stringify(value).replace(/</g, '\\u003c')
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
return
|
|
10
|
-
if (!item || typeof item !== 'object' || typeof item.id !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(item.id)
|
|
11
|
-
// eslint-disable-next-line no-control-regex -- rejects labels that contain control characters
|
|
12
|
-
|| typeof item.label !== 'string' || !item.label.trim() || item.label.length > 160 || /[\u0000-\u001f\u007f]/.test(item.label) || seen.has(item.id)) return []
|
|
13
|
-
seen.add(item.id)
|
|
14
|
-
return [{ id: item.id, label: item.label.trim() }]
|
|
15
|
-
})
|
|
5
|
+
export function browserPluginOperations(plugin: PluginInfo) {
|
|
6
|
+
const manifestCli = plugin.manifest?.cli as { subcommands?: NonNullable<PluginInfo['cli']>['subcommands'] } | undefined
|
|
7
|
+
const blocked = (command: { sensitive_output?: boolean; terminal_only?: boolean }) => command.sensitive_output === true || command.terminal_only === true
|
|
8
|
+
const excluded = new Set(manifestCli?.subcommands?.filter(blocked).map(command => command.name))
|
|
9
|
+
return (plugin.cli?.subcommands || []).filter(command => !blocked(command) && !excluded.has(command.name))
|
|
16
10
|
}
|
|
17
11
|
|
|
18
12
|
/** Owner code runs in an opaque origin, with no dashboard DOM/storage or direct network. */
|
|
19
|
-
export function pluginFrameDocument(source: string, tag: string, channel: string, theme: Record<string, string> = {}, pluginId = '', commandTimeouts: Record<string, number> = {}, operations: readonly string[] = []) {
|
|
13
|
+
export function pluginFrameDocument(source: string, tag: string, channel: string, theme: Record<string, string> = {}, pluginId = '', commandTimeouts: Record<string, number> = {}, operations: readonly string[] = [], credentialPanel = false, authInventory = false, authChanges = false) {
|
|
20
14
|
return `<!doctype html><html><head>
|
|
21
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${channel}' blob:; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com data:; img-src data: https
|
|
15
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${channel}' blob:; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com data:; img-src data:${authChanges ? '' : ' https:'}; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'">
|
|
22
16
|
<meta name="referrer" content="no-referrer">
|
|
23
17
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
24
18
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
@@ -27,20 +21,16 @@ export function pluginFrameDocument(source: string, tag: string, channel: string
|
|
|
27
21
|
</head><body><script nonce="${channel}" type="module">
|
|
28
22
|
const channel=${inlineJson(channel)};
|
|
29
23
|
const pending=new Map();let sequence=0;
|
|
24
|
+
const baseStyle=document.createElement('style');
|
|
25
|
+
const designStyle=document.createElement('style');
|
|
26
|
+
baseStyle.textContent=':root{}';
|
|
27
|
+
document.head.append(baseStyle,designStyle);
|
|
30
28
|
const send=(type,extra={})=>parent.postMessage({channel,type,...extra},'*');
|
|
31
|
-
|
|
32
|
-
const sanitizeScreens=${sanitizePluginScreens.toString()};
|
|
33
|
-
function publishScreens(element){
|
|
34
|
-
ownerScreens=sanitizeScreens(element.screens);
|
|
35
|
-
send('screens',{screens:ownerScreens});
|
|
36
|
-
if(ownerScreens.some(screen=>screen.id===element.screen))host.screenChanged(element.screen);
|
|
37
|
-
}
|
|
38
|
-
window.addEventListener('contextmenu',event=>{event.preventDefault();send('contextmenu');});
|
|
39
|
-
for(const [name,value] of Object.entries(${inlineJson(theme)}))document.documentElement.style.setProperty(name,value);
|
|
29
|
+
for(const [name,value] of Object.entries(${inlineJson(theme)}))baseStyle.sheet.cssRules[0].style.setProperty(name,value);
|
|
40
30
|
window.addEventListener('message',event=>{
|
|
41
31
|
if(event.source!==parent||event.data?.channel!==channel)return;
|
|
42
|
-
if(event.data.type==='
|
|
43
|
-
|
|
32
|
+
if(event.data.type==='design'&&typeof event.data.css==='string'){
|
|
33
|
+
designStyle.textContent=event.data.css;
|
|
44
34
|
return;
|
|
45
35
|
}
|
|
46
36
|
if(event.data.type!=='result')return;
|
|
@@ -48,22 +38,43 @@ window.addEventListener('message',event=>{
|
|
|
48
38
|
pending.delete(event.data.requestId);clearTimeout(request.timer);
|
|
49
39
|
if(event.data.error)request.reject(new Error(event.data.error));else request.resolve(event.data.result);
|
|
50
40
|
});
|
|
51
|
-
|
|
41
|
+
function request(type, extra, timeout=30000){return new Promise((resolve,reject)=>{
|
|
52
42
|
const requestId=String(++sequence);
|
|
43
|
+
const timer=setTimeout(()=>{pending.delete(requestId);reject(new Error('Operation timed out'));},timeout+5000);
|
|
44
|
+
pending.set(requestId,{resolve,reject,timer});send(type,{requestId,...extra});
|
|
45
|
+
});}
|
|
46
|
+
const host=Object.freeze({pluginId:${inlineJson(pluginId)},operations:Object.freeze(${inlineJson(operations)}),
|
|
47
|
+
...(${authInventory}?{readInventory:resource=>request('auth-inventory',{resource})}:{}),
|
|
48
|
+
...(${authChanges}?{changeCredential:change=>request('auth-credential-mutate',{change})}:{}),
|
|
49
|
+
run(subcommand,args=[],options={}){
|
|
53
50
|
const declared=${inlineJson(commandTimeouts)}[subcommand];
|
|
54
51
|
const timeout=Number.isInteger(declared)&&declared>=1&&declared<=600000?declared:30000;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});}});
|
|
52
|
+
return request('run',{subcommand,args,...(options.stdin!==undefined?{stdin:options.stdin}:{})},timeout);
|
|
53
|
+
}});
|
|
58
54
|
try{
|
|
59
55
|
const url=URL.createObjectURL(new Blob([${inlineJson(source)}],{type:'text/javascript'}));
|
|
60
56
|
try{await import(url);}finally{URL.revokeObjectURL(url);}
|
|
61
57
|
const tag=${inlineJson(tag)};
|
|
62
58
|
if(!customElements.get(tag))throw new Error('The plugin did not register its declared interface');
|
|
63
|
-
const element=document.createElement(tag);element.host=host;
|
|
64
|
-
|
|
59
|
+
const element=document.createElement(tag);element.host=host;
|
|
60
|
+
if(${credentialPanel}){
|
|
61
|
+
const message=document.createElement('p');message.setAttribute('role','status');
|
|
62
|
+
message.textContent='Checking saved credentials…';document.body.append(message);
|
|
63
|
+
try{
|
|
64
|
+
element.view=await request('credential-status',{});
|
|
65
|
+
message.textContent='';
|
|
66
|
+
element.addEventListener('save-token',async event=>{
|
|
67
|
+
message.textContent='Checking the credential…';
|
|
68
|
+
try{
|
|
69
|
+
element.view=await request('credential-save',{key:event.detail?.key,value:event.detail?.token});
|
|
70
|
+
message.textContent='Credential saved.';
|
|
71
|
+
}catch(error){message.textContent=error.message;}
|
|
72
|
+
});
|
|
73
|
+
document.body.append(element);
|
|
74
|
+
}catch(error){message.textContent=error.message;}
|
|
75
|
+
}else document.body.append(element);
|
|
65
76
|
if(typeof element.load==='function'&&!element.hasAttribute('data-loaded')){try{element.load();}catch(_){}}
|
|
66
|
-
if('view' in element||tag==='domains-console'||tag==='stripe-panel'){
|
|
77
|
+
if(!${credentialPanel}&&('view' in element||tag==='domains-console'||tag==='stripe-panel')){
|
|
67
78
|
host.run('view').then(v=>{element.view=v;}).catch(()=>{
|
|
68
79
|
host.run('status').then(s=>{element.view=s;}).catch(()=>{});
|
|
69
80
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PluginInfo } from '../../api/plugins'
|
|
2
|
+
import { browserPluginOperations } from './plugin-frame'
|
|
3
|
+
|
|
4
|
+
export type Argument = {
|
|
5
|
+
name: string
|
|
6
|
+
label: string
|
|
7
|
+
flag?: string
|
|
8
|
+
type: 'string' | 'number' | 'boolean' | 'choice'
|
|
9
|
+
options?: string[]
|
|
10
|
+
required?: boolean
|
|
11
|
+
secret?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type Command = ReturnType<typeof browserPluginOperations>[number] & {
|
|
15
|
+
effect?: 'read' | 'write' | 'destructive' | 'send'
|
|
16
|
+
args?: Argument[]
|
|
17
|
+
terminal_only_args?: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function scopedPluginOperations(plugin: PluginInfo, operationIds?: readonly string[]): Command[] {
|
|
21
|
+
const manifest = plugin.manifest?.cli as { subcommands?: Command[] } | undefined
|
|
22
|
+
const declared = new Map(manifest?.subcommands?.map(operation => [operation.name, operation]))
|
|
23
|
+
return browserPluginOperations(plugin)
|
|
24
|
+
.map(operation => ({ ...operation, ...declared.get(operation.name) }))
|
|
25
|
+
.filter(operation => operation.name !== 'on' && operation.name !== 'off' &&
|
|
26
|
+
(!operationIds || operationIds.includes(operation.name)) &&
|
|
27
|
+
!operation.args?.some(arg => arg.secret || (arg.required && operation.terminal_only_args?.includes(arg.flag || ''))))
|
|
28
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState, type FormEvent } from 'react'
|
|
2
|
+
import { runPluginCommand, type PluginInfo } from '../../api/plugins'
|
|
3
|
+
import { parsePluginOutput } from '../plugins/PluginPanel'
|
|
4
|
+
|
|
5
|
+
interface Harness { id: string; name: string }
|
|
6
|
+
interface ChatTarget {
|
|
7
|
+
ok: boolean
|
|
8
|
+
configured: boolean
|
|
9
|
+
harness?: Harness
|
|
10
|
+
href?: string
|
|
11
|
+
context?: string
|
|
12
|
+
choices?: Harness[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function launchHref(value?: string) {
|
|
16
|
+
try {
|
|
17
|
+
if (!value) return undefined
|
|
18
|
+
const url = new URL(value)
|
|
19
|
+
if (url.username || url.password) return undefined
|
|
20
|
+
if (url.protocol === 'https:') return url.href
|
|
21
|
+
if (url.protocol === 'codex:' && url.hostname === 'threads' && /^\/(?:new|[0-9a-f-]{36})$/i.test(url.pathname)) return url.href
|
|
22
|
+
} catch { /* An invalid configuration is shown without a launch link. */ }
|
|
23
|
+
return undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function DrawerChat({ plugin, activeBrainId }: { plugin?: PluginInfo; activeBrainId: string }) {
|
|
27
|
+
const [target, setTarget] = useState<ChatTarget | null>(null)
|
|
28
|
+
const [error, setError] = useState('')
|
|
29
|
+
const [edit, setEdit] = useState(false)
|
|
30
|
+
const [harness, setHarness] = useState('')
|
|
31
|
+
const [url, setUrl] = useState('')
|
|
32
|
+
const [saving, setSaving] = useState(false)
|
|
33
|
+
const [copied, setCopied] = useState(false)
|
|
34
|
+
const hasTarget = !!plugin?.valid && plugin.activation?.enabled === true &&
|
|
35
|
+
!!plugin.cli?.subcommands?.some(command => command.name === 'chat-target')
|
|
36
|
+
const getTarget = useCallback(async () => {
|
|
37
|
+
const result = await runPluginCommand('meta-harness', 'chat-target', ['--brains', activeBrainId, '--json'])
|
|
38
|
+
const data = parsePluginOutput(result.output) as Partial<ChatTarget> | undefined
|
|
39
|
+
if (!result.success || data?.ok !== true || typeof data.configured !== 'boolean') {
|
|
40
|
+
throw new Error('Could not read the default harness from Metaharness.')
|
|
41
|
+
}
|
|
42
|
+
return data as ChatTarget
|
|
43
|
+
}, [activeBrainId])
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!hasTarget) return
|
|
46
|
+
let current = true
|
|
47
|
+
void getTarget().then(data => { if (current) { setTarget(data); setError(''); setCopied(false) } },
|
|
48
|
+
error => { if (current) { setTarget(null); setError(error.message) } })
|
|
49
|
+
return () => { current = false }
|
|
50
|
+
}, [getTarget, hasTarget])
|
|
51
|
+
|
|
52
|
+
async function save(event: FormEvent<HTMLFormElement>) {
|
|
53
|
+
event.preventDefault()
|
|
54
|
+
setSaving(true)
|
|
55
|
+
setError('')
|
|
56
|
+
try {
|
|
57
|
+
const response = await runPluginCommand('meta-harness', 'chat-default', [
|
|
58
|
+
harness, ...(url.trim() ? ['--url', url.trim()] : []), '--json',
|
|
59
|
+
])
|
|
60
|
+
const data = parsePluginOutput(response.output) as { ok?: boolean; error?: string } | undefined
|
|
61
|
+
if (!response.success || data?.ok !== true) throw new Error(data?.error || response.error || 'Could not save the default harness.')
|
|
62
|
+
setTarget(await getTarget())
|
|
63
|
+
setEdit(false)
|
|
64
|
+
} catch (error) { setError(error instanceof Error ? error.message : 'Could not save the default harness.') }
|
|
65
|
+
finally { setSaving(false) }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!hasTarget) return <p className="drawer-note">
|
|
69
|
+
{!plugin ? 'Install Metaharness to open Chat.' : plugin.activation?.enabled !== true
|
|
70
|
+
? 'Enable Metaharness to open Chat.' : 'This Metaharness version does not provide a default Chat harness yet.'}
|
|
71
|
+
</p>
|
|
72
|
+
|
|
73
|
+
const href = launchHref(target?.href)
|
|
74
|
+
const selectedCount = activeBrainId.split(',').filter(Boolean).length
|
|
75
|
+
return <section className="drawer-stack">
|
|
76
|
+
<div className="drawer-row">
|
|
77
|
+
<div>
|
|
78
|
+
<h3>{target?.harness?.name || 'Default harness'}</h3>
|
|
79
|
+
<p className="drawer-note">{selectedCount === 1 ? '1 brain selected' : `${selectedCount} brains selected`}</p>
|
|
80
|
+
</div>
|
|
81
|
+
{target && <button type="button" className="btn btn-ghost btn-sm" onClick={() => {
|
|
82
|
+
setHarness(target.harness?.id || ''); setUrl(''); setEdit(!edit)
|
|
83
|
+
}}>{edit ? 'Cancel' : 'Configure default'}</button>}
|
|
84
|
+
</div>
|
|
85
|
+
{error && <p role="alert" className="drawer-error">{error}</p>}
|
|
86
|
+
{!target && !error && <p role="status">Loading the default harness…</p>}
|
|
87
|
+
{edit && <form className="drawer-stack" onSubmit={save}>
|
|
88
|
+
<label>Harness
|
|
89
|
+
<select className="select" value={harness} onChange={event => setHarness(event.target.value)} required>
|
|
90
|
+
<option value="">Choose a harness…</option>
|
|
91
|
+
{target?.choices?.map(item => <option key={item.id} value={item.id}>{item.name}</option>)}
|
|
92
|
+
</select>
|
|
93
|
+
</label>
|
|
94
|
+
<label>Custom launch URL (optional)<input className="input" type="text" value={url}
|
|
95
|
+
placeholder="Use the harness default" onChange={event => setUrl(event.target.value)} /></label>
|
|
96
|
+
<div><button className="btn btn-primary" disabled={saving || !harness}>{saving ? 'Saving…' : 'Save default'}</button></div>
|
|
97
|
+
</form>}
|
|
98
|
+
{!edit && target && (target.configured ? <>
|
|
99
|
+
<div className="drawer-actions">
|
|
100
|
+
{href && <a className="btn btn-primary" href={href} rel="noreferrer" target={href.startsWith('https:') ? '_blank' : undefined}>Open Chat</a>}
|
|
101
|
+
{target.context && <button type="button" className="btn btn-ghost" onClick={async () => {
|
|
102
|
+
try { await navigator.clipboard.writeText(target.context!); setCopied(true) }
|
|
103
|
+
catch { setError('Copy the selected brain context below.') }
|
|
104
|
+
}}>{copied ? 'Copied' : 'Copy brain context'}</button>}
|
|
105
|
+
</div>
|
|
106
|
+
{!href && <p className="drawer-note">This harness has no launch URL. Open it directly and paste the selected brain context.</p>}
|
|
107
|
+
{target.context && <details><summary>Selected brain context</summary><pre className="drawer-context">{target.context}</pre></details>}
|
|
108
|
+
</> : <p className="drawer-note">Choose the harness Chat should open for your selected brains.</p>)}
|
|
109
|
+
</section>
|
|
110
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { Link } from 'react-router-dom'
|
|
3
|
+
import type { PluginInfo } from '../../api/plugins'
|
|
4
|
+
import {
|
|
5
|
+
findWorkspaceChunks, readDrawer, readWorkspaceChunk, saveDrawer,
|
|
6
|
+
type DrawerConfig, type DrawerRevision, type DrawerSide, type PanelRef, type PluginDesign, type WorkspaceChunk,
|
|
7
|
+
} from '../../api/workspace-drawers'
|
|
8
|
+
import { PluginPanel, pluginElements } from '../plugins/PluginPanel'
|
|
9
|
+
import { ScopedPluginCommands } from '../plugins/ScopedPluginCommands'
|
|
10
|
+
import { DrawerMarkup } from './DrawerMarkup'
|
|
11
|
+
import { usePluginDesign } from './usePluginDesign'
|
|
12
|
+
|
|
13
|
+
export function drawerPanels(plugin: PluginInfo) {
|
|
14
|
+
return pluginElements(plugin).filter(item => item.kind !== 'settings')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function DrawerPanel({ plugins, reference, designCss, brainId }: {
|
|
18
|
+
plugins: PluginInfo[] | null; reference: PanelRef; designCss?: string; brainId?: string
|
|
19
|
+
}) {
|
|
20
|
+
if (!plugins) return <p role="status">Loading installed interfaces…</p>
|
|
21
|
+
const plugin = plugins.find(plugin => plugin.id === reference.plugin)
|
|
22
|
+
const panel = plugin && drawerPanels(plugin).find(item => item.id === reference.element)
|
|
23
|
+
if (!plugin || !panel) return <p className="drawer-note">This plugin interface is not installed.</p>
|
|
24
|
+
if (!plugin.valid || plugin.activation?.state === 'error') {
|
|
25
|
+
return <p role="alert" className="drawer-error">{plugin.name} could not load.</p>
|
|
26
|
+
}
|
|
27
|
+
if (plugin.activation?.enabled !== true) return <p className="drawer-note">
|
|
28
|
+
{plugin.name} is off. <Link to={{ search: `?plugin=${encodeURIComponent(plugin.id)}` }}>Open plugin settings</Link>
|
|
29
|
+
</p>
|
|
30
|
+
return <PluginPanel key={`${plugin.id}:${panel.id}`} plugin={plugin} elementId={panel.id} designCss={designCss} brainId={brainId} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function PublishedChunk({ name, side, plugins, design, designOwner, brainId }: {
|
|
34
|
+
name: string; side: DrawerSide | 'page'; plugins: PluginInfo[] | null; design?: PluginDesign; designOwner?: string; brainId?: string
|
|
35
|
+
}) {
|
|
36
|
+
const ownerDesign = usePluginDesign(designOwner, brainId)
|
|
37
|
+
const theme = ownerDesign?.design || design
|
|
38
|
+
const requestKey = JSON.stringify([name, side, brainId])
|
|
39
|
+
const [result, setResult] = useState<{ key: string; chunk: (WorkspaceChunk & { html: string }) | null }>()
|
|
40
|
+
const current = result?.key === requestKey ? result : undefined
|
|
41
|
+
const chunk = current?.chunk
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const controller = new AbortController()
|
|
44
|
+
void readWorkspaceChunk(name, side, controller.signal, brainId).then(
|
|
45
|
+
data => { if (!controller.signal.aborted) setResult({ key: requestKey, chunk: data }) },
|
|
46
|
+
() => { if (!controller.signal.aborted) setResult({ key: requestKey, chunk: null }) },
|
|
47
|
+
)
|
|
48
|
+
return () => controller.abort()
|
|
49
|
+
}, [name, side, brainId, requestKey])
|
|
50
|
+
if (current && (!chunk || !chunk.surfaces.includes(side))) return <p className="drawer-note">{name} is unavailable here.</p>
|
|
51
|
+
if (!chunk) return <p role="status">Loading {name}…</p>
|
|
52
|
+
const owner = plugins?.find(plugin => plugin.id === chunk.plugin)
|
|
53
|
+
return <section className="drawer-stack" aria-label={chunk.title}>
|
|
54
|
+
{chunk.html && <DrawerMarkup title={chunk.title} html={chunk.html} design={theme} />}
|
|
55
|
+
{chunk.panel && <DrawerPanel plugins={plugins} reference={{ plugin: chunk.plugin, element: chunk.panel }} designCss={theme?.css ?? ''} brainId={brainId} />}
|
|
56
|
+
{brainId && owner?.valid && owner.activation?.enabled === true && owner.activation.state !== 'error' &&
|
|
57
|
+
!!chunk.operations?.length && <ScopedPluginCommands key={`${requestKey}:${owner.sha256}:${chunk.sha256}`} plugin={owner} brainId={brainId}
|
|
58
|
+
operationIds={chunk.operations} label={`${chunk.title} operations`} />}
|
|
59
|
+
</section>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ShortcutList({ shortcuts }: { shortcuts: DrawerConfig['shortcuts'] }) {
|
|
63
|
+
if (!shortcuts.length) return <p className="drawer-note">Add a shortcut to a dashboard page, plugin, or website.</p>
|
|
64
|
+
return <nav className="drawer-actions" aria-label="Saved shortcuts">
|
|
65
|
+
{shortcuts.map(({ label, href }, index) => href.startsWith('https://')
|
|
66
|
+
? <a key={index} className="btn btn-ghost" href={href} target="_blank" rel="noopener noreferrer">{label}</a>
|
|
67
|
+
: <Link key={index} className="btn btn-ghost" to={href}>{label}</Link>)}
|
|
68
|
+
</nav>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function LayoutEditor({ side, draft, onChange, plugins, brainId }: {
|
|
72
|
+
side: DrawerSide; draft: DrawerConfig; onChange: (next: DrawerConfig) => void; plugins: PluginInfo[] | null; brainId?: string
|
|
73
|
+
}) {
|
|
74
|
+
const [query, setQuery] = useState('')
|
|
75
|
+
const [chunks, setChunks] = useState<WorkspaceChunk[]>([])
|
|
76
|
+
const [catalogError, setCatalogError] = useState(false)
|
|
77
|
+
const [preview, setPreview] = useState(false)
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const controller = new AbortController()
|
|
80
|
+
const timer = window.setTimeout(() => {
|
|
81
|
+
void findWorkspaceChunks(side, query, controller.signal, undefined, brainId).then(
|
|
82
|
+
data => { if (!controller.signal.aborted) { setChunks(data.chunks); setCatalogError(false) } },
|
|
83
|
+
() => { if (!controller.signal.aborted) { setChunks([]); setCatalogError(true) } },
|
|
84
|
+
)
|
|
85
|
+
}, 180)
|
|
86
|
+
return () => { controller.abort(); window.clearTimeout(timer) }
|
|
87
|
+
}, [side, query, brainId])
|
|
88
|
+
const panels = (plugins || []).filter(plugin => plugin.valid && plugin.activation?.enabled === true)
|
|
89
|
+
.flatMap(plugin => drawerPanels(plugin).map(element => ({ plugin, element })))
|
|
90
|
+
const choices = panels.filter(({ plugin, element }) => !draft.panels.some(ref => ref.plugin === plugin.id && ref.element === element.id))
|
|
91
|
+
return <div className="drawer-stack">
|
|
92
|
+
{side === 'bottom' && <fieldset className="drawer-stack drawer-fields">
|
|
93
|
+
<legend>Shortcuts</legend>
|
|
94
|
+
{draft.shortcuts.map((shortcut, index) => <div className="drawer-shortcut-edit" key={index}>
|
|
95
|
+
<input className="input" required maxLength={80} value={shortcut.label} aria-label={`Shortcut ${index + 1} name`}
|
|
96
|
+
placeholder="Name" onChange={event => onChange({ ...draft, shortcuts: draft.shortcuts.map((link, i) => i === index ? { ...link, label: event.target.value } : link) })} />
|
|
97
|
+
<input className="input" required maxLength={1024} value={shortcut.href} aria-label={`Shortcut ${index + 1} address`}
|
|
98
|
+
placeholder="/memory?plugin=… or https://…" onChange={event => onChange({ ...draft, shortcuts: draft.shortcuts.map((link, i) => i === index ? { ...link, href: event.target.value } : link) })} />
|
|
99
|
+
<button type="button" className="btn btn-ghost btn-sm" aria-label={`Remove shortcut ${index + 1}`}
|
|
100
|
+
onClick={() => onChange({ ...draft, shortcuts: draft.shortcuts.filter((_, i) => i !== index) })}>Remove</button>
|
|
101
|
+
</div>)}
|
|
102
|
+
<div><button type="button" className="btn btn-ghost btn-sm" disabled={draft.shortcuts.length >= 16}
|
|
103
|
+
onClick={() => onChange({ ...draft, shortcuts: [...draft.shortcuts, { label: '', href: '' }] })}>Add shortcut</button></div>
|
|
104
|
+
</fieldset>}
|
|
105
|
+
<label>Drawer HTML
|
|
106
|
+
<textarea className="input drawer-code" rows={7} maxLength={65536} value={draft.html}
|
|
107
|
+
placeholder="<section>…</section>" onChange={event => onChange({ ...draft, html: event.target.value })} />
|
|
108
|
+
</label>
|
|
109
|
+
<div><button type="button" className="btn btn-ghost btn-sm" aria-expanded={preview} disabled={!draft.html.trim()}
|
|
110
|
+
onClick={() => setPreview(!preview)}>{preview ? 'Hide preview' : 'Preview HTML'}</button></div>
|
|
111
|
+
{preview && draft.html.trim() && <DrawerMarkup title="Draft HTML preview" html={draft.html} />}
|
|
112
|
+
<fieldset className="drawer-stack drawer-fields">
|
|
113
|
+
<legend>Plugin chunks</legend>
|
|
114
|
+
{draft.chunks.map(key => <div key={key} className="drawer-row">
|
|
115
|
+
<span>{key}</span>
|
|
116
|
+
<button type="button" className="btn btn-ghost btn-sm" aria-label={`Remove ${key}`}
|
|
117
|
+
onClick={() => onChange({ ...draft, chunks: draft.chunks.filter(item => item !== key) })}>Remove</button>
|
|
118
|
+
</div>)}
|
|
119
|
+
<input className="input" type="search" aria-label="Search installed chunks" placeholder="Search installed chunks…"
|
|
120
|
+
value={query} onChange={event => setQuery(event.target.value)} />
|
|
121
|
+
{catalogError ? <p className="drawer-note">The chunk catalog is unavailable on this server.</p> :
|
|
122
|
+
<select className="select" aria-label="Add chunk" value="" disabled={draft.chunks.length >= 8}
|
|
123
|
+
onChange={event => { if (event.target.value) onChange({ ...draft, chunks: [...draft.chunks, event.target.value] }) }}>
|
|
124
|
+
<option value="">Add an installed chunk…</option>
|
|
125
|
+
{chunks.filter(chunk => !draft.chunks.includes(chunk.key)).map(chunk =>
|
|
126
|
+
<option key={chunk.key} value={chunk.key}>{chunk.title} · {chunk.plugin}</option>)}
|
|
127
|
+
</select>}
|
|
128
|
+
</fieldset>
|
|
129
|
+
<fieldset className="drawer-stack drawer-fields">
|
|
130
|
+
<legend>Plugin interfaces</legend>
|
|
131
|
+
{draft.panels.map(ref => <div key={`${ref.plugin}:${ref.element}`} className="drawer-row">
|
|
132
|
+
<span>{ref.plugin} · {ref.element}</span>
|
|
133
|
+
<button type="button" className="btn btn-ghost btn-sm" aria-label={`Remove ${ref.plugin} ${ref.element}`}
|
|
134
|
+
onClick={() => onChange({ ...draft, panels: draft.panels.filter(item => item !== ref) })}>Remove</button>
|
|
135
|
+
</div>)}
|
|
136
|
+
<select className="select" aria-label="Add plugin interface" value="" disabled={!choices.length || draft.panels.length >= 6}
|
|
137
|
+
onChange={event => {
|
|
138
|
+
const match = choices.find(({ plugin, element }) => `${plugin.id}:${element.id}` === event.target.value)
|
|
139
|
+
if (match) onChange({ ...draft, panels: [...draft.panels, { plugin: match.plugin.id, element: match.element.id }] })
|
|
140
|
+
}}>
|
|
141
|
+
<option value="">Add an installed interface…</option>
|
|
142
|
+
{choices.map(({ plugin, element }) =>
|
|
143
|
+
<option key={`${plugin.id}:${element.id}`} value={`${plugin.id}:${element.id}`}>{plugin.name} · {element.description || element.id}</option>)}
|
|
144
|
+
</select>
|
|
145
|
+
</fieldset>
|
|
146
|
+
</div>
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function DrawerLayout({ side, view, plugins, brainId }: {
|
|
150
|
+
side: DrawerSide; view: 'custom' | 'shortcuts'; plugins: PluginInfo[] | null; brainId?: string
|
|
151
|
+
}) {
|
|
152
|
+
const [revision, setRevision] = useState<DrawerRevision | null>(null)
|
|
153
|
+
const [draft, setDraft] = useState<DrawerConfig | null>(null)
|
|
154
|
+
const [error, setError] = useState('')
|
|
155
|
+
const [busy, setBusy] = useState(false)
|
|
156
|
+
const [refresh, setRefresh] = useState(0)
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const controller = new AbortController()
|
|
159
|
+
void readDrawer(side, controller.signal).then(
|
|
160
|
+
current => { if (!controller.signal.aborted) { setRevision(current); setError('') } },
|
|
161
|
+
error => { if (!controller.signal.aborted) setError(error.message) },
|
|
162
|
+
)
|
|
163
|
+
return () => controller.abort()
|
|
164
|
+
}, [refresh, side])
|
|
165
|
+
return <div className="drawer-stack">
|
|
166
|
+
<div className="drawer-actions">
|
|
167
|
+
{!draft && <button type="button" className="btn btn-primary btn-sm" disabled={!revision}
|
|
168
|
+
onClick={() => { setDraft(revision!.config); setError('') }}>{view === 'shortcuts' ? 'Edit shortcuts' : 'Edit drawer'}</button>}
|
|
169
|
+
{!draft && <button type="button" className="btn btn-ghost btn-sm" onClick={() => setRefresh(refresh + 1)}>Refresh</button>}
|
|
170
|
+
</div>
|
|
171
|
+
{error && <p className="drawer-error" role="alert">{error}</p>}
|
|
172
|
+
{!revision && !error && <p role="status">Loading the saved drawer…</p>}
|
|
173
|
+
{draft && revision && <form className="drawer-stack" onSubmit={async event => {
|
|
174
|
+
event.preventDefault()
|
|
175
|
+
setBusy(true); setError('')
|
|
176
|
+
try {
|
|
177
|
+
setRevision(await saveDrawer(side, { config: draft, version: revision.version }))
|
|
178
|
+
setDraft(null)
|
|
179
|
+
} catch (error) { setError(error instanceof Error ? error.message : 'Could not save the drawer.') }
|
|
180
|
+
finally { setBusy(false) }
|
|
181
|
+
}}>
|
|
182
|
+
<LayoutEditor side={side} draft={draft} onChange={setDraft} plugins={plugins} brainId={brainId} />
|
|
183
|
+
<div className="drawer-actions">
|
|
184
|
+
<button className="btn btn-primary" disabled={busy}>{busy ? 'Saving…' : 'Save drawer'}</button>
|
|
185
|
+
<button type="button" className="btn btn-ghost" disabled={busy} onClick={() => { setDraft(null); setError('') }}>Cancel</button>
|
|
186
|
+
</div>
|
|
187
|
+
</form>}
|
|
188
|
+
{!draft && revision && (view === 'shortcuts' ? <ShortcutList shortcuts={revision.config.shortcuts} /> : <>
|
|
189
|
+
{revision.config.html && <DrawerMarkup title={`${side} drawer custom HTML`} html={revision.config.html} />}
|
|
190
|
+
{revision.config.chunks.map(key => <PublishedChunk key={`${brainId}:${key}:${refresh}`} name={key} side={side} plugins={plugins} brainId={brainId} designOwner={key.split(':')[0]} />)}
|
|
191
|
+
{revision.config.panels.map(ref => <DrawerPanel key={`${brainId}:${ref.plugin}:${ref.element}:${refresh}`} plugins={plugins} reference={ref} brainId={brainId} />)}
|
|
192
|
+
{!revision.config.html && !revision.config.chunks.length && !revision.config.panels.length &&
|
|
193
|
+
<p className="drawer-note">Add HTML or an installed plugin interface to make this drawer yours.</p>}
|
|
194
|
+
</>)}
|
|
195
|
+
</div>
|
|
196
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import type { PluginDesign } from '../../api/workspace-drawers'
|
|
3
|
+
|
|
4
|
+
const TAGS = new Set('a abbr article aside b bdi blockquote br button caption code col colgroup dd del details div dl dt em fieldset figcaption figure footer h1 h2 h3 h4 h5 h6 header hr i img input kbd label legend li main mark meter nav ol optgroup option p pre progress q s samp section select small span strong style sub summary sup table tbody td textarea tfoot th thead time tr u ul var wbr'.split(' '))
|
|
5
|
+
const ATTRIBUTES = new Set('alt checked class cols colspan datetime dir disabled for height hidden id lang max maxlength min multiple name open placeholder role rows rowspan scope selected size step style tabindex title type value width'.split(' '))
|
|
6
|
+
const POLICY = "default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'; object-src 'none'"
|
|
7
|
+
|
|
8
|
+
/** Parse in an inert template; only the reconstructed markup goes into an opaque frame. */
|
|
9
|
+
export function drawerMarkupDocument(source: string, design?: Pick<PluginDesign, 'css' | 'components'>) {
|
|
10
|
+
const template = document.createElement('template')
|
|
11
|
+
template.innerHTML = source
|
|
12
|
+
for (const recipe of [...template.content.querySelectorAll<HTMLTemplateElement>('template[data-design-components]')].slice(0, 8)) {
|
|
13
|
+
const element = recipe.content.firstElementChild
|
|
14
|
+
if (!element || recipe.content.children.length !== 1 || recipe.innerHTML.length > 4096) continue
|
|
15
|
+
const copies = (design?.components || []).slice(0, 64).map(name => {
|
|
16
|
+
const node = element.cloneNode(true) as Element
|
|
17
|
+
node.setAttribute('data-design-component', name)
|
|
18
|
+
if (node.hasAttribute('data-design-label')) node.textContent = name
|
|
19
|
+
for (const label of node.querySelectorAll('[data-design-label]')) label.textContent = name
|
|
20
|
+
return node
|
|
21
|
+
})
|
|
22
|
+
recipe.replaceWith(...copies)
|
|
23
|
+
}
|
|
24
|
+
for (const element of template.content.querySelectorAll('*')) {
|
|
25
|
+
const name = element.localName
|
|
26
|
+
if (!TAGS.has(name) || element.namespaceURI !== 'http://www.w3.org/1999/xhtml') {
|
|
27
|
+
element.remove()
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
30
|
+
for (const attribute of [...element.attributes]) {
|
|
31
|
+
const { name: key, value } = attribute
|
|
32
|
+
if (ATTRIBUTES.has(key) || key === 'data-design-component' || /^aria-[a-z-]+$/.test(key)) continue
|
|
33
|
+
if (name === 'img' && key === 'src' && /^data:image\/(?:png|jpeg|gif|webp);base64,[a-z0-9+/=]+$/i.test(value)) continue
|
|
34
|
+
element.removeAttribute(key)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
38
|
+
<meta http-equiv="Content-Security-Policy" content="${POLICY}">
|
|
39
|
+
<style>:root{color-scheme:light dark}body{margin:0;padding:var(--spacing-md,16px);font:14px/1.55 var(--typography-body-font-family,var(--font-sans,system-ui)),sans-serif;color:var(--color-text,CanvasText);background:var(--color-background,Canvas)}img{max-width:100%;height:auto}*{box-sizing:border-box}</style>
|
|
40
|
+
<style>${(design?.css || '').replaceAll('<', '')}</style>
|
|
41
|
+
</head><body>${template.innerHTML}</body></html>`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function DrawerMarkup({ title, html, design }: { title: string; html: string; design?: PluginDesign }) {
|
|
45
|
+
const source = useMemo(() => drawerMarkupDocument(html, design), [html, design])
|
|
46
|
+
return <iframe className="drawer-html" title={title} sandbox="" referrerPolicy="no-referrer"
|
|
47
|
+
allow="camera 'none'; microphone 'none'; geolocation 'none'; clipboard-read 'none'; clipboard-write 'none'"
|
|
48
|
+
srcDoc={source} />
|
|
49
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { findWorkspaceChunks, type WorkspaceChunk } from '../../api/workspace-drawers'
|
|
3
|
+
import type { PluginInfo } from '../../api/plugins'
|
|
4
|
+
import { PublishedChunk } from './DrawerLayout'
|
|
5
|
+
|
|
6
|
+
const pageSize = 20
|
|
7
|
+
|
|
8
|
+
export function PluginChunkLibrary({ plugin, brainId }: {
|
|
9
|
+
plugin: PluginInfo; brainId: string
|
|
10
|
+
}) {
|
|
11
|
+
const [catalog, setCatalog] = useState<{ chunks: WorkspaceChunk[]; total: number } | null>(null)
|
|
12
|
+
const [key, setKey] = useState('')
|
|
13
|
+
const [draft, setDraft] = useState('')
|
|
14
|
+
const [query, setQuery] = useState('')
|
|
15
|
+
const [offset, setOffset] = useState(0)
|
|
16
|
+
const [refresh, setRefresh] = useState(0)
|
|
17
|
+
const [error, setError] = useState('')
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const controller = new AbortController()
|
|
21
|
+
void findWorkspaceChunks('page', query, controller.signal, plugin.id, brainId, { limit: pageSize, offset }).then(
|
|
22
|
+
data => {
|
|
23
|
+
if (controller.signal.aborted) return
|
|
24
|
+
if (!data.chunks.length && offset > 0) {
|
|
25
|
+
setOffset(Math.max(0, Math.floor((data.total - 1) / pageSize) * pageSize))
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
setCatalog(data)
|
|
29
|
+
setKey(current => data.chunks.some(chunk => chunk.key === current) ? current : data.chunks[0]?.key || '')
|
|
30
|
+
},
|
|
31
|
+
() => {
|
|
32
|
+
if (controller.signal.aborted) return
|
|
33
|
+
setError('Could not load the published blocks.')
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
return () => controller.abort()
|
|
37
|
+
}, [plugin.id, brainId, offset, query, refresh])
|
|
38
|
+
|
|
39
|
+
function clearResult() {
|
|
40
|
+
setCatalog(null)
|
|
41
|
+
setError('')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function movePage(next: number) {
|
|
45
|
+
clearResult()
|
|
46
|
+
setOffset(next)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const selected = catalog?.chunks.find(chunk => chunk.key === key)
|
|
50
|
+
|
|
51
|
+
return <div className="drawer-stack">
|
|
52
|
+
<form className="drawer-actions" onSubmit={event => {
|
|
53
|
+
event.preventDefault()
|
|
54
|
+
clearResult()
|
|
55
|
+
setQuery(draft.trim())
|
|
56
|
+
setOffset(0)
|
|
57
|
+
setRefresh(value => value + 1)
|
|
58
|
+
}}>
|
|
59
|
+
<label>Search blocks
|
|
60
|
+
<input className="input" type="search" value={draft} maxLength={1024}
|
|
61
|
+
onChange={event => setDraft(event.target.value)} />
|
|
62
|
+
</label>
|
|
63
|
+
<button type="submit" className="btn btn-ghost btn-sm">Search</button>
|
|
64
|
+
<button type="button" className="btn btn-ghost btn-sm" onClick={() => {
|
|
65
|
+
clearResult()
|
|
66
|
+
setRefresh(value => value + 1)
|
|
67
|
+
}}>Refresh blocks</button>
|
|
68
|
+
</form>
|
|
69
|
+
<div className="drawer-actions">
|
|
70
|
+
{catalog && catalog.chunks.length > 0 && <label>Block
|
|
71
|
+
<select className="select" value={key} onChange={event => setKey(event.target.value)}>
|
|
72
|
+
{catalog.chunks.map(chunk => <option key={chunk.key} value={chunk.key}>{chunk.title}</option>)}
|
|
73
|
+
</select>
|
|
74
|
+
</label>}
|
|
75
|
+
</div>
|
|
76
|
+
{catalog && catalog.total > 0 && <div className="drawer-actions">
|
|
77
|
+
<span role="status">Showing {offset + 1}–{offset + catalog.chunks.length} of {catalog.total} blocks</span>
|
|
78
|
+
{catalog.total > pageSize && <>
|
|
79
|
+
<button type="button" className="btn btn-ghost btn-sm" disabled={offset === 0}
|
|
80
|
+
onClick={() => movePage(offset - pageSize)}>Previous blocks</button>
|
|
81
|
+
<button type="button" className="btn btn-ghost btn-sm" disabled={offset + pageSize >= catalog.total}
|
|
82
|
+
onClick={() => movePage(offset + pageSize)}>Next blocks</button>
|
|
83
|
+
</>}
|
|
84
|
+
</div>}
|
|
85
|
+
{error && <p role="alert">{error}</p>}
|
|
86
|
+
{!catalog && !error && <p role="status">Loading published blocks…</p>}
|
|
87
|
+
{catalog?.total === 0 && <p className="drawer-note">{query
|
|
88
|
+
? 'No published blocks match your search.' : 'This plugin has no published page blocks yet.'}</p>}
|
|
89
|
+
{selected && <PublishedChunk key={`${selected.key}:${selected.sha256}:${refresh}`}
|
|
90
|
+
name={selected.key} side="page" plugins={[plugin]} brainId={brainId} designOwner={plugin.id} />}
|
|
91
|
+
</div>
|
|
92
|
+
}
|