Skip to content
goparPublic

About

A Pomodoro Server

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

pomo

CI

A pomodoro timer for people who live in the terminal and work across multiple machines. Sessions sync to your home-base server so you can start a session on one machine and see the countdown and get the overtime warning on another.

  • Multi-machine sync — laptop, desktop, any machine. Start on one, see it everywhere.
  • Works offline — the timer is local. Write the cache now, sync later.
  • Hooks, not built-ins — every side effect (notifications, Focus mode, sounds) is an executable script you control. Language-agnostic.
  • Project tagging — tag sessions to projects: pomo 25 -p website. Filter history by project, see everything you've worked on.
  • Web dashboard — public stats and session create/edit/archive pages, with a separate login-protected Django admin.

Install

uv tool install https://github.com/gopar/pomoism.git
# Can also install via pip

This puts pomo, pomo-agent, and pomo-server on your PATH.


Usage

Start a session

pomo start 25                        # 25-minute pomodoro
pomo start 25 -p website             # ...tagged to a project
pomo start 25 -p website -n "login"  # ...with a name
pomo break 5                         # 5-minute break
pomo clear                           # stop + optionally start a break

Check status

pomo status
# 🍅 18:22 [website] [fix login]

pomo status --json
# {"state": "pomodoro", "remaining": 1102, "display": "🍅 18:22 [website] [fix login]", ...}

History

pomo history
# 2026-08-01  09:14 – 09:39  🍅  25:00  [website]
# 2026-08-01  09:42 – 10:12  🍅  30:09  [website] [fix login]
# 2026-08-01  10:15 – 10:20  ☕  05:00  [website]

pomo history --from 2026-08-01                    # from date to now
pomo history --from 2026-08-01 --to 2026-08-07    # date range
pomo history --state ended                        # only completed sessions
pomo history --project website --state pomodoro   # combine filters
pomo history --json                               # machine-readable
Flag Description
--from DATE Start date (YYYY-MM-DD)
--to DATE End date (YYYY-MM-DD)
--state STATE Filter by session state (pomodoro, overtime, break, break-overtime, ended, archived)
--json Output as JSON
-p, --project Filter by project

Stats

pomo stats
# 2026-08-10
#   Sessions:    5
#   Focus time:  2h 05m
#
#   By project:
#   website      4 sessions   1h 40m
#   cli-tool     1 session      25m

pomo stats --from 2026-08-01 --to 2026-08-07   # date range
pomo stats --project website                     # filter by project
pomo stats --include-archived                    # include archived sessions
pomo stats --json                                # machine-readable
Flag Description
--from DATE Start date (YYYY-MM-DD)
--to DATE End date (YYYY-MM-DD)
-p, --project Filter by project
--include-archived Include archived sessions
--json Output as JSON

Projects

pomo projects
# website
# backend
# cli-tool

pomo projects --json
# [{"project": "website"}, {"project": "backend"}, ...]

Web dashboard

Open http://<home-base>:8787/ in a browser.

  • / — public dashboard: focus/break totals, daily chart, project breakdown, current session, and recent sessions.
  • /sessions/ — public history with date/project/state filters.
  • /sessions/new/, /sessions/<id>/edit/, /sessions/<id>/delete/ — public create/edit/archive pages.
  • /admin/ — Django admin, login required; session data is read-only. Create the first user with pomo-server createsuperuser.

The dashboard has no login. Anyone who can reach the server can create, edit, or archive sessions, so keep it on a trusted network or behind Tailscale. The JSON API still supports optional POMO_TOKEN bearer auth.

Config

pomo config
# Config file: ~/.config/pomo/agent.toml (found)
#
# server_url = "http://127.0.0.1:8787"
# machine_name = "laptop"
# poll_interval = 5
# run_for_remote_sessions = false
#
# [hooks]
# enabled = true
# timeout = 10
# dir = ""

pomo config --json      # machine-readable (path, exists, effective config)
pomo config --init      # create ~/.config/pomo/agent.toml from the built-in sample if missing

Full help

pomo --help
usage: pomo [-h] [--version]
            {start,break,clear,status,history,projects,stats,config,service} ...

Start, stop, and track pomodoro sessions.

positional arguments:
  {start,break,clear,status,history,projects,stats,config,service}
    start               Start a pomodoro for N minutes
    break               Start a break for N minutes
    clear               Stop current session, optionally start a break
    status              Show current session status
    history             Show session history
    projects            List all defined projects
    stats               Show session statistics
    config              Show configuration
    service             Manage pomo processes

options:
  -h, --help            show this help message and exit
  --version             show program's version number and exit

Setup

  1. Create the config file:

    pomo config --init

    Writes ~/.config/pomo/agent.toml from the built-in sample (defaults with explanatory comments).

    Edit server_url to point at your home-base machine. A Tailscale hostname works well. Set POMO_TOKEN on the server and all agents if you want bearer auth (disabled by default). Set POMO_SERVER_URL in your shell to pass it to the service at install time.

  2. Home-base machine — start the server:

    # Foreground (terminal, for debugging):
    pomo service server
    
    # Background service:
    pomo service server install

    The server uses Django. pomo-server applies database migrations automatically on startup (including --fake-initial adoption of an existing pomo SQLite database), then serves with waitress. Back up ~/.local/share/pomo/pomo.db before upgrading from an older release. If you run migrations manually against an existing database, use pomo-server migrate --fake-initial.

    Django needs a secret key. If POMO_SECRET_KEY is not set, one is generated in ~/.config/pomo/django_secret_key. Set it explicitly if you run multiple server instances.

    Static files for the dashboard and admin are collected automatically into ~/.local/share/pomo/static on startup. To manage the protected admin:

    pomo-server createsuperuser
  3. Every machine — start the agent:

    # Foreground (terminal, for debugging):
    pomo service agent
    
    # Background service:
    pomo service agent install
  4. Check service status or tail logs:

    pomo service agent status
    pomo service agent logs

