* Add newsletter worker: weekly community components email via Resend
New Cloudflare Worker (cloudflare-workers/newsletter/) that composes and
sends a weekly email featuring trending components (one Skill, Agent, MCP,
Hook and Setting per send). Selection is weighted-random by recent downloads
and all copy rotates from pools so no two emails read the same. Delivered as
a Resend Broadcast to the segment in RESEND_SEGMENT_ID, with per-recipient
unsubscribe handled by Resend, reply-to routing, and open/click tracking via
track.aitmpl.com. Cron: Sundays 16:00 UTC (slot freed by decommissioning the
claude-docs-monitor worker, documented in CLAUDE.md).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Address review: escape URL in HTML, surface broadcast id, cron dedupe guard
- escapeHtml() on the component URL in the HTML href/text, consistent with
every other dynamic field in the block
- Include the created broadcast id in the error when POST /broadcasts/{id}/send
fails, so an orphaned broadcast can be recovered manually
- Guard the scheduled path against Cloudflare's occasional cron double-fire:
pre-flight check for an existing non-draft "newsletter <today>" broadcast
before sending (fails open; manual /trigger bypasses it so pilots can send
multiple times a day)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Address review: log non-2xx Sentry responses in newsletter sentry.js
reportError and checkIn now check response.ok and console.error rejected
envelopes (invalid DSN, auth failure, rate limiting) so observability
failures leave a signal in worker logs instead of passing silently. The
never-throws guarantee is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Address review: close cron double-fire race with draft leader election
The read-then-create pre-check left a window where two overlapping cron
invocations could both pass the check and both send. The scheduled path now
runs a stateless leader election on Resend itself: each run creates its
broadcast as a draft, waits a 10s settle so concurrent drafts are visible,
lists today's broadcasts, and only the deterministic winner (smallest draft
id, and only if nobody already sent) calls /send; losers delete their draft
and yield. Chosen over Workers KV because KV has no compare-and-set and
cannot provide an atomic lock; a Durable Object would, but is overkill for
this worker. Manual /trigger sends still bypass dedupe.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Address review: prevent stale drafts from blocking same-day cron retries
Two guards against a transient /send failure leaving an abandoned draft that
would win every later election with nobody to send it:
- On /send failure the run now deletes its own draft before throwing, so a
retry starts clean (if the send actually succeeded despite the error
response, the broadcast is no longer a draft and the DELETE is a no-op).
- The election only yields to drafts created within the 5-minute overlap
window; older drafts belong to runs that died before cleanup and are
ignored as candidates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The daily-health-report worker now closes the loop the user asked for:
review Sentry issues on a routine and clean up what's noise. Deliberately
conservative — an issue is auto-resolved ONLY if its title matches a known
verification-noise pattern (NOISE_TITLE_PATTERNS in index.js); every other
issue (i.e. any real production error) is left untouched and always listed
in the Telegram digest for human review. Never widen this to "resolve
everything automatically" — that would hide real bugs instead of
surfacing them.
Verified end-to-end: sent one event matching the noise pattern and one
simulating a real error. The worker auto-resolved only the former (Sentry
confirmed 0 → 1 → 0 unresolved) and kept the latter open and reported.
Also manually cleaned up the 5 pre-existing test issues from today's
Sentry setup verification, now that this routine exists to keep the
projects clean going forward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a proactive daily heartbeat, since the existing Telegram flows
(docs-monitor, pulse) only notify on change/error/weekly schedule and never
confirm "everything's fine." Runs at 14:00 UTC (10:00 AM EDT) and reports:
- Dashboard site health (reuses the existing /api/health-check endpoint)
- Unresolved Sentry issues from the last 24h across all 3 Sentry projects
(aitmpl-workers, aitmpl-dashboard, aitmpl-cli)
Does NOT poll the other 3 workers' /status endpoints directly — Cloudflare
blocks Worker-to-Worker fetches over the public *.workers.dev hostname
within the same account (error 1042, confirmed via wrangler dev --remote).
Their health is covered by their own Sentry Cron Monitor check-ins instead.
Also adds a /status endpoint to aitmpl-crons (cloudflare-workers/crons/index.js)
for consistency with the other workers, even though it isn't polled here.
Verified end-to-end: manual /trigger call returns a clean report and
successfully delivers to Telegram (message_id confirmed). Sentry summary
section requires SENTRY_AUTH_TOKEN + SENTRY_ORG_SLUG secrets (pending —
degrades gracefully to "not configured" until set).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds free-tier Sentry error reporting to close the biggest observability
gap: automated cron/worker failures were previously invisible (aitmpl-crons
had no error handling at all). Uses a dependency-free fetch()-based Sentry
envelope client (cloudflare-workers/*/sentry.js, dashboard error-tracking.ts,
cli-tool error-reporting.js) instead of the official SDKs, to keep the
zero-dependency worker style and avoid Cloudflare Pages SSR friction.
- Cloudflare Workers: report failures + Cron Monitor check-ins in crons,
pulse, and docs-monitor (complementing, not replacing, existing Telegram
alerts). Also drops the pulse Vercel collector (VERCEL_API/collectVercel),
since the dashboard no longer deploys to Vercel.
- Dashboard: captureApiError() wired into claude-code-check, health-check,
and the three track-* endpoints.
- CLI: opt-in only crash reporting (CCT_ERROR_REPORTING=true), always
deferring to the existing CCT_NO_TRACKING/CCT_NO_ANALYTICS/CI opt-outs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* migrate: Vercel → Cloudflare Pages + Workers para reducir costos ($110 → ~$0-5/mes)
- Cambia adaptador Astro de @astrojs/vercel a @astrojs/cloudflare (mode: 'directory')
- Agrega dashboard/wrangler.toml con nodejs_compat para compatibilidad total
- Crea cloudflare-workers/crons/ Worker que reemplaza los cron jobs de Vercel
(claude-code-check cada 30min, health-check cada 1h en vez de cada 15min)
- Actualiza CI/CD: Vercel CLI → wrangler pages deploy (requiere CLOUDFLARE_API_TOKEN y CLOUDFLARE_ACCOUNT_ID en GitHub Secrets)
- Elimina crons duplicados del proyecto legacy aitmpl (root vercel.json)
- Quick win: agrega Cache-Control headers a components.json (86400s), plugins.json,
trending-data.json y claude-jobs.json para cortar el costo de bandwidth de $53
Próximos pasos manuales (ver PR description):
1. Crear proyecto en Cloudflare Pages
2. Agregar secrets de Cloudflare en GitHub
3. Migrar env vars de Vercel a Cloudflare Dashboard
4. Deployar cloudflare-workers/crons con wrangler
5. Apuntar dominios a Cloudflare Pages
* fix: nodejs_compat_v2 + react-dom/server.node alias para Cloudflare Workers
- Cambia compatibility_flags de nodejs_compat a nodejs_compat_v2
- Agrega alias vite react-dom/server → react-dom/server.node para evitar
MessageChannel error en module init time en Cloudflare Workers runtime
- Agrega ssr.noExternal para bundlear react-dom y aplicar el alias
* fix: revert compatibility_flags a nodejs_compat (estándar)
nodejs_compat_v2 no es el flag primario. El fix real del MessageChannel
error fue el alias vite react-dom/server → react-dom/server.node,
no el cambio de flag.
* fix: inject Cloudflare Pages runtime env into process.env via middleware
Add Astro middleware that copies context.locals.runtime.env (Cloudflare
Pages secrets) into process.env before each request, so API routes can
use process.env.X as a unified pattern without changing the call sites.
Also add || process.env.X fallbacks to all API routes/libs that were
using import.meta.env exclusively (build-time only in Vite).
Verified on staging: track-download-supabase, health-check,
claude-code-check, collections, github/token all return correct responses.
- Add pulse worker: weekly report collecting metrics from GitHub, Discord,
Supabase, and Vercel, sent via Telegram every Sunday 14:00 UTC
- Add docs-monitor worker: hourly documentation change monitor with
Telegram notifications (previously local-only, now tracked in repo)
- Update .gitignore to track cloudflare-workers/ directory
- Document Cloudflare Workers architecture in CLAUDE.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>