Skip to content

src/index.ts

138 lines · 5.2 KB · TypeScript
Raw
/**
 * 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.',
		],
	}
}