Offline behavior: the CLI writes the local cache immediately and queues the push; the agent syncs on reconnect (last-write-wins by timestamp).

You can also start the agent/server directly as foreground processes for debugging.


Hooks

Every side effect is a hook — the agent ships with zero built-in effects and is OS-agnostic. Drop executable scripts into per-machine directories:

~/.config/pomo/hooks/<event>.d/*

Events: pomodoro_start, break_start, pomodoro_overtime, break_overtime, session_stop. Scripts run in lexical filename order (prefix with 10-, 20-, …).

mkdir -p ~/.config/pomo/hooks/pomodoro_start.d
cp hooks/examples/pomodoro_start.d/10-focus-on.sh \
   ~/.config/pomo/hooks/pomodoro_start.d/
chmod +x ~/.config/pomo/hooks/pomodoro_start.d/10-focus-on.sh

Examples included in hooks/examples/:

Event Script What it does
pomodoro_start 10-focus-on.sh Enable macOS Focus / DnD
break_start 10-focus-off.sh Disable Focus
break_start 20-launch-emacs.sh Open Emacs (idle-time capture)
pomodoro_overtime 10-announce.sh say "Time's up" / alarm
break_overtime 10-announce.sh say "Break's over"
session_stop 10-focus-off.sh Disable Focus

Each script receives context:

  • Env vars: POMO_EVENT, POMO_STATE, POMO_START_EPOCH, POMO_DURATION, POMO_MACHINE, POMO_ORIGIN_MACHINE, POMO_REMOTE (0/1), POMO_SESSION_ID, POMO_SESSION_PROJECT.
  • Stdin: the full session dict as JSON.

Hooks are best-effort: a failing, missing, or slow script (killed after hooks.timeout seconds) never affects the timer or CLI. Tune via [hooks] in agent.toml.


Config & paths

What Path / env
Config ~/.config/pomo/agent.toml
Cache ~/.cache/pomo/ (session + offline outbox)
Server DB ~/.local/share/pomo/pomo.db (POMO_DB_PATH)
Django secret POMO_SECRET_KEY or ~/.config/pomo/django_secret_key
Allowed hosts POMO_ALLOWED_HOSTS (default *)
Static files POMO_STATIC_ROOT (default ~/.local/share/pomo/static)
Server URL POMO_SERVER_URL (agent/CLI)
Bearer token POMO_TOKEN (server + agents)
Server bind POMO_HOST (default 0.0.0.0)
Server port POMO_PORT (default 8787)
Auto-migrate POMO_AUTO_MIGRATE (default 1)
Auto-static POMO_AUTO_COLLECTSTATIC (default 1)

Config keys (agent.toml)

Key Default Notes
server_url http://127.0.0.1:8787 Overridden by POMO_SERVER_URL
machine_name hostname
poll_interval 5 Seconds, minimum 5
run_for_remote_sessions false Fire hooks for sessions started on other machines
hooks.enabled true
hooks.timeout 10 Seconds per hook script
hooks.dir ~/.config/pomo/hooks

Server API

Method Path Description
GET /health {"ok": true}
GET /version {"version": "0.1.0"}
GET /current Current session or {"state": "idle"}
GET /sessions Sessions (optional ?project=, ?from=, ?to=, ?state=)
GET /stats Aggregated statistics (optional ?project=, ?from=, ?to=, ?include_archived=)
GET /projects All defined project names
POST /sessions Upsert a session (LWW; stale writes return "applied": false)
POST /sessions/end End the current session (always wins; server-clamped timestamp)
PATCH /sessions/<id> Edit a session's name/project (LWW)
POST /sessions/<id>/archive Soft-delete a session (LWW)

Every route is also available under /api/ (for example /api/sessions). Auth: optional bearer token via POMO_TOKEN.


Architecture

Three small processes share pomo/common.py:

  • pomo/web/ — Django HTTP/JSON server and public dashboard. The JSON protocol is unchanged, and the persistence layer is still the stdlib sqlite3 store in pomo/server.py so last-write-wins and the current-pointer race guarantees are preserved. The public UI has no login; /admin/ requires Django auth and is read-only for sessions. Django also owns migrations, authentication tables, and static files. Runs on one home-base machine.
  • pomo/agent.py — per-machine daemon. Polls the server, owns the local countdown→overtime timer, fires hooks, flushes the offline outbox.
  • pomo/cli.py — the CLI. Writes the local cache immediately, pushes to the server (or queues to outbox if offline).

Sessions move through states: pomodoro/break → overtime/break-overtime → ended. Each session has a kind (set at creation: pomodoro or break) that survives all transitions, so history always shows the right icon.

Because the timer and hooks are local, everything works with no network.


Development

Tests are pytest (stdlib unittest.mock for patches), with pytest-django for the server API tests:

uv sync --dev
pytest

Dev install includes ruff and ty:

ruff check . ; ruff format . ; ty check src/pomo

Useful server checks:

POMO_SECRET_KEY=dev python -m django check --settings=pomo.web.settings
POMO_SECRET_KEY=dev python -m django makemigrations --check --dry-run --settings=pomo.web.settings

CI runs the test suite on Python 3.11–3.14, plus ruff lint/format, ty type check, a Django system check, and a compileall syntax gate. See AGENTS.md for architecture invariants.


License

MIT

About

A Pomodoro Server

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages