timerd-rs 0.6.0

A small foreground/background terminal timer for Linux
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# timerd-rs

[![crates.io](https://img.shields.io/crates/v/timerd-rs.svg)](https://crates.io/crates/timerd-rs)

`timerd-rs` is a small Linux terminal timer. Its name combines **timer** with
the Unix convention of using a trailing **d** for a daemon. It installs the
simple command `timer` and supports a stopwatch, a countdown, and one-time
wall-clock alarms. A per-user background process owns timer state, so the
foreground display can detach and attach again without stopping the timer.

## Install

Install [`timerd-rs` from crates.io](https://crates.io/crates/timerd-rs):

```bash
cargo install timerd-rs
```

Or install it from a local checkout:

```bash
cargo install --path .
```

The Cargo package is named `timerd-rs`; the installed executable is named
`timer`.

## Start a timer

Run without arguments for an interactive choice between a stopwatch and a
countdown:

```bash
timer
```

Or start one directly:

```bash
timer stopwatch
timer sw

timer 15
timer 15s
timer 5m
timer 1h30m
timer 00:15:00
timer countdown 15m
timer cd 15m
```

A bare number is interpreted as seconds. Two-part clock input is `MM:SS`, and
three-part clock input is `HH:MM:SS`.

Add a note or start detached:

```bash
timer 25m --note "stretch break"
timer 25m -n "stretch break" -d
timer stopwatch -n "coding session" -d
```

## Schedule an alarm

Use `at` to schedule a one-time reminder at a local clock time. `alarm` is a
readable alias for the same command:

```bash
timer at 08:00 -n "eat breakfast"
timer at 08:00:30 -n "join the call" -d
timer at "2030-10-30 18:45" -n "leave for the airport" -d
timer alarm 20:00 -n "take medicine"
```

Accepted inputs are `HH:MM`, `HH:MM:SS`, `YYYY-MM-DD HH:MM`, and
`YYYY-MM-DD HH:MM:SS`. A clock time such as `08:00` means the next occurrence:
today when it is still ahead, otherwise tomorrow. An explicit date must be in
the future. All inputs use the machine's local timezone.

In the foreground, the large value is the remaining time while the target and
description stay fixed:

```text
                 00:24:37
       alarm #12 · at 08:00 · running · eat breakfast
            d detach | x cancel | +/- size (medium)
```

Alarms are tied to an absolute time and therefore cannot be paused or resumed.
They can be detached, attached, listed, or cancelled like other timers. This
release schedules one occurrence only; it does not create a daily repeating
alarm.

## Terminal bell

Add `-b`/`--bell` to a countdown or alarm to ring the terminal bell when it
finishes:

```bash
timer 15s -b
timer countdown 5m --bell -d
timer at 08:00 -n "eat breakfast" -b -d
```

Use `--no-bell` to explicitly disable it. There is no `-nb` spelling because
short options are single characters and `-n` already means `--note`. Bell is
disabled by default, so `--no-bell` is mainly useful for explicit scripts and
future configuration overrides.

The bell is the standard terminal `BEL` control character (`0x07`). It has no
server-side audio or library dependency: over SSH, the byte travels through the
session and the local terminal emulator decides whether to play a sound, flash,
show a tab indicator, or ignore it. A foreground timer rings once before its
completion line; a detached timer sends the bell with its completion message to
the terminal that created it. Closing that SSH session still makes delivery
impossible, just like the ordinary completion message.

### iTerm2 setup

The server cannot override the local iTerm2 profile. To hear or see the bell:

1. Open **iTerm2 > Settings > Profiles > Terminal** for the profile used by
   the SSH session.
2. Under **Bell**, clear **Silence bell**. Leaving it selected suppresses the
   sound even when timerd successfully sends `BEL`.
3. Choose a **Bell sound** if desired. **Flash visual bell** and **Show bell
   icon in tabs** are optional visual alternatives.
4. To request a macOS banner, enable **Notification Center alerts**, open
   **Filter Alerts**, and enable **Send "Bell" alert**. macOS must also allow
   notifications for iTerm2 in System Settings.

Test the same SSH session before relying on a timer:

```bash
printf '\a'
(sleep 3; printf '\aBell test\n') &
```

If neither test produces the configured sound, flash, icon, or alert, timerd
cannot force that terminal to show one. Stopwatch does not accept `--bell`,
because it has no natural completion point.

Choose one of four fixed foreground display sizes:

```bash
timer sw --size small
timer sw -s m
timer 15m -s large
timer a -s xl
```

The accepted names are `small`, `medium`, `large`, and `xlarge`. Their short
values are `s`, `m`, `l`, and `xl`; `1`, `2`, `3`, and `4` are also accepted.
`small` keeps the original one-line display. Larger sizes use built-in block
digits and automatically fall back when the terminal is too narrow or short.
Because a detached timer has no foreground display, `--size` and `--detach`
cannot be used together; choose a size when attaching instead.

## Animated themes

Themes are optional. Without `-t`/`--theme`, the foreground display remains
the same plain timer as before. Enable the bundled `sleepy-cat` theme when
starting or attaching to a timer:

```bash
timer 25m -s m -t sleepy-cat
timer sw --theme sleepy-cat
timer a --theme sleepy-cat
```

Discover, preview, and validate themes with:

```bash
timer theme list
timer theme preview sleepy-cat
timer theme check ./my-cat.toml
timer theme path
```

During a themed foreground session, press `]` for the next discovered theme
or `[` for the previous one. The cycle includes `none`, which hides the art
without stopping the timer. These keys only become active when the session was
started or attached with a theme option.

User themes are loaded automatically from
`$XDG_CONFIG_HOME/timerd-rs/themes`, or `~/.config/timerd-rs/themes` when
`XDG_CONFIG_HOME` is not set. The filename is the theme name, so a file named
`my-cat.toml` is selected with `timer 5m -t my-cat`. A theme needs at least one
running frame; paused and done frames are optional:

```toml
version = 1
name = "My Cat"
fps = 4
align = "center"

[[running]]
art = """
   /\___/\
  (  o o  )
  (   ^   )
"""

[[running]]
art = """
   /\___/\
  (  - -  )
  (   ^   )
"""

[[paused]]
art = """
   /\___/\
  (  z z  )
  (   ^   )
"""

[[done]]
art = """
   \  |  /
    \(^)/
     / \
"""
```

Each `art` opening and closing `"""` must be on its own line. Theme art is
printable ASCII data only: terminal control characters are rejected, and frame
counts and dimensions are bounded. To contribute a bundled theme, add one TOML
file to the repository's [`themes`](./themes) directory; the build script discovers
and embeds it automatically, with no Rust registration table to edit.

## Foreground controls

While the foreground display is active:

| Key | Action |
| --- | --- |
| `Space` | Pause or resume |
| `d` | Detach while keeping the timer running |
| `Ctrl-B`, then `D` | tmux-style alias for detach |
| `x` | Stop the stopwatch or cancel the countdown |
| `Ctrl-C` | Same as `x` |
| `+` or `=` | Increase the display size |
| `-` | Decrease the display size |
| `1`–`4` | Select `small` through `xlarge` directly |
| `[` / `]` | Previous / next theme, including `none` (themed sessions only) |

Attach again with the complete command or its short alias:

```bash
timer attach
timer a
timer attach 3
timer a 3
```

Without an ID, attach selects the most recently used detached timer. Only one
foreground client may control a timer at a time.

## Configuration

The built-in default display size is `small`. To change it for future
foreground displays, create `$XDG_CONFIG_HOME/timerd-rs/config.toml`, or
`~/.config/timerd-rs/config.toml` when `XDG_CONFIG_HOME` is not set:

```toml
[display]
size = "large"
```

Configuration accepts the full values `small`, `medium`, `large`, and
`xlarge`. A command-line `--size` value takes precedence over the file, while
the `+`, `-`, and number keys affect only the current foreground session.

Inspect the path and active value with:

```bash
timer config path
timer config show
```

A missing file is valid and uses `small`; an invalid setting reports its file
and parse error. The configuration is read by each new foreground invocation,
so the daemon does not need to restart.

## Manage timers

Every frequent command has a readable name and a short alias:

```bash
timer list             # alias: timer ls
timer watch            # alias: timer w
timer pause [ID]       # alias: timer p [ID]; stopwatch/countdown only
timer resume [ID]      # alias: timer r [ID]
timer stop [ID]        # alias: timer x [ID]; also cancels an alarm
timer clear
```

When an ID is omitted, the daemon selects the most recently used compatible
timer. `clear` removes finished, stopped, and cancelled records.

`timer watch` opens a live view of every running or paused task. The final
`CREATED` column is the task creation time in the machine's local timezone:

```text
   ID   TYPE       STATUS   TIME       TARGET   NOTE          CREATED
>  12   alarm      running  00:24:37   08:00    eat breakfast 2026-10-29 07:35:23
   13   countdown  running  00:04:12   08:10    coffee        2026-10-29 08:05:48
   14   stopwatch  paused   01:32:08   -        coding        2026-10-29 06:37:52
```

Use the following keys inside the watch view:

| Key | Action |
| --- | --- |
| `Up` / `Down` (or `k` / `j`) | Select a task |
| `Enter` | Leave the watch view and attach to the selected task |
| `x` | Stop a stopwatch or cancel a countdown/alarm |
| `s` | Snooze: add a duration to a countdown or delay an alarm |
| `e` | Edit the note and, when applicable, remaining duration or alarm target |
| `q`, `Esc`, or `Ctrl-C` | Exit the watch view without stopping tasks |

Snooze accepts the same duration forms as countdown creation, such as `30s`,
`5m`, or `1h30m`. Editing a countdown replaces its remaining duration; editing
an alarm accepts the same local clock/date formats as `timer at`. A stopwatch
has no deadline, so it supports note editing but not snooze.

## Shell completions

Generate completion code with `timer completions SHELL` (the singular
`completion` spelling is also accepted). For Fish:

```fish
mkdir -p ~/.config/fish/completions
timer completions fish > ~/.config/fish/completions/timer.fish
```

For Bash:

```bash
mkdir -p ~/.local/share/bash-completion/completions
timer completions bash > ~/.local/share/bash-completion/completions/timer
```

For Zsh, generate `_timer` into a directory included in `fpath`:

```zsh
mkdir -p ~/.zfunc
timer completions zsh > ~/.zfunc/_timer
fpath=(~/.zfunc $fpath)
autoload -Uz compinit && compinit
```

Elvish and PowerShell are also supported. Generated completions cover commands,
aliases, options, shell names, and bundled theme names. User-installed theme
names remain free-form. Regenerate the completion file after an upgrade so
newly added options and bundled themes are included.

## Countdown and alarm completion

For a detached countdown, the daemon keeps a write handle to the terminal that
created it. If that terminal or SSH session is still open, completion appears
there as ordinary terminal output:

```text
[timer #3] ✓ countdown finished · stretch break
```

This does not depend on a desktop notification service, iTerm2 escape codes,
tmux, or screen. If the original terminal has closed, the write is allowed to
fail and the timer still becomes `done`. Its result remains visible in
`timer list`.

A detached alarm uses the same terminal-session mechanism and includes its
target clock and description:

```text
[timer #12] ⏰ 08:00 · eat breakfast
```

An asynchronous completion line can visually interrupt text currently being
typed at the shell prompt. The typed input remains owned by the shell, but a
prompt redraw may occasionally be necessary.

## Files and process model

`timer` is a single crate and a single executable. On first use, that executable
starts itself in a hidden daemon mode. The CLI and foreground renderer talk to
it over a private Unix socket.

Runtime files follow the XDG directory conventions:

```text
$XDG_RUNTIME_DIR/timer-rs/timer.sock
$XDG_STATE_HOME/timer-rs/state.json
$XDG_STATE_HOME/timer-rs/daemon.log
```

When the corresponding variables are unset, state is stored under
`~/.local/state/timer-rs`, and the socket uses a user-specific directory under
`/tmp`.

The daemon can be stopped explicitly:

```bash
timer shutdown
```

Shutdown is refused while any timer is running or paused, because stopping the
service would prevent timely completion messages.

Active task metadata is persisted, but a terminal write handle cannot survive
a daemon restart. On systems configured to kill all user processes at logout,
a future systemd user-service integration may be needed for timers that must
survive a complete logout.