init
This commit is contained in:
121 files changed
+17641
No files matched your search
@@ -0,0 +1,233 @@
|
||||
# Kadence
|
||||
|
||||
**Find the rhythm of your work day.** A pomodoro timer that knows what you have
|
||||
to do, stands between you and the distractions, and tells you in Telegram how
|
||||
the day went.
|
||||
|
||||
Three pieces, one backend:
|
||||
|
||||
| Piece | What it is | Where it runs |
|
||||
| --- | --- | --- |
|
||||
| **Backend** | FastAPI + Postgres: the clock, the task mirror, the blocking policy, the reports, the Telegram bot | Docker |
|
||||
| **Web app** | React + Material UI — the whole thing in a browser, on any device | Docker (same container) |
|
||||
| **Desktop app** | A **native Qt/QML** app: tray, D-Bus, real notifications, and the part that actually blocks websites and apps | Your machine |
|
||||
|
||||
The server owns the clock. Start a pomodoro on your phone's browser, watch it
|
||||
count down in the tray, finish it from the desktop — it is one interval.
|
||||
|
||||
---
|
||||
|
||||
## What it does
|
||||
|
||||
### A fully configurable pomodoro timer
|
||||
Focus / short break / long break lengths, how many cycles before a long break,
|
||||
auto-start of breaks and of the next interval, a daily goal, pausing (or not),
|
||||
strict mode (abandoning a focus interval costs you a written reason),
|
||||
interruption counting, alarm sound and volume. Intervals are recorded as they
|
||||
really happened — planned vs actual, paused time, interruptions — so the stats
|
||||
are not a guess.
|
||||
|
||||
### Every todo you own, in one list
|
||||
Sourced from the integrations worked out in [`~/code/jira-helper`](../jira-helper),
|
||||
and taking **all** of them:
|
||||
|
||||
* **Jira** — every open issue across the site (not just what is due today), with
|
||||
the due date, the "Start date" custom field, the status, the priority, the
|
||||
project. Finishing a Jira-backed task here **transitions the issue to Done**.
|
||||
* **Todoist** — every open task, or your own filter query. Finishing closes it in
|
||||
Todoist.
|
||||
* **Google Tasks** — every incomplete task, including undated ones. Finishing
|
||||
marks it completed in Google.
|
||||
* **Laundry bookings** (`4ka.inbicst.ru`) — the slots you booked today, because
|
||||
they are blocks of time that will interrupt your pomodoros.
|
||||
* **Local tasks** — the "and this too" you type at 9am.
|
||||
|
||||
Every source is pulled independently: an expired laundry cookie or a Jira outage
|
||||
costs you that one source and shows up as a warning next to it, never as an
|
||||
empty list. The list is mirrored into the database on every refresh, so the UI is
|
||||
instant and the daily report still works when a source is down.
|
||||
|
||||
### A focus guard that actually blocks
|
||||
Rules live on the server (edit them in the web app or the desktop app); the
|
||||
native desktop app enforces them on your machine:
|
||||
|
||||
* **Websites** — `/etc/hosts` is rewritten through a tiny privileged helper under
|
||||
polkit. No browser extension, no proxy, works for every app on the machine, and
|
||||
you can audit it with `cat /etc/hosts`.
|
||||
* **Applications** — `/proc` is watched and blocked apps are closed (or just
|
||||
reported, if you prefer a warning).
|
||||
* **When** — only while a focus interval runs (the default), always, or on a
|
||||
schedule. Breaks can keep the block on or let it go.
|
||||
* Every attempt that gets turned away is recorded — and teased about in the
|
||||
evening report.
|
||||
|
||||
### A daily report that is worth reading
|
||||
Pomodoros vs goal, focus and break minutes, the longest unbroken stretch, when
|
||||
you started and stopped, interruptions, abandoned intervals, tasks closed (with
|
||||
links), the sites you tried to sneak off to, your streak, a comparison against
|
||||
your 7-day average, **achievements** (🎯 goal met, 🧘 deep work, 🦉 night owl,
|
||||
🪃 comeback, 🚀 personal best…) and **fun facts**:
|
||||
|
||||
```
|
||||
🍅 Daily report — 2026-10-18
|
||||
|
||||
████████░░ 6/8 pomodoros
|
||||
⏱ Focus: 2h 35m · breaks: 35m
|
||||
🧠 longest stretch: 1h 20m · 🕘 from 09:12 to 18:40 · 📵 interruptions: 3
|
||||
🔥 Streak: 4 days (best 11)
|
||||
📊 7-day average: 5.4 · yesterday: 7
|
||||
|
||||
✅ Tasks closed (3)
|
||||
• PLAT-184 Fix the flaky importer (Jira)
|
||||
• Write the design doc (Todoist)
|
||||
…
|
||||
|
||||
🚫 Temptations blocked
|
||||
• youtube.com × 12
|
||||
• reddit.com × 4
|
||||
|
||||
🏅 Achievements
|
||||
🧘 Deep work — 80 minutes without breaking the chain.
|
||||
🛡️ Gatekeeper — The blocker turned you away 16 times. It won.
|
||||
|
||||
🍅 155 minutes of focus — that is 7.0 sitcom episodes you did not watch.
|
||||
⏰ Your peak hour was 10:00. Defend it.
|
||||
|
||||
Target hit. Now protect the streak. 💪
|
||||
```
|
||||
|
||||
Four tones (friendly / coach / drill sergeant / dry) and two languages (English,
|
||||
Russian).
|
||||
|
||||
### A Telegram bot that pushes back
|
||||
Unlike jira-helper's one-way daily message, this one is a remote control:
|
||||
|
||||
* **Morning briefing** — your day, grouped: overdue, due today, coming up, plus
|
||||
your streak and the goal to beat.
|
||||
* **Daily report** at your hour, **weekly review** on your day.
|
||||
* **Nudges** — "90 minutes without a pomodoro. You are at 2/8 — one block puts you
|
||||
back on plan." Windowed, rate-limited, and silent during quiet hours.
|
||||
* **Cheers** every N pomodoros.
|
||||
* **Commands**: `/start_work [minutes]`, `/break`, `/pause`, `/resume`, `/stop`,
|
||||
`/status`, `/today`, `/done 3`, `/report`, `/week`, `/streak`, `/block`.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
git clone <this repo> kadence && cd kadence
|
||||
cp .env.example .env # fill in Jira / Todoist / Telegram (optional)
|
||||
docker compose up -d --build # → http://localhost:8000
|
||||
```
|
||||
|
||||
That is the backend and the web app. Then the native desktop app:
|
||||
|
||||
```bash
|
||||
# Arch: the only dependency is PyQt6, which is a system package
|
||||
sudo pacman -S --needed python-pyqt6 qt6-declarative
|
||||
# (Debian/Ubuntu: sudo apt install python3-pyqt6 python3-pyqt6.qtquick qml6-module-qtquick-controls)
|
||||
|
||||
./desktop/packaging/install-desktop.sh # menu entry + icon + `kadence-desktop`
|
||||
sudo ./desktop/helper/install.sh # the privileged helper, for website blocking
|
||||
kadence-desktop
|
||||
```
|
||||
|
||||
Point it at the backend in **Settings → Backend** (it defaults to
|
||||
`http://localhost:8000`).
|
||||
|
||||
### Without Docker
|
||||
|
||||
```bash
|
||||
make backend-venv # backend/.venv with the dependencies
|
||||
cd backend && .venv/bin/python -m app.cli serve --reload
|
||||
cd frontend && npm install && npm run dev # http://localhost:5173
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Two layers, on purpose:
|
||||
|
||||
* **Environment** (`.env`) — infrastructure and **secrets**: the database, the
|
||||
API token, and the seeds for the integrations. The variable names are
|
||||
jira-helper's, so you can copy that `.env` across unchanged.
|
||||
* **Settings** (the UI) — everything else, stored as one validated document in
|
||||
the database and shared by the web app, the desktop app and the bot. Secrets
|
||||
are masked (`••••••••`) on the way out and never clobbered on the way back.
|
||||
|
||||
Nothing has to be in the environment: a fresh install is a working 25/5/15 timer,
|
||||
and you can fill everything in from **Settings**.
|
||||
|
||||
| Variable | What for |
|
||||
| --- | --- |
|
||||
| `DATABASE_URL` | set by compose; SQLite works too (`sqlite:///./kadence.db`) |
|
||||
| `API_TOKEN` | if set, every `/api` call must send `X-API-Token`. Set it the moment the backend leaves localhost |
|
||||
| `TIMEZONE` | which day a pomodoro belongs to, and when the jobs fire |
|
||||
| `JIRA_*`, `TODOIST_*`, `GOOGLE_*`, `LAUNDRY_*` | seeds for the sources |
|
||||
| `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID` | the bot |
|
||||
| `SCHEDULER_ENABLED`, `TELEGRAM_BOT_ENABLED` | switch the background jobs / bot off |
|
||||
|
||||
---
|
||||
|
||||
## How it fits together
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌───────────────────────────────┐ ┌───────────────┐
|
||||
│ Web app │ │ Backend (FastAPI) │ │ Jira │
|
||||
│ React + MUI │──/api──│ • timer engine (owns clock) │───────▶│ Todoist │
|
||||
└──────────────┘ │ • task mirror + write-back │ │ Google Tasks │
|
||||
│ • blocking policy │ │ Laundry │
|
||||
┌──────────────┐ │ • reports + achievements │ └───────────────┘
|
||||
│ Desktop app │──/api──│ • scheduler (briefing, │
|
||||
│ Qt/QML, D-Bus│ │ report, nudges) │───────▶ Telegram ⇄ you
|
||||
│ hosts + /proc│ └───────────────────────────────┘
|
||||
└──────────────┘ │
|
||||
enforces Postgres
|
||||
```
|
||||
|
||||
* `backend/` — FastAPI app. `app/services/` holds the thinking (timer, tasks,
|
||||
stats, report, blocker policy, scheduler, bot); `app/sources/` holds one module
|
||||
per integration; `app/api/` is a thin HTTP layer. 44 tests under `backend/tests`.
|
||||
* `frontend/` — React + TypeScript + MUI, built by Vite into the backend image and
|
||||
served from the same origin (so there is no CORS to configure).
|
||||
* `desktop/` — the native app. See [`desktop/README.md`](desktop/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Make targets
|
||||
|
||||
```
|
||||
make up # docker compose up --build
|
||||
make dev # backend with reload + Vite dev server
|
||||
make test # backend test suite
|
||||
make desktop # run the native app from the checkout
|
||||
make desktop-install # menu entry + icon
|
||||
make helper-install # the privileged blocking helper
|
||||
make report # build and send today's report right now
|
||||
```
|
||||
|
||||
The backend also has a CLI, handy from cron or a systemd timer:
|
||||
|
||||
```bash
|
||||
python -m app.cli refresh # pull every source once
|
||||
python -m app.cli briefing # send the morning briefing
|
||||
python -m app.cli report # build and send today's report
|
||||
python -m app.cli stats # print the numbers as JSON
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security notes
|
||||
|
||||
* The desktop app never runs as root. The only privileged code is
|
||||
`desktop/helper/kadence-helper` (~200 lines): it validates every hostname,
|
||||
rewrites only the region between its own markers in `/etc/hosts`, writes
|
||||
atomically, keeps a backup, and refuses anything that does not look like a
|
||||
hostname. It is authorised by polkit (`auth_admin_keep` by default — one prompt
|
||||
per session; `install.sh --passwordless` drops that for one user).
|
||||
* Tokens live in the database and in `.env`, are masked in every API response,
|
||||
and are never sent to the browser in full.
|
||||
* Set `API_TOKEN` before exposing the backend beyond localhost; put it behind TLS
|
||||
if it leaves your network.
|
||||
Reference in new issue
Block a user