import type { PageBuilderConfig } from '../page-builder/types.js';
import type { SectionsConfig } from '../sections/types.js';
export type FieldDefinition = TextField | RichTextField | NumberField | BooleanField | DateField | SlugField | SelectField | RelationshipField | MediaField | JsonField | ColorField | UrlField | EmailField | ArrayField | BlocksField | ComponentBlockField;
interface BaseField {
    label: string;
    required?: boolean;
    unique?: boolean;
    hidden?: boolean;
    defaultValue?: unknown;
    admin?: {
        description?: string;
        placeholder?: string;
        condition?: (data: Record<string, unknown>) => boolean;
        readOnly?: boolean;
        position?: 'sidebar' | 'main';
        width?: number;
    };
    access?: {
        read?: AccessFunction;
        update?: AccessFunction;
    };
    validate?: (value: unknown) => string | true;
    hooks?: {
        beforeChange?: (args: {
            value: unknown;
        }) => unknown;
        afterRead?: (args: {
            value: unknown;
        }) => unknown;
    };
}
export interface TextField extends BaseField {
    type: 'text';
    minLength?: number;
    maxLength?: number;
}
export interface RichTextField extends BaseField {
    type: 'richText';
    features?: ('bold' | 'italic' | 'link' | 'heading' | 'list' | 'code' | 'image' | 'embed')[];
}
export interface NumberField extends BaseField {
    type: 'number';
    min?: number;
    max?: number;
    step?: number;
}
export interface BooleanField extends BaseField {
    type: 'boolean';
}
export interface DateField extends BaseField {
    type: 'date';
    format?: string;
    includeTime?: boolean;
}
export interface SlugField extends BaseField {
    type: 'slug';
    from: string;
}
export interface SelectField extends BaseField {
    type: 'select';
    options: Array<{
        label: string;
        value: string;
    }>;
    multiple?: boolean;
}
export interface RelationshipField extends BaseField {
    type: 'relationship';
    relationTo: string | string[];
    hasMany?: boolean;
}
export interface MediaField extends BaseField {
    type: 'media';
    accept?: string[];
    maxSize?: number;
}
export interface JsonField extends BaseField {
    type: 'json';
    schema?: Record<string, unknown>;
}
export interface ColorField extends BaseField {
    type: 'color';
    format?: 'hex' | 'rgb' | 'hsl';
}
export interface UrlField extends BaseField {
    type: 'url';
    allowedProtocols?: string[];
}
export interface EmailField extends BaseField {
    type: 'email';
}
export interface ArrayField extends BaseField {
    type: 'array';
    fields: Record<string, FieldDefinition>;
    minRows?: number;
    maxRows?: number;
}
export interface BlocksField extends BaseField {
    type: 'blocks';
    blocks: BlockDefinition[];
}
export interface BlockDefinition {
    slug: string;
    labels: {
        singular: string;
        plural: string;
    };
    fields: Record<string, FieldDefinition>;
}
/**
 * Component-aware block field. Stores instances of real React
 * components as data, validated against an extracted prop manifest from
 * `@actuate-media/component-blocks`.
 *
 * Stored value shape:
 * ```ts
 * {
 *   component: 'Hero',
 *   props: { title: 'Welcome', alignment: 'center' }
 * }
 * ```
 *
 * The foundation slice (this PR) accepts only an inline manifest object.
 * Path-based references (loading a manifest JSON from disk during config
 * load) ship in PR #2 of the Phase 4 roadmap, where the same loader
 * powers the admin form generator and live preview.
 */
export interface ComponentBlockField extends BaseField {
    type: 'componentBlock';
    /**
     * Inline manifest object. Pass the imported JSON emitted by
     * `actuate-component-blocks extract` or the result of calling
     * `extractManifest()` programmatically.
     */
    manifest: ComponentBlockManifestInline;
    /**
     * Optional whitelist of component names the editor may pick from. If
     * omitted, every component in the manifest is allowed.
     */
    allow?: string[];
    /**
     * Component name used as the default when a new instance is created.
     * Must match one of `allow` (when set) and must exist in the manifest.
     */
    defaultComponent?: string;
}
/**
 * Inline shape — kept structurally typed so cms-core doesn't need a
 * compile-time import from `@actuate-media/component-blocks`. The
 * field-type validator confirms it matches the shipped Manifest schema.
 */
