

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1# cloudflare-uptime23**Version:** 1.6.4 | **Runtime:** Cloudflare Workers | **Stack:** TypeScript + Hono + D1 + R245This file is the canonical set of instructions for AI coding agents working in this repo6(Claude Code, Cursor, Copilot, Codex, etc.). If your tool reads a vendor-specific file7instead (e.g. `CLAUDE.md`), that file just points back here — keep this one up to date,8not a duplicate.910## What1112Self-hosted uptime monitoring on Cloudflare Workers. Cron checks run every minute (configurable),13store results in D1, and serve public status pages with 90-day latency history and an RSS feed.14No servers. No monthly fees beyond the Cloudflare free tier.1516## Quick Start1718```bash19npm install # Install dependencies20wrangler d1 create uptime-monitor # Create D1 — paste ID into wrangler.toml21wrangler d1 execute uptime-monitor --file=schema.sql # Apply schema (local)22wrangler r2 bucket create uptime-assets # Create R2 bucket23wrangler secret put API_KEY # Set admin auth secret24npm run deploy # Deploy to Cloudflare25```2627Or run the interactive bootstrap: `./setup.sh`2829## Commands3031```bash32# Development33npm install # Install dependencies34npm run dev # Local dev (wrangler dev)35npm run typecheck # tsc --noEmit — run this before committing3637# Schema38npm run db:init # Apply schema.sql (local D1)39npm run db:init:remote # Apply schema.sql (remote D1)4041# Deploy42npm run deploy # Deploy Worker to Cloudflare43```4445There is no automated test suite. `npm run typecheck` plus manual verification (see the46escaping gotcha below for why typecheck alone isn't always enough) is the check to run47before committing.4849## Architecture5051```52src/53 worker.ts # Hono app — all route registrations, custom domain middleware, scheduled() dispatch54 cron.ts # ScheduledEvent handler (the * * * * * trigger) — runs checks, fires alerts, daily cleanup55 health.ts # ScheduledEvent handler (the */15 * * * * trigger) — self-monitoring staleness check56 checks.ts # HTTP check runner (fetch + AbortController timeout)57 alerts.ts # Slack/Discord webhook payload builders (per-monitor and self-monitoring)58 db.ts # All D1 query functions (single source of truth for SQL)59 types.ts # Shared TypeScript interfaces (Env, Monitor, Check, Incident…)60 api/61 monitors.ts # CRUD for monitors62 pages.ts # CRUD for status pages + monitor assignments63 notices.ts # Maintenance notice lifecycle64 public.ts # Unauthenticated status page data endpoint65 rss.ts # RSS feed generator66 upload.ts # R2 logo upload/delete67 html/68 admin.ts # Admin dashboard (inline HTML/JS, no build step — see gotcha below)69 status.ts # Public status page shell (fetches /status/:slug/data at runtime)70schema.sql # Full D1 schema — run once with wrangler d1 execute71wrangler.toml # Worker config: D1 binding, R2 binding, cron schedules, routes72```7374Admin dashboard at `/` (auth via `X-API-Key` header). Public status pages at `/status/:slug`.75Custom domain routing: the `*` middleware maps an incoming hostname to its status page slug via D1.7677## Key Files7879```80wrangler.toml # Change database_id after `wrangler d1 create`, add custom routes here81schema.sql # Run this once — NOT auto-applied on deploy82src/worker.ts # Route table, custom domain middleware, and scheduled() dispatch by event.cron83src/cron.ts # Check interval + stagger-offset logic (see gotcha below)84src/health.ts # Self-monitoring staleness check — runs on its own cron, never touches the hot path85src/db.ts # Every D1 query — start here when debugging data issues86src/types.ts # Env interface (DB: D1Database, ASSETS: R2Bucket, API_KEY, HEALTH_ALERT_WEBHOOK)87src/checks.ts # What "ok" means: HTTP 200–399; anything else (including timeout) is down88src/alerts.ts # Webhook format: Slack/Discord compatible attachments payload89```9091## Configuration9293| Variable / Setting | Where set | Required | Description |94|--------------------|-----------|----------|-------------|95| `API_KEY` | `wrangler secret put API_KEY` | Yes | Admin auth — all `/api/*` routes check `X-API-Key` header |96| `database_id` | `wrangler.toml` | Yes | D1 database ID from `wrangler d1 create uptime-monitor` |97| `bucket_name` | `wrangler.toml` | Yes | R2 bucket for logos (default: `uptime-assets`) |98| `crons` | `wrangler.toml` `[triggers]` | Yes | `* * * * *` runs the check loop; `*/15 * * * *` runs the self-monitoring health check — both required, see gotcha below |99| `CLOUDFLARE_API_TOKEN` | GitHub Actions secret | CI only | Workers:Edit + D1:Edit + R2:Edit permissions |100| `alert_webhook` | per-monitor field | No | Slack or Discord incoming webhook URL — per-monitor up/down alerts |101| `HEALTH_ALERT_WEBHOOK` | `wrangler secret put HEALTH_ALERT_WEBHOOK` | No | Slack/Discord webhook for self-monitoring alerts (fires when checks stop landing on schedule) |102| `routes` | `wrangler.toml` | No | Custom domains for status pages (must be on Cloudflare DNS) |103104## Gotchas for AI Assistants105106- **Schema migrations are manual.** `wrangler deploy` does NOT run `schema.sql`. Use107 `wrangler d1 execute uptime-monitor --remote --file=schema.sql` for the initial apply.108 Subsequent changes go in a numbered `migrations/NNN_*.sql` file, applied with109 `wrangler d1 migrations apply uptime-monitor --remote`. Always apply the migration110 *before* deploying code that depends on it — if a migration adds a table that cron111 writes to on every check, deploying first means cron errors on that write until the112 migration catches up. `deploy.yml` here does not apply migrations automatically.113- **`src/html/admin.ts` nests a full `<script>` block inside a returned template-literal114 string — escape sequences behave differently than they look.** `admin.ts` returns one115 big JS string containing literal HTML, which itself contains a `<script>` block of116 literal JS text. Any `\'` or similar escape you write in that inline JS gets resolved117 by the *outer* TypeScript template literal before it ever becomes a string — the118 backslash is silently consumed, and what actually reaches the browser is unescaped.119 This broke admin login in production once (an apostrophe in banner text closed a JS120 string early, breaking the whole inline `<script>` parse) and `tsc --noEmit` did not121 catch it, because the broken JS lives inside a string literal that TypeScript never122 parses as code. If you touch the inline JS in `admin.ts` (or `status.ts`) and it needs123 an apostrophe or backslash, either avoid it (reword) or verify by actually rendering124 the function's output and syntax-checking the real `<script>` content — e.g.125 `npx tsx` to call `renderAdmin(true)`, extract the `<script>...</script>` block from126 the output, and run `node --check` on it. Don't rely on `tsc --noEmit` alone for this127 file.128- **No frontend build step.** All HTML is returned as template-literal strings from129 `src/html/admin.ts` and `src/html/status.ts`. Do not introduce a bundler.130- **`workers_dev = true`** in `wrangler.toml` exposes the Worker on a `.workers.dev` URL.131 Custom domains are added via `[[routes]]` blocks — each requires `custom_domain = true`132 and the domain must be proxied through Cloudflare DNS.133- **Cron runs from one datacenter**, not globally. D1 latency is lowest when the cron134 datacenter is geographically close to your D1 region.135- **`interval_minutes` is checked by modulo plus a per-monitor stagger offset, not a136 plain modulo.** `cron.ts` computes a deterministic hash-based offset from `monitor.id`137 and checks `(minuteOfDay + checkOffset(...)) % interval_minutes === 0`. Each monitor138 still runs exactly once per `interval_minutes`, just at a different phase — this139 exists so monitors sharing a common-multiple interval (1/5/10/30 all divide 30) don't140 all land in the same cron tick at once. Don't "simplify" this back to plain modulo.141- **The Workers Free plan hard-caps CPU time at 10ms per invocation.** This is a real142 constraint, not a soft limit — a cron tick checking many monitors can genuinely run out143 of budget. `cron.ts` batches all due monitors' D1 writes/reads into a couple of144 round-trips per tick (not one per monitor) specifically to stay under this. If you add145 a new per-monitor D1 call inside the cron loop, batch it across all due monitors rather146 than calling it once per monitor in a loop — the fixed dispatch overhead per D1 call is147 what blows the budget, not D1's own query time.148- **`src/health.ts` runs on a separate, independent cron trigger (`*/15 * * * *`) and149 must never be merged into or add cost to the `* * * * *` check-loop tick** — it exists150 specifically to detect when that tick stops completing, so it can't depend on it.151- **Checks table has a 90-day rolling window.** The cleanup in `cron.ts` deletes rows older152 than 90 days once per day (at midnight UTC); `uptime_bucket_rollups` is cleaned the same way.153- **`alert_webhook` is stored per-monitor** (not per status page). Set it to a Slack or154 Discord incoming webhook URL to receive up/down alerts. `HEALTH_ALERT_WEBHOOK` is a155 separate, account-level secret for self-monitoring alerts — don't conflate the two.156- **Regenerating `package-lock.json` from scratch (`rm -f package-lock.json && npm install`)157 can silently produce an incomplete lockfile** missing optional platform-variant entries158 (`@esbuild/*`, `@img/sharp-*`, `@cloudflare/workerd-*` for platforms other than the one159 that generated it). `npm ci` requires full cross-platform consistency and will fail on160 a genuinely clean checkout (i.e. in CI) even though `npm ci` succeeds locally against161 that same file — it's self-consistent with itself, which isn't the same thing. When162 bumping a dependency, prefer editing `package.json` and running a plain `npm install`163 on top of the existing lockfile (incremental update) over deleting it first.164165## Contributing166167See [CONTRIBUTING.md](CONTRIBUTING.md).168
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| ANDRS-Projects/cloudflare-uptime-ossCLAUDE.md · 5 | CLAUDE.md | no sections | 16/100 | 9 days ago |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| TryGhost/Ghoste2e/AGENTS.md · 55k | AGENTS.md | setupteststylearch+2 | 100/100 | today | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 14 days ago | |
| code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 68k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 13 days ago | |
| aaif-goose/gooseAGENTS.md · 53k | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 8 days ago | |
| deepseek-ai/deepseek-harnessnative/landlock-run/AGENTS.md · 104k | AGENTS.md | setupteststylearch+3 | 100/100 | today | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 14 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 201k | AGENTS.md | buildteststylearch+3 | 100/100 | 14 days ago | |
| elastic/elasticsearchx-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/AGENTS.md · 78k | AGENTS.md | buildtestlint-formatstyle+2 | 100/100 | 14 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/andrs-projects-cloudflare-uptime-oss-agents)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.