Omahub
← All plugins
K

Omapomodoro

by Kennet Postigo

Pomodoro timer, analytics, and streaks in the Omarchy bar. Click the chip, or: omarchy-shell shell toggle postman.omapomodoro

Security review

No obvious issues detected

Deterministic scan — not a security guarantee

None
Risk level
None
Analyzed commit
fb43378
Scanned
1 month ago

No potentially dangerous behavior detected in the analyzed commit.

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

None
AI risk level
None
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
fb43378
Reviewed
1 month ago

The plugin is a Pomodoro timer whose QML shell only supervises a small Rust tracker; the tracker writes exclusively under the user's XDG state/runtime directories and has no network dependencies. The build and optional menu-install scripts are opt-in and documented, and the deterministic scan found no issues. No obfuscation, credential access, destructive commands, or hidden persistence was found.

How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/kennetpostigo/omapomodoro --enable
Productivity #Hyprland #bar #quickshell

Omapomodoro

A tomato-timer chip on the Omarchy bar. Click it and a KeyboardPanel peeks out — the same chrome as Network, Display, and Omascreentime — with the live cycle, today's sessions, the week, and streaks.

The overlay does not own the clock. A small Rust tracker, omapomodoro-track, runs the phase machine, writes history, and publishes a capped snapshot the bar chip and panel watch. Sleep cannot complete a focus. A nap consumes the break. Lock pauses focus and leaves the break running.

Plugin id: postman.omapomodoro · License: MIT · Kinds: service + bar-widget

Sessions are unlabelled. There is no cloud, no accounts, and no website blocker. It does not track which window you focused — that is Omascreentime.

What you get

A chip on the bar. Idle, it shows today's completed count. Running, paused, or armed, it shows remaining MM:SS. Click to open the panel. Middle-click starts, pauses, or resumes without opening anything.

Four tabs:

  • Time — phase ring, start / pause / skip / reset, and a Cycle card for focus / short / long / every. Click the card header to show or hide the steppers. Duration changes apply to the next phase.
  • Day — today's sessions: completed, abandoned, and the live one.
  • Week — last 7 local days, weekends called out, daily goal hit or miss.
  • Streak — daily goal, current / best streak, keepDays heatmap.

Omapomodoro Time tab with the live ring and cycle editor

Omapomodoro Day tab with today's session list

Omapomodoro Week tab with a 7-day strip and hit/miss rows

Omapomodoro Streak tab with goal, streak, and heatmap

Install

omarchy plugin add clones the plugin. It does not compile the tracker. You need mise (or any Rust toolchain) once, after clone.

omarchy plugin add https://github.com/kennetpostigo/omapomodoro.git --enable
cd ~/.config/omarchy/plugins/postman.omapomodoro
mise install
./scripts/build.sh
omarchy-shell shell rescanPlugins

If the chip did not land on the right of the bar:

omarchy plugin enable postman.omapomodoro --section right

A clone without ./scripts/build.sh shows Tracker not built in the overlay. Run the build, then omarchy-shell shell rescanPlugins.

Optional Super-menu entry (opt-in; writes only trigger.omapomodoro into your menu extension):

./scripts/install-menu.sh

Use

Click the chip, or:

omarchy-shell shell toggle postman.omapomodoro

Keys are the real PanelKeyCatcher signals — Space is not a text key. While a cycle stepper is focused, Space is blocked so it cannot start or pause.

Key Action
Space / Enter Start, pause, or resume. On Streak, cycle the daily pomodoro goal 4 → 6 → 8 → 12 → 0
x Reset to idle
s Skip the current or armed phase
1 / 2 / 3 / 4 Time / Day / Week / Streak
arrows / h j k l Day: move in the session list. Other tabs: scroll. On a focused stepper: change the value
r Refresh the view
g / G Scroll to top / bottom
Esc Close. On a focused stepper: leave the stepper
Tab Switch to the next bar panel
Right-click the chip Icon-only mode
Middle-click the chip Start / pause / resume

On Time, click Cycle to show or hide the duration steppers. Collapsed, the card shows 25 · 5 · 15 × 4. Open, it edits focus / short / long / every. Changes apply to the next phase. You can also set them from the command line:

omarchy bar set postman.omapomodoro focusMin 50
omarchy bar set postman.omapomodoro shortBreakMin 10
omarchy bar set postman.omapomodoro longBreakMin 20
omarchy bar set postman.omapomodoro longBreakEvery 3
Key Default Meaning
focusMin 25 Focus length in minutes
shortBreakMin 5 Short break in minutes
longBreakMin 15 Long break in minutes
longBreakEvery 4 Long break every N completed focuses
autoStartNext false Auto-start the next armed phase
dailyGoalPomos 8 Daily completed-focus goal (0 = off). On Streak, Space cycles 4 → 6 → 8 → 12 → 0
dailyGoalMin 0 Daily focus-minutes goal (0 = off)
keepDays 31 History window (heatmap length)
iconOnly false Hide the duration / count on the bar
showCycle true Show the Cycle steppers on Time. Click the card header, or: omarchy bar set postman.omapomodoro showCycle false
notifications true Announce phase end (OSD + desktop notification)
sound true Play a sound on phase end
omarchy bar move postman.omapomodoro --section right

