Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions docs/contributing/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,23 +91,21 @@ Optional Worker secret:
dev uses offline search while `WRANGLER_IS_LOCAL_DEV` is set or the binding is
missing.

## Cloudflare API (Worker + Browser Rendering + Email)
## Cloudflare API (Worker + Email)

Optional Worker secrets/vars (see `packages/worker/src/env-schema.ts` and
`packages/worker/src/mcp/cloudflare/cloudflare-rest-client.ts`):

- `CLOUDFLARE_API_TOKEN` — Cloudflare API token used by the internal API client
(`Authorization: Bearer ...`) for `page_to_markdown` Browser Rendering and the
Cloudflare Email sender. User Cloudflare API calls from codemode use saved
secrets and secret-aware `fetch` (see
`docs/contributing/skill-patterns/cloudflare-api-v4.md`). Local `npm run dev`
sets this to the Cloudflare mock token unless `AI_MODE=remote` or
`SKIP_CLOUDFLARE_MOCK=1`; when unset and no mock is attached, the billed
`page_to_markdown` Browser Rendering fallback fails fast with a setup hint.
- `CLOUDFLARE_ACCOUNT_ID` — Cloudflare account id required by the
`page_to_markdown` capability when it falls back to Browser Rendering
`POST /client/v4/accounts/{account_id}/browser-rendering/markdown`. This is a
Worker var (not a secret) and should match the account behind
(`Authorization: Bearer ...`) for Worker-side Cloudflare REST calls such as
the Cloudflare Email sender. User
Cloudflare API calls from codemode use saved secrets and secret-aware `fetch`
(see `docs/contributing/skill-patterns/cloudflare-api-v4.md`). Local
`npm run dev` sets this to the Cloudflare mock token unless
`AI_MODE=remote` or `SKIP_CLOUDFLARE_MOCK=1`.
- `CLOUDFLARE_ACCOUNT_ID` — Cloudflare account id required by the Cloudflare
Email Service REST API fallback used by local mocks and preview deploys. This
is a Worker var (not a secret) and should match the account behind
`CLOUDFLARE_API_TOKEN`.
- `CLOUDFLARE_API_BASE_URL` — API base URL; defaults to
`https://api.cloudflare.com` when unset, including for outbound email sending.
Expand Down
4 changes: 2 additions & 2 deletions docs/contributing/mock-api-servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ Worker package so it can also be deployed alongside the main app.
and set `ACME_API_BASE_URL` to the mock Worker origin.

See `packages/mock-servers/cloudflare/` for a small Cloudflare API v4 subset
mock used by tests and `page_to_markdown` / internal API client (started by
`npm run dev` unless `SKIP_CLOUDFLARE_MOCK=1`).
mock used by tests and the internal API client (started by `npm run dev`
unless `SKIP_CLOUDFLARE_MOCK=1`).

### Tips

