klok-cli 1.0.0__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.
klok_cli-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rajat Singh
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.
@@ -0,0 +1,466 @@
1
+ Metadata-Version: 2.4
2
+ Name: klok-cli
3
+ Version: 1.0.0
4
+ Summary: A batteries-included command-line time tracker with a plaintext backend.
5
+ Author: Rajat Singh
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/technomad641/klok
8
+ Project-URL: Repository, https://github.com/technomad641/klok
9
+ Project-URL: Issues, https://github.com/technomad641/klok/issues
10
+ Keywords: time-tracking,cli,timesheet,pomodoro,productivity
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Office/Business
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Dynamic: license-file
26
+
27
+ # klok
28
+
29
+ [![CI](https://github.com/technomad641/klok/actions/workflows/ci.yml/badge.svg)](https://github.com/technomad641/klok/actions/workflows/ci.yml)
30
+ [![PyPI](https://img.shields.io/pypi/v/klok-cli.svg)](https://pypi.org/project/klok-cli/)
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
32
+
33
+ A command-line time tracker that pulls together the features people actually
34
+ use from Timetrap, Timewarrior, Watson, utt, Bartib, Helm and friends — plus
35
+ breathing, sitting and mood check-ins — in one tool, with a plaintext backend
36
+ and no dependencies beyond Python 3.9.
37
+
38
+ ```console
39
+ $ klok start acme +api -n "auth endpoint"
40
+ Starting acme +api at 2026-08-31 09:00 [7d2d3734]
41
+
42
+ $ klok status
43
+ acme +api 1h 30m (started 09:00, an hour ago)
44
+ sheet default | today 1h 30m | id 7d2d3734
45
+
46
+ $ klok stop
47
+ Stopped acme +api after 1h 30m [7d2d3734]
48
+
49
+ $ klok report :week
50
+ Mon 31 August 2026 -> Sun 06 September 2026
51
+
52
+ acme - 3h 00m
53
+ +api 1h 30m
54
+ +docs 45m
55
+ +review 45m
56
+
57
+ internal - 55m
58
+ +meeting 40m
59
+ +email 15m
60
+
61
+ Total: 3h 55m
62
+ ```
63
+
64
+ ## Install
65
+
66
+ ```sh
67
+ pip install klok-cli # or: pipx install klok-cli
68
+ ```
69
+
70
+ The PyPI package is `klok-cli` ("klok" itself is an unrelated, empty
71
+ placeholder someone else registered) but the installed command is plain
72
+ `klok`. No dependencies.
73
+
74
+ To install from a checkout instead of PyPI:
75
+
76
+ ```sh
77
+ pip install --user . # or: pipx install .
78
+ ```
79
+
80
+ It also runs straight from a checkout with no install at all:
81
+
82
+ ```sh
83
+ PYTHONPATH=. python3 -m klok status
84
+ ```
85
+
86
+ ## Why this exists
87
+
88
+ Each of the well-loved terminal time trackers is missing one thing the others
89
+ have. Timewarrior has the best interval surgery (`join`, `split`, `fill`,
90
+ `gaps`) but no pomodoro. Watson has the nicest project/tag model but no
91
+ timesheets. Timetrap has sheets and a pile of export formats but no tags. utt
92
+ has the "log it after the fact" flow nobody else does. Helm and arttime are
93
+ lovely timers that record nothing.
94
+
95
+ klok takes the useful parts of each and keeps a single, boring data file.
96
+
97
+ ### Feature map
98
+
99
+ | From | Feature | In klok |
100
+ | --- | --- | --- |
101
+ | **Timetrap** | `t in` / `t out` | `klok in` / `klok out` (aliases of `start`/`stop`) |
102
+ | | Timesheets you switch between | `klok sheet NAME`, `klok sheets`, `klok sheet -` |
103
+ | | `t archive` | `klok archive :lastmonth` |
104
+ | | Retroactive `--at` | `klok start --at 9am`, `klok stop --at -15m` |
105
+ | | Formatters (csv, ical, json, ledger) | `klok export -f csv\|ical\|json\|ledger\|timeclock\|md\|tsv` |
106
+ | | Rounding | `--round 15`, or `general.round` in config |
107
+ | **Timewarrior** | `start` / `stop` / `continue` | `klok start` / `stop` / `continue` |
108
+ | | Tags on intervals | `klok start acme +api +urgent`, `klok tag ID +x -y` |
109
+ | | `annotate` | `klok annotate ID "text"` |
110
+ | | `summary` with daily subtotals | `klok summary :week` |
111
+ | | Range hints (`:week`, `:lastmonth`) | same hints, plus `last 7 days`, `since monday` |
112
+ | | `join` / `split` / `lengthen` / `shorten` / `move` / `fill` | all present, same names |
113
+ | | `gaps` | `klok gaps :day --min 15m` |
114
+ | | Exclusions (working hours, days off) | `exclusions.hours`, `exclusions.days`, `exclusions.holidays` |
115
+ | | `undo` | `klok undo` (100 levels deep) |
116
+ | | Charts | `klok chart --by day\|project\|tag\|sheet\|punchcard` |
117
+ | **Watson** | `project +tag` model | same |
118
+ | | `status`, `cancel`, `restart`, `log`, `report` | same names |
119
+ | | `rename project\|tag OLD NEW` | `klok rename` (also renames sheets) |
120
+ | | `projects`, `tags` listings | `klok projects`, `klok tags` (with totals) |
121
+ | | `edit` in `$EDITOR` | `klok edit` — or change fields inline with flags |
122
+ | | `aggregate` daily totals | `klok chart --by day`, `klok stats` |
123
+ | **utt** | Name the activity *after* doing it | `klok add acme +api` closes the open stretch |
124
+ | | `utt stretch` | `klok stretch` |
125
+ | | Break activities | tag them and filter with `-T break` |
126
+ | **Bartib** | Plaintext, hand-editable backend | JSONL, one entry per line |
127
+ | | `continue`, `last`, `change` | `klok continue`, `klok log --limit`, `klok switch` |
128
+ | | `sanity` check | `klok check` (overlaps, negatives, runaway timers) |
129
+ | **Helm / arttime** | Minimal countdown timer | `klok timer 25m --big --art coffee` |
130
+ | | Desktop notification + bell | automatic; `focus.notify`, `focus.bell` |
131
+ | **Focusd** | Focus sessions | `klok pomodoro acme +deep --rounds 4` — recorded as real entries |
132
+ | **feeling** | Mood tracking over time | `klok checkin --mood 4 --energy 3`, `klok mood :month` |
133
+ | *(new)* | Guided breathing | `klok breathe box`, `klok breathe 4-7-8 --for 5m` |
134
+ | *(new)* | Timed sitting with bells | `klok meditate 20m --interval-bell 5m` |
135
+ | *(new)* | Practice streaks | `klok mindful :month` |
136
+
137
+ ## Finding your way around
138
+
139
+ `klok --help` doesn't dump all 70-odd commands and aliases in one
140
+ alphabetical wall — it opens with a five-line quick start, then groups the
141
+ rest (Tracking, Fixing entries, Reporting, Sheets & config, Focus &
142
+ mindfulness) so the shape of the tool is visible at a glance. Each
143
+ command's own `--help` still has the full detail — `klok start --help`,
144
+ `klok breathe --help`, and so on.
145
+
146
+ ```console
147
+ $ klok --help
148
+ Quick start:
149
+ klok start acme +api start tracking, tag it
150
+ klok stop stop the clock
151
+ klok status what's running right now
152
+ klok report :week where the week went
153
+ klok breathe sixty seconds, box breathing
154
+
155
+ Tracking:
156
+ start Start tracking a project now (or at a given time). (in, on)
157
+ stop Stop the running entry. (out, off)
158
+ ...
159
+ ```
160
+
161
+ A typo in a command name gets a one-line correction instead of the default
162
+ list of all seventy choices:
163
+
164
+ ```console
165
+ $ klok statuss
166
+ klok: unknown command 'statuss' - did you mean 'status'?
167
+ Run `klok --help` for the full list.
168
+ ```
169
+
170
+ And a brand new install says what to do next, rather than just "nothing
171
+ running":
172
+
173
+ ```console
174
+ $ klok status
175
+ Nothing tracked yet.
176
+ Try `klok start acme +api` to begin, or `klok --help` for the full picture.
177
+ ```
178
+
179
+ ## Commands
180
+
181
+ ### Tracking
182
+
183
+ ```sh
184
+ klok start acme +api -n "auth endpoint" # aliases: in, on
185
+ klok start acme --at 9am # retroactive start
186
+ klok stop --at -15m # stopped a quarter hour ago
187
+ klok cancel # discard the running entry
188
+ klok status # aliases: now, current
189
+ klok switch internal +meeting # stop and start at one instant
190
+ klok continue # same project and tags as last time
191
+ klok track 09:00 to 11:30 acme +api # record a finished interval
192
+ klok track --from 09:00 -d 45m acme # ...or give a length
193
+ klok add internal +email # close the gap since the last entry
194
+ klok stretch # pull an entry back to the previous one
195
+ ```
196
+
197
+ `start` stops whatever was running (set `general.autostop = false`, or pass
198
+ `--no-stop`, to make that an error instead).
199
+
200
+ ### Fixing what you recorded
201
+
202
+ Entries are addressed by id prefix (`7d2d`), by position (`@1` is the newest,
203
+ `@2` the one before), or by `@` for "the running one, or the last one".
204
+
205
+ ```sh
206
+ klok annotate @1 "sprint planning"
207
+ klok tag @1 +urgent -meeting # + adds, - removes
208
+ klok edit @1 --set-project acme --tags api docs
209
+ klok edit @1 # opens $EDITOR with the entry as JSON
210
+ klok edit --range :day # edit a whole day at once
211
+ klok move @1 08:00 # keep the length, change the start
212
+ klok lengthen @1 30m / klok shorten @1 15m
213
+ klok fill @1 # grow until it touches its neighbours
214
+ klok split @1 --at 10:15 / --into 3
215
+ klok join 7d2d e57b # merge entries, union their tags
216
+ klok delete @1 @2
217
+ klok undo # every change is undoable
218
+ ```
219
+
220
+ ### Reporting
221
+
222
+ ```sh
223
+ klok log :week # entries day by day (aliases: display, list)
224
+ klok summary :day # dense table with daily subtotals
225
+ klok report :month --entries # totals per project, broken down by tag
226
+ klok chart :month --by day # bar chart per calendar day
227
+ klok chart :month --by punchcard # weekday x hour heat map
228
+ klok gaps :day --min 15m # untracked stretches
229
+ klok stats :month # totals, streak, busiest day
230
+ klok projects / klok tags # with totals
231
+ ```
232
+
233
+ Every report takes the same filters:
234
+
235
+ ```sh
236
+ -p, --project NAME repeatable; matches exactly, as a parent (acme matches acme.api), or as a glob
237
+ -t, --tag TAG repeatable; all listed tags must be present
238
+ -T, --exclude-tag TAG repeatable
239
+ --contains TEXT substring of project, tags or note
240
+ --sheet NAME a specific sheet
241
+ --all-sheets every sheet
242
+ -f, --format FMT text (default), json, csv, tsv, ical, ledger, timeclock, md
243
+ ```
244
+
245
+ ### Time syntax
246
+
247
+ Anywhere klok takes a time: `now`, `noon`, `midnight`, `today`, `yesterday`,
248
+ `monday`, `last friday`, `9am`, `9:30pm`, `09:30`, `-15m`, `+1h30m`,
249
+ `2 hours ago`, `2026-08-31`, `2026-08-31 09:00`, `2026-08-31T09:00:00`, or a
250
+ unix timestamp.
251
+
252
+ Durations: `90` (minutes), `45s`, `25m`, `1h30m`, `1:30`, `2 hours`, `1w`.
253
+
254
+ Ranges: `:day` `:yesterday` `:week` `:lastweek` `:month` `:lastmonth`
255
+ `:quarter` `:year` `:lastyear` `:7d` `:30d` `:all`, plus `last 7 days`,
256
+ `since monday`, `2026-08-01 to 2026-08-31`, or a bare date for that one day.
257
+ `--from` / `--to` work too.
258
+
259
+ ### Sheets
260
+
261
+ Separate timesheets, as in Timetrap — useful for keeping a client's hours apart
262
+ from your own.
263
+
264
+ ```sh
265
+ klok sheet client-x # switch
266
+ klok sheet # print the active one
267
+ klok sheet - # back to the previous one
268
+ klok sheets # list, with totals
269
+ klok archive :lastmonth # move entries to _default so they leave reports
270
+ ```
271
+
272
+ ### Focus timers
273
+
274
+ ```sh
275
+ klok timer 25m -m "Deep work" --big --art coffee
276
+ klok timer 10m -p acme -t review # also records the time
277
+ klok pomodoro acme +deep --rounds 4 # 4 x 25m work with breaks
278
+ klok pomodoro --work 50m --break 10m --no-prompt
279
+ ```
280
+
281
+ Work rounds longer than a minute are recorded as ordinary entries, so they show
282
+ up in `report` alongside everything else. Notifications go through
283
+ `notify-send`, `terminal-notifier` or `osascript` when one is available, and
284
+ always ring the terminal bell.
285
+
286
+ ### Mindfulness and meditation
287
+
288
+ Practice is tracked like everything else — but on its own sheet (`wellbeing`
289
+ by default), so a month of sitting never turns up in a client's invoice.
290
+
291
+ #### Breathing
292
+
293
+ ```sh
294
+ klok breathe # the configured default, six rounds of box breathing
295
+ klok breathe 4-7-8 # any counts: in-hold-out, or in-hold-out-hold
296
+ klok breathe calm --for 5m # a named pattern, for a length instead of a round count
297
+ klok breathe --list # what the named patterns are
298
+ klok breathe box --plan # describe the session without running it
299
+ ```
300
+
301
+ A pacer fills and empties a bar in time with the phase, naming each one and
302
+ counting it down. `Ctrl-C` stops it and still records what you did.
303
+
304
+ | Pattern | Counts | |
305
+ | --- | --- | --- |
306
+ | `box` | 4-4-4-4 | equal counts all round |
307
+ | `calm` | 4-0-6-0 | longer out-breath than in-breath |
308
+ | `coherent` | 5.5-0-5.5-0 | about five and a half breaths a minute |
309
+ | `relax` | 4-7-8-0 | the 4-7-8 count |
310
+ | `triangle` | 4-4-4-0 | no pause at the bottom |
311
+ | `even` | 5-0-5-0 | plain and symmetrical |
312
+
313
+ Custom counts work anywhere a name does: two counts are in and out
314
+ (`4-6`), three add the hold after the in-breath (`4-7-8`), four set every
315
+ phase (`5-2-7-2`).
316
+
317
+ #### Sitting
318
+
319
+ ```sh
320
+ klok meditate # mindful.default_sit, ten minutes out of the box
321
+ klok meditate 20m --interval-bell 5m
322
+ klok meditate 30m --warmup 30s # a settling bell before the sit proper
323
+ klok meditate 15m --no-guidance # bells only, no prompts
324
+ klok meditate 20m --plan # when the bells will land
325
+ ```
326
+
327
+ A progress line, a bell at the end (and at each interval), and — unless you
328
+ turn them off — a few short prompts along the way. Sits are recorded as
329
+ `mindfulness +meditation`; breathing as `mindfulness +breathing`.
330
+
331
+ #### Check-ins
332
+
333
+ ```sh
334
+ klok checkin --mood 4 --energy 3 --stress 2 after standup
335
+ klok checkin --mood 2 --at 09:15 -t monday
336
+ klok checkin # prompts for the three scales when run in a terminal
337
+ klok mood :month # the entries, plus a trend line per scale
338
+ klok mood --json
339
+ klok checkin --delete @ # or by id prefix
340
+ ```
341
+
342
+ Three optional 1–5 scales (mood, energy, stress) and a free-text note. `klok
343
+ mood` renders each scale as a sparkline with its average, and — once there are
344
+ enough days to compare — notes how mood sat on your busier days versus your
345
+ lighter ones. That is an observation about two averages, not a claim about
346
+ cause.
347
+
348
+ #### How the practice is going
349
+
350
+ ```sh
351
+ klok mindful :month # sessions, time, averages, streaks, last practice
352
+ klok mindful :year --no-chart
353
+ ```
354
+
355
+ #### Break nudge
356
+
357
+ Once a single unbroken stretch of work passes `mindful.break_after` (90
358
+ minutes by default), `klok status` says so and suggests a minute of breathing.
359
+ Set the key to empty to switch it off.
360
+
361
+ ### Configuration
362
+
363
+ ```sh
364
+ klok config list
365
+ klok config set general.round 15
366
+ klok config set exclusions.hours 09:00-18:00
367
+ klok config edit
368
+ ```
369
+
370
+ | Key | Default | Meaning |
371
+ | --- | --- | --- |
372
+ | `general.default_project` | – | project for a bare `klok start` |
373
+ | `general.week_start` | `monday` | drives `:week` and the week charts |
374
+ | `general.round` | `0` | round durations up to N minutes in reports |
375
+ | `general.time_format` | `24` | `24` or `12` hour clock |
376
+ | `general.autostop` | `true` | `start` stops what was running |
377
+ | `general.color` | `auto` | `auto`, `always`, `never` (`NO_COLOR` is honoured) |
378
+ | `general.editor` | `$VISUAL`/`$EDITOR` | editor for `klok edit` |
379
+ | `general.confirm_new_project` | `false` | refuse unknown projects without `--force` |
380
+ | `exclusions.hours` | – | e.g. `09:00-18:00`; `gaps` ignores time outside |
381
+ | `exclusions.days` | – | e.g. `saturday,sunday` |
382
+ | `exclusions.holidays` | – | comma-separated `YYYY-MM-DD` dates |
383
+ | `focus.work` / `focus.break` / `focus.long_break` / `focus.rounds` | `25m` / `5m` / `15m` / `4` | pomodoro defaults |
384
+ | `focus.notify` / `focus.bell` / `focus.track` | `true` | timer behaviour |
385
+ | `mindful.project` | `mindfulness` | project practice is recorded against |
386
+ | `mindful.sheet` | `wellbeing` | sheet practice is recorded on |
387
+ | `mindful.track` | `true` | record practice at all |
388
+ | `mindful.pattern` | `box` | default breathing pattern |
389
+ | `mindful.breath_rounds` | `6` | default number of breaths |
390
+ | `mindful.default_sit` | `10m` | default length of `klok meditate` |
391
+ | `mindful.interval_bell` | – | default interval bell during a sit |
392
+ | `mindful.warmup` | – | default settling bell |
393
+ | `mindful.guidance` | `true` | show prompts during a sit |
394
+ | `mindful.break_after` | `90m` | when `klok status` suggests a pause (empty = never) |
395
+
396
+ ### Where the data lives
397
+
398
+ ```sh
399
+ klok where
400
+ ```
401
+
402
+ Everything sits in one directory (`$KLOK_HOME`, else `$XDG_DATA_HOME/klok`,
403
+ else `~/.local/share/klok`):
404
+
405
+ ```
406
+ frames.jsonl one JSON object per line, sorted by start time
407
+ checkins.jsonl mood/energy/stress check-ins, same shape
408
+ config.ini configuration
409
+ state.json which sheet is active
410
+ undo.jsonl the last 100 states, for klok undo
411
+ ```
412
+
413
+ `frames.jsonl` is the whole database. It diffs well in git, syncs with any file
414
+ sync tool, and can be hand-edited:
415
+
416
+ ```json
417
+ {"id": "7d2d3734", "start": "2026-08-31T09:00:00+02:00", "stop": "2026-08-31T10:30:00+02:00", "project": "acme", "tags": ["api"], "note": "auth endpoint", "sheet": "default"}
418
+ ```
419
+
420
+ Writes are atomic and take a lock file, so a `klok stop` from a shell hook
421
+ cannot interleave with one from your prompt. Every mutation snapshots the
422
+ previous file first — `klok undo` walks back through them.
423
+
424
+ ### Exports
425
+
426
+ ```sh
427
+ klok export :month -f csv -o august.csv
428
+ klok export :month -f ical > august.ics
429
+ klok export :month -f timeclock | hledger -f - balance # ledger/hledger native
430
+ klok export :month -f md # paste into an invoice
431
+ klok import august.json # JSON, JSONL or CSV
432
+ ```
433
+
434
+ ### Shell completion
435
+
436
+ ```sh
437
+ source <(klok completion bash) # or zsh
438
+ klok completion fish > ~/.config/fish/completions/klok.fish
439
+ ```
440
+
441
+ ## Tests
442
+
443
+ ```sh
444
+ cd klok && PYTHONPATH=. python3 -m unittest discover -s tests -t .
445
+ ```
446
+
447
+ 170 tests covering the time parser, the store (including undo, locking,
448
+ overlaps and gaps), the breathing and sitting timers (driven by an injected
449
+ clock, so the suite stays fast), the exporters, and every command end to end.
450
+
451
+ ## What it deliberately does not do
452
+
453
+ - **No automatic screen-time or app-usage capture.** Focusd-style monitoring
454
+ needs per-platform window hooks; klok records what you tell it, plus what its
455
+ own timers observe.
456
+ - **No sync server.** `export`/`import` and a synced `frames.jsonl` cover the
457
+ ground Watson's `sync` does, without a service to run.
458
+ - **No plugin/extension API.** The JSON and CSV exports are the extension
459
+ point.
460
+ - **No health claims.** The breathing patterns and sitting timers are a
461
+ pacer and a bell. klok is a tracker, not advice, and not a substitute for
462
+ care from someone qualified to give it.
463
+
464
+ ## Licence
465
+
466
+ MIT. See [LICENSE](LICENSE).