# Omascreentime NDJSON protocol Every stdout line is one JSON object. UTF-8, no pretty-print. `v` is the protocol version. v1 clients reject `v !== 1`. Unknown `type` values are ignored (forward compatible). stderr is human logs only. State lives under `$XDG_STATE_HOME/omascreentime` or `~/.local/state/omascreentime` (`0700` dirs, `0600` files). The control socket is `$XDG_RUNTIME_DIR/omascreentime/omascreentime.sock`. ## Commands ``` omascreentime-track proto omascreentime-track ensure [--idle-sec N] [--keep-days N] [--goal-min N] omascreentime-track daemon [--idle-sec N] [--keep-days N] [--goal-min N] omascreentime-track status omascreentime-track view omascreentime-track stop ``` `--state-dir` and `--socket` override the default paths (tests). Exit codes: `0` ok, `1` usage, `2` io/lock. `ensure` starts `daemon` if the socket is down, then prints one `status`. `status` / `view` talk to the daemon when it is up, otherwise they print the last `snapshot.json` (or a synthesized empty day) with `daemon: false`. ## Events ### `proto` ```json {"v":1,"type":"proto","protocol":1,"stateDir":"/home/postman/.local/state/omascreentime","socket":"/run/user/1000/omascreentime/omascreentime.sock","pid":4242} ``` ### `hello` First stderr line of `daemon`. ```json {"v":1,"type":"hello","pid":4242,"stateDir":"/home/postman/.local/state/omascreentime","startedAt":1755302400} ``` ### `status` ```json {"v":1,"type":"status","todayKey":"2026-08-17","todayMs":8040000,"label":"2h 14m","activeApp":"brave","activeLabel":"Brave","tracking":true,"idle":false,"locked":false,"daemon":true} ``` ### `view` `apps` lists every focused app of at least 5 seconds, largest first. The overlay groups the tail into an Other donut slice; the list keeps every row. `week` is the trailing 7 local-time days, oldest first. Apps under 5s are omitted from `apps` but still included in `todayMs`. ```json {"v":1,"type":"view","todayKey":"2026-08-17","todayMs":8040000,"label":"2h 14m","words":"2 HOURS 14 MINUTES","goalMs":28800000,"activeApp":"brave","activeLabel":"Brave","tracking":true,"idle":false,"locked":false,"daemon":true,"apps":[{"id":"brave","name":"Brave","ms":3600000,"pct":45,"kind":"app"}],"week":[{"key":"2026-08-11","label":"Tue","ms":0,"isToday":false}],"insights":[{"label":"Top app","value":"Brave ยท 1h (45%)"}]} ``` ### `error` ```json {"v":1,"type":"error","message":"daemon did not start","fatal":true} ``` ## Persistence ``` ~/.local/state/omascreentime/ history.json # { "v": 1, "days": { "YYYY-MM-DD": { "total", "apps" } } } snapshot.json # last view event, watched by the bar daemon.lock daemon.pid ``` Atomic publish: write `*.tmp`, `fsync`, `rename`. Days older than `keepDays` (default 31) are pruned on load and on midnight rollover. Focus is credited to the local day the bucket started on, so a session that spans midnight stays on the first day. Idle, lock, screensaver (`org.omarchy.screensaver`), and an empty desktop are not counted. A window with `inhibitingIdle` (video) keeps counting through session idle.