Expand Down
20 changes: 8 additions & 12 deletions docs/contributing/setup-manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,8 @@ automatically:
Cloudflare AI Gateway)
- `CLOUDFLARE_ACCOUNT_ID` (required for local development when `AI_MODE=remote`
so Wrangler can authenticate Workers AI requests against the correct account;
also required when using the `page_to_markdown` capability's Cloudflare
Browser Rendering fallback against the live API and for the Cloudflare Email
Service REST API fallback used by local mocks and preview deploys)
also required for the Cloudflare Email Service REST API fallback used by
local mocks and preview deploys)
- `CLOUDFLARE_API_TOKEN` (required for local development when `AI_MODE=remote`
so Wrangler can authenticate Workers AI requests; also reused by the
Cloudflare Email Service REST API fallback when local/preview email is routed
Expand Down Expand Up @@ -106,7 +105,8 @@ secrets from `packages/worker/.env`.
Configure these GitHub Actions secrets and variables for workflows:

- `CLOUDFLARE_API_TOKEN` (Workers deploy + D1 edit access on the correct
account)
account; also reused for remote AI and Cloudflare API workflows that run with
account secrets + skills)
- `COOKIE_SECRET` (same format as local)
- `APP_BASE_URL` (optional GitHub Actions **variable**, used by the production
deploy as the canonical public app origin and written into the generated
Expand Down Expand Up @@ -136,7 +136,10 @@ How to get/set each value:

- `CLOUDFLARE_API_TOKEN`
- In Cloudflare Dashboard, create an API Token with permissions to deploy
Workers and edit D1 on the target account.
Workers and edit D1 on the target account. This is the same token to reuse
for remote AI and Cloudflare API workflows that run with account secrets +
skills; when you do, also include the product permissions needed for those
APIs.
- In GitHub: `Settings` → `Secrets and variables` → `Actions` →
`New repository secret`.
- `COOKIE_SECRET`
Expand Down Expand Up @@ -174,13 +177,6 @@ How to get/set each value:
- In GitHub: **Settings → Secrets and variables → Actions → Variables**, add
`SENTRY_ORG` and `SENTRY_PROJECT` with your Sentry slugs (for example from
`npx @sentry/wizard@latest -i sourcemaps`).
- `CLOUDFLARE_API_TOKEN` (required for remote AI; used by the Worker for
`page_to_markdown` Browser Rendering; user API calls use account secrets +
skills)
- Create a Cloudflare API token with the account permissions needed for the
product APIs you want to call. This same secret already powers production
deploys and the `page_to_markdown` Browser Rendering path. For Browser
Rendering fallback, include the **Browser Rendering - Edit** permission.
- `CAPABILITY_REINDEX_SECRET` (optional)
- Generate a long random secret (for example `openssl rand -hex 32`), store it
as the repository secret `CAPABILITY_REINDEX_SECRET`, and let the deploy
Expand Down
4 changes: 2 additions & 2 deletions docs/contributing/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ Quick notes for getting a local kody environment running.
- `npm run dev` (starts mock API servers automatically, the main worker, and the
local home connector; it sets `AI_MODE=mock`, `AI_MOCK_BASE_URL`, and
`CLOUDFLARE_API_BASE_URL` + `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID`
to the local Cloudflare API mock Worker for `page_to_markdown`, the internal
Cloudflare API client, and local email sending. Unless you already set
to the local Cloudflare API mock Worker for the internal Cloudflare API client
and local email sending. Unless you already set
`CLOUDFLARE_EMAIL_FROM`, the launcher also defaults it to `reset@kody.dev`.
Set `SKIP_CLOUDFLARE_MOCK=1` to skip the local Cloudflare mock entirely. The
home connector receives the resolved worker origin via `WORKER_BASE_URL`. When
Expand Down
4 changes: 0 additions & 4 deletions docs/contributing/skill-patterns/cloudflare-api-v4.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ Capability **allowlists** on secrets apply to **capability inputs** that use

All API paths must be under **`/client/v4/`** (see
[Cloudflare API docs](https://developers.cloudflare.com/fundamentals/api/how-to/make-api-calls/)).
The internal `CloudflareRestClient` used for Browser Rendering enforces the same
shape.

## Example skill (save via `meta_save_skill`)

Expand Down Expand Up @@ -61,7 +59,5 @@ For **POST / PUT / PATCH / DELETE**, set `Content-Type: application/json` and

## Related

- Browser Rendering billing path still uses the Worker’s internal client for
`page_to_markdown` — that is unchanged.
- Developer docs (read-only) pattern:
[cloudflare-developer-docs.md](./cloudflare-developer-docs.md).
30 changes: 10 additions & 20 deletions docs/contributing/skill-patterns/cloudflare-developer-docs.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
# Skill pattern: Cloudflare Developer Docs (`developers.cloudflare.com`)

The **`cloudflare_api_docs`** builtin capability was removed in favor of **saved
skills** (or one-off **`execute`** code) so Cloudflare-specific doc reading can
evolve without shipping Worker changes.

Use this pattern when you need API or product documentation from
[developers.cloudflare.com](https://developers.cloudflare.com).

## Prefer cheaper steps first
## Preferred approach

1. **Markdown-preferred `fetch`** against a path under the docs site (see
allowlist below). Matches what the old builtin did before any billed
fallback.
2. If the response is still hard to use (heavy HTML, etc.), call
**`codemode.page_to_markdown`** with the same URL so Browser Rendering runs
only when needed (billed).
Use **markdown-preferred `fetch`** against a path under the docs site (see
allowlist below). Keep the skill focused on direct docs retrieval instead of
adding site-specific fallback machinery.

## Path allowlist (same policy as the former builtin)
## Path allowlist

Paths must:

Expand Down Expand Up @@ -77,26 +70,23 @@ Adjust `name`, `title`, `description`, and trust flags when saving.
const url = new URL(path, ORIGIN).toString()
const res = await fetch(url, { headers: { Accept: MARKDOWN_ACCEPT } })
const body = await res.text()
const contentType = res.headers.get('content-type')
const needsFallback =
contentType?.includes('text/html') && body.trimStart().startsWith('<')
if (needsFallback) {
return await codemode.page_to_markdown({ url })
}
return {
status: res.status,
contentType,
contentType: res.headers.get('content-type'),
markdownTokenEstimate: res.headers.get('x-markdown-tokens'),
body: body.slice(0, 500_000),
}
}
```

Callers should inspect `contentType` before treating `body` as Markdown. This
helper returns the raw sliced response body plus `markdownTokenEstimate`; HTML
responses are not auto-converted.

For parameterized skills, use **`meta_save_skill`** **`parameters`** (e.g. a
required `path` string) and read **`params.path`** instead of a hard-coded path.

## Related

- Billed fallback: **`page_to_markdown`** (`packages/worker` coding domain).
- Tracking: [issue #120](https://github.com/kentcdodds/kody/issues/120) (broader
official patterns folder).
2 changes: 1 addition & 1 deletion docs/contributing/skill-patterns/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ Worker-only primitives (billing bindings, secret storage, etc.).
| Pattern | Summary |
| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| [cloudflare-api-v4.md](./cloudflare-api-v4.md) | Call Cloudflare API v4 with secret-aware `fetch` to `api.cloudflare.com` (replaces removed `cloudflare_rest`). |
| [cloudflare-developer-docs.md](./cloudflare-developer-docs.md) | Fetch markdown from `developers.cloudflare.com` with an allowlisted path; optional `page_to_markdown` fallback. |
| [cloudflare-developer-docs.md](./cloudflare-developer-docs.md) | Fetch markdown from `developers.cloudflare.com` with an allowlisted path and return a bounded preview. |
2 changes: 1 addition & 1 deletion docs/use/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ To get **full markdown and schemas for one hit** (for example a capability’s

Examples:

- `page_to_markdown:capability`
- `kody_official_guide:capability`
- `user:preferred_org:value`
- `github:connector`
- `my-skill-name:skill`
Expand Down
69 changes: 0 additions & 69 deletions packages/mock-servers/cloudflare/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,6 @@ type DnsRecord = {
ttl: number
}

type BrowserRenderingMarkdownBody = {
url?: unknown
html?: unknown
userAgent?: unknown
rejectRequestPattern?: unknown
gotoOptions?: unknown
}

type DashboardEndpoint = {
method: string
path: string
Expand Down Expand Up @@ -98,12 +90,6 @@ const dashboardEndpoints: Array<DashboardEndpoint> = [
description: 'Create DNS record',
requiresAuth: true,
},
{
method: 'POST',
path: `/client/v4/accounts/${fixtureAccount.id}/browser-rendering/markdown`,
description: 'Convert a page or HTML snippet to markdown',
requiresAuth: true,
},
{
method: 'POST',
path: `/client/v4/accounts/${fixtureAccount.id}/email/sending/send`,
Expand Down Expand Up @@ -532,24 +518,6 @@ async function handleEmailSend(
}

async function routeApi(request: Request, env: MockCloudflareEnv, url: URL) {
if (request.method === 'GET' && url.pathname === '/__mocks/markdown') {
return new Response('# Mock markdown\n\nServed as markdown.\n', {
headers: {
'content-type': 'text/markdown; charset=utf-8',
'x-markdown-tokens': '8',
},
})
}
if (request.method === 'GET' && url.pathname === '/__mocks/markdown-error') {
return new Response('# Mock markdown error\n\nServer error page.\n', {
status: 500,
headers: {
'content-type': 'text/markdown; charset=utf-8',
'x-markdown-tokens': '9',
},
})
}

if (!isAuthorized(request, env, url)) {
return errorEnvelope(401, 10000, 'Authentication error')
}
Expand Down Expand Up @@ -596,43 +564,6 @@ async function routeApi(request: Request, env: MockCloudflareEnv, url: URL) {
)
}

const markdownMatch = url.pathname.match(
/^\/client\/v4\/accounts\/([^/]+)\/browser-rendering\/markdown\/?$/,
)
if (markdownMatch && request.method === 'POST') {
const accountId = markdownMatch[1]!
const payload = (await readJsonBody(
request,
)) as BrowserRenderingMarkdownBody | null
if (payload === null) {
return errorEnvelope(400, 1001, 'invalid JSON body')
}
if (accountId !== fixtureAccount.id) {
return errorEnvelope(404, 1002, 'account not found')
}
const hasUrl =
typeof payload.url === 'string' && payload.url.trim().length > 0
const hasHtml =
typeof payload.html === 'string' && payload.html.trim().length > 0
if (!hasUrl && !hasHtml) {
return errorEnvelope(400, 1003, 'Either url or html is required.')
}
const mode = hasHtml ? 'html' : 'url'
const sourceValue = hasHtml
? String(payload.html).trim()
: String(payload.url).trim()
const markdown = [
'# Mock Browser Rendering',
'',
`mode: ${mode}`,
`source: ${sourceValue}`,
...(typeof payload.userAgent === 'string' && payload.userAgent.length > 0
? [`userAgent: ${payload.userAgent}`]
: []),
].join('\n')
return envelope(markdown, { status: 200 })
}

const emailMatch = url.pathname.match(
/^\/client\/v4\/accounts\/([^/]+)\/email\/sending\/send\/?$/,
)
Expand Down
3 changes: 1 addition & 2 deletions packages/worker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ CLOUDFLARE_EMAIL_FROM=reset@kody.dev
# SENTRY_ORG=your-org-slug
# SENTRY_PROJECT=your-project-slug

# Cloudflare API / Browser Rendering / Email (optional; defaults to the public Cloudflare API, and `npm run dev` sets a mock URL + token for `page_to_markdown`, local email, and the internal API client)
# Cloudflare API / Email (optional; defaults to the public Cloudflare API, and `npm run dev` sets a mock URL + token for local email and the internal API client)
# Uses Authorization: Bearer <token> against /client/v4/* endpoints.
# Reuse `CLOUDFLARE_API_TOKEN` for the live API. In mock mode `npm run dev` injects a local token.
# page_to_markdown also needs CLOUDFLARE_ACCOUNT_ID when it falls back to Cloudflare Browser Rendering `/markdown`.
# CLOUDFLARE_API_BASE_URL=https://api.cloudflare.com
# Skip starting the Cloudflare mock during dev (use live API + token in `packages/worker/.env` instead):
# SKIP_CLOUDFLARE_MOCK=1
Expand Down
Loading
Loading