Architecture

This section is for anyone who wants to change, port, or harden Omapomodoro. Coding agents should also read AGENTS.md. The on-the-wire contract is protocol.md.

Why two processes

omarchy-shell is one long-lived Quickshell process. A QML Timer as source of truth would stall the desktop and lie across sleep, lock, midnight, and shell restart.

Omapomodoro therefore splits in two:

service/Service.qml            keep the tracker daemon alive
bar/BarWidget.qml              KeyboardPanel peek + bar chip
        │
        ▼
overlay/Overlay.qml            tabs, FileView, one-shot Process children
        │
        │  Unix socket  +  atomic snapshot.json
        ▼
target/release/omapomodoro-track
        daemon | ensure | status | view | proto | stop
        start | pause | resume | skip | reset | config | goal
flowchart TB
  subgraph shell["omarchy-shell"]
    SVC["service/Service.qml<br/>ensure every 60s"]
    BAR["bar/BarWidget.qml<br/>chip + KeyboardPanel"]
    OVL["overlay/Overlay.qml<br/>FileView + one-shot Process"]
    BAR --> OVL
  end

  SNAP["snapshot.json"]
  SOCK["Unix socket"]
  D["omapomodoro-track daemon"]

  SVC -->|"ensure"| D
  BAR -->|"watch"| SNAP
  OVL -->|"watch + view"| SNAP
  OVL -->|"start / pause / skip / reset / config / goal"| SOCK
  SOCK --> D
  D -->|"atomic write"| SNAP

The daemon samples three clocks (REALTIME, BOOTTIME, MONOTONIC), runs the phase machine, and atomically writes snapshot.json. The bar chip and the overlay watch that file. Opening the panel also asks view once for a fresh read. There is no 1-second process poll. The overlay interpolates remaining from the last snapshot — it never calls Date.now().

Clock rules

Does Does not
Focus remaining is monotonic elapsed Sleep / lid-close completing a focus
Break remaining is boottime elapsed A nap leaving a 5-minute break intact
LockedHint pauses focus IdleHint / screensaver-without-lock pausing
Break keeps counting while locked Skip of a focus incrementing cycleIndex
Complete = focus remaining reached 0 Skip, reset, or crash counting as a pomodoro
Credit the session to the local day it started Midnight mutating an open day's totals

Crash: a quick bounce (no boottime hole) may resume running. Anything else reloads the envelope paused (focus) or applies the break-consume rule. stop / SIGINT / SIGTERM flush the open phase — they do not abandon it.

Config changes apply to the next phase. A running 25:00 focus is not rewritten to 30:00 mid-flight.

Tracker (src/)

Module Job
clock.rs REALTIME / BOOTTIME / MONOTONIC, classify suspend vs NTP, local day keys
timer.rs Phase machine, remaining, armed, lock pause, midnight
session.rs loginctl show-session for IdleHint / LockedHint
store.rs Atomic history.json / session.json / snapshot.json / config.json
stats.rs Today sessions, week strip, heatmap, insights
streaks.rs Current/best streak; today-in-play (yesterday can still count)
goals.rs goals.json daily pomos / minutes
config.rs Durations and flags; omitted patch keys do not change
daemon.rs poll loop, control socket, commit, snapshot
paths.rs State dir (0700), flock, unique tmp names
protocol.rs NDJSON constructors
main.rs CLI

Crash loss is bounded by the periodic commit. A signal flushes the envelope and does not leave a torn JSON file. Snapshots are rename-replaced without fsync; history and session are fsync'd.

State:

~/.local/state/omapomodoro/     # or $XDG_STATE_HOME/omapomodoro
  history.json                  # { v, days: { YYYY-MM-DD: { focusCompleted, …, sessions } } }
  snapshot.json                 # last view event (bar + overlay watch this)
  session.json                  # open-phase envelope (survives crash / stop)
  config.json                   # last-seen durations / flags
  goals.json                    # dailyPomos + dailyMin
  daemon.lock / daemon.pid

$XDG_RUNTIME_DIR/omapomodoro/omapomodoro.sock

