import { r as ContentFieldFilters } from "./content-list-query-BhejKVqb.mjs"; import { f as FieldType } from "./types-BjBDp25t.mjs"; import { Permission } from "@premium-cms/auth"; import { z } from "astro/zod"; import { CAPABILITY_RENAMES, CurrentPluginCapability, DeclaredAccess, DeprecatedPluginCapability, ManifestHookEntry as ManifestHookEntry$1, ManifestRouteEntry as ManifestRouteEntry$1, PluginCapability, PluginMcpManifestConfig, PluginStorageConfig, StorageCollectionConfig, isDeprecatedCapability, normalizeCapabilities, normalizeCapability } from "@premium-cms/plugin-types"; import "react/jsx-runtime"; import { JSX } from "astro/jsx-runtime"; //#region ../blocks/dist/validation-z1u7mSwG.d.ts //#region src/types.d.ts interface ConfirmDialog { title: string; text: string; confirm: string; deny: string; style?: "danger"; } interface ButtonElement { type: "button"; action_id: string; label: string; style?: "primary" | "danger" | "secondary"; value?: unknown; confirm?: ConfirmDialog; } interface TextInputElement { type: "text_input"; action_id: string; label: string; placeholder?: string; initial_value?: string; multiline?: boolean; } interface NumberInputElement { type: "number_input"; action_id: string; label: string; initial_value?: number; min?: number; max?: number; } interface SelectElement { type: "select"; action_id: string; label: string; options: Array<{ label: string; value: string; }>; initial_value?: string; /** Plugin route that returns `{ items: Array<{ id, name }> }` to populate options dynamically */ optionsRoute?: string; } interface ToggleElement { type: "toggle"; action_id: string; label: string; description?: string; initial_value?: boolean; } interface SecretInputElement { type: "secret_input"; action_id: string; label: string; placeholder?: string; has_value?: boolean; } interface CheckboxElement { type: "checkbox"; action_id: string; label: string; options: Array<{ label: string; value: string; }>; initial_value?: string[]; } interface DateInputElement { type: "date_input"; action_id: string; label: string; initial_value?: string; placeholder?: string; } interface ComboboxElement { type: "combobox"; action_id: string; label: string; options: Array<{ label: string; value: string; }>; initial_value?: string; placeholder?: string; } interface RadioElement { type: "radio"; action_id: string; label: string; options: Array<{ label: string; value: string; }>; initial_value?: string; } /** * Sub-field types allowed inside a RepeaterElement. Limited to the scalar * inputs the admin widget currently renders inline. */ type RepeaterSubField = TextInputElement | NumberInputElement | SelectElement | ToggleElement; /** * Array-of-objects field. Renders as a list of collapsible cards with inline * add/remove and drag-and-drop reordering. Sub-fields are scalar Block Kit * elements keyed by their `action_id`. * * Admin-authoring only: this element is rendered by the admin widget so plugin * blocks can capture repeating data. The runtime block renderer * (`renderElement`) deliberately returns `null` for `repeater` — repeater * values are persisted on the parent block and consumed by the plugin's own * runtime component, not re-rendered as a stand-alone block. */ interface RepeaterElement { type: "repeater"; action_id: string; label: string; /** Singular label used in the UI (e.g. "FAQ" → "Add FAQ"). */ item_label?: string; fields: RepeaterSubField[]; min_items?: number; max_items?: number; /** * Default rows for the field. Note: the admin widget seeds new rows from * the sub-field types (empty string / `false`), not from `initial_value`; * plugins should populate persisted state via the form `values` payload * instead of relying on `initial_value` for pre-filled rows. */ initial_value?: Array>; } /** * Picks an item from the media library (or uploads a new one). The stored value * is the selected asset's URL string, so this element is value-compatible with a * plain `text_input` — existing content continues to work after swapping. */ interface MediaPickerElement { type: "media_picker"; action_id: string; label: string; /** Mime-type prefix filter (e.g. "image/"). Defaults to "image/". */ mime_type_filter?: string; initial_value?: string; placeholder?: string; } type Element = ButtonElement | TextInputElement | NumberInputElement | SelectElement | ToggleElement | SecretInputElement | CheckboxElement | DateInputElement | ComboboxElement | RadioElement | RepeaterElement | MediaPickerElement; //#endregion //#region src/plugins/types.d.ts /** * Query filter operators */ interface RangeFilter { gt?: number | string; gte?: number | string; lt?: number | string; lte?: number | string; } interface InFilter { in: Array; } interface StartsWithFilter { startsWith: string; } /** * Where clause value types */ type WhereValue = string | number | boolean | null | RangeFilter | InFilter | StartsWithFilter; /** * Where clause for storage queries */ type WhereClause = Record; /** * Query options for storage.query() */ interface QueryOptions { where?: WhereClause; orderBy?: Record; limit?: number; cursor?: string; } /** * Paginated result (used by storage.query, content.list, media.list) */ interface PaginatedResult { items: T[]; cursor?: string; hasMore: boolean; } /** * Storage collection interface - the API exposed to plugins * No async iterators - all operations return promises with pagination */ interface StorageCollection { get(id: string): Promise; put(id: string, data: T): Promise; delete(id: string): Promise; exists(id: string): Promise; getMany(ids: string[]): Promise>; putMany(items: Array<{ id: string; data: T; }>): Promise; deleteMany(ids: string[]): Promise; query(options?: QueryOptions): Promise>; count(where?: WhereClause): Promise; } /** * Plugin storage context - typed based on declared collections */ type PluginStorage = { [K in keyof T]: StorageCollection }; /** * KV store interface - unified replacement for settings + options * * Convention: * - `settings:*` - User-configurable preferences (shown in admin UI) * - `state:*` - Internal plugin state (not shown to users) */ interface KVAccess { get(key: string): Promise; set(key: string, value: unknown): Promise; delete(key: string): Promise; list(prefix?: string): Promise>; } /** * SEO metadata for a content item, as stored in the core SEO panel. * * Only present on items in collections with `has_seo = 1`. For collections * without SEO enabled, `ContentItem.seo` is `undefined`. */ interface ContentItemSeo { title: string | null; description: string | null; image: string | null; canonical: string | null; noIndex: boolean; } /** * SEO input accepted by content write operations. * * All fields are optional — only fields that are present overwrite existing * values. An empty object is treated as a no-op. */ interface ContentItemSeoInput { title?: string | null; description?: string | null; image?: string | null; canonical?: string | null; noIndex?: boolean; } /** * Content item returned from content API */ interface ContentItem { id: string; type: string; slug: string | null; status: string; locale: string | null; data: Record; /** * SEO metadata, populated when the collection has SEO enabled * (`has_seo = 1`). `undefined` for non-SEO collections. */ seo?: ContentItemSeo; createdAt: string; updatedAt: string; publishedAt: string | null; /** Scheduled publication time, if set (e.g. scheduled items or scheduled draft changes). */ scheduledAt?: string | null; } interface ContentListWhere { /** Exact match on `status` (e.g. `"published"`, `"draft"`). */ status?: string; /** Exact match on `locale` (e.g. `"en"`, `"fr-CA"`). */ locale?: string; /** AND-combined filters over custom fields explicitly marked as indexed. */ fieldFilters?: ContentFieldFilters; } /** * Content list options */ interface ContentListOptions { limit?: number; cursor?: string; orderBy?: Record; where?: ContentListWhere; } /** * Input accepted by `content.create` / `content.update`. * * Most entries are field slugs mapped to their values. The reserved `seo` * key is extracted and routed to the core SEO panel (the `_emdash_seo` * table), matching the shape accepted by the REST API. Passing `seo` for a * collection that does not have SEO enabled throws a validation error. */ type ContentWriteInput = Record & { seo?: ContentItemSeoInput; }; /** Options accepted by `content.create`. */ interface ContentCreateOptions { /** Locale for the new content row. Defaults to the configured site locale, then `en`. */ locale?: string; } /** * Taxonomy definition returned from the taxonomy API (e.g. "category", "tag"). */ interface TaxonomyDefInfo { name: string; label: string; labelSingular: string | null; hierarchical: boolean; /** Collections this taxonomy is attached to (e.g. `["posts"]`). */ collections: string[]; locale: string; } /** * Taxonomy term returned from the taxonomy API. Flat shape — for hierarchical * taxonomies the tree is reconstructed via `parentId` (which stores the * parent's locale-agnostic `translationGroup`). */ interface TaxonomyTermInfo { id: string; /** Taxonomy name this term belongs to (e.g. "category"). */ taxonomy: string; slug: string; label: string; parentId: string | null; /** Term metadata as edited in the admin (`description` etc.). */ data: Record | null; locale: string; translationGroup: string | null; } /** * Options accepted by taxonomy read operations. Omitting `locale` returns * rows for every locale. */ interface TaxonomyReadOptions { locale?: string; } /** * Content access interface - capability-gated */ interface ContentAccess { get(collection: string, id: string): Promise; list(collection: string, options?: ContentListOptions): Promise>; create?(collection: string, data: ContentWriteInput, options?: ContentCreateOptions): Promise; update?(collection: string, id: string, data: ContentWriteInput): Promise; delete?(collection: string, id: string): Promise; } /** * Taxonomy access interface — capability-gated on `taxonomies:read`. * Read-only: there is no plugin-facing taxonomy write API. */ interface TaxonomyAccess { /** List taxonomy definitions. */ getAll(options?: TaxonomyReadOptions): Promise; /** All terms of a taxonomy, ordered by label. */ getTerms(taxonomy: string, options?: TaxonomyReadOptions): Promise; /** Terms assigned to a content entry, optionally scoped to one taxonomy. */ getEntryTerms(collection: string, entryId: string, options?: TaxonomyReadOptions & { taxonomy?: string; }): Promise; } /** * Full content access with write operations */ interface ContentAccessWithWrite extends ContentAccess { create(collection: string, data: ContentWriteInput, options?: ContentCreateOptions): Promise; update(collection: string, id: string, data: ContentWriteInput): Promise; delete(collection: string, id: string): Promise; /** * Delete an entry outright (no trash). Fires `content:afterDelete` with * `permanent: true`. For entries whose existence IS the resource (a * provisioned project), where a trashed row would be a lie. */ permanentDelete(collection: string, id: string): Promise; } /** * Media item returned from media API */ interface MediaItem { id: string; filename: string; mimeType: string; size: number | null; url: string; createdAt: string; } /** * Media list options */ interface MediaListOptions { limit?: number; cursor?: string; mimeType?: string; } /** * Media access interface - capability-gated */ interface MediaAccess { get(id: string): Promise; list(options?: MediaListOptions): Promise>; getUploadUrl?(filename: string, contentType: string): Promise<{ uploadUrl: string; mediaId: string; }>; /** * Upload media bytes directly. Preferred in sandboxed mode where * plugins cannot make external requests to a presigned URL. * Returns the created media item. */ upload?(filename: string, contentType: string, bytes: ArrayBuffer): Promise<{ mediaId: string; storageKey: string; url: string; }>; delete?(id: string): Promise; } /** * Full media access with write operations */ interface MediaAccessWithWrite extends MediaAccess { getUploadUrl(filename: string, contentType: string): Promise<{ uploadUrl: string; mediaId: string; }>; upload(filename: string, contentType: string, bytes: ArrayBuffer): Promise<{ mediaId: string; storageKey: string; url: string; }>; delete(id: string): Promise; } /** * HTTP client interface - requires network:fetch capability */ interface HttpAccess { fetch(url: string, init?: RequestInit): Promise; } /** * Logger interface - always available */ interface LogAccess { debug(message: string, data?: unknown): void; info(message: string, data?: unknown): void; warn(message: string, data?: unknown): void; error(message: string, data?: unknown): void; } /** * Site information available to all plugins */ interface SiteInfo { /** Site name (from settings) */ name: string; /** Site URL (from settings or request) */ url: string; /** * The site's origin on the hosting platform (`https://p.premium-cms.com`), * which stays the same when a custom domain becomes the site URL. Absent on * sites that are not hosted by a control plane. */ platformUrl?: string; /** Site locale (from settings, defaults to "en") */ locale: string; /** * Astro's `trailingSlash` routing policy, from the host's Astro config. * Plugins that build absolute URLs (sitemap, canonical, hreflang) should * honor this so the URLs they emit match what the site serves. `createSiteInfo` * always populates it (defaulting to `"ignore"`, Astro's default); it is * optional on the type so pre-existing `SiteInfo` construction stays valid. */ trailingSlash?: "always" | "never" | "ignore"; } /** * Read-only user information exposed to plugins. * Sensitive fields (password hashes, sessions, passkeys) are excluded. */ interface UserInfo { id: string; email: string; name: string | null; /** Legacy role level (10 subscriber … 50 admin); the role itself is `roleId`. */ role: number; /** The role the user holds (`role:admin`, a custom role id …); null on rows predating roles. */ roleId?: string | null; createdAt: string; /** True when the request was authenticated by an API token rather than a signed-in session. */ tokenAuth?: boolean; } /** A role of the site, as plugins may list them (`ctx.users.listRoles()`). */ interface RoleInfo { id: string; slug: string; name: string; /** Legacy level the role most resembles. */ level: number; builtin: boolean; } /** * User access interface - requires read:users capability */ interface UserAccess { /** Get a user by ID */ get(id: string): Promise; /** Get a user by email */ getByEmail(email: string): Promise; /** List users with optional filters */ list(opts?: { role?: number; limit?: number; cursor?: string; }): Promise<{ items: UserInfo[]; nextCursor?: string; }>; /** The site's roles, highest level first (so a plugin can offer a role picker or scope something per role). */ listRoles(): Promise; } /** * The unified plugin context - same shape for all hooks and routes */ interface PluginContext { /** Plugin metadata */ plugin: { id: string; version: string; }; /** Storage collections - only if plugin declares storage */ storage: PluginStorage; /** Key-value store for config and state */ kv: KVAccess; /** Content access - only if read:content or write:content capability */ content?: ContentAccess | ContentAccessWithWrite; /** Taxonomy access (read-only) - only if taxonomies:read capability */ taxonomies?: TaxonomyAccess; /** Media access - only if read:media or write:media capability */ media?: MediaAccess | MediaAccessWithWrite; /** HTTP client - only if network:fetch capability */ http?: HttpAccess; /** Logger - always available */ log: LogAccess; /** Site information - always available */ site: SiteInfo; /** URL helper - generates absolute URLs from paths. Always available. */ url(path: string): string; /** User access - only if read:users capability */ users?: UserAccess; /** * The site's connected GitHub repository — requires `github:connection`. * Undefined until the owner connects GitHub (Settings → General). */ github?: GitHubConnectionAccess; /** Cron task scheduling - always available, scoped to plugin */ cron?: CronAccess; /** Email access - only if email:send capability and a provider is configured */ email?: EmailAccess; /** Think agents hosted by this instance — requires `agents:run`. */ agents?: AgentsAccess; /** The instance's build sandbox — requires `sandbox:build`. */ sandbox?: SandboxAccess; } /** A skill handed to an agent: the same shape as a bundled Think skill. */ interface AgentSkillSpec { name: string; description: string; body: string; } interface AgentMcpServerSpec { name: string; url: string; headers?: Record; transport?: "streamable-http" | "sse" | "auto"; } /** Where the runtime reports: a route of the calling plugin, signed with `secret` (`X-Agent-Signature: sha256=` over the body). */ interface AgentCallbackSpec { /** Route name within the plugin (public route). */ route: string; secret: string; /** Copied into every callback body. */ data?: Record; } interface AgentRunSpec { /** Stable per task (an issue number, a job id): reruns land on the same agent. */ id: string; model?: string; reasoning?: "low" | "medium" | "high"; systemPrompt?: string; skills?: AgentSkillSpec[]; mcp?: AgentMcpServerSpec[]; /** Also load the repository's own `.agents/skills` and `.mcp.json`. */ repo?: { owner: string; repo: string; branch: string; token: string; }; /** The message that starts (or continues) the run. */ input: string; idempotencyKey?: string; /** Tool names matching this regex source are refused. */ forbidTools?: string; maxSteps?: number; callback?: AgentCallbackSpec; } interface AgentSessionSpec { id: string; model?: string; reasoning?: "low" | "medium" | "high"; systemPrompt?: string; skills?: AgentSkillSpec[]; mcp?: AgentMcpServerSpec[]; /** Attach the browser bridge (tools that run in the editor's tab). */ browser?: boolean; user: { id: string; name: string | null; email: string; role: number; }; /** Kept on the runtime for child sessions; `{{secret:name}}` in MCP headers expands to them. */ secrets?: Record; expiresAt: string; /** Inherit user, secrets and expiry from this session of the same plugin. */ parent?: string; maxSteps?: number; } interface AgentsAccess { /** Queue a run; the callback (if any) reports `{status, answer, submissionId, ...data}` when it ends. */ run(spec: AgentRunSpec): Promise<{ submissionId: string; accepted: boolean; }>; status(id: string, submissionId: string): Promise<{ status: string; answer: string | null; }>; transcript(id: string, limit?: number): Promise>; /** Open a chat session; the browser connects to `/_emdash/agents/chat/plugin-agent/?ticket=…` on the site origin. */ session(spec: AgentSessionSpec): Promise<{ agent: string; ticket: string; expiresAt: string; }>; sessionInfo(id: string): Promise<{ open: boolean; user: AgentSessionSpec["user"] | null; expiresAt: string | null; }>; endSession(id: string): Promise; /** One turn without a client (operators, tests). */ say(id: string, text: string): Promise<{ status: string; answer: string | null; }>; } /** A site build in the instance's container: check → build → static branch → tests → preview wait → preview tests. */ interface SandboxBuildSpec { /** Stable key for the build lane (a PR number, a branch). */ id: string; owner: string; repo: string; headRef: string; headSha: string; pr: number; attempt: number; staticBranch: string; token: string; backendUrl: string; /** The frontend service account's API token (EMDASH_API_TOKEN in the build). */ apiToken: string; siteUrl: string; previewUrl?: string | null; previous?: number; previousUrls?: Array; /** Final result → this route; stage reports → `stageRoute` (same secret). */ callback: AgentCallbackSpec; stageRoute?: string; } interface SandboxAccess { build(spec: SandboxBuildSpec): Promise<{ accepted: true; }>; buildStatus(id: string): Promise<{ running: unknown; last: unknown; }>; } /** * Cron access interface �� always available on plugin context, scoped to plugin. */ interface CronAccess { /** Schedule a recurring or one-shot task */ schedule(name: string, opts: { schedule: string; data?: Record; }): Promise; /** Cancel a scheduled task */ cancel(name: string): Promise; /** List this plugin's scheduled tasks */ list(): Promise; } /** * Task info returned from CronAccess.list() */ interface CronTaskInfo { name: string; schedule: string; nextRunAt: string; lastRunAt: string | null; } /** * Event passed to the `cron` hook handler */ /** Read access to the site's GitHub connection (`github:connection`). */ interface GitHubConnectionAccess { /** * The connection, or null while GitHub is not connected. Never cached. * `frontendToken` is the frontend service account's API token — what a * build passes as EMDASH_API_TOKEN to read the content snapshot — so a * plugin can build the site elsewhere; empty when the site has none yet. */ get(): Promise<{ token: string; owner: string; repo: string; branch: string; frontendToken: string; } | null>; } interface CronEvent { name: string; data?: Record; scheduledAt: string; } /** * Cron hook handler type */ type CronHandler = (event: CronEvent, ctx: PluginContext) => Promise; /** * Email access interface — requires `email:send` capability. * Undefined when no `email:deliver` provider is configured. * * Related capabilities: * - `email:send` — grants ctx.email (this interface) * - `email:provide` — allows registering the `email:deliver` exclusive hook * - `email:intercept` — allows registering `email:beforeSend` / `email:afterSend` hooks */ interface EmailAccess { send(message: EmailMessage): Promise; } /** * Email message shape */ interface EmailMessage { to: string; subject: string; text: string; html?: string; } /** * Event passed to email:beforeSend hooks (middleware — transform, validate, cancel) */ interface EmailBeforeSendEvent { message: EmailMessage; /** Where the email originated — "system" for auth emails, plugin ID for plugin emails */ source: string; } /** * Event passed to email:deliver hook (exclusive — exactly one provider delivers) */ interface EmailDeliverEvent { message: EmailMessage; source: string; } /** * Event passed to email:afterSend hooks (logging, analytics, fire-and-forget) */ interface EmailAfterSendEvent { message: EmailMessage; source: string; } /** * Handler type for email:beforeSend hooks. * Returns modified message, or false to cancel delivery. */ type EmailBeforeSendHandler = (event: EmailBeforeSendEvent, ctx: PluginContext) => Promise; /** * Handler type for email:deliver hooks (exclusive provider). */ type EmailDeliverHandler = (event: EmailDeliverEvent, ctx: PluginContext) => Promise; /** * Handler type for email:afterSend hooks (fire-and-forget). */ type EmailAfterSendHandler = (event: EmailAfterSendEvent, ctx: PluginContext) => Promise; /** * Collection comment settings (read from _emdash_collections) */ interface CollectionCommentSettings { commentsEnabled: boolean; commentsModeration: "all" | "first_time" | "none"; commentsClosedAfterDays: number; commentsAutoApproveUsers: boolean; } /** * Event passed to comment:beforeCreate hooks (middleware — transform, enrich, reject) */ interface CommentBeforeCreateEvent { comment: { collection: string; contentId: string; parentId: string | null; authorName: string; authorEmail: string; authorUserId: string | null; body: string; ipHash: string | null; userAgent: string | null; }; /** Metadata bag — plugins can attach signals for the moderator */ metadata: Record; } /** * Event passed to comment:moderate hook (exclusive — decides initial status) */ interface CommentModerateEvent { comment: CommentBeforeCreateEvent["comment"]; metadata: Record; collectionSettings: CollectionCommentSettings; /** Number of prior approved comments from this email address */ priorApprovedCount: number; } /** * Moderation decision returned by the comment:moderate handler */ interface ModerationDecision { status: "approved" | "pending" | "spam"; /** Optional reason for admin visibility */ reason?: string; } /** * Stored comment shape (full record with id, status, timestamps) */ interface StoredComment { id: string; collection: string; contentId: string; parentId: string | null; authorName: string; authorEmail: string; authorUserId: string | null; body: string; status: string; moderationMetadata: Record | null; createdAt: string; updatedAt: string; } /** * Event passed to comment:afterCreate hooks (fire-and-forget) */ interface CommentAfterCreateEvent { comment: StoredComment; metadata: Record; /** The content item the comment is on */ content: { id: string; collection: string; slug: string; title?: string; }; /** The content author (for notifications) */ contentAuthor?: { id: string; name: string | null; email: string; }; } /** * Event passed to comment:afterModerate hooks (fire-and-forget, admin status change) */ interface CommentAfterModerateEvent { comment: StoredComment; previousStatus: string; newStatus: string; /** The admin who moderated */ moderator: { id: string; name: string | null; }; } /** * Handler type for comment:beforeCreate hooks. * Returns modified event, or false to reject the comment. */ type CommentBeforeCreateHandler = (event: CommentBeforeCreateEvent, ctx: PluginContext) => Promise; /** * Handler type for comment:moderate hook (exclusive provider). */ type CommentModerateHandler = (event: CommentModerateEvent, ctx: PluginContext) => Promise; /** * Handler type for comment:afterCreate hooks (fire-and-forget). */ type CommentAfterCreateHandler = (event: CommentAfterCreateEvent, ctx: PluginContext) => Promise; /** * Handler type for comment:afterModerate hooks (fire-and-forget). */ type CommentAfterModerateHandler = (event: CommentAfterModerateEvent, ctx: PluginContext) => Promise; /** * Hook configuration */ interface HookConfig { /** Explicit ordering - lower numbers run first (default: 100) */ priority?: number; /** Max execution time in ms (default: 5000) */ timeout?: number; /** Run after these plugins */ dependencies?: string[]; /** Error handling policy */ errorPolicy?: "continue" | "abort"; /** * Mark this hook as exclusive — only one plugin can be the active provider. * Exclusive hooks skip the priority pipeline and dispatch only to the * admin-selected provider. Used for email:deliver, search, image optimization, etc. */ exclusive?: boolean; /** The hook handler */ handler: THandler; } /** * Content hook event */ interface ContentHookEvent { content: Record; collection: string; isNew: boolean; } /** * Content delete hook event */ interface ContentDeleteEvent { id: string; collection: string; /** `true` when the content is permanently deleted (not just trashed). */ permanent: boolean; } /** * Content state-change hook event (fired after publish, unpublish, restore, * schedule, or unschedule). */ interface ContentStateChangeEvent { content: Record; collection: string; } /** * Content publish/unpublish hook event. */ type ContentPublishStateChangeEvent = ContentStateChangeEvent; /** * Content restore hook event. */ type ContentRestoreStateChangeEvent = ContentStateChangeEvent; /** * Content schedule/unschedule hook event. */ type ContentScheduleStateChangeEvent = ContentStateChangeEvent; /** * Media hook event */ interface MediaUploadEvent { file: { name: string; type: string; size: number; }; } /** * Media after upload event */ interface MediaAfterUploadEvent { media: MediaItem; } /** * Lifecycle hook event */ interface LifecycleEvent {} /** * Uninstall hook event */ interface UninstallEvent { deleteData: boolean; } type ContentBeforeSaveHandler = (event: ContentHookEvent, ctx: PluginContext) => Promise | void>; type ContentAfterSaveHandler = (event: ContentHookEvent, ctx: PluginContext) => Promise; type ContentBeforeDeleteHandler = (event: ContentDeleteEvent, ctx: PluginContext) => Promise; type ContentAfterDeleteHandler = (event: ContentDeleteEvent, ctx: PluginContext) => Promise; type ContentAfterPublishHandler = (event: ContentPublishStateChangeEvent, ctx: PluginContext) => Promise; type ContentAfterUnpublishHandler = (event: ContentPublishStateChangeEvent, ctx: PluginContext) => Promise; type ContentAfterRestoreHandler = (event: ContentRestoreStateChangeEvent, ctx: PluginContext) => Promise; type ContentAfterScheduleHandler = (event: ContentScheduleStateChangeEvent, ctx: PluginContext) => Promise; type ContentAfterUnscheduleHandler = (event: ContentScheduleStateChangeEvent, ctx: PluginContext) => Promise; type MediaBeforeUploadHandler = (event: MediaUploadEvent, ctx: PluginContext) => Promise<{ name: string; type: string; size: number; } | void>; type MediaAfterUploadHandler = (event: MediaAfterUploadEvent, ctx: PluginContext) => Promise; type LifecycleHandler = (event: LifecycleEvent, ctx: PluginContext) => Promise; type UninstallHandler = (event: UninstallEvent, ctx: PluginContext) => Promise; /** Placement targets for page fragment contributions */ type PagePlacement = "head" | "body:start" | "body:end"; /** * A single breadcrumb trail item. Used by `PublicPageContext.breadcrumbs` * so themes can publish breadcrumb trails that SEO plugins consume. */ interface BreadcrumbItem { /** Display name for this crumb (e.g. "Home", "Blog", "My Post"). */ name: string; /** Absolute or root-relative URL for this crumb. */ url: string; } /** * Describes the page being rendered. Passed to page hooks so plugins * can decide what to contribute without fetching content themselves. */ interface PublicPageContext { url: string; path: string; locale: string | null; kind: "content" | "custom"; pageType: string; /** Full document title for the rendered page */ title: string | null; /** Page-only title for OG/Twitter/JSON-LD headline output */ pageTitle?: string | null; description: string | null; canonical: string | null; image: string | null; content?: { collection: string; id: string; slug: string | null; }; /** SEO meta for base metadata generation in EmDashHead */ seo?: { ogTitle?: string | null; ogDescription?: string | null; ogImage?: string | null; robots?: string | null; }; /** Article metadata for Open Graph article: tags */ articleMeta?: { publishedTime?: string | null; modifiedTime?: string | null; author?: string | null; }; /** Site name for structured data and og:site_name */ siteName?: string; /** * Optional breadcrumb trail for this page, root first. When set, * SEO plugins should use this verbatim rather than deriving a trail * from `path`. Themes typically populate this at the point they * build the context (e.g. from a content hierarchy walk, taxonomy * lookup, or per-`pageType` routing logic). * * Semantics for consumers: * - `undefined` — theme has no opinion; consumer falls back to * its own derivation. * - `[]` — this page has no breadcrumbs (e.g. homepage); consumer * should skip `BreadcrumbList` emission entirely. * - Non-empty array — used verbatim for `BreadcrumbList` output. */ breadcrumbs?: BreadcrumbItem[]; /** Public-facing site URL (origin) for structured data */ siteUrl?: string; } interface PageMetadataEvent { page: PublicPageContext; } /** * Allowed rel values for link contributions. * This is a security-critical allowlist -- sandboxed plugins can only inject * link tags with these rel values. Adding "stylesheet", "prefetch", "prerender" * etc. would allow sandboxed plugins to inject external resources. */ type PageMetadataLinkRel = "canonical" | "alternate" | "author" | "license" | "nlweb" | "site.standard.document"; type PageMetadataContribution = { kind: "meta"; name: string; content: string; key?: string; } | { kind: "property"; property: string; content: string; key?: string; } | { kind: "link"; rel: PageMetadataLinkRel; href: string; hreflang?: string; key?: string; } | { kind: "jsonld"; id?: string; graph: Record | Array>; }; type PageMetadataHandler = (event: PageMetadataEvent, ctx: PluginContext) => PageMetadataContribution | PageMetadataContribution[] | null | Promise; interface PageFragmentEvent { page: PublicPageContext; } type PageFragmentContribution = { kind: "external-script"; placement: PagePlacement; src: string; async?: boolean; defer?: boolean; attributes?: Record; key?: string; } | { kind: "inline-script"; placement: PagePlacement; code: string; attributes?: Record; key?: string; } | { kind: "html"; placement: PagePlacement; html: string; key?: string; }; type PageFragmentHandler = (event: PageFragmentEvent, ctx: PluginContext) => PageFragmentContribution | PageFragmentContribution[] | null | Promise; /** * Plugin hooks definition */ interface PluginHooks { "plugin:install"?: HookConfig | LifecycleHandler; "plugin:activate"?: HookConfig | LifecycleHandler; "plugin:deactivate"?: HookConfig | LifecycleHandler; "plugin:uninstall"?: HookConfig | UninstallHandler; "content:beforeSave"?: HookConfig | ContentBeforeSaveHandler; "content:afterSave"?: HookConfig | ContentAfterSaveHandler; "content:beforeDelete"?: HookConfig | ContentBeforeDeleteHandler; "content:afterDelete"?: HookConfig | ContentAfterDeleteHandler; "content:afterPublish"?: HookConfig | ContentAfterPublishHandler; "content:afterUnpublish"?: HookConfig | ContentAfterUnpublishHandler; "content:afterRestore"?: HookConfig | ContentAfterRestoreHandler; "content:afterSchedule"?: HookConfig | ContentAfterScheduleHandler; "content:afterUnschedule"?: HookConfig | ContentAfterUnscheduleHandler; "media:beforeUpload"?: HookConfig | MediaBeforeUploadHandler; "media:afterUpload"?: HookConfig | MediaAfterUploadHandler; cron?: HookConfig | CronHandler; "email:beforeSend"?: HookConfig | EmailBeforeSendHandler; "email:deliver"?: HookConfig | EmailDeliverHandler; "email:afterSend"?: HookConfig | EmailAfterSendHandler; "comment:beforeCreate"?: HookConfig | CommentBeforeCreateHandler; "comment:moderate"?: HookConfig | CommentModerateHandler; "comment:afterCreate"?: HookConfig | CommentAfterCreateHandler; "comment:afterModerate"?: HookConfig | CommentAfterModerateHandler; "page:metadata"?: HookConfig | PageMetadataHandler; "page:fragments"?: HookConfig | PageFragmentHandler; } /** * Hook names */ /** * Hook name in a manifest. Core's exhaustive union of recognised hook names, * derived from the `PluginHooks` registry. The serialised manifest carries * these as opaque strings; this stricter type is only used for type-checking * inside core. `ManifestHookEntry` is re-exported from * `@premium-cms/plugin-types` near the top of this file. */ type HookName = keyof PluginHooks; /** * Resolved hook with normalized config */ interface ResolvedHook { priority: number; timeout: number; dependencies: string[]; errorPolicy: "continue" | "abort"; /** Whether this hook is exclusive (provider pattern) */ exclusive: boolean; handler: THandler; pluginId: string; } /** * Geographic location information derived from the request. * Available when running on Cloudflare Workers (via the `cf` object). */ interface GeoInfo { country: string | null; region: string | null; city: string | null; } /** * Normalized request metadata available to plugin route handlers. * Extracted from request headers and platform-specific properties. */ interface RequestMeta { ip: string | null; userAgent: string | null; referer: string | null; geo: GeoInfo | null; } /** * Route handler context extends plugin context with request-specific data */ interface RouteContext extends PluginContext { /** Validated input from request body */ input: TInput; /** Original request */ request: Request; /** Normalized request metadata (IP, user agent, geo) */ requestMeta: RequestMeta; /** * Authenticated caller, if the route is private. The host has already * authenticated and authorized this user before dispatch, so the value * is trustworthy — unlike a user id read from the request body. * * `undefined` for public routes (which skip auth entirely) and for * token-authed requests where no user is bound to the token. * * Not gated by the `users:read` capability: this is the caller's own * identity for the current request, not a user directory lookup. */ user?: UserInfo; } /** * Route definition */ interface PluginRoute { /** Zod schema for input validation */ input?: z.ZodType; /** * Mark this route as publicly accessible (no authentication required). * Public routes skip session/token auth and CSRF checks. */ public?: boolean; /** RBAC permission required to invoke the route. Legacy routes default to plugins:manage. */ permission?: Permission; /** * `Cache-Control` header value for successful GET responses, e.g. * `"public, max-age=60, stale-while-revalidate=300"`. Only honored on * routes that are also `public: true` — authenticated responses always * keep the default `private, no-store`. Errors are never cached. */ cacheControl?: string; /** Route handler */ handler: (ctx: RouteContext) => Promise; } interface PluginMcpToolDefinition { description: string; route: string; input: z.ZodType; output?: z.ZodType; destructive?: boolean; } interface PluginMcpConfig { tools: Record; } /** * Admin page definition */ interface PluginAdminPage { path: string; label: string; icon?: string; } /** * Dashboard widget definition */ interface PluginDashboardWidget { id: string; size?: "full" | "half" | "third"; title?: string; } /** * Settings field types (for admin UI generation) */ type SettingFieldType = "string" | "number" | "boolean" | "select" | "secret" | "url" | "email"; interface BaseSettingField { type: SettingFieldType; label: string; description?: string; } interface StringSettingField extends BaseSettingField { type: "string"; default?: string; multiline?: boolean; } interface NumberSettingField extends BaseSettingField { type: "number"; default?: number; min?: number; max?: number; } interface BooleanSettingField extends BaseSettingField { type: "boolean"; default?: boolean; } interface SelectSettingField extends BaseSettingField { type: "select"; options: Array<{ value: string; label: string; }>; default?: string; } interface SecretSettingField extends BaseSettingField { type: "secret"; } interface UrlSettingField extends BaseSettingField { type: "url"; default?: string; placeholder?: string; } interface EmailSettingField extends BaseSettingField { type: "email"; default?: string; placeholder?: string; } type SettingField = StringSettingField | NumberSettingField | BooleanSettingField | SelectSettingField | SecretSettingField | UrlSettingField | EmailSettingField; /** * Block Kit element for block editing fields. * This is the `Element` discriminated union from `@premium-cms/blocks`. * Plugin authors should use `@premium-cms/blocks` builder functions to create these. */ type PortableTextBlockField = Element; /** * Configuration for a Portable Text block type contributed by a plugin */ interface PortableTextBlockConfig { /** Block type name (must match the `_type` in Portable Text) */ type: string; /** Human-readable label shown in slash commands and modals */ label: string; /** Icon key (e.g., "video", "code", "link", "link-external") */ icon?: string; /** Description shown in slash command menu */ description?: string; /** Placeholder text for the URL input */ placeholder?: string; /** Block Kit form fields for the editing UI. If declared, replaces the simple URL input. */ fields?: PortableTextBlockField[]; /** * Optional. Display category in the slash menu. Defaults to "Embeds". * * Plugin authors should pick a meaningful category that reflects what the * block actually is — e.g. "Sections", "Marketing", "Media", "Embeds", * "Layout". Blocks with the same category are grouped together in the * editor's slash menu. */ category?: string; } /** * Configuration for a field widget type contributed by a plugin. * A field widget provides a custom editing UI for a schema field. * The field references the widget via `widget: "pluginId:widgetName"`. */ interface FieldWidgetConfig { /** Widget name (without plugin ID prefix) */ name: string; /** Human-readable label for the admin UI */ label: string; /** Which field types this widget can edit (e.g., ["json", "string"]) */ fieldTypes: FieldType[]; /** Block Kit elements for sandboxed rendering. Omit for trusted plugins using React. */ elements?: Element[]; } /** * Admin configuration */ interface PluginAdminConfig { /** Module specifier for admin UI exports (e.g., "@premium-cms/plugin-audit-log/admin") */ entry?: string; /** Settings schema for auto-generated UI */ settingsSchema?: Record; /** Admin pages */ pages?: PluginAdminPage[]; /** Dashboard widgets */ widgets?: PluginDashboardWidget[]; /** Portable Text block types this plugin provides */ portableTextBlocks?: PortableTextBlockConfig[]; /** Field widget types this plugin provides */ fieldWidgets?: FieldWidgetConfig[]; } /** * Plugin definition - input to definePlugin() */ interface PluginDefinition { /** Unique plugin identifier */ id: string; /** Plugin version (semver) */ version: string; /** Declared capabilities */ capabilities?: PluginCapability[]; /** Allowed hosts for network:fetch (wildcards supported: *.example.com) */ allowedHosts?: string[]; /** Storage collections with indexes */ storage?: TStorage; /** Hooks */ hooks?: PluginHooks; /** API routes */ routes?: Record; /** Routes explicitly exposed as agent-callable MCP tools. */ mcp?: PluginMcpConfig; /** Admin UI configuration */ admin?: PluginAdminConfig; } /** * Resolved plugin - after definePlugin() processing */ interface ResolvedPlugin { id: string; version: string; capabilities: PluginCapability[]; allowedHosts: string[]; storage: TStorage; hooks: ResolvedPluginHooks; routes: Record; mcp?: PluginMcpConfig; admin: PluginAdminConfig; } /** * Resolved hooks with normalized config */ interface ResolvedPluginHooks { "plugin:install"?: ResolvedHook; "plugin:activate"?: ResolvedHook; "plugin:deactivate"?: ResolvedHook; "plugin:uninstall"?: ResolvedHook; "content:beforeSave"?: ResolvedHook; "content:afterSave"?: ResolvedHook; "content:beforeDelete"?: ResolvedHook; "content:afterDelete"?: ResolvedHook; "content:afterPublish"?: ResolvedHook; "content:afterUnpublish"?: ResolvedHook; "content:afterRestore"?: ResolvedHook; "content:afterSchedule"?: ResolvedHook; "content:afterUnschedule"?: ResolvedHook; "media:beforeUpload"?: ResolvedHook; "media:afterUpload"?: ResolvedHook; cron?: ResolvedHook; "email:beforeSend"?: ResolvedHook; "email:deliver"?: ResolvedHook; "email:afterSend"?: ResolvedHook; "comment:beforeCreate"?: ResolvedHook; "comment:moderate"?: ResolvedHook; "comment:afterCreate"?: ResolvedHook; "comment:afterModerate"?: ResolvedHook; "page:metadata"?: ResolvedHook; "page:fragments"?: ResolvedHook; } /** * What a plugin exports from its /admin entrypoint * Uses generic component type to avoid React dependency */ interface PluginAdminExports { widgets?: Record; pages?: Record; fields?: Record; } /** * Plugin manifest — the metadata portion of a plugin bundle, used for * sandboxed plugins loaded from the marketplace. * * This interface is core's stricter version of the manifest contract: it * uses the exhaustive `HookName` union and core's typed `PluginAdminConfig`. * The wire-shape lives in `@premium-cms/plugin-types` as `PluginManifest` * with looser types (so the registry CLI can serialise hook names it * doesn't know about). Both must stay structurally compatible: every value * of this type must be assignable to the shared one. The static assertion * below catches any drift at compile time. */ interface PluginManifest { id: string; version: string; /** * The trust contract (see `@premium-cms/plugin-types`). Authoritative; * `capabilities`/`allowedHosts` are derived from it at the parse boundary * via `reconcileManifestAccess`. Optional during the wire-format migration. */ declaredAccess?: DeclaredAccess; capabilities: PluginCapability[]; allowedHosts: string[]; storage: PluginStorageConfig; /** Hook declarations — either plain name strings or structured objects */ hooks: Array; /** Route declarations — either plain name strings or structured objects */ routes: Array; mcp?: PluginMcpManifestConfig; admin: PluginAdminConfig; } //#endregion export { KVAccess as $, isDeprecatedCapability as $t, ContentHookEvent as A, PortableTextBlockConfig as At, CurrentPluginCapability as B, SandboxBuildSpec as Bt, ContentAfterScheduleHandler as C, PluginContext as Ct, ContentBeforeSaveHandler as D, PluginMcpManifestConfig as Dt, ContentBeforeDeleteHandler as E, PluginManifest as Et, ContentStateChangeEvent as F, ResolvedHook as Ft, EmailBeforeSendHandler as G, TaxonomyAccess as Gt, EmailAfterSendEvent as H, StorageCollection as Ht, CronAccess as I, ResolvedPlugin as It, EmailMessage as J, TaxonomyTermInfo as Jt, EmailDeliverEvent as K, TaxonomyDefInfo as Kt, CronEvent as L, ResolvedPluginHooks as Lt, ContentPublishStateChangeEvent as M, PublicPageContext as Mt, ContentRestoreStateChangeEvent as N, QueryOptions as Nt, ContentCreateOptions as O, PluginRoute as Ot, ContentScheduleStateChangeEvent as P, RequestMeta as Pt, HttpAccess as Q, WhereClause as Qt, CronHandler as R, RouteContext as Rt, ContentAfterSaveHandler as S, PluginCapability as St, ContentAfterUnscheduleHandler as T, PluginHooks as Tt, EmailAfterSendHandler as U, StorageCollectionConfig as Ut, DeprecatedPluginCapability as V, SettingField as Vt, EmailBeforeSendEvent as W, StoredComment as Wt, HookConfig as X, UninstallHandler as Xt, FieldWidgetConfig as Y, UninstallEvent as Yt, HookName as Z, UserInfo as Zt, CommentModerateHandler as _, PagePlacement as _t, AgentSkillSpec as a, MediaAfterUploadHandler as at, ContentAfterPublishHandler as b, PluginAdminExports as bt, CAPABILITY_RENAMES as c, MediaUploadEvent as ct, CommentAfterCreateHandler as d, PageFragmentEvent as dt, normalizeCapabilities as en, LifecycleEvent as et, CommentAfterModerateEvent as f, PageFragmentHandler as ft, CommentModerateEvent as g, PageMetadataLinkRel as gt, CommentBeforeCreateHandler as h, PageMetadataHandler as ht, AgentSessionSpec as i, MediaAfterUploadEvent as it, ContentItem as j, PortableTextBlockField as jt, ContentDeleteEvent as k, PluginStorageConfig as kt, CollectionCommentSettings as l, ModerationDecision as lt, CommentBeforeCreateEvent as m, PageMetadataEvent as mt, AgentMcpServerSpec as n, Element as nn, LogAccess as nt, AgentsAccess as o, MediaBeforeUploadHandler as ot, CommentAfterModerateHandler as p, PageMetadataContribution as pt, EmailDeliverHandler as q, TaxonomyReadOptions as qt, AgentRunSpec as r, MediaAccess as rt, BreadcrumbItem as s, MediaItem as st, AgentCallbackSpec as t, normalizeCapability as tn, LifecycleHandler as tt, CommentAfterCreateEvent as u, PageFragmentContribution as ut, ContentAccess as v, PaginatedResult as vt, ContentAfterUnpublishHandler as w, PluginDefinition as wt, ContentAfterRestoreHandler as x, PluginAdminPage as xt, ContentAfterDeleteHandler as y, PluginAdminConfig as yt, CronTaskInfo as z, SandboxAccess as zt }; //# sourceMappingURL=types-XJPFqvc1.d.mts.map