Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

alarmclock — a CLI alarm clock

A dependency-free, terminal-native alarm clock. No web UI, no database — alarms persist to a small JSON file on disk.

Process: this was built spec-first, not prompt-first

Given no detailed spec, I didn't jump straight to code. The order was:

  1. SPEC.md — written first. Every behavior is a numbered, testable requirement (FR-1.1, NFR-3, etc.) with Given/When/Then acceptance criteria, plus an explicit "out of scope" section so scope cuts are visible decisions, not silent omissions.
  2. PLAN.md — derived from the spec, not the other way around. Tasks are sequenced by dependency (data model → storage → scheduling logic → CLI), and each task cites which requirement IDs it exists to satisfy.
  3. Implementation — every non-trivial line in alarmclock/*.py that implements a requirement is tagged inline with its ID (e.g. # FR-4.2), so you can grep the code against the spec directly.
  4. TRACEABILITY.md — a matrix mapping every requirement to its implementation location and its test. Built by walking the spec line-by-line against the finished code, which is what surfaced two real, documented coverage gaps (see that file's "Known gaps" section) rather than assuming coverage was complete.

The point of this ordering: the spec is the thing under version control and review, not a single prompt's output. If a requirement turns out to be wrong or missing, that's a spec change with a visible diff — not a silent edit to whatever the AI happened to generate.

Why it's built this way

Scope. Given a 30-minute, no-spec exercise, I decided the core value is: add an alarm, see your alarms, remove one, and have the clock actually ring at the right time with a snooze/dismiss interaction. Everything else (custom sounds, weekday-only repeats, timezones) is explicitly out of scope — see "Not built (and why)" below.

No external dependencies. Sound and scheduling libraries (playsound, schedule, etc.) add platform-specific fragility that isn't worth it for this scope. The terminal bell (\a) plus a printed banner works everywhere Python runs, with zero install steps.

JSON file, not a database. The exercise explicitly says no database. Alarms are stored at ~/.alarmclock/alarms.json by default (overridable via --store <path> or the ALARMCLOCK_STORE env var), written atomically via a temp-file-then-rename to avoid partial writes if the process is killed mid-save.

Separate add and run commands. A real alarm clock needs to accept new alarms while it's not necessarily running the watch loop right now (e.g. you set an alarm the night before). Splitting these means the JSON file is the single source of truth shared between invocations.

Design decisions & edge cases handled

Decision Reasoning
Alarm times are HH:MM, 24-hour, validated via datetime.time() Fails fast on 25:99 etc. at add time, not at 3am when it silently never fires
once alarms auto-disable after ringing; daily alarms don't Matches what a user actually expects from each mode
last_triggered_date tracked per alarm The watch loop polls every second, so without this a daily alarm would ring 60 times in its matching minute
Setting a once alarm for a time already past today Not treated as an error — it will correctly ring at that time tomorrow, since the loop matches on time-of-day only. A warning is printed at add time so the user isn't surprised
Ctrl+C during run Caught explicitly, exits cleanly instead of a stack trace
Snooze Reschedules the same alarm object to fire again in 5 minutes as a one-shot re-fire, rather than creating a duplicate alarm entry
Corrupted/unreadable JSON store Loads as empty rather than crashing the CLI — a corrupted cache shouldn't brick the tool
input() raising EOFError (stdin closed, e.g. piped/non-interactive run) Caught and treated as an auto-dismiss instead of crashing the whole process — found via live testing, see commit history

Not built (and why)

Left out deliberately, given the time box — noted here rather than silently omitted:

  • Timezones — assumes local system time throughout.
  • Weekday-specific repeats (e.g. "weekdays only") — only once/daily.
  • Custom/audio-file sounds — terminal bell only, to stay dependency-free.
  • Background/daemon moderun is a foreground blocking process by design; wrapping it in a systemd unit or nohup is an OS-level concern, not this tool's.
  • Concurrent multi-alarm ringing — if two alarms are due in the same second, they ring one after another, not simultaneously. A queue, not a interruption.

Install & run

No dependencies required to run the tool itself (only pytest for the test suite).

git clone <this-repo>
cd alarmclock-cli

# Add an alarm
python -m alarmclock add --time 07:30 --label "Wake up" --repeat daily

# Add a one-off alarm
python -m alarmclock add --time 14:00 --label "Standup"

# See what's scheduled
python -m alarmclock list

# Remove one (id comes from `list`)
python -m alarmclock remove d05c361e

# Start the clock — blocking, watches every second, rings when due
python -m alarmclock run

When an alarm rings, you'll see a banner and be prompted to press Enter to dismiss, or s to snooze 5 minutes. Ctrl+C stops the watcher entirely.

By default alarms are stored at ~/.alarmclock/alarms.json. Override with --store /path/to/file.json on any command, or set ALARMCLOCK_STORE.

Tests

pip install pytest
python -m pytest tests/ -v

15 tests covering: time/repeat validation, JSON persistence round-trips (including missing/corrupted files), the due-alarm detection logic (the actual scheduling correctness — same-time match, disabled alarms, no double-fire within a day, daily re-fire next day), and end-to-end CLI smoke tests via subprocess (add/list/remove, and rejecting invalid input).

Project layout

alarmclock/
  models.py    — Alarm dataclass + validation
  storage.py   — JSON load/save (atomic writes)
  engine.py    — the watch loop: due-alarm detection, ringing, snooze/dismiss
  cli.py       — argparse subcommands: add / list / remove / run
  __main__.py  — `python -m alarmclock` entrypoint
tests/
  test_alarmclock.py

About

Alarm cli application built using the python

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages