npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

screen-pomodoro

v0.5.2

Published

A tiny CLI pomodoro timer that pauses when your screen locks.

Readme

screen-pomodoro

CI

A tiny CLI pomodoro timer that pauses when your screen locks.

  • Runnable via npx — no install step required
  • Fully typed TypeScript (strict, ESM), tested with Vitest

What is the Pomodoro Technique?

The Pomodoro Technique is a simple time-management method created by Francesco Cirillo in the late 1980s (named after his tomato-shaped kitchen timer — pomodoro is Italian for tomato). The idea:

  1. Pick one task and focus on it for a fixed interval (traditionally 25 minutes).
  2. Take a short break (traditionally 5 minutes) to rest and reset.
  3. After every few focus sessions (traditionally 4), take a longer break (15–30 minutes).

Working in short, deliberate bursts makes it easier to start, stay focused, and avoid burnout — and the regular breaks give your brain a chance to recharge.

screen-pomodoro follows exactly this rhythm out of the box: it loops Focus → Short break, inserting a Long break after every --cycles focuses (4 by default), with the classic 25 / 5 / 15 minute defaults. The twist: it pauses automatically when your screen locks (macOS — Linux/Windows keep running; support planned; --no-screen-pause opts out), so a coffee run or a chat never eats into your focus time.

Quick start

# Run without installing
npx screen-pomodoro

# Show usage
npx screen-pomodoro --help

CLI

Usage: screen-pomodoro [options]

A pomodoro timer that pauses when your screen locks.

Options:
  -V, --version        Print the version number.
  --focus <duration>   Focus duration (minutes or with s/m/h suffix). (default: "25")
  --short <duration>   Short break duration (minutes or with s/m/h suffix). (default: "5")
  --long <duration>    Long break duration (minutes or with s/m/h suffix). (default: "15")
  --cycles <n>         Focuses per long break (integer >= 1). Loops forever unless --no-loop is given. (default: "4")
  --no-loop            Stop after the first long break (with the default start, after --cycles focuses) instead of looping forever.
  --start <phase>      Starting phase: focus, short, or long (aliases short-break, long-break). With --confirm/--notify-confirm, omit to choose at startup.
  -q, --quiet          Log transitions only, no live countdown.
  --timestamp          Prefix history lines with the current time ([HH:MM:SS]).
  --confirm            Awaits y/n on each phase transition (requires interactive stdin).
  --notify             Send a macOS notification on each phase transition (macOS + terminal-notifier required).
  --notify-confirm     Answer phase transitions by clicking the notification (click = start next, Restart = redo). Implies the confirm gate; does not require interactive stdin.
  --no-screen-pause    Do not pause when the screen locks.
  --no-bell            Disable the terminal bell (\x07) on phase changes and prompts.
  --notify-group <id>  Notification group for overlapping timers (default shares one toast).
  --focus-name <name>  Custom label for focus phases. (default: "Focus")
  --short-name <name>  Custom label for short breaks. (default: "Short break")
  --long-name <name>   Custom label for long breaks. (default: "Long break")
  -h, --help           display help for command

Custom names appear in every phase line, pause/resume notice, confirm prompt, and summary (--focus-name "Deep work" → Deep work 1/4, Completed 3 Deep work, 2 Coffee, 1 Lunch). Names must be non-empty after trimming, at most 40 characters, with no control characters. The exit summary always reports completed focuses plus short and long breaks (Completed 2 focuses, 1 short breaks, 0 long breaks).

With --confirm, each deadline rings once (unless --no-bell) and asks:

Deep work complete. Start Coffee? [y/n] y

y advances one phase, n restarts the current phase with a full deadline. Anything else re-prompts. --confirm needs an interactive terminal (--confirm requires an interactive terminal otherwise). --no-loop still exits after the long break without a trailing prompt.

--cycles sets the long-break cadence; without --no-loop the timer loops forever (--cycles 2 --no-loop runs 2 focuses then exits).

--start <phase> begins in that phase instead of focus: focus (default), short (short-break), or long (long-break, case-insensitive). With --confirm and no --start, the timer asks once at startup (Choose starting phase: 1) Focus 2) Short break 3) Long break [1] — 1/2/3, f/s/l, names, or empty for Focus; anything else re-asks) and then gates each transition as usual, so opening on a break flows into the first focus via Short break complete. Start Focus? [y/n]. With --notify-confirm and no --start, the timer shows one toast with three actions (the phase labels, custom-name aware) instead — click a button to start there, click the body for Focus; dismissing re-sends until answered. An explicit --start skips the question under either gate. Without a gate the timer just auto-flows from the start phase; --no-loop --start long runs one long break then exits.

--timestamp prefixes every history line (phase lines, pause/resume, summary, confirm prompt) with the current local time ([14:03:22] Focus 1/4 — …). The ephemeral live countdown stays unstamped — wall-clock seconds and remaining seconds flip on different boundaries, so stamping it would show two clocks ticking out of phase. Notifications are unchanged (the OS already timestamps those).

