A robust Pomodoro timer for the terminal β built with TypeScript and Ink (React for TUI).
- Reactive TUI β built with Ink (React component tree rendering to the terminal)
- Pomodoro cycles with short/long breaks, fully configurable
- Keyboard-driven config menu β no prompts, pure arrow-key navigation
- Task labeling: tag sessions with
--task <name>to track time per project - Time report grouped by task (
--report, or--report-jsonpipeable tojq) - Session statistics: total Pomodoros, average durations, break times
- Desktop notifications via
notify-send(Linux, optional) - Sound notifications via
ffplay(optional, graceful fallback if unavailable) - tmux bell on session end β works headless, no display required
- Persistent config and metrics (JSON files, paths configurable)
- Tested core logic with Vitest (48 tests)
- Node.js v18+
- Unix-like shell (Linux, macOS, or WSL2 on Windows)
notify-send(optional, for desktop notifications βlibnotify-binon Debian/Ubuntu)ffplay(optional, for sound βffmpegpackage)
git clone https://github.com/pfei/tomate-cli.git
cd tomate-cli
npm install
npm run buildnode ./dist/main.js
# or with a task label:
node ./dist/main.js --task myprojectTo install globally:
npm install -g .
tomate --task myproject| Key | Action |
|---|---|
p or space |
Pause / Resume |
s (Γ2) |
Skip current session (no metrics recorded) |
c |
Open config menu |
q |
Quit |
Usage: tomate [options]
Options:
--help Show help and exit
--task <name> Label sessions for time tracking
--stats Show productivity stats
--report Show time report grouped by task
--report-json JSON report, pipeable to jq
--reset-config Reset config to defaults
--config-path <path> Custom config file path
--metrics-path <path> Custom metrics file path
--show-paths Print config and metrics paths
# Start a labeled session
tomate --task myproject
# View stats
tomate --stats
# View task report
tomate --report
# JSON report sorted by time spent
tomate --report-json | jq '
to_entries
| sort_by(-.value.totalDecimalHours)
| map({task: .key, sessions: .value.sessions, duration: .value.totalTimeHours})
'Config and metrics are stored as JSON in ~/.config/tomate-cli/ by default.
# Use custom paths
tomate --config-path ~/myconfigs/tomate-config.json \
--metrics-path ~/myconfigs/tomate-metrics.json
# Persist custom paths across sessions
tomate --set-config-path ~/myconfigs/tomate-config.json
tomate --set-metrics-path ~/myconfigs/tomate-metrics.jsonUseful for syncing config/metrics with cloud storage or keeping multiple profiles.
tomate-cli works on headless servers (no display required):
- Sound and desktop notifications are automatically skipped when
$DISPLAYand$WAYLAND_DISPLAYare unset - tmux bell fires on session end β configure tmux to flash the status bar:
# ~/.tmux.conf
set -g visual-bell on
set -g bell-action anyThis project uses Unix shell commands in its build scripts (
rm -rf, etc.). Windows users must use WSL2 β native Windows shells are not supported.
npm test # run tests (48 tests, Vitest)
npm run coverage # test coverage
npm run lint # ESLint
npm run format # Prettier
npm run build # compile TypeScript -> dist/
npm run bundle # build + ncc single-file bundle -> bundle/src/
βββ main.ts # CLI entry point β flag parsing, render(<App>)
βββ core/
β βββ state.ts # shared timer state (closure pattern)
βββ ui/
β βββ App.tsx # root component β screen routing (timer/config/timeup)
β βββ TimerScreen.tsx # countdown, keyboard input, live state sync
β βββ ConfigScreen.tsx # keyboard-driven config menu (ink-select-input)
β βββ TimeUpScreen.tsx # session-end screen with tmux bell
βββ utils/
βββ config.ts # load/save config (Zod validation)
βββ metrics.ts # session recording and aggregation
βββ sound.ts # ffplay wrapper with headless fallback
βββ notifications.ts # notify-send wrapper with headless fallback
State management: a single _state object lives in a closure in core/state.ts. Ink components hold local React state (useState) for rendering, and sync from _state via a polling interval. This keeps the React component tree decoupled from the global timer state.
| Package | Purpose |
|---|---|
| ink | React renderer for terminal UI |
| react | Component model and state management |
| ink-select-input | Keyboard-driven select menu |
| zod | Config schema validation |
| chalk | Terminal colors |
| boxen | Boxed output for stats display |
| date-fns | Date formatting for metrics |
Pierre Feilles β github.com/pfei
MIT
