pypomodo 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pypomodo-0.3.1/LICENSE +21 -0
- pypomodo-0.3.1/PKG-INFO +377 -0
- pypomodo-0.3.1/README.md +352 -0
- pypomodo-0.3.1/pyproject.toml +62 -0
- pypomodo-0.3.1/pyproject.toml.orig +51 -0
- pypomodo-0.3.1/src/pypomo/__init__.py +4 -0
- pypomodo-0.3.1/src/pypomo/__main__.py +3 -0
- pypomodo-0.3.1/src/pypomo/cli.py +193 -0
- pypomodo-0.3.1/src/pypomo/config.py +87 -0
- pypomodo-0.3.1/src/pypomo/durations.py +41 -0
- pypomodo-0.3.1/src/pypomo/events.py +69 -0
- pypomodo-0.3.1/src/pypomo/hooks.py +91 -0
- pypomodo-0.3.1/src/pypomo/keys.py +127 -0
- pypomodo-0.3.1/src/pypomo/output.py +55 -0
- pypomodo-0.3.1/src/pypomo/pomodoro.py +71 -0
- pypomodo-0.3.1/src/pypomo/pretty.py +107 -0
- pypomodo-0.3.1/src/pypomo/signals.py +29 -0
- pypomodo-0.3.1/src/pypomo/timer.py +147 -0
pypomodo-0.3.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrew Davidson (WulfAlpha)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pypomodo-0.3.1/PKG-INFO
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pypomodo
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: A terminal-first Pomodoro timer in the Unix tradition, with named timers, routines, and event hooks
|
|
5
|
+
Keywords: pomodoro,timer,countdown,cli,terminal
|
|
6
|
+
Author: Andrew Davidson
|
|
7
|
+
Author-email: Andrew Davidson <drace.davidson@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Project-URL: Homepage, https://github.com/wulfalpha/pypomo
|
|
22
|
+
Project-URL: Issues, https://github.com/wulfalpha/pypomo/issues
|
|
23
|
+
Project-URL: Changelog, https://github.com/wulfalpha/pypomo/blob/main/CHANGELOG.md
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# pypomo
|
|
27
|
+
|
|
28
|
+
A terminal-first Pomodoro timer in the Unix tradition. pypomo keeps time well:
|
|
29
|
+
durations, named timers, finite Pomodoro routines, pause and resume, and clean
|
|
30
|
+
output for terminals and pipes. Notifications, sounds, and logging are left to
|
|
31
|
+
the tools you already use, through [event hooks](#hooks) and
|
|
32
|
+
[JSON lines output](#json-lines-output).
|
|
33
|
+
Requires Python 3.12 or newer; no runtime dependencies.
|
|
34
|
+
|
|
35
|
+
Licensed under the [MIT License](LICENSE). See the [changelog](CHANGELOG.md)
|
|
36
|
+
for what changed in each release.
|
|
37
|
+
|
|
38
|
+
Install from PyPI with uv or pipx. The package is named `pypomodo`; the command
|
|
39
|
+
is `pypomo`:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv tool install pypomodo
|
|
43
|
+
pipx install pypomodo
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or install from a checkout with `pip install .`, or run with `uv run pypomo`.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pypomo 15m
|
|
50
|
+
pypomo 30s
|
|
51
|
+
pypomo 2h
|
|
52
|
+
pypomo 1h30m15s
|
|
53
|
+
pypomo 1h 30m
|
|
54
|
+
pypomo pomo
|
|
55
|
+
pypomo --list
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Durations use positive whole-number totals with `d`, `h`, `m`, and `s`
|
|
59
|
+
(days, hours, minutes, seconds). Compound units must appear in that order
|
|
60
|
+
without repeats. Values such as `90m` and `36h` are valid. Countdowns always
|
|
61
|
+
display as `HH:MM:SS`, so `2d` shows `48:00:00`. Each duration is limited to
|
|
62
|
+
365 days (`365d`), including durations in config files.
|
|
63
|
+
Spaces may separate units, with or without quotes: `pypomo 1h 30m` and
|
|
64
|
+
`pypomo "1h 30m"` both equal `1h30m`, and config durations such as
|
|
65
|
+
`"1h 30m"` work too. Spaces are allowed only between units (`5 m` is rejected),
|
|
66
|
+
and tabs or other whitespace are not accepted.
|
|
67
|
+
|
|
68
|
+
In a terminal, the timer name and `HH:MM:SS` countdown update on a single line:
|
|
69
|
+
`pomo · 00:24:59 remaining`. Completion or cancellation replaces that line,
|
|
70
|
+
then ends it with a newline so your shell prompt starts cleanly. Completion
|
|
71
|
+
also rings the terminal bell.
|
|
72
|
+
Use `--silent` to suppress the bell while keeping the countdown visible.
|
|
73
|
+
|
|
74
|
+
Redirected output contains separate start and completion (or cancellation)
|
|
75
|
+
lines without terminal control characters. Use `--live` to force in-place
|
|
76
|
+
updates through a pipe, for example `pypomo 5m --live | lolcat`. The receiving
|
|
77
|
+
program must support terminal control sequences and flush output promptly.
|
|
78
|
+
Ctrl+C cancels the timer with exit status 130. Invalid input exits with status 2.
|
|
79
|
+
On POSIX systems, SIGTERM and SIGHUP also cancel the timer, with exit statuses
|
|
80
|
+
143 and 129 respectively. On Windows, Ctrl+Break cancels the same way, with
|
|
81
|
+
exit status 149. A broken output pipe exits quietly with status 141.
|
|
82
|
+
If stdout is unavailable, the timer runs silently to its deadline.
|
|
83
|
+
|
|
84
|
+
## Pausing
|
|
85
|
+
|
|
86
|
+
While a timer runs in a terminal, press `p` (or Space) to pause and `p` again
|
|
87
|
+
to resume; the paused time is added back to the timer. Press `q` to cancel,
|
|
88
|
+
exactly like Ctrl+C. Keys work whenever standard input is the terminal and
|
|
89
|
+
pypomo is the foreground job, including with redirected output, where the log
|
|
90
|
+
gains `paused at …` and `resumed at …` lines. Keys are read immediately and are
|
|
91
|
+
not echoed; the terminal's normal input mode is restored on exit.
|
|
92
|
+
|
|
93
|
+
The timer uses a monotonic clock, so changing the system clock does not change
|
|
94
|
+
the countdown. Ctrl+Z does not pause it: stopped process time still counts.
|
|
95
|
+
Use `p` to pause.
|
|
96
|
+
Laptop-suspend behavior depends on the platform's monotonic clock; pypomo does
|
|
97
|
+
not currently normalize it or guarantee that suspended time counts. Ordinary
|
|
98
|
+
sleep overshoot does not accumulate into display drift, though long scheduling
|
|
99
|
+
stalls can still skip displayed seconds.
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
Create `~/.config/pypomo/config.toml` (or `$XDG_CONFIG_HOME/pypomo/config.toml`
|
|
104
|
+
when set).
|
|
105
|
+
Relative `XDG_CONFIG_HOME` values are ignored. On Windows the default remains
|
|
106
|
+
`%USERPROFILE%\.config\pypomo`. UTF-8 files with or without a BOM are accepted.
|
|
107
|
+
TOML supports comments and sections:
|
|
108
|
+
|
|
109
|
+
```toml
|
|
110
|
+
default_units = "minutes"
|
|
111
|
+
|
|
112
|
+
[timers]
|
|
113
|
+
break = "15m"
|
|
114
|
+
lunch = "30m"
|
|
115
|
+
deep-work = "1h30m"
|
|
116
|
+
|
|
117
|
+
[pretty]
|
|
118
|
+
enabled = false # Use --pretty to enable for a single run
|
|
119
|
+
|
|
120
|
+
[pretty.bar]
|
|
121
|
+
enabled = true
|
|
122
|
+
width = 20
|
|
123
|
+
filled = "█"
|
|
124
|
+
empty = "░"
|
|
125
|
+
|
|
126
|
+
[pretty.colors]
|
|
127
|
+
enabled = true
|
|
128
|
+
label = "cyan"
|
|
129
|
+
remaining = "white"
|
|
130
|
+
bar = "green"
|
|
131
|
+
complete = "green"
|
|
132
|
+
cancelled = "yellow"
|
|
133
|
+
paused = "yellow" # paused and ready-to-start lines
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Existing `config.json` files remain supported with the same settings as nested
|
|
137
|
+
JSON objects. If both files exist, `config.toml` takes precedence; files are not
|
|
138
|
+
merged. Use `--config PATH` to explicitly select a `.toml` or `.json` file.
|
|
139
|
+
An invalid TOML config reports an error rather than silently falling back to JSON.
|
|
140
|
+
|
|
141
|
+
With this config, `pypomo 15` means 15 minutes, `pypomo break` means 15 minutes,
|
|
142
|
+
and `pypomo pomo` runs the default Pomodoro routine. Explicit units always take precedence.
|
|
143
|
+
`default_units` accepts `seconds`, `minutes`, `hours`, `days`, or `s`, `m`, `h`, `d`.
|
|
144
|
+
Without it, bare numbers are rejected. Configured durations are strings and
|
|
145
|
+
use the same syntax and defaults as CLI durations.
|
|
146
|
+
|
|
147
|
+
Names are case-sensitive, start with a letter, and contain only letters,
|
|
148
|
+
digits, underscores, or hyphens. Missing settings retain built-in defaults.
|
|
149
|
+
No config is required; an explicitly selected config must exist.
|
|
150
|
+
`pomo` is reserved for the default Pomodoro routine. For a single focus timer,
|
|
151
|
+
use `pypomo 25m` or define a timer named `focus`.
|
|
152
|
+
|
|
153
|
+
## Pomodoro routines
|
|
154
|
+
|
|
155
|
+
`pypomo pomo` runs four 25-minute focus sessions, with 5-minute short breaks
|
|
156
|
+
between them and a 15-minute long break after the last focus session. Phases
|
|
157
|
+
advance automatically by default; the routine finishes after the long break
|
|
158
|
+
rather than repeating indefinitely. Ctrl+C or `q` cancels the entire routine.
|
|
159
|
+
|
|
160
|
+
To wait for you between phases, set `auto_start = false` or pass
|
|
161
|
+
`--no-auto-start` (`--auto-start` overrides a config that disables it). After
|
|
162
|
+
each phase ends, the display shows `sprint.pomo · short break 1/3 · ready ·
|
|
163
|
+
press Enter to start`, and the next phase begins when you press Enter. Waiting
|
|
164
|
+
time does not count against the next phase; `q` or Ctrl+C cancels instead.
|
|
165
|
+
Without a terminal keyboard, pypomo reads a line from standard input instead,
|
|
166
|
+
and starts immediately if standard input is closed or exhausted, so unattended
|
|
167
|
+
runs never block. The flags have no effect on single timers.
|
|
168
|
+
|
|
169
|
+
Customize the default or add named routines in your config:
|
|
170
|
+
|
|
171
|
+
```toml
|
|
172
|
+
[pomo]
|
|
173
|
+
focus = "25m"
|
|
174
|
+
short_break = "5m"
|
|
175
|
+
long_break = "15m"
|
|
176
|
+
rounds = 4
|
|
177
|
+
auto_start = true # false: wait for Enter before each phase after the first
|
|
178
|
+
|
|
179
|
+
[pomodoros.sprint]
|
|
180
|
+
focus = "15m"
|
|
181
|
+
short_break = "3m"
|
|
182
|
+
long_break = "10m"
|
|
183
|
+
rounds = 3
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
pypomo pomo --pretty
|
|
188
|
+
pypomo sprint.pomo --pretty
|
|
189
|
+
pypomo --list
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`rounds` is a positive integer counting focus sessions. `auto_start` is a boolean. A one-round routine
|
|
193
|
+
has one focus session followed by a long break, with no short break.
|
|
194
|
+
The maximum is 1,000 focus rounds per routine.
|
|
195
|
+
Durations must be positive strings, using the same duration syntax and
|
|
196
|
+
`default_units` as ordinary timers. Omitted default settings use built-in
|
|
197
|
+
values; custom routines inherit omitted settings from the configured `[pomo]`.
|
|
198
|
+
Routine names follow the same naming rules as timers. A timer `sprint` and a
|
|
199
|
+
routine `sprint.pomo` can coexist.
|
|
200
|
+
|
|
201
|
+
The display includes the routine name, phase and round, such as
|
|
202
|
+
`sprint.pomo · focus 1/3`. Live output stays on one line across phase changes;
|
|
203
|
+
the progress bar measures the current phase and resets at each transition.
|
|
204
|
+
The terminal bell rings when each phase ends. Redirected output records the
|
|
205
|
+
start and completion of each phase on separate lines.
|
|
206
|
+
|
|
207
|
+
Preview a routine's phases and total duration without starting it:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
pypomo pomo --preview
|
|
211
|
+
pypomo sprint.pomo --preview
|
|
212
|
+
pypomo 1h30m --preview
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The total includes all focus sessions and breaks, including the final long
|
|
216
|
+
break. The built-in routine totals `02:10:00`. A final line estimates the local
|
|
217
|
+
end time if the timer started now, such as `Ends: 14:35 if started now`, dated
|
|
218
|
+
when it falls on a later day. It does not account for pauses, and routines that
|
|
219
|
+
wait between phases say so. `--auto-start` and `--no-auto-start` apply to the
|
|
220
|
+
preview. `--preview` also supports named
|
|
221
|
+
timers and explicit durations. It cannot be combined with `--list`.
|
|
222
|
+
`--silent` suppresses bells for every phase of a running routine.
|
|
223
|
+
|
|
224
|
+
For compatibility, a legacy `[timers] pomo = "25m"` setting (or its JSON
|
|
225
|
+
equivalent) supplies the default routine's focus duration. An explicit
|
|
226
|
+
`[pomo] focus` takes precedence. **The `pomo` command now runs a complete
|
|
227
|
+
routine, even with a legacy config**, rather than a single focus timer.
|
|
228
|
+
JSON supports the same structure using `pomo` and `pomodoros` objects.
|
|
229
|
+
|
|
230
|
+
## Pretty output
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
pypomo pomo --pretty
|
|
234
|
+
pypomo pomo --no-pretty
|
|
235
|
+
pypomo 5m --pretty --live
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`--pretty` enables the configured bar and theme; `--no-pretty` overrides
|
|
239
|
+
`pretty.enabled = true`. Without a flag, the config decides (default: false).
|
|
240
|
+
The progress bar fills as time elapses and remains visible on completion or
|
|
241
|
+
cancellation. Disable `pretty.bar.enabled` for colors only, or disable
|
|
242
|
+
`pretty.colors.enabled` for a bar without colors. Disable both to retain plain
|
|
243
|
+
text. Each styled segment resets its color so it does not affect the shell prompt.
|
|
244
|
+
|
|
245
|
+
Bar width is 1–80 characters. Filled and empty glyphs must each be a single
|
|
246
|
+
narrow printable character (for example `#` and `-`, or `█` and `░`); spaces
|
|
247
|
+
are allowed, but emoji, wide characters, combining marks and control characters
|
|
248
|
+
are rejected. The bar shrinks or disappears when the terminal is narrow;
|
|
249
|
+
long labels are truncated to preserve the status, and extremely narrow displays
|
|
250
|
+
truncate the status as well. Available colors are
|
|
251
|
+
`black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, and `default`
|
|
252
|
+
(the terminal's default foreground). Actual shades follow your terminal palette.
|
|
253
|
+
|
|
254
|
+
Styling applies to terminal output or output forced live with `--live`.
|
|
255
|
+
Redirected output remains plain without `--live`, even with `--pretty`.
|
|
256
|
+
For coloring with an external program, use `pypomo 5m --live | lolcat`;
|
|
257
|
+
add `--no-pretty` if styling is enabled in your config.
|
|
258
|
+
|
|
259
|
+
A nonempty `NO_COLOR` disables colors while preserving the progress bar.
|
|
260
|
+
`TERM=dumb` uses plain start/end lines, including when `--live` is supplied.
|
|
261
|
+
Output uses readable ASCII separators and bar glyphs when the stream's encoding
|
|
262
|
+
cannot represent the configured characters. Other unencodable text is escaped.
|
|
263
|
+
Live rendering requires a terminal that understands ANSI control sequences.
|
|
264
|
+
|
|
265
|
+
## Hooks
|
|
266
|
+
|
|
267
|
+
pypomo does the timing and leaves notifications, sounds, and logging to other
|
|
268
|
+
programs. A hook is a command run when a timer event happens:
|
|
269
|
+
|
|
270
|
+
```toml
|
|
271
|
+
[hooks]
|
|
272
|
+
end = ["notify-send", "pypomo", "Time is up"]
|
|
273
|
+
pause = "playerctl pause"
|
|
274
|
+
resume = "playerctl play"
|
|
275
|
+
any = 'echo "$(date -Is) $PYPOMO_EVENT $PYPOMO_LABEL" >> ~/.local/state/pomodoro.log'
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Events are `start`, `end`, `pause`, `resume`, `cancel`, and `wait`; an `any`
|
|
279
|
+
hook runs for every event, after the event's own hook. Each phase of a routine
|
|
280
|
+
has its own `start` and `end`. `wait` happens when a routine with
|
|
281
|
+
`auto_start = false` begins waiting for Enter before a phase, so a hook can say
|
|
282
|
+
"break ready". Quitting while waiting sends `cancel` for that phase.
|
|
283
|
+
A list runs the program directly; a string runs through the system shell
|
|
284
|
+
(`sh` on POSIX, `cmd` on Windows), so shell variables, pipes, and redirection work.
|
|
285
|
+
Add hooks for a single run with `--hook CMD` (repeatable, shell syntax,
|
|
286
|
+
equivalent to an extra `any` hook), or ignore configured hooks with `--no-hooks`.
|
|
287
|
+
|
|
288
|
+
| Variable | Value |
|
|
289
|
+
| --- | --- |
|
|
290
|
+
| `PYPOMO_EVENT` | `start`, `end`, `pause`, `resume`, `cancel`, or `wait` |
|
|
291
|
+
| `PYPOMO_TIMER` | the timer as given: `25m`, `tea`, `sprint.pomo` |
|
|
292
|
+
| `PYPOMO_LABEL` | the displayed label: `sprint.pomo · focus 1/3` |
|
|
293
|
+
| `PYPOMO_PHASE` | `timer`, `focus`, `short_break`, or `long_break` |
|
|
294
|
+
| `PYPOMO_ROUND`, `PYPOMO_ROUNDS` | round and total rounds; empty for single timers |
|
|
295
|
+
| `PYPOMO_DURATION` | the timer or phase length in seconds |
|
|
296
|
+
| `PYPOMO_REMAINING` | whole seconds left (`0` at `end`) |
|
|
297
|
+
| `PYPOMO_LAST` | `1` for a single timer or a routine's final phase, else `0` |
|
|
298
|
+
|
|
299
|
+
Hooks never affect the timer. pypomo starts them in the background without
|
|
300
|
+
waiting, so a slow hook does not delay the countdown, and hooks for consecutive
|
|
301
|
+
events may overlap. A hook's exit status is ignored. A command that cannot be
|
|
302
|
+
started produces one warning on stderr and the timer continues. Hooks receive no
|
|
303
|
+
standard input and their standard output is discarded, keeping pypomo's output
|
|
304
|
+
clean for pipes; standard error stays connected so failures are visible. Each
|
|
305
|
+
hook runs in its own session or process group, so Ctrl+C or closing the
|
|
306
|
+
terminal does not interrupt a hook that is already running.
|
|
307
|
+
`--preview` and `--list` never run hooks.
|
|
308
|
+
|
|
309
|
+
Example: a desktop notification only when a whole routine finishes:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
pypomo pomo --hook '[ "$PYPOMO_EVENT" = end ] && [ "$PYPOMO_LAST" = 1 ] && notify-send "Routine done"'
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## JSON lines output
|
|
316
|
+
|
|
317
|
+
`--format jsonl` replaces the countdown display with one JSON object per event
|
|
318
|
+
on standard output, for `jq`, scripts, and logs:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
pypomo pomo --format jsonl | jq -r '"\(.time) \(.event) \(.label)"'
|
|
322
|
+
pypomo pomo --format jsonl >> ~/.local/state/pomodoro.jsonl
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{"event": "end", "timer": "pomo", "label": "pomo \u00b7 focus 1/4", "phase": "focus", "round": 1, "rounds": 4, "duration": 1500, "remaining": 0, "last": false, "time": "2026-11-07T14:25:00-05:00"}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Events and fields match the [hook variables](#hooks), plus `time`, the local
|
|
330
|
+
time with its UTC offset. `round` and `rounds` are `null` for single timers.
|
|
331
|
+
Each line is written and flushed as the event happens, and the output is ASCII
|
|
332
|
+
(other characters use JSON escapes), so it is valid in any locale.
|
|
333
|
+
Nothing else is written to standard output: no countdown, prompts, or bell.
|
|
334
|
+
Pause keys still work, and their events appear in the stream; a routine that
|
|
335
|
+
waits between phases emits `wait` and starts on Enter (or a line of input).
|
|
336
|
+
Hooks run alongside JSON output. The text display remains the default
|
|
337
|
+
(`--format text`). `--format jsonl` cannot be combined with `--list`,
|
|
338
|
+
`--preview`, or `--live`. Exit statuses are unchanged, so a stream that ends
|
|
339
|
+
in `cancel` is followed by exit status 130.
|
|
340
|
+
|
|
341
|
+
## Development
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
uv sync # create .venv with pypomo (editable) and ruff
|
|
345
|
+
uv run python -m unittest discover -s tests -v
|
|
346
|
+
uv run pypomo 1s
|
|
347
|
+
uv run ruff check src tests
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Without uv, the tests need no dependencies:
|
|
351
|
+
`PYTHONPATH=src python -m unittest discover -s tests -v`.
|
|
352
|
+
|
|
353
|
+
## Releasing
|
|
354
|
+
|
|
355
|
+
The package is named `pypomodo` on PyPI (`pypomo` was too similar to an
|
|
356
|
+
existing project); the command and the Python package remain `pypomo`.
|
|
357
|
+
To release, bump `version` in `pyproject.toml`, date the changelog section,
|
|
358
|
+
merge, then tag the merge commit and push the tag:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
git tag -a v0.4.0 -m "pypomo 0.4.0" && git push origin v0.4.0
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
The `Publish` workflow checks that the tag matches the package version, builds
|
|
365
|
+
and tests the wheel, and uploads it to PyPI with trusted publishing from the
|
|
366
|
+
`pypi` environment. No API token is stored in the repository.
|
|
367
|
+
|
|
368
|
+
## Continuous integration
|
|
369
|
+
|
|
370
|
+
GitHub Actions runs on pushes to `main` and on pull requests, with a manual run
|
|
371
|
+
option. It tests Python 3.12 and 3.15 on Linux, macOS, and Windows, and 3.13 and
|
|
372
|
+
3.14 on Linux. Each job lints the code, builds the source distribution and
|
|
373
|
+
wheel, installs the wheel, runs the test suite against the installed package,
|
|
374
|
+
and checks `pypomo --help`, `pypomo --list`, and a one-second timer.
|
|
375
|
+
Linux PTY tests cover live output, Ctrl+C, pause/resume keys, and waiting between phases; POSIX signal tests run on Linux and
|
|
376
|
+
macOS. Encoding tests run on every platform. Terminal appearance and interactive
|
|
377
|
+
Windows console behavior still benefit from manual testing.
|