Pomodoro, tasks, habits & calendar in one place. Auto-detect any tool you use
翻訳待ち
Loading the full README.
Read the full README ↗Unified daily work tracking — pomodoro timer, tasks & priorities, habits, and Google Calendar — as a Chrome extension with a companion web dashboard. Local-first, with real-time sync across devices for paid users.
Apps let anyone add their own trackers: a single YAML file, usually written by an AI assistant, becomes a small app inside Pomodoso.
backend/ Rust API (self-contained Cargo project)
extension/ Chrome MV3 extension (React + Vite)
web/ Landing (pomodoso.com), public app directory + web dashboard (React + Vite)
mobile/ Expo app
api/ Shared TS HTTP/WS client (@pomodoso/api)
shared/
types/ Shared TS types and rules, including the app format (@pomodoso/types)
ui/ Shared React components (@pomodoso/ui)
plugin/ App file validator CLI, published to npm (@pomodoso/plugin)
docs/ Spec, ADRs, logos
pnpm install # all workspace deps
# Backend env + DB
cp backend/.env.example backend/.env # fill DATABASE_URL, Supabase, Stripe, Resend
cd backend && sqlx migrate run && cd ..
Client env files are committed with dev/prod values:
extension/.env.development + extension/.env.production, and web/.env.local.
Dev points at http://localhost:8080, with the same Supabase project as production: sign-in emails are real.
make dev starts Postgres (Docker or Podman), the API, the web app and the extension's
watch build together; make help lists the targets. Load extension/dist unpacked in
chrome://extensions, and reload it there after the manifest changes. One by one:
# Backend (port 8080)
cd backend && cargo run
# Stripe webhooks (separate shell) — needed for checkout to provision plans
stripe listen --forward-to localhost:8080/webhooks/stripe
# put the printed whsec_… in backend/.env as STRIPE_WEBHOOK_SECRET
# Web dashboard
pnpm --filter web dev
# Extension — build (dev) then load extension/dist unpacked in chrome://extensions
pnpm --filter extension dev # watch-rebuild on change
Every normal build is dev (loads .env.development → localhost:8080).
Production only happens when packaging the store zip — and it flips back to dev afterward,
so you can never accidentally load a prod-pointing build locally.
| Command | Output |
|---|---|
pnpm --filter extension dev | dev build, watch mode |
pnpm --filter extension build | dev build (load extension/dist) |
pnpm --filter extension build:prod | explicit production build (rare) |
cd extension && make zip | prod zip releases/pomodoso-<version>.zip, then restores the dev build |
So: load extension/dist for local work; run make zip only to produce the Chrome Web
Store upload.
Before cutting a release, run pnpm --filter extension e2e — it drives the real
popup window, which is where drag-and-drop and the challenge flow can break
while every other check passes. See extension/e2e/README.md.
Packaging lives in extension/Makefile and nowhere else. There used to be a second
path (pnpm --filter extension zip) that produced the same artifact under a different
name — pomodoso-extension-v<version>.zip against the Makefile's pomodoso-<version>.zip
— and every release from 1.1.2 on used the Makefile's. Two ways to build one thing is
how you end up uploading the wrong file.
An app (a "plugin" in the code) is one YAML file: its data, what it computes and which of Pomodoso's built-in components to show. It's never code: the file is validated against a closed catalog and rendered with components that ship in the clients, which is what keeps it within Chrome's MV3 remote-code rules and makes a public directory safe.
Using them: browse at pomodoso.com/apps, or in the extension under Apps → Add an app.
Writing one: by hand, with the docs at pomodoso.com/apps/docs
(every block with a live example), or with AI from pomodoso.com/apps/build:
point an assistant at llms-full.txt. Either way, check the result
in the browser at pomodoso.com/apps/validate (paste or upload, then try it) or with:
npx @pomodoso/plugin validate my-app.yaml
To update an app, keep its id and raise its version. Add the new file in the extension, or
publish it from Dashboard → Apps; entries carry over.
In this repo:
| Where | What |
|---|---|
shared/types/src/plugins/ | The format: schema, validation, computed values, update checks, migrations, docs for LLMs (docs.ts), examples for the human docs (guide.ts), example apps |
shared/ui/src/plugins/ | The components an app renders with |
shared/plugin/ | The CLI (shared/plugin/skill/ has the agent skill) |
extension/src/popup/apps/, extension/src/plugins.ts | Apps tab, storage, hooks, install and updates |
web/src/pages/apps/, web/src/pages/dashboard/AppsPage.tsx | Public directory, docs (DocsPage.tsx), publishing, installed apps |
backend/src/routes/plugin_directory.rs, installed_plugins.rs | Directory API, installed apps; sync lives in sync.rs |
The JSON Schema and llms.txt / llms-full.txt are generated from the catalog, and a test
fails if they go stale. A new block, widget, hook or operator also needs its example in guide.ts
for /apps/docs (the typecheck fails without it):
This is the beginning of the README. Read the rest at the original. Read the full README ↗