Overlay data flow

  1. Service starts (and every 60s ensures) the daemon.
  2. Bar chip FileViews snapshot.json. No periodic Process.
  3. Open → startSession: reload snapshot, ensure (bar/overlay pass schema flags; omitted keys stay), one view.
  4. Control actions are one-shot Process children (start / pause / skip / reset / goal). Duration edits go overlay.applyDuration → BarWidget.setDuration → updateEntryInline + onSettingsChanged ensure. Do not write config.json from QML.
  5. Header (logo, title, tabs) stays outside the Flickable so it stays pinned while the body scrolls.
  6. Remaining on the ring is last snapshot minus tick count, floored to remainingMs - 1000. Never Date.now().
  7. 1 2 3 4 switch tabs. Space is onActivateRequested. x is onDeleteRequested. Arrows / h j k l are onMoveRequested. Cycle steppers set pickerOpen so PanelKeyCatcher is blocked while they have focus.

ListModel must not use a role named color — Quickshell treats that as reserved. Use fill.

Protocol

One JSON object per stdout line. v must be 1. Unknown type → ignore. stderr is human logs only; the overlay must not parse it. The daemon's first stderr line is a JSON hello.

label is today's completed count, never the streak. Session IdleHint is sessionIdle — do not ship a field named idle. Omitted config / goal flags never reset last-seen values. Drop a view with sessions.length > 80 or heatmap.length > 400.

Full schema: protocol.md.

Limits

  • The tracker does not watch windows or /proc. It does not keylog.
  • History is unlabelled sessions on this machine — treat the state dir as private.
  • Plugins run unsandboxed inside omarchy-shell. Review the source before enabling anything.
  • omarchy plugin add never runs install hooks. A clone without ./scripts/build.sh shows a missing-tracker error.

Repository map

manifest.json                 plugin id, service + bar-widget schema
service/Service.qml           supervise omapomodoro-track
bar/BarWidget.qml             chip + KeyboardPanel host
bar/Model.js                  chip text / tooltip / parse
overlay/Overlay.qml           session, tabs, FileView, keyboard
overlay/OverlayModel.js       parse, merge, palette, actions
overlay/Hero.qml              pinned title + Time/Day/Week/Streak
overlay/TimerPanel.qml        ring + start/pause/skip/reset + cycle
overlay/DurationStepper.qml   focus / short / long / every row
overlay/RingCanvas.qml        phase ring
overlay/TodayPanel.qml        today's sessions
overlay/WeekPanel.qml         7-day strip + rows
overlay/StreaksPanel.qml      goals + streak + heatmap
overlay/SessionList.qml       session rows
overlay/EmptyState.qml        first-run / missing binary / error
overlay/StatusBar.qml         running / paused / offline
overlay/Format.js             MM:SS, durations, words
src/*.rs                      tracker daemon
tests/                        unit + CLI
protocol.md                   NDJSON contract
scripts/build.sh              mise exec cargo build --release
scripts/test.sh               cargo test + plugin validate
scripts/dev-install.sh        symlink into ~/.config/omarchy/plugins
scripts/install-menu.sh       optional trigger.omapomodoro
scripts/publish.sh            GitHub repo + marketplace URL

Develop

mise install
./scripts/test.sh
./scripts/dev-install.sh
./scripts/dev-watch.sh

After QML edits on a symlink install, force a reload with omarchy-shell shell rescanPlugins — inotify does not follow the plugin symlink.

Tracker:

./target/release/omapomodoro-track proto
./target/release/omapomodoro-track ensure
./target/release/omapomodoro-track view
./target/release/omapomodoro-track status
./target/release/omapomodoro-track start
./target/release/omapomodoro-track pause
./target/release/omapomodoro-track skip
./target/release/omapomodoro-track reset
./target/release/omapomodoro-track stop

./scripts/test.sh is the gate: cargo fmt --check, clippy, unit + CLI tests, omarchy plugin validate, and a JSON parse of manifest.json.

Manual (not in CI): close the lid mid-focus and wake paused; lock mid-focus (pauses) vs mid-break (keeps falling); omarchy restart shell mid-focus (chip still ticking); cross local midnight with a running focus (complete counts on the start day).

Good first changes: overlay chrome, cycle-card layout, heatmap density, insight copy. Harder: clock classification, midnight credit, snapshot cadence. Do not add window tracking, tasks/tags, or cloud sync unless that is the request.

Remove

omarchy plugin disable postman.omapomodoro
./target/release/omapomodoro-track stop || true
omarchy plugin remove postman.omapomodoro --yes
rm -rf ~/.local/state/omapomodoro
rm -rf "${XDG_RUNTIME_DIR:-/run/user/$UID}/omapomodoro"

Disable does not stop a running daemon — omapomodoro-track stop does.

If you added the optional menu trigger, delete the trigger.omapomodoro block from ~/.config/omarchy/extensions/omarchy-menu.jsonc.

A local symlink install (from ./scripts/dev-install.sh) is not a git checkout:

rm -f ~/.config/omarchy/plugins/postman.omapomodoro

License

MIT. See LICENSE.