@songsid/agend 2.2.1 → 2.3.0-beta.10
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/dist/agent-cli-instructions.md +5 -2
- package/dist/agent-cli.js +284 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +2 -0
- package/dist/agent-endpoint.js +29 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +38 -0
- package/dist/agent-file-read.js +317 -0
- package/dist/agent-file-read.js.map +1 -0
- package/dist/agent-whats-new.d.ts +11 -0
- package/dist/agent-whats-new.js +29 -2
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/atomic-write.js +9 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.d.ts +30 -1
- package/dist/auth-api.js +61 -12
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +38 -0
- package/dist/authority/authority-state.js +114 -0
- package/dist/authority/authority-state.js.map +1 -0
- package/dist/authority/session-authorizations.d.ts +43 -0
- package/dist/authority/session-authorizations.js +102 -0
- package/dist/authority/session-authorizations.js.map +1 -0
- package/dist/authority/web-authority.d.ts +120 -0
- package/dist/authority/web-authority.js +157 -0
- package/dist/authority/web-authority.js.map +1 -0
- package/dist/backend/antigravity.js +3 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +2 -2
- package/dist/backend/claude-code.js +10 -5
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/launch-effort.js +1 -1
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/muse.js +10 -2
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +14 -0
- package/dist/backend/types.js +25 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-availability.d.ts +120 -0
- package/dist/backend-availability.js +396 -0
- package/dist/backend-availability.js.map +1 -0
- package/dist/backend-command.d.ts +21 -3
- package/dist/backend-command.js +23 -4
- package/dist/backend-command.js.map +1 -1
- package/dist/bundled-skills.d.ts +14 -0
- package/dist/bundled-skills.js +29 -0
- package/dist/bundled-skills.js.map +1 -0
- package/dist/cache-analysis.d.ts +47 -1
- package/dist/cache-analysis.js +68 -8
- package/dist/cache-analysis.js.map +1 -1
- package/dist/cache-ledger.d.ts +16 -3
- package/dist/cache-ledger.js +33 -13
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-report-tool.d.ts +17 -0
- package/dist/cache-report-tool.js +40 -0
- package/dist/cache-report-tool.js.map +1 -0
- package/dist/cache-service.d.ts +70 -3
- package/dist/cache-service.js +178 -17
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +9 -0
- package/dist/channel/adapters/discord.js +52 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +8 -1
- package/dist/channel/adapters/telegram.js +74 -4
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +48 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +21 -0
- package/dist/classic-channel-manager.d.ts +10 -5
- package/dist/classic-channel-manager.js +12 -9
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +130 -31
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +119 -4
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +5 -0
- package/dist/daemon.js +273 -22
- package/dist/daemon.js.map +1 -1
- package/dist/decision-list.d.ts +25 -0
- package/dist/decision-list.js +30 -0
- package/dist/decision-list.js.map +1 -0
- package/dist/delivery-outbox.d.ts +10 -0
- package/dist/delivery-outbox.js +12 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-context.d.ts +15 -0
- package/dist/fleet-manager.d.ts +506 -11
- package/dist/fleet-manager.js +1784 -130
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-traffic.d.ts +74 -0
- package/dist/fleet-traffic.js +196 -0
- package/dist/fleet-traffic.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +492 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +476 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/human-delivery-receipts.d.ts +24 -1
- package/dist/human-delivery-receipts.js +64 -11
- package/dist/human-delivery-receipts.js.map +1 -1
- package/dist/instance-avatar.js +3 -0
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-config-impact.js +6 -1
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +9 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instance-names.d.ts +4 -0
- package/dist/instance-names.js +12 -0
- package/dist/instance-names.js.map +1 -0
- package/dist/instructions.d.ts +12 -1
- package/dist/instructions.js +13 -2
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +100 -8
- package/dist/locale.js.map +1 -1
- package/dist/ls-attach-resolve.d.ts +95 -0
- package/dist/ls-attach-resolve.js +174 -0
- package/dist/ls-attach-resolve.js.map +1 -0
- package/dist/memory-hogs.d.ts +27 -0
- package/dist/memory-hogs.js +124 -0
- package/dist/memory-hogs.js.map +1 -0
- package/dist/needs-you-hub.d.ts +4 -0
- package/dist/needs-you-hub.js +31 -6
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +1 -13
- package/dist/needs-you.js +3 -37
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +15 -0
- package/dist/outbound-handlers.js +152 -11
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +228 -4
- package/dist/outbound-schemas.js +147 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +21 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +9 -0
- package/dist/public-web-link.js +8 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +10 -1
- package/dist/quickstart-api.js +12 -4
- package/dist/quickstart-api.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +4 -0
- package/dist/reply-buttons-controller.js +2 -2
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/repo-worktree-guard.d.ts +36 -0
- package/dist/repo-worktree-guard.js +143 -0
- package/dist/repo-worktree-guard.js.map +1 -0
- package/dist/restart-progress.d.ts +65 -1
- package/dist/restart-progress.js +236 -10
- package/dist/restart-progress.js.map +1 -1
- package/dist/rollout-index.d.ts +5 -0
- package/dist/rollout-index.js +26 -5
- package/dist/rollout-index.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts +70 -1
- package/dist/scheduler/scheduler.js +256 -17
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/service-installer.d.ts +2 -1
- package/dist/service-installer.js +18 -3
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.d.ts +19 -0
- package/dist/settings-api.js +210 -2
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +112 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +32 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +5 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.js +16 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.d.ts +4 -6
- package/dist/tool-permissions.js +100 -2
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +17 -7
- package/dist/topic-commands.js +39 -21
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.d.ts +128 -0
- package/dist/topic-link.js +269 -0
- package/dist/topic-link.js.map +1 -0
- package/dist/types.d.ts +61 -1
- package/dist/ui/app-effects.js +233 -0
- package/dist/ui/chat-ambient.js +307 -0
- package/dist/ui/chat-effect-posters.js +42 -0
- package/dist/ui/chat-effects.js +160 -0
- package/dist/ui/chat-live-replies.js +58 -0
- package/dist/ui/chat-render.js +9 -6
- package/dist/ui/chat-store.js +9 -3
- package/dist/ui/chat-strings.js +70 -2
- package/dist/ui/chat-thread.js +191 -37
- package/dist/ui/chat-tour.js +1 -1
- package/dist/ui/effect-trial-controller.js +124 -0
- package/dist/ui/effect-trial-offer.js +31 -0
- package/dist/ui/effect-trial-panel.js +40 -0
- package/dist/ui/fleet-cache.js +23 -1
- package/dist/ui/fleet-canvas-layout.js +111 -0
- package/dist/ui/fleet-canvas.js +525 -0
- package/dist/ui/fleet-org.js +27 -12
- package/dist/ui/fleet-traffic.js +343 -0
- package/dist/ui/fleet-usage.js +401 -0
- package/dist/ui/image-lightbox.js +1 -1
- package/dist/ui/panel-chat.js +203 -57
- package/dist/ui/panel-details.js +20 -6
- package/dist/ui/panel-fleet.js +43 -18
- package/dist/ui/panel-log.js +6 -6
- package/dist/ui/panel-needs.js +3 -3
- package/dist/ui/panel-settings.js +34 -13
- package/dist/ui/preview-panel.js +25 -9
- package/dist/ui/preview.js +3 -2
- package/dist/ui/settings-appearance.js +349 -0
- package/dist/ui/settings-catalog.js +92 -0
- package/dist/ui/settings-dialogs.js +40 -12
- package/dist/ui/settings-effects.js +202 -0
- package/dist/ui/settings-guests.js +117 -0
- package/dist/ui/settings-learn.js +25 -0
- package/dist/ui/settings-model.js +15 -6
- package/dist/ui/settings-mods.js +271 -0
- package/dist/ui/settings-owner-effects.js +80 -0
- package/dist/ui/settings-strings.js +333 -10
- package/dist/ui/settings-web-authority.js +91 -0
- package/dist/ui/shared/agend-auth.js +28 -4
- package/dist/ui/shared/agent-widget.js +248 -0
- package/dist/ui/shared/app-i18n.js +6 -2
- package/dist/ui/shared/app-prefs.js +34 -13
- package/dist/ui/shared/app-route.js +24 -2
- package/dist/ui/shared/app-session.js +3 -1
- package/dist/ui/shared/app-shell.js +102 -23
- package/dist/ui/shared/app-store.js +28 -1
- package/dist/ui/shared/app-stream.js +14 -7
- package/dist/ui/shared/app-themes.js +206 -0
- package/dist/ui/shared/app.css +565 -4
- package/dist/ui/shared/app.js +23 -10
- package/dist/ui/shared/card-strings.js +32 -0
- package/dist/ui/shared/effect-app-runtime.js +297 -0
- package/dist/ui/shared/effect-baseline.js +64 -0
- package/dist/ui/shared/effect-chat-runtime.js +167 -0
- package/dist/ui/shared/effect-cursor.js +125 -0
- package/dist/ui/shared/effect-frame.js +146 -0
- package/dist/ui/shared/effect-geometry.js +137 -0
- package/dist/ui/shared/effect-host-style.js +10 -0
- package/dist/ui/shared/effect-owner-regions.js +74 -0
- package/dist/ui/shared/effect-owner-source.js +115 -0
- package/dist/ui/shared/effect-policy.js +151 -0
- package/dist/ui/shared/effect-poster-reader.js +98 -0
- package/dist/ui/shared/effect-posters.js +117 -0
- package/dist/ui/shared/effect-profiler.js +184 -0
- package/dist/ui/shared/effect-profiles.js +65 -0
- package/dist/ui/shared/effect-registry.js +535 -0
- package/dist/ui/shared/effect-runtime-loader.js +234 -0
- package/dist/ui/shared/effect-safety.js +153 -0
- package/dist/ui/shared/effect-stock-snapshot.js +43 -0
- package/dist/ui/shared/effect-stock-trust.js +217 -0
- package/dist/ui/shared/effect-trial-shadow.js +62 -0
- package/dist/ui/shared/effect-trial-source.js +103 -0
- package/dist/ui/shared/effect-trial-stage.js +170 -0
- package/dist/ui/shared/effect-trial.css +40 -0
- package/dist/ui/shared/fx-motion.js +67 -0
- package/dist/ui/shared/header-tools.js +1 -1
- package/dist/ui/shared/identity-card.js +273 -0
- package/dist/ui/shared/identity-color.js +261 -0
- package/dist/ui/shared/instance-nav.js +215 -41
- package/dist/ui/shared/owner-css.js +221 -0
- package/dist/ui/shared/owner-effect-prefs.js +117 -0
- package/dist/ui/shared/panel-view.js +103 -10
- package/dist/ui/shared/signin.js +2 -0
- package/dist/ui/shared/tokens.css +9 -3
- package/dist/ui/shared/ui-dialog.js +1 -1
- package/dist/ui/shared/ui-icons.js +3 -0
- package/dist/ui/shared/ui-menu.js +1 -1
- package/dist/ui/shared/ui-toast.js +1 -1
- package/dist/ui/shared/usage-dialog.js +22 -2
- package/dist/ui/shared/view-strings.js +98 -2
- package/dist/ui/shared/web-authorize.js +151 -0
- package/dist/ui/signin.html +1 -0
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/usage/providers.d.ts +28 -0
- package/dist/usage/providers.js +126 -36
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage-report.d.ts +158 -0
- package/dist/usage-report.js +226 -0
- package/dist/usage-report.js.map +1 -0
- package/dist/usage-rollup.d.ts +51 -0
- package/dist/usage-rollup.js +168 -0
- package/dist/usage-rollup.js.map +1 -0
- package/dist/view-api.d.ts +68 -0
- package/dist/view-api.js +249 -8
- package/dist/view-api.js.map +1 -1
- package/dist/web-ambient.d.ts +72 -0
- package/dist/web-ambient.js +161 -0
- package/dist/web-ambient.js.map +1 -0
- package/dist/web-api.d.ts +111 -5
- package/dist/web-api.js +1235 -58
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-limits.d.ts +82 -0
- package/dist/web-appearance-limits.js +83 -0
- package/dist/web-appearance-limits.js.map +1 -0
- package/dist/web-appearance-policy.d.ts +64 -0
- package/dist/web-appearance-policy.js +87 -0
- package/dist/web-appearance-policy.js.map +1 -0
- package/dist/web-appearance-tools.d.ts +53 -0
- package/dist/web-appearance-tools.js +124 -0
- package/dist/web-appearance-tools.js.map +1 -0
- package/dist/web-appearance-view.d.ts +124 -0
- package/dist/web-appearance-view.js +278 -0
- package/dist/web-appearance-view.js.map +1 -0
- package/dist/web-appearance.d.ts +93 -0
- package/dist/web-appearance.js +373 -0
- package/dist/web-appearance.js.map +1 -0
- package/dist/web-asset-parts.d.ts +93 -0
- package/dist/web-asset-parts.js +133 -0
- package/dist/web-asset-parts.js.map +1 -0
- package/dist/web-auth.js +20 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-card.d.ts +110 -0
- package/dist/web-card.js +266 -0
- package/dist/web-card.js.map +1 -0
- package/dist/web-channel-echo.d.ts +7 -1
- package/dist/web-channel-echo.js +6 -2
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +59 -0
- package/dist/web-chat-history.js +102 -4
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +1 -0
- package/dist/web-effect-bootstrap.d.ts +12 -0
- package/dist/web-effect-bootstrap.js +49 -0
- package/dist/web-effect-bootstrap.js.map +1 -0
- package/dist/web-effect-bundle.d.ts +28 -0
- package/dist/web-effect-bundle.js +171 -0
- package/dist/web-effect-bundle.js.map +1 -0
- package/dist/web-effect-canonical.d.ts +8 -0
- package/dist/web-effect-canonical.js +27 -0
- package/dist/web-effect-canonical.js.map +1 -0
- package/dist/web-effect-library.d.ts +114 -0
- package/dist/web-effect-library.js +530 -0
- package/dist/web-effect-library.js.map +1 -0
- package/dist/web-effect-playback.d.ts +30 -0
- package/dist/web-effect-playback.js +71 -0
- package/dist/web-effect-playback.js.map +1 -0
- package/dist/web-effect-poster.d.ts +8 -0
- package/dist/web-effect-poster.js +52 -0
- package/dist/web-effect-poster.js.map +1 -0
- package/dist/web-effect-profiles.d.ts +118 -0
- package/dist/web-effect-profiles.js +180 -0
- package/dist/web-effect-profiles.js.map +1 -0
- package/dist/web-effect-service.d.ts +93 -0
- package/dist/web-effect-service.js +335 -0
- package/dist/web-effect-service.js.map +1 -0
- package/dist/web-effect-tools.d.ts +48 -0
- package/dist/web-effect-tools.js +129 -0
- package/dist/web-effect-tools.js.map +1 -0
- package/dist/web-effect-trials.d.ts +86 -0
- package/dist/web-effect-trials.js +204 -0
- package/dist/web-effect-trials.js.map +1 -0
- package/dist/web-effect-upload.d.ts +27 -0
- package/dist/web-effect-upload.js +185 -0
- package/dist/web-effect-upload.js.map +1 -0
- package/dist/web-effect-worker.d.ts +8 -0
- package/dist/web-effect-worker.js +170 -0
- package/dist/web-effect-worker.js.map +1 -0
- package/dist/web-effect.d.ts +103 -0
- package/dist/web-effect.js +153 -0
- package/dist/web-effect.js.map +1 -0
- package/dist/web-guest-access.d.ts +125 -0
- package/dist/web-guest-access.js +234 -0
- package/dist/web-guest-access.js.map +1 -0
- package/dist/web-guest-rooms.d.ts +82 -0
- package/dist/web-guest-rooms.js +135 -0
- package/dist/web-guest-rooms.js.map +1 -0
- package/dist/web-identity-color.d.ts +87 -0
- package/dist/web-identity-color.js +209 -0
- package/dist/web-identity-color.js.map +1 -0
- package/dist/web-identity-palette.d.ts +113 -0
- package/dist/web-identity-palette.js +84 -0
- package/dist/web-identity-palette.js.map +1 -0
- package/dist/web-image-meta.d.ts +25 -0
- package/dist/web-image-meta.js +200 -0
- package/dist/web-image-meta.js.map +1 -0
- package/dist/web-login.d.ts +47 -0
- package/dist/web-login.js +115 -0
- package/dist/web-login.js.map +1 -1
- package/dist/web-mod-agent.d.ts +50 -0
- package/dist/web-mod-agent.js +207 -0
- package/dist/web-mod-agent.js.map +1 -0
- package/dist/web-mod-bundle.d.ts +157 -0
- package/dist/web-mod-bundle.js +529 -0
- package/dist/web-mod-bundle.js.map +1 -0
- package/dist/web-mod-export.d.ts +68 -0
- package/dist/web-mod-export.js +100 -0
- package/dist/web-mod-export.js.map +1 -0
- package/dist/web-mod-store.d.ts +289 -0
- package/dist/web-mod-store.js +638 -0
- package/dist/web-mod-store.js.map +1 -0
- package/dist/web-org.d.ts +3 -3
- package/dist/web-org.js +11 -17
- package/dist/web-org.js.map +1 -1
- package/dist/web-owner-css-store.d.ts +180 -0
- package/dist/web-owner-css-store.js +384 -0
- package/dist/web-owner-css-store.js.map +1 -0
- package/dist/web-owner-css.d.ts +58 -0
- package/dist/web-owner-css.js +552 -0
- package/dist/web-owner-css.js.map +1 -0
- package/dist/web-prefs.d.ts +62 -0
- package/dist/web-prefs.js +232 -36
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +15 -3
- package/dist/web-preview.js +28 -9
- package/dist/web-preview.js.map +1 -1
- package/dist/web-returning-device.d.ts +41 -0
- package/dist/web-returning-device.js +57 -5
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-session-authorization.d.ts +132 -0
- package/dist/web-session-authorization.js +193 -0
- package/dist/web-session-authorization.js.map +1 -0
- package/dist/web-session.d.ts +70 -10
- package/dist/web-session.js +137 -22
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +67 -2
- package/dist/web-settings-patch.js +196 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +89 -0
- package/dist/web-settings-tools.js +270 -0
- package/dist/web-settings-tools.js.map +1 -0
- package/dist/web-shell-routes.d.ts +2 -2
- package/dist/web-shell-routes.js +2 -2
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-theme-store.d.ts +257 -0
- package/dist/web-theme-store.js +627 -0
- package/dist/web-theme-store.js.map +1 -0
- package/dist/web-theme.d.ts +123 -0
- package/dist/web-theme.js +416 -0
- package/dist/web-theme.js.map +1 -0
- package/dist/web-widget.d.ts +72 -0
- package/dist/web-widget.js +245 -0
- package/dist/web-widget.js.map +1 -0
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
- package/templates/systemd.service.ejs +8 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { AppearanceLimits } from "./web-appearance-limits.js";
|
|
2
|
+
import { type MediaKind } from "./web-image-meta.js";
|
|
3
|
+
import { type Theme } from "./web-theme.js";
|
|
4
|
+
import { type AmbientKind, type AmbientSpec } from "./web-ambient.js";
|
|
5
|
+
import { type WidgetData, type WidgetSlot, type WidgetSpec } from "./web-widget.js";
|
|
6
|
+
export declare const MOD_FORMAT = "agend-mod";
|
|
7
|
+
export declare const MOD_MAX_BYTES: number;
|
|
8
|
+
export declare const MOD_MAX_FILES = 128;
|
|
9
|
+
export declare const MOD_MAX_EFFECTS = 8;
|
|
10
|
+
/** A path inside a bundle: lowercase, no leading dot or slash, no `..` segment, no empty segment. */
|
|
11
|
+
export declare const MOD_PATH: RegExp;
|
|
12
|
+
export interface ModProblem {
|
|
13
|
+
path: string;
|
|
14
|
+
problem: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ModManifest {
|
|
17
|
+
name: string;
|
|
18
|
+
version: string;
|
|
19
|
+
title: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
author?: string;
|
|
22
|
+
requires?: {
|
|
23
|
+
agend: string;
|
|
24
|
+
};
|
|
25
|
+
contents: {
|
|
26
|
+
theme?: string;
|
|
27
|
+
identity?: string;
|
|
28
|
+
widgets?: Partial<Record<WidgetSlot, string>>;
|
|
29
|
+
/** #2117: `{ loop?, click? }`, each a set_web_ambient spec whose glyph pictures are `{ asset: <path in the bundle> }`. */
|
|
30
|
+
ambient?: string;
|
|
31
|
+
effects?: string[];
|
|
32
|
+
ownerCss?: {
|
|
33
|
+
css: string;
|
|
34
|
+
assets?: string[];
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** An effect entry's files (relative to its directory), as the effect runtime's checker takes them. */
|
|
39
|
+
export type EffectFiles = ReadonlyMap<string, Uint8Array>;
|
|
40
|
+
/** What the plan shows of an effect entry (the runtime's checker says it). */
|
|
41
|
+
export interface EffectSummary {
|
|
42
|
+
id: string;
|
|
43
|
+
title: string;
|
|
44
|
+
bytes: number;
|
|
45
|
+
regions: string[];
|
|
46
|
+
wants: string[];
|
|
47
|
+
params: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The effect runtime's side, as the bundle needs it (CW1, Sol's B2): the pure entry checker and param resolver. Null
|
|
51
|
+
* while this fleet has no effect runtime: a bundle with effects is then refused (effects_unavailable).
|
|
52
|
+
*/
|
|
53
|
+
export interface ModEffectChecker {
|
|
54
|
+
check(files: EffectFiles): {
|
|
55
|
+
ok: true;
|
|
56
|
+
summary: EffectSummary;
|
|
57
|
+
manifest: unknown;
|
|
58
|
+
} | {
|
|
59
|
+
ok: false;
|
|
60
|
+
problems: ModProblem[];
|
|
61
|
+
};
|
|
62
|
+
/** Resolve a placement's overrides against the entry's params: the non-default ones, or refused. */
|
|
63
|
+
params(manifest: unknown, overrides: unknown): {
|
|
64
|
+
ok: true;
|
|
65
|
+
overrides: Record<string, unknown>;
|
|
66
|
+
} | {
|
|
67
|
+
ok: false;
|
|
68
|
+
error: string;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export interface ModPicture {
|
|
72
|
+
path: string;
|
|
73
|
+
slot: "background" | "avatar" | "glyph";
|
|
74
|
+
id: string;
|
|
75
|
+
kind: MediaKind;
|
|
76
|
+
kept: Uint8Array;
|
|
77
|
+
}
|
|
78
|
+
export interface CheckedMod {
|
|
79
|
+
id: string;
|
|
80
|
+
manifest: ModManifest;
|
|
81
|
+
/** The raw file's size. */
|
|
82
|
+
bytes: number;
|
|
83
|
+
theme?: {
|
|
84
|
+
theme: Theme;
|
|
85
|
+
pictures: ModPicture[];
|
|
86
|
+
effects: Array<{
|
|
87
|
+
effect: string;
|
|
88
|
+
params: Record<string, unknown>;
|
|
89
|
+
}>;
|
|
90
|
+
};
|
|
91
|
+
identity?: {
|
|
92
|
+
tagline?: string;
|
|
93
|
+
bio?: string;
|
|
94
|
+
};
|
|
95
|
+
widgets: Partial<Record<WidgetSlot, {
|
|
96
|
+
spec: WidgetSpec;
|
|
97
|
+
data: WidgetData;
|
|
98
|
+
}>>;
|
|
99
|
+
/** #2117: the ambients (one per kind) and the glyph pictures they name. */
|
|
100
|
+
ambient?: {
|
|
101
|
+
specs: Partial<Record<AmbientKind, AmbientSpec>>;
|
|
102
|
+
pictures: ModPicture[];
|
|
103
|
+
};
|
|
104
|
+
effects: Array<{
|
|
105
|
+
path: string;
|
|
106
|
+
files: EffectFiles;
|
|
107
|
+
summary: EffectSummary;
|
|
108
|
+
manifest: unknown;
|
|
109
|
+
}>;
|
|
110
|
+
ownerCss?: {
|
|
111
|
+
text: string;
|
|
112
|
+
assets: Array<{
|
|
113
|
+
path: string;
|
|
114
|
+
id: string;
|
|
115
|
+
kind: MediaKind;
|
|
116
|
+
kept: Uint8Array;
|
|
117
|
+
}>;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export interface ModContext {
|
|
121
|
+
/** The running AgEnD version (`requires.agend` is checked against it); null: unknown, a `requires` is refused. */
|
|
122
|
+
agendVersion: string | null;
|
|
123
|
+
limits: AppearanceLimits;
|
|
124
|
+
effects: ModEffectChecker | null;
|
|
125
|
+
/**
|
|
126
|
+
* #2017: whether a theme's placements must name effects that play in chat. On for a new import or proposal (the
|
|
127
|
+
* default); off when an installed mod's file is read again — what was installed before the rule stays as it was.
|
|
128
|
+
*/
|
|
129
|
+
placementRegions?: boolean;
|
|
130
|
+
}
|
|
131
|
+
/** Stable JSON: keys sorted at every level. */
|
|
132
|
+
export declare function canonicalJson(v: unknown): string;
|
|
133
|
+
/** A bundle's identity: the first 32 hex of the sha256 of its canonical manifest and sorted [path, sha256] list. */
|
|
134
|
+
export declare function modBundleId(manifest: unknown, files: Iterable<[string, string]>): string;
|
|
135
|
+
/**
|
|
136
|
+
* Parse and check a whole bundle. `text` is the file as uploaded (UTF-8 JSON). All or nothing: the first problems are
|
|
137
|
+
* returned (at most 20), and nothing in a refused bundle is ever used.
|
|
138
|
+
*/
|
|
139
|
+
export declare function checkModBundle(text: string, ctx: ModContext): {
|
|
140
|
+
ok: true;
|
|
141
|
+
mod: CheckedMod;
|
|
142
|
+
} | {
|
|
143
|
+
ok: false;
|
|
144
|
+
problems: ModProblem[];
|
|
145
|
+
};
|
|
146
|
+
/** A file's entry for a bundle being built (export). */
|
|
147
|
+
export declare function modFile(bytes: Uint8Array): {
|
|
148
|
+
sha256: string;
|
|
149
|
+
b64: string;
|
|
150
|
+
};
|
|
151
|
+
/** The extension a picture or CSS asset is exported under. */
|
|
152
|
+
export declare function modAssetName(base: string, kind: MediaKind): string;
|
|
153
|
+
/**
|
|
154
|
+
* The effect runtime's checker (CW1: Sol's src/web-effect-bundle.ts and src/web-effect.ts) as a bundle uses it. Used
|
|
155
|
+
* once the fleet has an effect library to install into; until then the fleet passes null and effects are refused.
|
|
156
|
+
*/
|
|
157
|
+
export declare function cw1EffectChecker(): ModEffectChecker;
|
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1629 r4 §4 (option A): a mod bundle — one JSON file, `<name>-<version>.agendmod.json`, at most 8 MiB, that carries a
|
|
3
|
+
* look: a theme (with its pictures), identity defaults (a card's tagline and bio — never card pictures), widget specs
|
|
4
|
+
* with their values, effect entries (CW1: Sol's runtime validates them), and a person's own CSS (with its assets).
|
|
5
|
+
* No zip: no extraction, no symlinks, no paths outside the file.
|
|
6
|
+
*
|
|
7
|
+
* Checking is pure and whole: closed schemas everywhere; every path matches MOD_PATH with no `..`; every file's sha256
|
|
8
|
+
* matches its bytes and every file is named by exactly one part; each part goes through its own validator (the theme's
|
|
9
|
+
* checkTheme and picture checks, web-widget.ts, the owner-CSS checker, the effect entry checker). Nothing is written
|
|
10
|
+
* here. The bundle's identity is the hash of its canonical manifest and file list — `version` is for people.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { compareVersions } from "./agent-whats-new.js";
|
|
14
|
+
import { checkEffectEntry } from "./web-effect-bundle.js";
|
|
15
|
+
import { resolveEffectParams } from "./web-effect.js";
|
|
16
|
+
import { checkOwnerCss, OWNER_CSS_MAX_BYTES } from "./web-owner-css.js";
|
|
17
|
+
import { MEDIA_EXT, sniffMedia, stripMetadata } from "./web-image-meta.js";
|
|
18
|
+
import { canonicalBio, canonicalTagline, canonicalText, checkTheme } from "./web-theme.js";
|
|
19
|
+
import { GLYPH_ASSET_LIMIT, prepareThemeAsset } from "./web-theme-store.js";
|
|
20
|
+
import { AMBIENT_KINDS, ambientKind, checkAmbient } from "./web-ambient.js";
|
|
21
|
+
import { checkWidgetData, checkWidgetSpec, WIDGET_SLOTS } from "./web-widget.js";
|
|
22
|
+
export const MOD_FORMAT = "agend-mod";
|
|
23
|
+
export const MOD_MAX_BYTES = 8 * 1024 * 1024;
|
|
24
|
+
export const MOD_MAX_FILES = 128;
|
|
25
|
+
export const MOD_MAX_EFFECTS = 8;
|
|
26
|
+
/** A path inside a bundle: lowercase, no leading dot or slash, no `..` segment, no empty segment. */
|
|
27
|
+
export const MOD_PATH = /^[a-z0-9][a-z0-9._/-]{0,127}$/;
|
|
28
|
+
const SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
29
|
+
const SEMVER = /^(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})$/;
|
|
30
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
31
|
+
const B64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
32
|
+
/** Owner-CSS assets a bundle may carry (the owner-CSS store's own kinds). */
|
|
33
|
+
const CSS_ASSET_KINDS = new Set(["png", "jpeg", "webp", "gif", "woff2"]);
|
|
34
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
|
|
35
|
+
const sha256 = (b) => createHash("sha256").update(b).digest("hex");
|
|
36
|
+
const hash32 = (b) => sha256(b).slice(0, 32);
|
|
37
|
+
const utf8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
|
|
38
|
+
function pathOk(p) {
|
|
39
|
+
return typeof p === "string" && MOD_PATH.test(p) && !p.split("/").some(seg => seg === "" || seg === "." || seg === "..") && !p.endsWith("/");
|
|
40
|
+
}
|
|
41
|
+
/** Stable JSON: keys sorted at every level. */
|
|
42
|
+
export function canonicalJson(v) {
|
|
43
|
+
const norm = (x) => Array.isArray(x) ? x.map(norm)
|
|
44
|
+
: x !== null && typeof x === "object" ? Object.fromEntries(Object.keys(x).sort().map(k => [k, norm(x[k])])) : x;
|
|
45
|
+
return JSON.stringify(norm(v));
|
|
46
|
+
}
|
|
47
|
+
/** A bundle's identity: the first 32 hex of the sha256 of its canonical manifest and sorted [path, sha256] list. */
|
|
48
|
+
export function modBundleId(manifest, files) {
|
|
49
|
+
const list = [...files].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
50
|
+
return sha256(canonicalJson({ manifest, files: list })).slice(0, 32);
|
|
51
|
+
}
|
|
52
|
+
function checkManifest(raw, ctx, bad) {
|
|
53
|
+
if (!isObj(raw)) {
|
|
54
|
+
bad("manifest", "an object");
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const known = ["name", "version", "title", "description", "author", "requires", "contents"];
|
|
58
|
+
for (const k of Object.keys(raw))
|
|
59
|
+
if (!known.includes(k))
|
|
60
|
+
bad(`manifest.${k}`, "unknown key");
|
|
61
|
+
const m = {};
|
|
62
|
+
if (typeof raw.name === "string" && SLUG.test(raw.name))
|
|
63
|
+
m.name = raw.name;
|
|
64
|
+
else
|
|
65
|
+
bad("manifest.name", "a-z, 0-9 and - (1..64)");
|
|
66
|
+
if (typeof raw.version === "string" && SEMVER.test(raw.version))
|
|
67
|
+
m.version = raw.version;
|
|
68
|
+
else
|
|
69
|
+
bad("manifest.version", "MAJOR.MINOR.PATCH");
|
|
70
|
+
const title = canonicalText(raw.title, 1, 60);
|
|
71
|
+
if (title === null)
|
|
72
|
+
bad("manifest.title", "1..60 characters on one line");
|
|
73
|
+
else
|
|
74
|
+
m.title = title;
|
|
75
|
+
for (const [k, max] of [["description", 280], ["author", 60]]) {
|
|
76
|
+
if (raw[k] === undefined)
|
|
77
|
+
continue;
|
|
78
|
+
const t = canonicalText(raw[k], 1, max);
|
|
79
|
+
if (t === null)
|
|
80
|
+
bad(`manifest.${k}`, `1..${max} characters on one line`);
|
|
81
|
+
else
|
|
82
|
+
m[k] = t;
|
|
83
|
+
}
|
|
84
|
+
if (raw.requires !== undefined) {
|
|
85
|
+
const r = raw.requires;
|
|
86
|
+
const want = isObj(r) && Object.keys(r).length === 1 && typeof r.agend === "string" ? r.agend : null;
|
|
87
|
+
const min = want ? /^>=(\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?)$/.exec(want) : null;
|
|
88
|
+
if (!min)
|
|
89
|
+
bad("manifest.requires", "{ agend: \">=MAJOR.MINOR.PATCH\" }");
|
|
90
|
+
else {
|
|
91
|
+
const cmp = ctx.agendVersion ? compareVersions(ctx.agendVersion, min[1]) : null;
|
|
92
|
+
if (cmp === null || cmp < 0)
|
|
93
|
+
bad("manifest.requires", `needs AgEnD ${want}; this fleet runs ${ctx.agendVersion ?? "an unknown version"}`);
|
|
94
|
+
else
|
|
95
|
+
m.requires = { agend: want };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const c = raw.contents;
|
|
99
|
+
if (!isObj(c)) {
|
|
100
|
+
bad("manifest.contents", "an object");
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
for (const k of Object.keys(c))
|
|
104
|
+
if (!["theme", "identity", "widgets", "ambient", "effects", "ownerCss"].includes(k))
|
|
105
|
+
bad(`manifest.contents.${k}`, "unknown key");
|
|
106
|
+
const contents = {};
|
|
107
|
+
for (const k of ["theme", "identity", "ambient"])
|
|
108
|
+
if (c[k] !== undefined) {
|
|
109
|
+
if (pathOk(c[k]))
|
|
110
|
+
contents[k] = c[k];
|
|
111
|
+
else
|
|
112
|
+
bad(`manifest.contents.${k}`, "a path in the bundle");
|
|
113
|
+
}
|
|
114
|
+
if (c.widgets !== undefined) {
|
|
115
|
+
if (!isObj(c.widgets))
|
|
116
|
+
bad("manifest.contents.widgets", "{ chat_header?, details?, sidebar? }");
|
|
117
|
+
else {
|
|
118
|
+
const w = {};
|
|
119
|
+
for (const [slot, p] of Object.entries(c.widgets)) {
|
|
120
|
+
if (!WIDGET_SLOTS.includes(slot))
|
|
121
|
+
bad(`manifest.contents.widgets.${slot}`, "chat_header, details or sidebar");
|
|
122
|
+
else if (!pathOk(p))
|
|
123
|
+
bad(`manifest.contents.widgets.${slot}`, "a path in the bundle");
|
|
124
|
+
else
|
|
125
|
+
w[slot] = p;
|
|
126
|
+
}
|
|
127
|
+
contents.widgets = w;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (c.effects !== undefined) {
|
|
131
|
+
if (!Array.isArray(c.effects) || c.effects.length > MOD_MAX_EFFECTS || new Set(c.effects).size !== c.effects.length || !c.effects.every(pathOk))
|
|
132
|
+
bad("manifest.contents.effects", `at most ${MOD_MAX_EFFECTS} distinct directory paths`);
|
|
133
|
+
else
|
|
134
|
+
contents.effects = c.effects;
|
|
135
|
+
}
|
|
136
|
+
if (c.ownerCss !== undefined) {
|
|
137
|
+
const o = c.ownerCss;
|
|
138
|
+
if (!isObj(o) || Object.keys(o).some(k => k !== "css" && k !== "assets") || !pathOk(o.css) || (o.assets !== undefined && (!Array.isArray(o.assets) || !o.assets.every(pathOk) || new Set(o.assets).size !== o.assets.length)))
|
|
139
|
+
bad("manifest.contents.ownerCss", "{ css: path, assets?: [path] }");
|
|
140
|
+
else
|
|
141
|
+
contents.ownerCss = { css: o.css, ...(o.assets ? { assets: o.assets } : {}) };
|
|
142
|
+
}
|
|
143
|
+
if (!Object.keys(contents).length)
|
|
144
|
+
bad("manifest.contents", "names nothing");
|
|
145
|
+
m.contents = contents;
|
|
146
|
+
return m;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Parse and check a whole bundle. `text` is the file as uploaded (UTF-8 JSON). All or nothing: the first problems are
|
|
150
|
+
* returned (at most 20), and nothing in a refused bundle is ever used.
|
|
151
|
+
*/
|
|
152
|
+
export function checkModBundle(text, ctx) {
|
|
153
|
+
const problems = [];
|
|
154
|
+
const bad = (path, problem) => { if (problems.length < 20)
|
|
155
|
+
problems.push({ path, problem }); };
|
|
156
|
+
const fail = () => ({ ok: false, problems });
|
|
157
|
+
const size = Buffer.byteLength(text, "utf8");
|
|
158
|
+
if (size > MOD_MAX_BYTES) {
|
|
159
|
+
bad("", `at most ${MOD_MAX_BYTES / 1024 / 1024} MiB`);
|
|
160
|
+
return fail();
|
|
161
|
+
}
|
|
162
|
+
let doc;
|
|
163
|
+
try {
|
|
164
|
+
doc = JSON.parse(text);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
bad("", "not JSON");
|
|
168
|
+
return fail();
|
|
169
|
+
}
|
|
170
|
+
if (!isObj(doc)) {
|
|
171
|
+
bad("", "an object { format, v, manifest, files }");
|
|
172
|
+
return fail();
|
|
173
|
+
}
|
|
174
|
+
for (const k of Object.keys(doc))
|
|
175
|
+
if (!["format", "v", "manifest", "files"].includes(k))
|
|
176
|
+
bad(k, "unknown key");
|
|
177
|
+
if (doc.format !== MOD_FORMAT)
|
|
178
|
+
bad("format", `must be "${MOD_FORMAT}"`);
|
|
179
|
+
if (doc.v !== 1)
|
|
180
|
+
bad("v", "must be 1");
|
|
181
|
+
const manifest = checkManifest(doc.manifest, ctx, bad);
|
|
182
|
+
// Every file: a good path, its bytes, its sha256.
|
|
183
|
+
const files = new Map();
|
|
184
|
+
const shas = new Map();
|
|
185
|
+
if (!isObj(doc.files))
|
|
186
|
+
bad("files", "an object { path: { sha256, b64 } }");
|
|
187
|
+
else {
|
|
188
|
+
const entries = Object.entries(doc.files);
|
|
189
|
+
if (entries.length > MOD_MAX_FILES)
|
|
190
|
+
bad("files", `at most ${MOD_MAX_FILES}`);
|
|
191
|
+
else
|
|
192
|
+
for (const [p, f] of entries) {
|
|
193
|
+
if (!pathOk(p)) {
|
|
194
|
+
bad(`files.${String(p).slice(0, 80)}`, "a lowercase path: a-z 0-9 . _ - /, no .. or empty segment");
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!isObj(f) || Object.keys(f).length !== 2 || typeof f.sha256 !== "string" || !SHA256.test(f.sha256) || typeof f.b64 !== "string" || !B64.test(f.b64) || f.b64.length % 4 !== 0) {
|
|
198
|
+
bad(`files.${p}`, "{ sha256, b64 }");
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const bytes = Buffer.from(f.b64, "base64");
|
|
202
|
+
if (sha256(bytes) !== f.sha256) {
|
|
203
|
+
bad(`files.${p}`, "sha256 does not match its bytes");
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
files.set(p, bytes);
|
|
207
|
+
shas.set(p, f.sha256);
|
|
208
|
+
}
|
|
209
|
+
// A path that is both a file and a directory prefix of another would be two things.
|
|
210
|
+
for (const p of files.keys())
|
|
211
|
+
if ([...files.keys()].some(q => q.startsWith(`${p}/`)))
|
|
212
|
+
bad(`files.${p}`, "both a file and a directory");
|
|
213
|
+
}
|
|
214
|
+
if (problems.length || !manifest)
|
|
215
|
+
return fail();
|
|
216
|
+
const used = new Set();
|
|
217
|
+
const take = (p, where) => {
|
|
218
|
+
const b = files.get(p);
|
|
219
|
+
if (!b) {
|
|
220
|
+
bad(where, `no file ${p}`);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (used.has(p)) {
|
|
224
|
+
bad(where, `${p} is already used by another part`);
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
used.add(p);
|
|
228
|
+
return b;
|
|
229
|
+
};
|
|
230
|
+
const json = (p, where) => {
|
|
231
|
+
const b = take(p, where);
|
|
232
|
+
if (!b)
|
|
233
|
+
return undefined;
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(utf8.decode(b));
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
bad(where, `${p}: not UTF-8 JSON`);
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
const mod = { id: modBundleId(manifest, shas), manifest, bytes: size, widgets: {}, effects: [] };
|
|
243
|
+
const c = manifest.contents;
|
|
244
|
+
// Effects first: the theme's placements name them.
|
|
245
|
+
const effectPaths = new Set(c.effects ?? []);
|
|
246
|
+
if (c.effects?.length) {
|
|
247
|
+
if (!ctx.effects)
|
|
248
|
+
bad("manifest.contents.effects", "effects_unavailable: this fleet has no effect runtime yet");
|
|
249
|
+
else
|
|
250
|
+
for (const dir of c.effects) {
|
|
251
|
+
const entry = new Map();
|
|
252
|
+
for (const [p, b] of files)
|
|
253
|
+
if (p.startsWith(`${dir}/`)) {
|
|
254
|
+
entry.set(p.slice(dir.length + 1), b);
|
|
255
|
+
used.add(p);
|
|
256
|
+
}
|
|
257
|
+
if (!entry.size) {
|
|
258
|
+
bad(`effects.${dir}`, "no files under it");
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const r = ctx.effects.check(entry);
|
|
262
|
+
if (!r.ok) {
|
|
263
|
+
for (const q of r.problems)
|
|
264
|
+
bad(`effects.${dir}/${q.path}`, q.problem);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
mod.effects.push({ path: dir, files: entry, summary: r.summary, manifest: r.manifest });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (c.theme) {
|
|
271
|
+
const raw = json(c.theme, "manifest.contents.theme");
|
|
272
|
+
if (raw !== undefined) {
|
|
273
|
+
if (!isObj(raw))
|
|
274
|
+
bad("theme", "an object");
|
|
275
|
+
else {
|
|
276
|
+
const { effects: placed, ...look } = raw;
|
|
277
|
+
if (look.card !== undefined)
|
|
278
|
+
bad("theme.card", "a card travels as identity (tagline, bio) — never in the theme, and its pictures never in a bundle");
|
|
279
|
+
// Pictures are named by path in a bundle: each is checked as an upload to its slot, then named by its id.
|
|
280
|
+
const pictures = [];
|
|
281
|
+
const picture = (p, slot, where) => {
|
|
282
|
+
if (!pathOk(p)) {
|
|
283
|
+
bad(where, "a path in the bundle");
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const b = take(p, where);
|
|
287
|
+
if (!b)
|
|
288
|
+
return null;
|
|
289
|
+
const r = prepareThemeAsset(slot, b, ctx.limits);
|
|
290
|
+
if (!r.ok) {
|
|
291
|
+
bad(where, `${r.error}${r.detail ? `: ${r.detail}` : ""}`);
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
if (pictures.some(x => x.id === r.id)) {
|
|
295
|
+
bad(where, "the same picture as another slot's: a picture holds one slot");
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
pictures.push({ path: p, slot, id: r.id, kind: r.kind, kept: r.kept });
|
|
299
|
+
return r.id;
|
|
300
|
+
};
|
|
301
|
+
const input = { ...look };
|
|
302
|
+
if (isObj(look.background)) {
|
|
303
|
+
const id = picture(look.background.image, "background", "theme.background.image");
|
|
304
|
+
if (id)
|
|
305
|
+
input.background = { ...look.background, image: id };
|
|
306
|
+
}
|
|
307
|
+
if (look.avatar !== undefined) {
|
|
308
|
+
const id = picture(look.avatar, "avatar", "theme.avatar");
|
|
309
|
+
if (id)
|
|
310
|
+
input.avatar = id;
|
|
311
|
+
}
|
|
312
|
+
const checked = checkTheme(input, { assets: new Set(pictures.map(p => p.id)) });
|
|
313
|
+
if (!checked.ok)
|
|
314
|
+
for (const e of checked.errors)
|
|
315
|
+
bad(`theme.${e.path}`, e.problem);
|
|
316
|
+
const effects = [];
|
|
317
|
+
if (placed !== undefined) {
|
|
318
|
+
if (!Array.isArray(placed) || placed.length > 2)
|
|
319
|
+
bad("theme.effects", "at most 2 [{ effect, params? }]");
|
|
320
|
+
else
|
|
321
|
+
placed.forEach((p, i) => {
|
|
322
|
+
const where = `theme.effects.${i}`;
|
|
323
|
+
if (!isObj(p) || Object.keys(p).some(k => k !== "effect" && k !== "params") || typeof p.effect !== "string") {
|
|
324
|
+
bad(where, "{ effect: path, params? }");
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (!effectPaths.has(p.effect)) {
|
|
328
|
+
bad(where, `${p.effect} is not one of this bundle's effects`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const entry = mod.effects.find(e => e.path === p.effect);
|
|
332
|
+
if (!entry || !ctx.effects)
|
|
333
|
+
return; // its own problem is reported already
|
|
334
|
+
// #2017: a chat placement needs an effect that plays in chat. A non-chat effect may still come as a library
|
|
335
|
+
// entry (contents.effects) without being placed.
|
|
336
|
+
if (ctx.placementRegions !== false && !entry.summary.regions.includes("chat")) {
|
|
337
|
+
bad(where, `effect_region: ${p.effect}'s regions do not include chat`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const r = ctx.effects.params(entry.manifest, p.params ?? {});
|
|
341
|
+
if (!r.ok) {
|
|
342
|
+
bad(`${where}.params`, r.error);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (effects.some(e => e.effect === p.effect)) {
|
|
346
|
+
bad(where, "placed twice");
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
effects.push({ effect: p.effect, params: r.overrides });
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (checked.ok)
|
|
353
|
+
mod.theme = { theme: checked.theme, pictures, effects };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (c.identity) {
|
|
358
|
+
const raw = json(c.identity, "manifest.contents.identity");
|
|
359
|
+
if (raw !== undefined) {
|
|
360
|
+
if (!isObj(raw) || Object.keys(raw).some(k => k !== "tagline" && k !== "bio") || !Object.keys(raw).length)
|
|
361
|
+
bad("identity", "{ tagline?, bio? }");
|
|
362
|
+
else {
|
|
363
|
+
const id = {};
|
|
364
|
+
if (raw.tagline !== undefined) {
|
|
365
|
+
const t = canonicalTagline(raw.tagline);
|
|
366
|
+
if (t === null)
|
|
367
|
+
bad("identity.tagline", "one line, 1..60 characters");
|
|
368
|
+
else
|
|
369
|
+
id.tagline = t;
|
|
370
|
+
}
|
|
371
|
+
if (raw.bio !== undefined) {
|
|
372
|
+
const t = canonicalBio(raw.bio);
|
|
373
|
+
if (t === null)
|
|
374
|
+
bad("identity.bio", "1..280 characters, at most 3 line breaks");
|
|
375
|
+
else
|
|
376
|
+
id.bio = t;
|
|
377
|
+
}
|
|
378
|
+
mod.identity = id;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
for (const [slot, p] of Object.entries(c.widgets ?? {})) {
|
|
383
|
+
const raw = json(p, `manifest.contents.widgets.${slot}`);
|
|
384
|
+
if (raw === undefined)
|
|
385
|
+
continue;
|
|
386
|
+
if (!isObj(raw) || Object.keys(raw).length !== 2 || !("spec" in raw) || !("data" in raw)) {
|
|
387
|
+
bad(`widgets.${slot}`, "{ spec, data }");
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const spec = checkWidgetSpec(raw.spec, slot);
|
|
391
|
+
if (!spec.ok) {
|
|
392
|
+
for (const e of spec.errors)
|
|
393
|
+
bad(`widgets.${slot}.${e.path}`, e.problem);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const data = checkWidgetData(raw.data, spec.value);
|
|
397
|
+
if (!data.ok) {
|
|
398
|
+
for (const e of data.errors)
|
|
399
|
+
bad(`widgets.${slot}.${e.path}`, e.problem);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
mod.widgets[slot] = { spec: spec.value, data: { ...data.value } };
|
|
403
|
+
}
|
|
404
|
+
// #2117: the ambients, their glyph pictures named by path (each checked as an upload to the glyph slot).
|
|
405
|
+
if (c.ambient) {
|
|
406
|
+
const raw = json(c.ambient, "manifest.contents.ambient");
|
|
407
|
+
if (raw !== undefined) {
|
|
408
|
+
if (!isObj(raw) || !Object.keys(raw).length || Object.keys(raw).some(k => !AMBIENT_KINDS.includes(k)))
|
|
409
|
+
bad("ambient", "{ loop?, click? }");
|
|
410
|
+
else {
|
|
411
|
+
const pictures = [];
|
|
412
|
+
const specs = {};
|
|
413
|
+
for (const kind of AMBIENT_KINDS) {
|
|
414
|
+
const s = raw[kind];
|
|
415
|
+
if (s === undefined)
|
|
416
|
+
continue;
|
|
417
|
+
if (!isObj(s)) {
|
|
418
|
+
bad(`ambient.${kind}`, "a set_web_ambient spec");
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const glyphs = Array.isArray(s.glyphs) ? s.glyphs.map((g, i) => {
|
|
422
|
+
if (!isObj(g))
|
|
423
|
+
return g;
|
|
424
|
+
const where = `ambient.${kind}.glyphs.${i}.asset`;
|
|
425
|
+
if (Object.keys(g).length !== 1 || !pathOk(g.asset)) {
|
|
426
|
+
bad(where, "a path in the bundle");
|
|
427
|
+
return g;
|
|
428
|
+
}
|
|
429
|
+
const had = pictures.find(p => p.path === g.asset);
|
|
430
|
+
if (had)
|
|
431
|
+
return { asset: had.id };
|
|
432
|
+
const b = take(g.asset, where);
|
|
433
|
+
if (!b)
|
|
434
|
+
return g;
|
|
435
|
+
const r = prepareThemeAsset("glyph", b, ctx.limits);
|
|
436
|
+
if (!r.ok) {
|
|
437
|
+
bad(where, `${r.error}${r.detail ? `: ${r.detail}` : ""}`);
|
|
438
|
+
return g;
|
|
439
|
+
}
|
|
440
|
+
pictures.push({ path: g.asset, slot: "glyph", id: r.id, kind: r.kind, kept: r.kept });
|
|
441
|
+
return { asset: r.id };
|
|
442
|
+
}) : s.glyphs;
|
|
443
|
+
const checked = checkAmbient({ ...s, glyphs }, { glyphAsset: id => pictures.some(p => p.id === id) });
|
|
444
|
+
if (!checked.ok) {
|
|
445
|
+
for (const e of checked.errors)
|
|
446
|
+
bad(`ambient.${kind}.${e.path}`, e.problem);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (ambientKind(checked.value.preset) !== kind) {
|
|
450
|
+
bad(`ambient.${kind}.preset`, kind === "click" ? "burst-on-click" : "fall, rise or twinkle");
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
specs[kind] = checked.value;
|
|
454
|
+
}
|
|
455
|
+
if (pictures.length > GLYPH_ASSET_LIMIT.files)
|
|
456
|
+
bad("ambient", `at most ${GLYPH_ASSET_LIMIT.files} glyph pictures`);
|
|
457
|
+
else if (Object.keys(specs).length)
|
|
458
|
+
mod.ambient = { specs, pictures };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (c.ownerCss) {
|
|
463
|
+
const assets = [];
|
|
464
|
+
for (const p of c.ownerCss.assets ?? []) {
|
|
465
|
+
const b = take(p, `ownerCss.assets.${p}`);
|
|
466
|
+
if (!b)
|
|
467
|
+
continue;
|
|
468
|
+
const kind = sniffMedia(b);
|
|
469
|
+
const kept = kind && CSS_ASSET_KINDS.has(kind) ? stripMetadata(b, kind) : null;
|
|
470
|
+
if (!kind || !kept) {
|
|
471
|
+
bad(`ownerCss.assets.${p}`, "png, webp, jpeg, gif or woff2");
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
assets.push({ path: p, id: hash32(kept), kind, kept });
|
|
475
|
+
}
|
|
476
|
+
const cssBytes = take(c.ownerCss.css, "manifest.contents.ownerCss.css");
|
|
477
|
+
if (cssBytes) {
|
|
478
|
+
let text = null;
|
|
479
|
+
try {
|
|
480
|
+
text = utf8.decode(cssBytes);
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
bad("ownerCss.css", "not UTF-8");
|
|
484
|
+
}
|
|
485
|
+
if (text !== null) {
|
|
486
|
+
if (cssBytes.length > OWNER_CSS_MAX_BYTES)
|
|
487
|
+
bad("ownerCss.css", `at most ${OWNER_CSS_MAX_BYTES / 1024} KiB`);
|
|
488
|
+
else {
|
|
489
|
+
// url("asset:<id>") names one of the bundle's CSS assets by the id it will have (the hash of its kept bytes).
|
|
490
|
+
const checked = checkOwnerCss(text, new Set(assets.map(a => a.id)));
|
|
491
|
+
if (!checked.ok)
|
|
492
|
+
bad("ownerCss.css", `${checked.error.problem}${checked.error.detail ? `: ${checked.error.detail}` : ""}`);
|
|
493
|
+
else
|
|
494
|
+
mod.ownerCss = { text, assets };
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
for (const p of files.keys())
|
|
500
|
+
if (!used.has(p))
|
|
501
|
+
bad(`files.${p}`, "not used by any part");
|
|
502
|
+
return problems.length ? fail() : { ok: true, mod };
|
|
503
|
+
}
|
|
504
|
+
/** A file's entry for a bundle being built (export). */
|
|
505
|
+
export function modFile(bytes) {
|
|
506
|
+
return { sha256: sha256(bytes), b64: Buffer.from(bytes).toString("base64") };
|
|
507
|
+
}
|
|
508
|
+
/** The extension a picture or CSS asset is exported under. */
|
|
509
|
+
export function modAssetName(base, kind) { return `${base}.${MEDIA_EXT[kind]}`; }
|
|
510
|
+
/**
|
|
511
|
+
* The effect runtime's checker (CW1: Sol's src/web-effect-bundle.ts and src/web-effect.ts) as a bundle uses it. Used
|
|
512
|
+
* once the fleet has an effect library to install into; until then the fleet passes null and effects are refused.
|
|
513
|
+
*/
|
|
514
|
+
export function cw1EffectChecker() {
|
|
515
|
+
return {
|
|
516
|
+
check(files) {
|
|
517
|
+
const r = checkEffectEntry(files);
|
|
518
|
+
if (!r.ok)
|
|
519
|
+
return { ok: false, problems: r.problems };
|
|
520
|
+
const m = r.manifest;
|
|
521
|
+
return { ok: true, manifest: m, summary: { id: r.id, title: m.title, bytes: r.bytes, regions: [...m.regions], wants: [...m.wants], params: Object.keys(m.params ?? {}) } };
|
|
522
|
+
},
|
|
523
|
+
params(manifest, overrides) {
|
|
524
|
+
const r = resolveEffectParams(manifest, overrides);
|
|
525
|
+
return r.ok ? { ok: true, overrides: { ...r.overrides } } : { ok: false, error: r.error };
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
//# sourceMappingURL=web-mod-bundle.js.map
|