/** * Agent-readiness feature toggles (writes via POST /agent-audit/settings). * The free list is the default. A `toggles` prop or window.llmagnetAgentReadyToggles * replaces that list when it is a non-empty array. */ import { LlmsUiverseSwitch } from '@/components/llms-uiverse-switch'; import type { AgentReadySettings } from './types'; import { __, sprintf } from '../../lib/i18n'; interface ToggleMeta { key: string; label: string; description: string; implemented?: boolean; } function getDefaultToggles(): ToggleMeta[] { return [ { key: 'well_known', label: __('Well-known endpoints', 'llmagnet-llm-txt-generator'), description: __( 'Serve agent-card.json, agent-skills, mcp.json, security.txt, the api-catalog, and the change-password redirect.', 'llmagnet-llm-txt-generator', ), implemented: true, }, { key: 'robots_ai_rules', label: __('robots.txt AI crawler rules', 'llmagnet-llm-txt-generator'), description: __( 'Managed User-agent groups for AI crawlers plus the Content-Signal block.', 'llmagnet-llm-txt-generator', ), implemented: true, }, { key: 'markdown_endpoints', label: __('Markdown endpoints (.md)', 'llmagnet-llm-txt-generator'), description: __( 'Serve any page as clean markdown at {permalink}.md.', 'llmagnet-llm-txt-generator', ), implemented: true, }, { key: 'link_headers', label: __('Link discovery headers', 'llmagnet-llm-txt-generator'), description: __( 'HTTP Link headers advertising llms.txt, the agent card, and schemamap.xml.', 'llmagnet-llm-txt-generator', ), implemented: true, }, { key: 'security_headers', label: __('Security headers', 'llmagnet-llm-txt-generator'), description: __( 'nosniff, Referrer-Policy, Permissions-Policy, HSTS, CSP report-only.', 'llmagnet-llm-txt-generator', ), implemented: true, }, { key: 'og_fill', label: __('Open Graph verify-or-fill', 'llmagnet-llm-txt-generator'), description: __( 'Fill OG/canonical tags only when no SEO plugin owns them.', 'llmagnet-llm-txt-generator', ), implemented: true, }, { key: 'indexnow', label: __('IndexNow', 'llmagnet-llm-txt-generator'), description: __( 'Ping IndexNow-backed engines when content is published or updated.', 'llmagnet-llm-txt-generator', ), implemented: true, }, ]; } type SubOptionKey = | 'markdown_conneg' | 'restore_feed_links' | 'canonical_fill' | 'meta_description_fill'; type SecurityHeaderKey = | 'nosniff' | 'referrer' | 'permissions' | 'hsts' | 'hsts_subdomains' | 'csp_report'; const SECURITY_HEADER_LABELS: Array<[SecurityHeaderKey, string]> = [ ['nosniff', 'X-Content-Type-Options'], ['referrer', 'Referrer-Policy'], ['permissions', 'Permissions-Policy'], ['hsts', 'HSTS'], ['hsts_subdomains', 'HSTS includeSubDomains'], ['csp_report', 'CSP (report-only)'], ]; function isToggleMeta(value: unknown): value is ToggleMeta { if (!value || typeof value !== 'object') return false; const item = value as Record; return ( typeof item.key === 'string' && item.key.trim() !== '' && typeof item.label === 'string' && item.label.trim() !== '' && typeof item.description === 'string' ); } function usableToggles(items: ToggleMeta[]): ToggleMeta[] { return items.filter((item) => item.implemented !== false); } function readWindowToggles(): ToggleMeta[] | null { const raw = window.llmagnetAgentReadyToggles; if (!Array.isArray(raw) || raw.length === 0) return null; const items = usableToggles(raw.filter(isToggleMeta)); return items.length > 0 ? items : null; } function resolveToggles(provided?: ToggleMeta[]): ToggleMeta[] { if (provided && provided.length > 0) { const items = usableToggles(provided.filter(isToggleMeta)); if (items.length > 0) return items; } return readWindowToggles() ?? getDefaultToggles(); } interface FeatureTogglesProps { settings: AgentReadySettings; saving: boolean; onToggle: (key: string, value: boolean) => void; onContentSignal: (key: 'enabled' | 'search' | 'ai_input' | 'ai_train', value: boolean) => void; toggles?: ToggleMeta[]; onSubOption?: (key: SubOptionKey, value: boolean) => void; onSecurityHeader?: (key: SecurityHeaderKey, value: boolean) => void; } export function FeatureToggles({ settings, saving, onToggle, onContentSignal, toggles: togglesProp, onSubOption, onSecurityHeader, }: FeatureTogglesProps) { const toggles = resolveToggles(togglesProp); return (

{__('Agent-readiness features', 'llmagnet-llm-txt-generator')}

{__( 'Everything ships off by default โ€” enable what you want agents to use.', 'llmagnet-llm-txt-generator', )}

{toggles.map((toggle) => (
{toggle.label}

{toggle.description}

{toggle.key === 'indexnow' && settings.indexnow?.handled_externally && (

{sprintf( __( 'IndexNow is already provided by %1$s โ€” LLMagnet defers to avoid double pings.', 'llmagnet-llm-txt-generator', ), settings.indexnow.owner ?? __('another SEO plugin', 'llmagnet-llm-txt-generator'), )}

)} {toggle.key === 'indexnow' && Boolean(settings.toggles.indexnow) && settings.indexnow?.key_url && (

{__('Key file:', 'llmagnet-llm-txt-generator')}{' '} {settings.indexnow.key_url} {settings.indexnow.log.length > 0 && sprintf( __(' ยท Last ping: %1$d URL(s), HTTP %2$d', 'llmagnet-llm-txt-generator'), settings.indexnow.log[settings.indexnow.log.length - 1].urls, settings.indexnow.log[settings.indexnow.log.length - 1].status, )}

)}
onToggle(toggle.key, checked)} aria-label={toggle.label} />
))} {/* FA-2: Accept: text/markdown content negotiation (separate opt-in) */} {onSubOption && Boolean(settings.toggles.markdown_endpoints) && (
{__('Content negotiation (Accept: text/markdown)', 'llmagnet-llm-txt-generator')}

{__( 'Serve markdown on the normal permalink when an agent sends Accept: text/markdown.', 'llmagnet-llm-txt-generator', )}

onSubOption('markdown_conneg', checked)} aria-label={__( 'Markdown content negotiation', 'llmagnet-llm-txt-generator', )} />
{settings.cache_plugin && (

{sprintf( __( '%1$s is active. Page caches that ignore the Vary: Accept header can serve markdown to regular visitors. Exclude this site from full-page caching for negotiated responses, or keep this off and rely on .md URLs.', 'llmagnet-llm-txt-generator', ), settings.cache_plugin, )}

)}
)} {/* FA-5: feed-link restoration (independent of the toggles above) */} {onSubOption && (
{__('Restore RSS feed links', 'llmagnet-llm-txt-generator')}

{__( 'Re-add the feed tags if your theme removed them.', 'llmagnet-llm-txt-generator', )}

onSubOption('restore_feed_links', checked)} aria-label={__('Restore RSS feed links', 'llmagnet-llm-txt-generator')} />
)} {/* FA-6: canonical / meta-description fill (ride on og_fill) */} {onSubOption && Boolean(settings.toggles.og_fill) && (
)} {/* FA-7: per-header switches (ride on security_headers) */} {onSecurityHeader && Boolean(settings.toggles.security_headers) && (
{SECURITY_HEADER_LABELS.map(([key, label]) => ( ))}
{Boolean(settings.security_headers?.hsts_subdomains) && (

{__( 'includeSubDomains applies HSTS to every subdomain for a year โ€” make sure all of them serve HTTPS before enabling.', 'llmagnet-llm-txt-generator', )}

)}
)} {/* Content-Signal switches (rides on robots AI rules) */}
{__('Content-Signal', 'llmagnet-llm-txt-generator')}

{__( 'State your AI content-usage preferences in robots.txt (requires AI crawler rules).', 'llmagnet-llm-txt-generator', )}

onContentSignal('enabled', checked)} aria-label={__('Content-Signal', 'llmagnet-llm-txt-generator')} />
{settings.content_signal.enabled && (
{( [ ['search', __('Search', 'llmagnet-llm-txt-generator')], ['ai_input', __('AI input', 'llmagnet-llm-txt-generator')], ['ai_train', __('AI training', 'llmagnet-llm-txt-generator')], ] as const ).map(([key, label]) => ( ))}
)}
); }