/** * Return Cloudflare package discovery metadata: exports, secrets, and usage notes. */ export default async function describeCloudflareToolkit() { return { packageId: 'cloudflare', exports: [ 'overview', 'accounts', 'api-v4', 'api-v4-list', 'analytics', 'docs', 'observability-logs', 'publish-static', 'rulesets', 'r2-put-object', 'verify', ], helpers: [ { export: 'accounts', purpose: 'List Cloudflare API token aliases (default, kody, pages) with secret name, known accountId, and what each can/cannot do.', mutating: false, requiredSecrets: [], }, { export: 'verify', purpose: 'Verify a Cloudflare API token using the correct endpoint for user-scoped vs account-scoped tokens, and return the accountId to use when unambiguous. Never treat /user/tokens/verify 401 alone as “token invalid.”', mutating: false, requiredSecrets: [ 'cloudflareApiToken', 'cloudflareApiTokenKodyAccount', 'cloudflarePagesApiToken', ], }, { export: 'api-v4', purpose: 'Call Cloudflare API v4 endpoints under /client/v4/ with JSON, multipart form, or raw body, using an optional account alias or apiTokenSecret.', mutating: true, requiredSecrets: [ 'cloudflareApiToken', 'cloudflareApiTokenKodyAccount', 'cloudflarePagesApiToken', ], }, { export: 'api-v4-list', purpose: 'Auto-paginate Cloudflare API v4 list endpoints (page or cursor). Pages project lists reject per_page above 10 — omit perPage for the endpoint default.', mutating: false, requiredSecrets: [ 'cloudflareApiToken', 'cloudflareApiTokenKodyAccount', 'cloudflarePagesApiToken', ], }, { export: 'publish-static', purpose: 'Publish a static site to Cloudflare Pages (Direct Upload): create-if-missing, upload JWT refresh, scan for forbidden content, wait until live.', mutating: true, requiredSecrets: [ 'cloudflareApiToken', 'cloudflareApiTokenKodyAccount', 'cloudflarePagesApiToken', ], }, { export: 'analytics', purpose: 'Query Cloudflare GraphQL analytics for zone traffic summaries, request paths, statuses, user agents, and outage forensics.', mutating: false, requiredSecrets: [ 'cloudflareApiToken', 'cloudflareApiTokenKodyAccount', 'cloudflarePagesApiToken', ], }, { export: 'docs', purpose: 'Fetch allowlisted Cloudflare developer docs pages as markdown-oriented text.', mutating: false, requiredSecrets: [], }, { export: 'observability-logs', purpose: 'Search Cloudflare Log Explorer datasets with structured filters or custom SQL, with an analytics fallback for missing entitlement.', mutating: false, requiredSecrets: [ 'cloudflareApiToken', 'cloudflareApiTokenKodyAccount', 'cloudflarePagesApiToken', ], }, { export: 'r2-put-object', purpose: 'PUT binary objects into an R2 bucket (sourceUrl preferred for large files; bytesBase64 for small). Use instead of api-v4 for R2 file uploads from a URL.', mutating: true, requiredSecrets: [ 'cloudflareApiToken', 'cloudflareApiTokenKodyAccount', 'cloudflarePagesApiToken', ], }, { export: 'rulesets', purpose: 'Preview or apply Cloudflare Rulesets/WAF custom rules, including cheap bogus-route blocks before traffic reaches origin.', mutating: true, requiredSecrets: [ 'cloudflareApiToken', 'cloudflareApiTokenKodyAccount', 'cloudflarePagesApiToken', ], }, ], notes: [ 'Authenticate with account: "default" | "kody" | "pages" (default token: cloudflareApiToken), or apiTokenSecret for an unlisted secret name.', 'Never pass a raw API token string; always reference a Kody secret by alias or name.', 'Approve api.cloudflare.com for each Cloudflare token secret before using authenticated exports.', 'Token verification: use export verify (or /accounts/{account_id}/tokens/verify for account-scoped tokens). /user/tokens/verify often 401s for valid account tokens — that is not proof the secret is broken.', 'Permission checks after verify: 403 means lacks permission; 400/404 on a mutating call against a bad/missing target means the token is authorized for that operation.', 'Account ids: call accounts() for alias metadata (pages → KCD, kody → Kody). default spans multiple accounts — verify().accessibleAccounts lists them, or pass accountId.', 'Publish public static sites with publish-static (Pages Direct Upload). Workers Assets and public R2 hosting are not implemented yet.', 'api-v4 accepts body (JSON), form (multipart), or rawBody. Prefer publish-static for Pages deploys and r2-put-object for R2 PUTs from a URL.', 'Use analytics for zone HTTP request aggregates, especially when Log Explorer SQL is unavailable or returns missing entitlement.', 'Use rulesets in dry-run mode first to preview WAF/custom-rule changes; pass apply: true only after review.', 'Use observability-logs for Workers trace events, HTTP request forensics, Access requests, Ray IDs, errors, and slow-request investigations.', 'Use docs for current Cloudflare documentation under allowlisted product prefixes.', ], } }