macOS notifications (terminal-notifier)

macOS only — Linux/Windows use of either flag is a usage error (exit 1). Install the notifier once:

brew install terminal-notifier
  • --notify sends one fire-and-forget toast per transition (startup included) alongside the usual bell + phase line, in both live and quiet modes. Pass --no-bell for toast + sound only (single chime instead of two). A missed toast never kills the timer (one stderr note, then bell+text). Nothing fires while the screen is locked (skipped, not queued — the next entry replaces in place), and locking withdraws the visible toast.
  • --notify-confirm replaces the stdin gate: each deadline shows a toast (<current> complete. Start <next>? / <spent> spent. Click for <next> — <upcoming>, Restart <current> to redo.). Click the body to start next, the Restart <current> button to redo. Dismissing the toast re-sends it until answered (same group, no stacking) — the toast equivalent of invalid stdin input. Clearing it from Notification Center (including Clear All) re-sends it the same way, so the timer never waits behind a toast that is no longer there. A prompt owed across a screen lock is likewise re-sent on unlock, so the click still counts exactly once. Without --start, one startup toast offers the three phases first (click = Focus) before the first phase line.
  • --notify-confirm cannot be combined with --confirm or --notify (one source only), and unlike --confirm it works without a TTY.
  • Overlapping timers: by default all timers share one group, so the second timer's toast replaces the first's. Pass distinct --notify-group <id> values (e.g. --notify-group work vs --notify-group stretch) to keep each timer's toasts, prompts, and cleanup isolated. Requires --notify or --notify-confirm; at most 64 characters, no control characters.
  • Caveats: Focus/DnD holds the toast (timers stay frozen, as with stdin); over SSH / launchd-as-root delivery fails (exit 4) → --notify logs and continues, --notify-confirm resolves the pending prompt false with a stderr note instead of hanging. A comma inside a custom phase name splits the notification buttons (terminal-notifier comma-splits -action with no escaping) — avoid commas in --*-name when using --notify-confirm. A toast-body click is reported by an -execute /bin/sleep 0.3 click action instead of @ACTIONCLICKED, which keeps the notifier alive past macOS' own activation of terminal-notifier.app on that click — without it the app has already exited and macOS shows a stray The application "terminal-notifier.app" is not open anymore. (or can't be opened. -609) dialog. The click still counts as exactly one answer.

Screen lock (macOS)

macOS only — Linux/Windows keep running; support planned. --no-screen-pause opts out (no polling at all, quiet mode stays fully idle).

  • How it works: polls /usr/sbin/ioreg -n Root -d1 every 2s (execFile only, no new dependencies). Locked = "IOConsoleLocked" = Yes (or defensive CGSSessionScreenIsLocked = Yes when present); anything else means active. Pause lands within ~2s of locking, resume on unlock — in live and quiet modes, reusing the pause/resume copy with no new strings and no bell. Both modes leave a Paused/Resumed pair in scrollback; live suspends its countdown ticks while paused and commits each history row over the countdown row, so scrollback holds one row per event. -q logs the pair only (no countdown).
  • Fail open: probe errors, parse misses, or a missing binary mean "assume active, timer runs" (one stderr note until the next success, never spam). A missed lock overcounts seconds; a false lock would freeze a focus — the costs are asymmetric, so V1 biases toward running.
  • Password grace period (Settings → Lock Screen → "Require password after…"): the console is genuinely unlocked until the grace expires, so the timer correctly keeps running (latency = grace + ≤1 poll).
  • SSH: ioreg reads kernel state, so a timer in an SSH session correctly pauses when the desk locks.
  • Password-less screensaver, display sleep without a password, and idle-while-thinking do not pause — "keep running" is the right lock-semantics answer there, and idle-time pause is out of scope.
  • Fast-user-switch scoping is unverified and deferred.
  • Rapid lock↔unlock within one interval coalesces silently; lock while --confirm is pending is a no-op and the answer still wins (a pending --notify-confirm toast is re-sent on unlock so it stays answerable). Starting while locked freezes immediately instead of running until the first poll.
  • Lid-close sleep freezes the process; on wake a >5s wall-clock jump probes before ticking so a lock-freeze lands before any phase cascade (unlocked- on-wake cascade on no-password machines is a known V1 limitation).

Development

Requires Node.js >= 22.

npm install              # also installs git hooks (husky)
npm run dev              # run the CLI from source
npm run dev -- --help    # …with arguments

| Script | What it does | | ----------------------------------------------- | ----------------------------------------------------------- | | npm run build | Bundle to dist/ with tsdown (ESM) | | npm run dev | Run the CLI from source with tsx | | npm run typecheck | tsc --noEmit | | npm run lint | ESLint (flat config, type-checked rules) | | npm run format / format:check | Prettier | | npm run test / test:watch / test:coverage | Vitest | | npm run verify | typecheck + lint + format:check + test + build (runs in CI) |

Pre-commit, lint-staged lints and formats staged files via husky.

License

MIT