Lightweight personal kanban (draft → ready → done → deploy) running on Cloudflare Workers + D1, with an MCP server so Claude Code and Codex CLI can dispatch work directly from the board.
Before you deploy: read SECURITY.md. Authentication is IP-allowlist only (no login, no API key, no per-user isolation), and the Worker IP gate is fail-open by default. Without setting up either a Cloudflare WAF rule or the
ALLOWED_IPSsecret, every API endpoint (includingDELETE) is open to the internet. SECURITY.md has the full threat model, deploy checklist, and gate setup. Do not skip it.
- Stack: Hono (Workers) + React + Vite + D1
- Domain: bring your own (Cloudflare-managed zone) → custom domain binding
workers.devURL: disabled (workers_dev = false) — closes the bypass route around your WAF rules
| Doc | Read when |
|---|---|
| SECURITY.md | Before your first deploy. Threat model, known limitations, deploy checklist, and IP-gate setup. Required reading. |
| docs/operations.md | When you get locked out by an IP change, need to back up / restore D1, or are running a schema migration. |
| docs/mcp-setup.md | When wiring the MCP server into Claude Code or Codex CLI. |
| docs/agent-workflow.md | If you're an AI agent (or curious about what one sees). The MCP server serves this via get_workflows. |
| docs/product.md | Product purpose, design principles, anti-references. Read if you're contributing UI changes. |
npm install
npm run db:migrate:local # apply schema to local D1
npm run build # build the React UI into dist/web
npm run dev:worker # start wrangler dev on :8787
# (optional) hot-reload UI on :5173 with /api proxied to :8787
npm run dev:webwrangler.toml is committed with placeholders (<your-d1-database-id>, kanban.example.com). Pick one of the two paths below; both assume you've followed the SECURITY.md deploy checklist first.
First-time setup (only once per fork):
npx wrangler login # authenticate with your Cloudflare account
npx wrangler d1 create kanban-ready # creates the D1 database, prints a database_idCopy the printed database_id into wrangler.toml, and replace the route host with your own domain:
routes = [
{ pattern = "kanban.your-domain.com", custom_domain = true }
]
[[d1_databases]]
database_id = "paste-the-id-from-wrangler-d1-create"The custom domain must already be a zone in your Cloudflare account —
wrangler deploywill fail if it isn't.
Then deploy:
npm run db:migrate:remote # one-time / when schema changes
npm run deploy # builds web + wrangler deployTo keep your edited wrangler.toml from showing up in git diffs (recommended for forks), run git update-index --skip-worktree wrangler.toml after editing.
.github/workflows/deploy.yml builds and deploys on every push to main. It's gated by if: github.repository == 'devstefancho/kanban-ready', so forks don't trigger doomed deploys. To enable on your fork:
- Edit the
if:line in.github/workflows/deploy.ymlto your<owner>/<repo>. - Register four secrets in Settings → Secrets and variables → Actions:
Secret Value CLOUDFLARE_API_TOKENToken with Workers Scripts:Edit+D1:Editfor your accountCLOUDFLARE_ACCOUNT_IDCloudflare dashboard → right sidebar D1_DATABASE_IDOutput of wrangler d1 listfor your DBPROD_DOMAINThe host you want bound (e.g. kanban.your-domain.com)
The workflow's "Inject deploy config" step rewrites wrangler.toml placeholders with these secrets at build time. Secrets are passed as env vars (not shell-interpolated) and the rewritten file lives only on the ephemeral runner.
Schema migrations are still manual — by design, no workflow auto-runs them on push (a bad migration would break prod with no review step). Pick whichever surface fits:
- From your machine:
npm run db:migrate:remote(needswrangler loginand the realdatabase_idinwrangler.toml). - From GitHub Actions UI: open the Migrate D1 workflow (
.github/workflows/migrate.yml) → "Run workflow" → pick the branch holding the new migration. The workflow isworkflow_dispatch-only, lists pending migrations first, then applies. Reuses the same secrets as deploy. Useful when you don't havewranglerset up locally, or when applying migrations from a feature branch before merging the PR.
Run the migration before merging the PR — the widened schema is forward-compatible with the old worker, so applying first creates a no-failure window for the new code to roll out.
src/
worker.ts Cloudflare Workers entry (Hono routes + IP gate + ASSETS fallback)
core/ Card type, status transitions, D1-backed CRUD, prompt builder
web/ React + Vite UI (drag-and-drop board)
mcp/
server.ts Local stdio MCP server (thin client over the remote API)
migrations/ D1 schema migrations (idempotent)
wrangler.toml Workers + D1 + Assets + custom domain config (placeholders)
docs/ operations / product / mcp-setup / agent-workflow
| Method | Path | Body |
|---|---|---|
| GET | /api/health |
– |
| GET | /api/whoami |
– (returns your cf-connecting-ip) |
| GET | /api/cards?status=draft|agent_working|ready|done|deploy|discarded |
– |
| GET | /api/cards/:id |
– |
| POST | /api/cards |
{title, body?, tags?, status?} |
| PATCH | /api/cards/:id |
{title?, body?, tags?} |
| POST | /api/cards/:id/move |
{status} |
| DELETE | /api/cards/:id |
– |
| GET | /api/cards/:id/prompt |
– (markdown) |