timer-cli 0.11.0

Simple countdown terminal alarm
# AGENTS.md - Timer CLI Codebase Guide

## Project Overview

**Timer CLI** is a Rust-based terminal countdown timer and stopwatch with ASCII art display, sound alerts, and hardware PC speaker support. It provides a simple, elegant way to set countdown timers or run a stopwatch with large, centered FIGlet text output.

**Repository:** https://github.com/pando85/timer
**Binary name:** `timer`
**Package name:** `timer-cli`

## Architecture

```
src/
├── main.rs          # Entry point, CLI parsing, signal handling, main loop
├── opts.rs          # CLI options structure (clap derive)
├── timer.rs         # Core countdown logic, time string parsing
├── stopwatch.rs     # Core stopwatch logic, state machine, keyboard handling
├── time.rs          # Time struct, formatting, FIGlet ASCII rendering
├── ui.rs            # Terminal UI (crossterm): setup, draw, restore
├── beep.rs          # Linux PC speaker hardware beep (ioctl)
├── constants.rs     # Timing constants for beep/sound
├── utils.rs         # Thread utilities with timeout
├── figlet/
│   ├── mod.rs       # FIGlet ASCII art text conversion
│   └── univers.flf  # FIGlet font file (embedded)
└── sound/
    ├── mod.rs       # Audio playback via rodio
    └── beep.ogg     # Embedded notification sound
```

## Module Responsibilities

### `main.rs` - Application Entry Point
- Parses CLI arguments using `clap`
- Routes to countdown or stopwatch based on subcommand
- Converts input time to `OffsetDateTime` target (for countdown)
- Sets up terminal (alternate screen, hidden cursor)
- Spawns countdown thread
- Handles Unix signals (SIGWINCH for resize, SIGTERM/SIGINT/SIGQUIT for exit)
- Supports loop mode (`--loop` flag)

**Key functions:**
- `parse_time(input_time: &str) -> Option<OffsetDateTime>` - Parses duration or target time
- `handle_countdown(w, end, opts) -> Result<()>` - Runs countdown with terminal restoration
- `run_stopwatch()` - Runs stopwatch mode
- `run_countdown(opts)` - Runs countdown mode
- `main()` - Entry point orchestrating everything

### `opts.rs` - CLI Options
Defines the `Opts` struct with clap derive:
```rust
pub struct Opts {
    pub command: Option<Command>, // Subcommand (stopwatch)
    pub r#loop: bool,             // -l, --loop: Repeat countdown
    pub silence: bool,            // -s, --silence: Disable sounds
    pub terminal_bell: bool,      // -t, --terminal-bell: Enable bell character
    pub time: Vec<String>,        // Positional: time input
}

pub enum Command {
    Stopwatch,  // Start a stopwatch (counts up from zero)
}
```

**Supported time formats (countdown):**
- Duration: `10s`, `5m`, `2h10m`, `1h30m45s`, `15min`, `10` (seconds)
- Target time: `12:00`, `08:25`, `13:45:43`

### `timer.rs` - Core Countdown Logic
- `parse_counter_time(s: &str) -> Option<Duration>` - Parses duration strings using regex
- `parse_end_time(s: &str) -> Option<OffsetDateTime>` - Parses target time strings
- `countdown(w, end, opts) -> Result<()>` - Main tail-recursive countdown loop
- `resize_term(w, end) -> Result<()>` - Redraws on terminal resize
- `play_beep() -> Result<()>` - Plays hardware PC speaker beep
- `play_sound() -> Result<()>` - Plays audio file with timeout

**Countdown algorithm:** Uses tail-call optimization (`tailcall` crate) to recursively count down, drawing the UI each second until the end time is reached.

### `stopwatch.rs` - Core Stopwatch Logic
- `run(w) -> Result<()>` - Main stopwatch loop with keyboard handling
- `State` enum - State machine (Running/Paused) tracking elapsed time
- `handle_key(key) -> Action` - Maps keyboard input to actions

**Stopwatch controls:**
- `Space` / `p` - Toggle pause/resume
- `l` / `Enter` - Record lap time
- `r` - Reset stopwatch
- `q` / `Ctrl+C` - Quit

**State machine:**
```rust
enum State {
    Running { start: Instant, accumulated: Duration },
    Paused { accumulated: Duration },
}
```

### `time.rs` - Time Representation & Rendering
Defines the `Time` struct:
```rust
pub struct Time {
    hours: u64,
    minutes: u8,
    seconds: u8,
}
```

**Key functions:**
- `From<&Duration> for Time` - Converts `time::Duration` to `Time`
- `format() -> String` - Returns formatted string like `2h 10m 5s`
- `render(size: (u16, u16)) -> String` - Renders as centered FIGlet ASCII art