export interface ComponentBlockManifestInline {
    version: 1;
    generatedAt: string;
    rootDir: string;
    components: Array<{
        name: string;
        displayName: string;
        filePath: string;
        description?: string;
        props: Array<{
            name: string;
            required: boolean;
            type: unknown;
            description?: string;
            defaultValue?: string | number | boolean | null;
        }>;
    }>;
}
export type AccessFunction = (args: {
    user: {
        id: string;
        role: string;
    } | null;
    doc?: Record<string, unknown>;
}) => boolean | Promise<boolean>;
export interface AccessConfig {
    read?: AccessFunction;
    create?: AccessFunction;
    update?: AccessFunction;
    delete?: AccessFunction;
}
export interface CollectionSEOConfig {
    sitemapPriority?: number;
    sitemapChangeFreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
    excludeFromSitemap?: boolean;
    /** Path for the archive/listing page (e.g. "/blog" for posts). */
    archivePath?: string;
    /**
     * Default Schema.org type for documents in this collection (Article, Service,
     * LocalBusiness, FAQPage, JobPosting, etc.). When set, the `/resolve`
     * endpoint composes JSON-LD automatically from the document data unless the
     * doc explicitly overrides `structuredData`. Falls back to auto-detection
     * by collection slug when omitted.
     */
    defaultSchemaType?: string;
    /** Default robots directives for documents in this collection. */
    defaultRobots?: {
        noIndex?: boolean;
        noFollow?: boolean;
        noArchive?: boolean;
        noSnippet?: boolean;
    };
    /** Default OG image template options for documents in this collection. */
    defaultOgImage?: {
        /** Template URL with `{title}`, `{description}`, `{siteName}` placeholders. Leave empty to use the built-in /og.png endpoint. */
        template?: string;
        theme?: 'light' | 'dark';
    };
}
export interface CollectionDefinition {
    slug: string;
    labels: {
        singular: string;
        plural: string;
    };
    /**
     * URL prefix for this collection's public pages.
     * - `""` (empty string) = root-level pages (`/about`, `/contact`)
     * - `"blog"` = nested under `/blog/slug`
     * - `"case-studies"` = nested under `/case-studies/slug`
     * Defaults to the collection slug if omitted.
     */
    urlPrefix?: string;
    /** Whether this collection behaves as standalone pages or as an archive of posts. */
    type?: 'page' | 'post';
    /** Per-collection SEO overrides for sitemap generation. */
    seo?: CollectionSEOConfig;
    fields: Record<string, FieldDefinition>;
    access?: AccessConfig;
    hooks?: CollectionHooks;
    timestamps?: boolean;
    softDelete?: boolean;
    versions?: boolean | {
        maxPerDoc?: number;
    };
    defaultSort?: string;
    admin?: {
        useAsTitle?: string;
        defaultColumns?: string[];
        group?: string;
        hidden?: boolean;
        description?: string;
        preview?: (doc: Record<string, unknown>) => string;
        icon?: string;
        /**
         * Keep this post-type collection out of the admin's aggregate Posts area
         * (the cross-collection list, type tabs, and New Post picker). Use for
         * post-shaped collections that have their own dedicated workflow — the
         * collection itself stays fully accessible via its own admin list and
         * API routes.
         */
        excludeFromPostsAggregate?: boolean;
        /**
         * Accent color for the collection's badges, type pill, and Post Types
         * card top-border. Stored as a semantic token (`purple`, `green`,
         * `orange`, `red`, `blue`, `pink`) so the admin theme can map it
         * through the design tokens; raw hex values are allowed but won't
         * react to light/dark mode tweaks.
         */
        color?: string;
    };
}
export interface CollectionHooks {
    beforeCreate?: (args: HookArgs) => Promise<void> | void;
    afterCreate?: (args: HookArgs) => Promise<void> | void;
    beforeUpdate?: (args: HookArgs) => Promise<void> | void;
    afterUpdate?: (args: HookArgs) => Promise<void> | void;
    beforeDelete?: (args: HookArgs) => Promise<void> | void;
    afterDelete?: (args: HookArgs) => Promise<void> | void;
}
export interface HookArgs {
    doc: Record<string, unknown>;
    previousDoc?: Record<string, unknown>;
    user?: {
        id: string;
        role: string;
    };
    operation: 'create' | 'update' | 'delete';
}
export interface GlobalDefinition {
    slug: string;
    label: string;
    fields: Record<string, FieldDefinition>;
    access?: Omit<AccessConfig, 'create' | 'delete'>;
    versions?: boolean;
}
export interface AuthConfig {
    providers: AuthProviderConfig[];
    credentials?: {
        enabled: boolean;
        requireEmailVerification?: boolean;
        passwordPolicy?: PasswordPolicy;
    };
    autoProvision?: boolean;
    sessionMaxAge?: number;
    mfa?: {
        totp?: boolean;
    };
    /** Maximum concurrent sessions per user. Defaults to 5. */
    maxConcurrentSessions?: number;
    /** Top-level password policy applied to credential flows when set. */
    passwordPolicy?: PasswordPolicy;
    /** OAuth provider configuration (alternative to `providers[]` for some adapters). */
    oauth?: {
        providers?: Record<string, {
            clientId: string;
            clientSecret: string;
            scope?: string[];
            issuer?: string;
            tenantId?: string;
        }>;
        /** Allow signups for users not yet in `actuate_users` (default false). */
        allowSelfSignup?: boolean;
    };
}
export interface AuthProviderConfig {
    id: string;
    type: 'google' | 'github' | 'microsoft' | 'oidc' | 'saml';
    clientId: string;
    clientSecret: string;
    tenantId?: string;
    issuer?: string;
}
export interface PasswordPolicy {
    minLength?: number;
    requireUppercase?: boolean;
    requireLowercase?: boolean;
    requireNumbers?: boolean;
    requireSpecialChars?: boolean;
    preventReuse?: number;
    checkBreached?: boolean;
}
export interface PluginDefinition {
    name: string;
    version?: string;
    init?: (config: ActuateCMSConfig) => ActuateCMSConfig;
    fields?: Record<string, FieldDefinition>;
    collections?: Record<string, CollectionDefinition>;
    adminNavItems?: Array<{
        label: string;
        icon: string;
        path: string;
    }>;
    hooks?: Array<{
        event: string;
        handler: (...args: unknown[]) => Promise<void> | void;
    }>;
}
export interface DatabaseAdapter {
    connect(): Promise<void>;
    disconnect(): Promise<void>;
    query<T>(sql: string, params?: unknown[]): Promise<T[]>;
    transaction<T>(fn: () => Promise<T>): Promise<T>;
}
export interface StorageAdapter {
    upload(key: string, data: Buffer | ReadableStream, contentType: string): Promise<string>;
    download(key: string): Promise<Buffer>;
    delete(key: string): Promise<void>;
    getSignedUrl(key: string, expiresIn?: number): Promise<string>;
}
export interface CacheAdapter {
    get<T>(key: string): Promise<T | null>;
    set<T>(key: string, value: T, ttl?: number): Promise<void>;
    delete(key: string): Promise<void>;
    invalidateTag(tag: string): Promise<void>;
}
export interface EmailSendOptions {
    to: string;
    subject: string;
    html: string;
    text?: string;
    /** Optional Reply-To address (e.g. the form submitter). Adapters may ignore it. */
    replyTo?: string;
    /** Optional From override. Adapters may ignore it and use their default sender. */
    from?: string;
}
export interface EmailAdapter {
    send(options: EmailSendOptions): Promise<void>;
}
export interface SchedulerAdapter {
    schedule(id: string, cronExpression: string, handler: () => Promise<void>): Promise<void>;
    cancel(id: string): Promise<void>;
    list(): Promise<Array<{
        id: string;
        expression: string;
        nextRun: Date;
    }>>;
}
export interface RateLimitAdapter {
    check(key: string, limit: number, windowMs: number): Promise<{
        allowed: boolean;
        remaining: number;
        resetAt: Date;
    }>;
    reset(key: string): Promise<void>;
}
export interface AdminBrandingConfig {
    name?: string;
    logo?: string;
    favicon?: string;
    title?: string;
    primaryColor?: string;
    darkMode?: boolean;
}
export interface AdminConfig {
    /** URL path for the admin panel (default: '/admin'). Use a custom path like '/securelogin' to obscure the admin entry point. */
    path?: string;
    /**
     * Separate URL path for the unauthenticated auth screens — login, MFA,
     * setup wizard, forgot/reset password (e.g. '/securelogin'). When set, the
     * admin mount at `path` renders a 404 lookalike for signed-out visitors
     * instead of a login form, so the admin's existence isn't advertised at the
     * default `/admin`. Emailed reset/invite links point here. Omit to keep the
     * classic behavior (login inline at `path`).
     */
    loginPath?: string;
    branding?: AdminBrandingConfig;
    ipAllowlist?: string[];
}
export interface I18nConfig {
    defaultLocale: string;
    locales: Array<{
        code: string;
        label: string;
        dir?: 'ltr' | 'rtl';
    }>;
    fallbackLocale?: string;
}
export interface DatabaseShorthand {
    provider: 'prisma' | 'postgres' | 'sqlite' | 'mysql';
    url?: string;
}
export interface StorageShorthand {
    provider: 'vercel-blob' | 's3' | 'local';
    bucket?: string;
    region?: string;
}
export interface LayoutRegionConfig {
    /** Slug of the collection that holds layout variants for this region. */
    collection: string;
    /** Display label in the admin UI (e.g. "Header", "Footer"). */
    label?: string;
}
export interface LayoutConfig {
    /** Named regions whose variants are stored as collection documents. */
    regions: Record<string, LayoutRegionConfig>;
    /** Region name → default variant document ID used when no page in the ancestor chain sets one. */
    defaults?: Record<string, string>;
    /** When true (default), child pages inherit layout assignments from ancestor pages via URL path. */
    inherit?: boolean;
}
/**
 * HTTP cache + invalidation config for the CMS REST API. See
 * `docs/caching.md` for the full contract.
 *
 * - `publicReads.enabled` opts the read endpoints into emitting a
 *   `Cache-Control` header so CDN tiers (Vercel, Cloudflare, etc.) can
 *   short-circuit cache hits without the function ever waking. The
 *   header is only emitted for unauthenticated or API-key-authenticated
 *   GETs — session-cookie requests always get `private, no-store` so a
 *   shared cache cannot leak per-user state.
 * - `onInvalidate` is fired by the action layer after every write (create
 *   / update / delete / global update). The callback receives the
 *   canonical tag set (matching the SDK's `actuateTags()`) so a Next.js
 *   host app can wire it to `revalidateTag()`.
 */