**Rendering strategy (graceful degradation):**
1. Full format: `2h 10m 5s`
2. If too wide: `2h 10m` (omit seconds)
3. If still too wide: `2h` (omit minutes)
4. If still too wide: plain text (no FIGlet)

### `ui.rs` - Terminal UI Management
Uses `crossterm` for terminal operations:
- `draw(w, counter: Duration) -> Result<()>` - Clears screen, sets title, renders time
- `draw_with_laps(w, elapsed, laps, is_running) -> Result<()>` - Draws stopwatch with lap times
- `set_up_terminal(w) -> Result<()>` - Enters alternate screen, hides cursor
- `restore_terminal(w) -> Result<()>` - Shows cursor, leaves alternate screen

### `beep.rs` - Hardware PC Speaker
Linux-specific hardware beep via ioctl:
- Searches for beep devices: `/dev/input/by-path/platform-pcspkr-event-spkr`, `/dev/console`, TTYs
- Two driver strategies: Console driver (KIOCSOUND) and Evdev driver (input_event)
- `beep(freq: i32, time: Duration) -> Result<()>` - Plays beep at frequency

### `sound/mod.rs` - Audio Playback
Uses `rodio` crate:
- Embeds `beep.ogg` at compile time
- `Sound::new() -> Result<Sound>` - Opens audio stream
- `Sound::play(&self) -> Result<()>` - Plays the embedded sound

### `figlet/mod.rs` - ASCII Art Text
FIGlet font rendering:
- Embeds `univers.flf` font at compile time
- `Figlet::convert(&self, s: &str) -> String` - Converts text to ASCII art

### `constants.rs` - Timing Constants
```rust
pub const BEEP_DELAY: u64 = 100;        // ms between beeps
pub const BEEP_DURATION: u64 = 400;     // ms per beep
pub const BEEP_REPETITIONS: usize = 5;  // number of beeps
pub const BEEP_FREQ: i32 = 440;         // Hz (A4 note)
```

### `utils.rs` - Thread Utilities
- `JoinWithTimeout<T>` - Thread handle with timeout support
- `spawn_thread(f) -> JoinWithTimeout<T>` - Spawns thread with completion signal
- `join(timeout) -> Option<T>` - Waits with timeout

## Key Dependencies

| Crate | Purpose |
|-------|---------|
| `clap` | CLI argument parsing with derive macros |
| `crossterm` | Cross-platform terminal manipulation |
| `time` | Date/time handling and formatting |
| `regex` | Time input string parsing |
| `rodio` | Audio playback (vorbis support) |
| `signal-hook` | Unix signal handling |
| `tailcall` | Tail-call optimization for recursion |
| `libc` / `nix` | Low-level Linux ioctl for PC speaker |
| `glob` | Finding TTY devices |

## Application Flow

### Countdown Flow
```
1. main()
   ├── Parse CLI args → Opts
   ├── parse_time(input) → OffsetDateTime
   │   ├── Try parse_counter_time() for "5m30s" style
   │   └── Or parse_end_time() for "12:30" style
   ├── ui::set_up_terminal()
   ├── Spawn countdown thread
   │   └── timer::countdown() [tail-recursive]
   │       ├── Calculate remaining = end - now
   │       ├── ui::draw(remaining)
   │       ├── sleep(1 second)
   │       └── Recurse until done, then play alerts
   └── Main thread: Signal handler loop
       ├── SIGWINCH → resize_term()
       └── SIGTERM/SIGINT/SIGQUIT → cleanup & exit
```

### Stopwatch Flow
```
1. main()
   ├── Parse CLI args → Opts
   ├── Match Command::Stopwatch
   ├── ui::set_up_terminal()
   └── stopwatch::run()
       ├── Initialize State::Running
       └── Loop:
           ├── Calculate elapsed time
           ├── ui::draw_with_laps(elapsed, laps)
           ├── Poll keyboard events (100ms timeout)
           └── Handle key → Action (Quit/Pause/Lap/Reset)
```

## Testing

Run tests with:
```bash
cargo test
```

## Building

```bash
cargo build --release
```

The binary is optimized with LTO and symbol stripping (see `Cargo.toml` profile).

## Code Style

- Uses Rust 2024 edition
- Tail-call optimization for recursive functions
- Embedded assets (font, sound) at compile time
- Error handling via `Box<dyn std::error::Error>`
- Modular separation of concerns

## Extension Points

When adding new features:
- **New CLI options:** Add to `Opts` struct in `opts.rs`
- **New time formats:** Extend parsing in `timer.rs`
- **UI changes:** Modify `ui.rs` and `time.rs` rendering
- **New alerts:** Add to `beep.rs` or `sound/mod.rs`
- **Subcommands:** Consider using clap subcommands in `opts.rs`