export interface CacheConfig {
    publicReads?: {
        /** Off by default for safety. Set true to emit Cache-Control on GET reads. */
        enabled?: boolean;
        /** Seconds of fresh CDN cache (`s-maxage`). Default: 60. */
        maxAge?: number;
        /** Seconds of stale-while-revalidate. Default: 300. */
        staleWhileRevalidate?: number;
    };
    /**
     * Optional **runtime cache** layer for the four public read endpoints.
     *
     * Sits BETWEEN the CDN tier (Cache-Control / publicReads) and the
     * database. When the CDN expires an entry, the request reaches the
     * function — and a runtime-cache hit lets the function return the
     * cached JSON body in <10 ms instead of hitting Postgres.
     *
     * The adapter contract is intentionally minimal so the canonical Vercel
     * `getCache()` from `@vercel/functions` satisfies it without an
     * adapter shim. Self-hosted projects can plug in any KV store
     * (Upstash, in-memory, Redis) that implements `get` / `set` with
     * tag-based metadata.
     *
     * Session-cookie requests ALWAYS bypass this layer (per-user data must
     * never enter a shared cache). Errors during get/set are logged and
     * swallowed; cache failures never break a response.
     *
     * Wire `expireTag()` (or `invalidateByTag()` on Vercel) from inside
     * `onInvalidate` to keep the layer in sync with writes.
     */
    runtimeCache?: {
        adapter: RuntimeCacheAdapter;
        /** Seconds the cache entry stays warm. Default: 60. */
        ttl?: number;
    };
    /**
     * Called once per write, after the DB transaction has committed.
     * Failures here MUST NOT bubble — invalidation is best-effort. The
     * implementation should never throw; downstream callers swallow errors.
     */
    onInvalidate?: (tags: string[]) => void | Promise<void>;
}
/**
 * Minimal contract for a runtime / KV cache adapter. Matches the shape
 * of Vercel's `getCache()` object so it can be passed directly:
 *
 * ```ts
 * import { getCache } from '@vercel/functions'
 * handleActuateAPI({ config: { cache: { runtimeCache: { adapter: getCache() } } } })
 * ```
 *
 * Errors thrown from `get` or `set` are caught by the caller and never
 * surface to the user — cache failures degrade silently to a direct
 * database read.
 */
export interface RuntimeCacheAdapter {
    /**
     * Retrieve a previously cached value. Return `undefined` (or `null`)
     * on miss; any non-null, non-undefined value is treated as a hit.
     */
    get(key: string): Promise<unknown>;
    /**
     * Store a value with a TTL and a tag set used for bulk invalidation.
     *
     * - `ttl` is in seconds.
     * - `tags` are case-sensitive and must not contain commas. The cms-core
     *   layer always sends the same tag set the SDK's `actuateTags()`
     *   helper emits, so a single `expireTag()` call from `onInvalidate`
     *   drains every dependent entry.
     */
    set(key: string, value: unknown, options: {
        ttl: number;
        tags: string[];
    }): Promise<void>;
}
/** Top-level configuration for an Actuate CMS instance. */
export interface ActuateCMSConfig {
    secret?: string;
    platform: {
        database: DatabaseAdapter | DatabaseShorthand;
        storage: StorageAdapter | StorageShorthand;
        cache?: CacheAdapter;
        email?: EmailAdapter;
        scheduler?: SchedulerAdapter;
        rateLimit?: RateLimitAdapter;
    };
    auth: AuthConfig;
    plugins?: PluginDefinition[];
    collections: Record<string, CollectionDefinition>;
    globals?: Record<string, GlobalDefinition>;
    /** Layout region configuration for variant headers, footers, and other page-shell areas. */
    layout?: LayoutConfig;
    i18n?: I18nConfig;
    admin?: AdminConfig;
    /** Page builder configuration for block types and preview theming. */
    pageBuilder?: PageBuilderConfig;
    /**
     * Flat Page Editor section configuration. `sections.types` declares custom
     * section types so they are recognized, listed in the Add Section gallery,
     * and editable in the admin (the public site registers the matching renderer
     * via `registerSectionRenderer` from `@actuate-media/sections-react`).
     */
    sections?: SectionsConfig;
    /** HTTP cache + invalidation behaviour for the REST API. */
    cache?: CacheConfig;
    /** Top-level redirects configuration consumed by the redirects plugin. */
    redirects?: {
        /** Hosts that are allowed as redirect destinations (others rejected). */
        allowedExternalHosts?: string[];
    };
    /**
     * Site-wide SEO configuration. Consumed by the public sitemap/robots/og.png
     * routes and by `composePageMeta()` when building per-page meta. Per-collection
     * `CollectionSEOConfig` overrides take precedence over these defaults.
     */
    seo?: SiteSEOConfig;
    /**
     * Forms system configuration. Governs which field types are allowed, default
     * spam protection, file-upload limits, notification/webhook availability, and
     * PII/retention policy. Per-form settings (stored on the form definition)
     * override these defaults.
     */
    forms?: FormsConfig;
    /**
     * Commercial plan tier for hosted deployments. Self-hosted instances omit this
     * (defaults to `pro`). Override at runtime with `CMS_PLAN_TIER`.
     */
    plan?: PlanConfig;
}
/** Plan tier and upgrade messaging for feature gating. */
export interface PlanConfig {
    tier?: 'starter' | 'pro' | 'enterprise';
    /** Link shown in admin upgrade callouts (e.g. pricing page). */
    upgradeUrl?: string;
}
/** Site-wide Forms configuration. Per-form settings override these defaults. */
export interface FormsConfig {
    /** Master switch for the Forms feature. Defaults to enabled. */
    enabled?: boolean;
    /** Default spam protections applied to forms that don't override them. */
    defaultSpamProtection?: Array<'honeypot' | 'timeToSubmit' | 'rateLimit' | 'captcha' | 'domainAllowlist' | 'blocklist' | 'keyword' | 'aiClassifier'>;
    /** Restrict the field types editors can add (defaults to all supported types). */
    allowedFieldTypes?: string[];
    /** Hard cap on the number of fields per form. */
    maxFieldsPerForm?: number;
    /** Global submission rate ceiling per IP. */
    maxSubmissionsPerMinute?: number;
    fileUpload?: {
        enabled?: boolean;
        maxSizeMB?: number;
        allowedMimeTypes?: string[];
    };
    notifications?: {
        enabled?: boolean;
    };
    webhooks?: {
        enabled?: boolean;
    };
    autoresponder?: {
        enabled?: boolean;
    };
    /** Days to retain submissions before automated cleanup (0/undefined = forever). */
    retentionDays?: number;
    /** Encrypt fields flagged as sensitive at rest. */
    encryptSensitiveFields?: boolean;
    /** Fallback success message when a form doesn't define its own. */
    defaultSuccessMessage?: string;
    /** Public submission path prefix (defaults to `/api/forms`). */
    publicSubmissionPath?: string;
}
/**
 * Site-wide search-engine verification tokens. Each value is the bare
 * `content` of the provider's verification `<meta>` tag — never raw HTML. The
 * public renderer escapes all values and emits only safe `<meta>` tags, so
 * these fields can never inject script.
 */
export interface SeoVerificationConfig {
    /** Google Search Console — rendered as `<meta name="google-site-verification">`. */
    googleSearchConsole?: string;
    /** Bing Webmaster Tools — rendered as `<meta name="msvalidate.01">`. */
    bingWebmasterTools?: string;
    /** Yandex — rendered as `<meta name="yandex-verification">`. */
    yandex?: string;
    /** Pinterest — rendered as `<meta name="p:domain_verify">`. */
    pinterest?: string;
    /** Facebook domain verification — rendered as `<meta name="facebook-domain-verification">`. */
    facebookDomain?: string;
    /**
     * Custom verification meta tags. Only `name`/`property` + `content` are
     * rendered (escaped). Reserved for advanced operators — do NOT use for
     * arbitrary HTML.
     */
    customMetaTags?: Array<{
        id: string;
        name?: string;
        property?: string;
        content: string;
    }>;
}
/** Site-wide SEO configuration. */
export interface SiteSEOConfig {
    /** Canonical site origin, e.g. "https://maidpro.co". No trailing slash. */
    siteUrl?: string;
    /** Brand / site name used in OG cards, sitemap, schema.org Organization. */
    siteName?: string;
    /**
     * Site-wide title template applied when a page/post has no explicit SEO
     * title. Supports `%page%`, `%site%`, `%type%`, `%separator%`, `%author%`,
     * `%date%`, `%category%`, `%tag%` tokens. Empty falls back to `%page% — %site%`.
     */
    titleTemplate?: string;
    /**
     * Default meta description used when a page/post does not define its own.
     * Feeds the public renderer and SEO scoring fallback.
     */
    defaultMetaDescription?: string;
    /**
     * Media asset id for the default social share (Open Graph / Twitter) image,
     * used as the fallback when a page/post has no custom social image. The
     * public renderer resolves the asset id to a URL. Prefer this over a raw
     * `defaultOgImage` URL.
     */
    defaultSocialImageAssetId?: string;
    /** Default OG image URL used when a document doesn't supply one. Can be a relative path (`/og-default.png`) or absolute URL. */
    defaultOgImage?: string;
    /** Default Twitter handle (with leading @). */
    twitterHandle?: string;
    /** Search-engine verification tokens injected into public `<head>` metadata. */
    verification?: SeoVerificationConfig;
    /** Schema.org Organization data injected into the site-wide JSON-LD. */
    organization?: {
        name: string;
        url?: string;
        logo?: string;
        sameAs?: string[];
    };
    /**
     * Server-side publish validation gate for publicly-routed content
     * (`page`/`post` collections). Enforced in the data layer so the SDK, MCP,
     * agents, and the API can't publish broken pages past the admin's checks.
     *   - `block` (default): reject a publish that fails a hard invariant
     *     (missing title or URL).
     *   - `warn`: allow, but log the failure.
     *   - `off`: disable entirely.
     */
    publishValidation?: 'block' | 'warn' | 'off';
    /** robots.txt generation defaults. */
    robots?: {
        /** Disable the public /robots.txt route entirely. */
        disabled?: boolean;
        /**
         * Legacy switch: block ALL known AI bots (training + retrieval). Prefer
         * `aiPolicy` for the nuanced training-vs-retrieval distinction. Kept for
         * backward compatibility — used only when `aiPolicy` is not set.
         */
        blockAIBots?: boolean;
        /**
         * Nuanced AI-crawler policy. Training crawlers (GPTBot, ClaudeBot,
         * Google-Extended, CCBot, …) ingest content into models with no citation;
         * retrieval/answer-engine crawlers (OAI-SearchBot, PerplexityBot,
         * ChatGPT-User, …) cite and refer users back. Default for both is `allow`.
         * A common stance is `{ training: 'block', retrieval: 'allow' }`.
         */
        aiPolicy?: {
            training?: 'allow' | 'block';
            retrieval?: 'allow' | 'block';
        };
        /** Additional User-agent / disallow rules to append. */
        additionalRules?: Array<{
            userAgent: string;
            allow?: string[];
            disallow?: string[];
        }>;
        /** Default noindex for documents (site-wide). */
        defaultNoIndex?: boolean;
        /** Default nofollow for documents (site-wide). */
        defaultNoFollow?: boolean;
        /** Force noindex on every page in non-production environments (prevents
         * preview/staging deploys from being indexed). */
        noIndexNonProduction?: boolean;
    };
    /** Sitemap generation defaults. */
    sitemap?: {
        /** Disable the public /sitemap.xml route entirely. */
        disabled?: boolean;
        /** Collection slugs to exclude from the sitemap index. */
        excludeCollections?: string[];
        /** Default changefreq applied when a collection doesn't supply one. */
        defaultChangeFreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
        /** Default priority applied when a collection doesn't supply one. */
        defaultPriority?: number;
    };
    /** /og.png defaults. */
    ogImage?: {
        /** Disable the public /og.png route. */
        disabled?: boolean;
        theme?: 'light' | 'dark';
    };
    /** /llms.txt generation defaults (the curated AI/agent guide). */
    llms?: {
        /** Disable the public /llms.txt route entirely (returns 404). */
        disabled?: boolean;
        /**
         * Reviewed one-to-three sentence summary rendered as the file's blockquote.
         * Falls back to `defaultMetaDescription` when unset. This is a reviewed
         * field, not editor-freeform — keep it accurate and concise.
         */
        summary?: string;
        /** Optional free-text context block (no headings) after the summary. */
        intro?: string;
        /**
         * Restrict AUTO selection to these collection slugs. When omitted, all
         * public (non-internal) collections are eligible. Use this to keep the file
         * to high-value, stable types (pages, services, docs) rather than every
         * blog post — the curated 20–50 link sweet spot.
         */
        eligibleCollections?: string[];
        /** Max entries emitted per section/collection (default 25) so a large
         * archive can't flood the file. */
        maxPerSection?: number;
        /** Overall cap across all sections (default 50). */
        maxEntries?: number;
        /**
         * Also serve `/llms-full.txt` — the same curated selection with each page's
         * content inlined as clean Markdown (so agents can read the body, not just
         * the link). Off by default; these files get large.
         */
        fullEnabled?: boolean;
        /** Byte budget for `/llms-full.txt` (default 2,000,000). Output is
         * truncated cleanly once the budget is reached. */
        maxFullBytes?: number;
        /**
         * Append a honeypot link to `llms.txt` — a unique trap URL no human would
         * visit. A fetch of it means an automated reader followed a link from the
         * file, which the admin "AI crawler activity" panel records. Off by default
         * (a visible trap link can confuse answer engines); UA-based detection on
         * `llms.txt` / `llms-full.txt` / `.md` works regardless of this setting.
         */
        honeypot?: boolean;
    };
}
export {};
//# sourceMappingURL=types.d.ts.map