tklr-dgraham 0.0.0rc8__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.

Potentially problematic release.


This version of tklr-dgraham might be problematic. Click here for more details.

@@ -0,0 +1,697 @@
1
+ Metadata-Version: 2.4
2
+ Name: tklr-dgraham
3
+ Version: 0.0.0rc8
4
+ Summary: Reminders Tickler / CLI and Textual UI
5
+ Author-email: Daniel Graham <dnlgrhm@gmail.com>
6
+ Requires-Python: <4.0,>=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: textual>=0.60
9
+ Requires-Dist: python-dateutil>=2.8.2
10
+ Requires-Dist: tzlocal>=5.3.1
11
+ Requires-Dist: packaging>=25.0
12
+ Requires-Dist: pydantic>=2.11.7
13
+ Requires-Dist: jinja2>=3.1.6
14
+ Requires-Dist: click>=8.2.1
15
+ Requires-Dist: lorem>=0.1.1
16
+ Requires-Dist: readchar>=4.2.1
17
+ Requires-Dist: numpy>=2.3.3
18
+ Requires-Dist: pyperclip>=1.11.0
19
+ Requires-Dist: tomlkit>=0.13.3
20
+ Provides-Extra: dev
21
+ Requires-Dist: lorem>=0.1.1; extra == "dev"
22
+
23
+ <table>
24
+ <tr>
25
+ <td style="vertical-align: top; width: 75%;">
26
+ <h1>tklr</h1>
27
+ <p>
28
+ The term <em>tickler file</em> originally referred to a file system for reminders which used 12 monthly files and 31 daily files. <em>Tklr</em>, pronounced "tickler", is a digital version that ranks tasks by urgency and generally facilitates the same purpose - managing what you need to know quickly and easily. It supports the entry format and projects of <strong>etm</strong>, the datetime parsing and recurrence features of <strong>dateutil</strong> and provides both command line (Click) and graphical user interfaces (Textual).
29
+ </p>
30
+ <p>Make the most of your time!</p>
31
+ </td>
32
+ <td style="width: 25%; vertical-align: middle;">
33
+ <img src="https://raw.githubusercontent.com/dagraham/tklr-dgraham/master/tklr_logo.avif"
34
+ alt="tklr logo" title="Tklr" style="max-width: 360px; width: 100%; height: auto;">
35
+ </td>
36
+ </tr>
37
+ </table>
38
+
39
+ <p><a href="https://github.com/dagraham/tklr-dgraham/discussions">Join the conversation</a></p>
40
+
41
+ ❌ Preliminary and incomplete. This notice will be removed when the code is ready for general use.
42
+
43
+ ## Overview
44
+
45
+ _tklr_ began life in 2013 as _etm-qt_ sporting a gui based on _Qt_. The intent was to provide an app supporting GTD (David Allen's Getting Things Done) and exploiting the power of python-dateutil. The name changed to _etmtk_ in 2014 when _Tk_ replaced _Qt_. Development of _etmtk_ continued until 2019 when name changed to _etm-dgraham_, to honor the PyPi naming convention, and the interface changed to a terminal based one based on _prompt_toolkit_. In 2025 the name changed to "tklr", the database to SQLite3 and the interface to Click (CLI) and Textual. Features have changed over the years but the text based interface and basic format of the reminders has changed very little. The goal has always been to be the Swiss Army Knife of tools for managing reminders.
46
+
47
+ ## Reminders
48
+
49
+ _tklr_ offers a simple way to manage your events, tasks and other reminders.
50
+
51
+ Rather than filling out fields in a form to create or edit reminders, a simple text-based format is used. Each reminder in _tklr_ begins with a _type character_ followed by the _subject_ of the reminder and then, perhaps, by one or more _@key value_ pairs to specify other attributes of the reminder. Mnemonics are used to make the keys easy to remember, e.g, @s for scheduled datetime, @l for location, @d for details and so forth.
52
+
53
+ The 4 types of reminders in _tklr_ with their associated type characters:
54
+
55
+ | type | char |
56
+ | ------- | ---- |
57
+ | event | \* |
58
+ | task | ~ |
59
+ | project | ^ |
60
+ | note | % |
61
+ | draft | ? |
62
+
63
+ ### examples
64
+
65
+ - A _task_ (~) reminder to pick up milk.
66
+
67
+ ~ pick up milk
68
+
69
+ - An _event_ (\*) reminder to have lunch with Ed starting (@s) next Tuesday at 12pm with an extent (@e) of 1 hour and 30 minutes, i.e., lasting from 12pm until 1:30pm.
70
+
71
+ * Lunch with Ed @s tue 12p @e 1h30m
72
+
73
+ - A _note_ (%) reminder of a favorite Churchill quotation with the quote itself as the details (@d).
74
+
75
+ % Give me a pig - Churchill @d Dogs look up at
76
+ you. Cats look down at you. Give me a pig - they
77
+ look you in the eye and treat you as an equal.
78
+
79
+ The _subject_, "Give me a pig - Churchill" in this example, follows the type character and is meant to be brief - analogous to the subject of an email. The optional _details_ follows the "@d" and is meant to be more expansive - analogous to the body of an email.
80
+
81
+ - A _project_ (^) reminder to build a dog house with component tasks (@~).
82
+
83
+ ^ Build dog house
84
+ @~ pick up materials &r 1 &e 4h
85
+ @~ cut pieces &r 2: 1 &e 3h
86
+ @~ assemble &r 3: 2 &e 2h
87
+ @~ sand &r 4: 3 &e 1h
88
+ @~ paint &r 5: 4 &e 4h
89
+
90
+ The "&r X: Y" entries set "X" as the label for the task and the task labeled "Y" as a prerequisite. E.g., "&r 3: 2" establishes "3" as the label for assemble and "2" (cut pieces) as a prerequisite. The "&e _extent_" entries give estimates of the times required to complete the various tasks.
91
+
92
+ - A _draft_ reminder, **?**: meet Alex for coffee Friday.
93
+
94
+ ? Coffee with Alex @s fri @e 1h
95
+
96
+ This can be changed to an event when the details are confirmed by replacing the **?** with an **\*** and adding the time to `@s`. This _draft_ will appear highlighted on the current day until you make the changes to complete it.
97
+
98
+ ### Simple repetition
99
+
100
+ - An appointment (_event_) for a dental exam and cleaning at 2pm on Feb 5 and then again, **@+**, at 9am on Sep 3.
101
+
102
+ * dental exam and cleaning @s 2p feb 5 @e 45m @+ 9am Sep 3
103
+
104
+ - A reminder (_task_) to fill the bird feeders starting Friday of the current week and repeat after an _offset_ of 4 days from datetime of the previous completion.
105
+
106
+ ~ fill bird feeders @s fri @o 4d
107
+
108
+ ### More complex repetition
109
+
110
+ - The full flexibility of the superb Python _dateutil_ package is supported. Consider, for example, a reminder for Presidential election day which starts in November, 2020 and repeats every 4 years on the first Tuesday after a Monday in November (a Tuesday whose month day falls between 2 and 8 in the 11th month). In _tklr_, this event would be
111
+
112
+ * Presidential election day @s nov 1 2020 @r y &i 4
113
+ &w TU &m 2, 3, 4, 5, 6, 7, 8 &M 11
114
+
115
+ ## Views
116
+
117
+ Each of the views listed below can be opened by entering the first letter of the view's name, e.g., pressing `A` (`shift+a`) will open _Agenda View_.
118
+
119
+ These views involve vertical lists of reminders, each row beginning with a tag from "a", "b", ..., "z", followed by the pertinent details of the reminder. When necessary, lists are split into pages so that no more than 26 reminders appear on any one page. The left and right cursor keys are used to move back and forth between pages.
120
+
121
+ On any page, pressing the key corresponding to a tag will open a display with all the details of the corresponding reminder. When the details of reminder are being displayed, various commands are available to modify the reminder. E.g., `,e` (comma followed by e) to edit the reminder or `,d` to delete the reminder are two examples. Additionally, the key corresponding to the tag of another reminder will switch the details display to that reminder, `escape` will close the details display and entering the upper case letter corresponding to another view will open that view.
122
+
123
+ The point of using tags to select and display reminders in this way is to minimize key presses. Any reminder on a page can be selected and its details displayed with a single key press.
124
+
125
+ ### Weeks
126
+
127
+ Scheduled Reminders for the Week with busy times displayed by a leading _busy bar_.
128
+
129
+ <p align="center">
130
+ <img src="https://raw.githubusercontent.com/dagraham/tklr-dgraham/master/screenshots/week_screenshot.svg"
131
+ alt="Agenda view in Tklr" width="540">
132
+ <br>
133
+ <em>Weeks View: busy bar and scheduled reminders for week</em>
134
+ </p>
135
+
136
+ There are 5 cells in the _busy bar_ for each week day. The first (furthest left) displays a yellow square if an _all day event_ such as a holiday is scheduled for that date. The remaining 4 cells correspond to the 6-hour periods during the day:
137
+
138
+ - 00:00-05:59 _night_
139
+ - 06:00-11:59 _morning_
140
+ - 12:00-17:59 _afternoon_
141
+ - 18:00-23:59 _evening_
142
+
143
+ If the busy period for an event overlaps one or more of these periods then those periods are tentatively colored green. If the busy periods for two events overlap within one or more periods, then those periods are colored red to indicate the conflict. E.g., the red _evening_ cell for Monday, reflects the conflict between the reminders tagged _d_ and _e_
144
+
145
+ Pressing _g_ displays the details for that reminder.
146
+
147
+ <p align="center">
148
+ <img src="https://raw.githubusercontent.com/dagraham/tklr-dgraham/master/screenshots/week_with_details_screenshot.svg"
149
+ alt="Agenda view in Tklr" width="540">
150
+ <br>
151
+ <em>Weeks View: details for the reminder tagged g</em>
152
+ </p>
153
+
154
+ ### Agenda
155
+
156
+ The next three days of _events_ with _notices_ and _drafts_ followed by tasks ordered by their urgency.
157
+
158
+ The first day will always include any _notice_ or _drafts_ in addition to any scheduled events. In this case the reminber tagged _b_ indicates that there is an event beginning in 11 days (`+11d`) whose subject begins with "Quisquam" and which has a _notice_ entry, "@n INTERVAL" in which `INTERVAL >= 11d`. This notice of the upcoming event will be displayed on the first day (current date) of Agenda View each day until the day of the event.
159
+
160
+ There is also a draft entry displayed in red. This is simply a reminder whose item type is "?". This is used to flag a reminder as incomplete as would be the case, e.g., if a final datetime for the event had not yet been finalized. Draft reminders are displayed on the current, first day in Agenda view until the item type is changed.
161
+
162
+ <p align="center">
163
+ <img src="https://raw.githubusercontent.com/dagraham/tklr-dgraham/master/screenshots/events_screenshot.svg"
164
+ alt="Agenda view in Tklr" width="540">
165
+ <br>
166
+ <em>Agenda View: Upcoming events and tasks ordered by urgency</em>
167
+ </p>
168
+
169
+ ### Next
170
+
171
+ The next instance of every scheduled reminder
172
+
173
+ ### Last
174
+
175
+ The last instance of every scheduled reminder
176
+
177
+ ### Find
178
+
179
+ Reminders whose subject or detail entries contain a case-insensitive match for an entered expression
180
+
181
+ ### Bins
182
+
183
+ Reminders listed by their assigned _bins_
184
+
185
+ ## Details
186
+
187
+ ### Dates and times
188
+
189
+ Intelligent parsing of the user's entry of a datetime is supported. Suppose it is Thursday, November 6 2025 in the US/Eastern timezone. When a datetime is entered it is interpreted _relative_ to the current date, time and timezone. When entering the scheduled datetime for a reminder using `@s`, the following table illustrates how various entries would be interpreted and the resulting user feedback.
190
+
191
+ | @s entry | interpretation | user feedback |
192
+ | --------------- | -------------------- | -------------------------- |
193
+ | wed | 2025-11-12 | Wed, Nov 12 2025 |
194
+ | 9a | 2025-11-06 09:00 EST | Thu, Nov 6 2025 09:00 EST |
195
+ | 9a fri | 2025-11-07 09:00 EST | Fri, Nov 7 2025 09:00 EST |
196
+ | 10 9p z none | 2025-11-10 09:00 | Mon, Nov 10 2025 21:00 |
197
+ | 3p z US/Pacific | 2025-11-06 18:00 EST | Thu, Nov 6 2025 18:00 EST |
198
+ | 10 13:30 z CET | 2025-11-10 07:30 EST | Mon, Nov 10 2025 07:30 EST |
199
+ | 10 20h z none | 2025-11-23 20:00 | Mon, Nov 10 2025 20:00 |
200
+
201
+ Datetimes entered with "z none" and dates are _naive_ - have no timezone information. Datetimes entered with "z TIMEZONE" are interpreted as _aware_ datetimes in TIMEZONE. Datetimes without a "z" entry are also interpreted as _aware_ but in the timezone of the user's computer. Aware datetimes are always reported using the timezone of the user's computer, wherever it might be. Times can be entered using the suffix of either a/p or am/pm for AM/PM times or h for 24-hour times. Times are reported using the preference of the user, here as 24-hour times.
202
+
203
+ Why would you want to use a "z" in specifying a time? Suppose a colleague in Europe at asked you to call Friday at 18:00 CET time. Then setting "@s fri 18h z CET" will schedule your reminder for the correct time to call wherever you might be. In the US/Eastern timezone, this would be "Fri, Nov 12 2025 12:00 EST". As a second example, suppose you want to take a daily medication at 4pm in whatever timezone you happen to be. Then you will want to schedule the reminder for "@s 4p z none".
204
+
205
+ When dates and datetimes are recorded, _aware_ datetimes are first converted to UTC time and then stored with a "Z" appended. E.g., the "3p z US/Pacific" datetime would be interpreted as "2025-11-06 18:00 EST" but would be recorded as "20251106T2300Z". Dates and _naive_ datetimes are recorded without conversion and without the trailing "Z". When _aware_ datetimes are displayed to the user, they are first converted to the timezone of the user's computer. Thus the "PST" example would be displayed as scheduled for 6pm today in US/Eastern. Dates and _naive_ datetimes are displayed without change in every timezone.
206
+
207
+ When an `@s` scheduled entry specifies a date without a time, i.e., a date instead of a datetime, the interpretation is that the task is due sometime on that day. Specifically, it is not due until `00:00` on that day and not past due until `00:00` on the following day. The interpretation of `@b` and `@u` in this circumstance is similar. For example, if `@s 2025-04-06` is specified with `@b 3d` and `@u 2d` then the task status would change from waiting to pending at `2025-04-03 00:00` and, if not completed, to deleted at `2025-04-09 00:00`.
208
+
209
+ Note that times can only be specified, stored and displayed in hours and minutes - seconds and microseconds are not supported. Internally datetimes are interpreted as having seconds equal to 0.
210
+
211
+ ### Intervals
212
+
213
+ An interval is just a period of time and is entered in _tklr_ using expressions such as
214
+
215
+ | entry | period of time |
216
+ | ----- | ----------------------- |
217
+ | 2h | 2 hours |
218
+ | -2h | - 2 hours |
219
+ | 1w7d | 1 week and 7 days |
220
+ | 2h30m | 2 hours and 30 minutes |
221
+ | 1m27s | 1 minute and 27 seconds |
222
+
223
+ Note that w (weeks), d (days), h (hours), m (minutes) and s (seconds) are the available _units_ for entering _intervals_. Seconds are ignored save for their use in alerts - more on alerts later.
224
+
225
+ An interval, `I`, can be added to a datetime, `T`, to get a datetime, `T + I`, that will be after `T` if `I > 0` and before `T` if `I < 0`. Similarly, one datetime, `A`, can be subtracted from another, `B`, to get an interval, `I = B - A`, with `I > 0` if `B` is after (greater than) `A` and `I < 0` if `B` is before (less than) `A`.
226
+
227
+ ### Scheduled datetimes and related intervals
228
+
229
+ For the discussion that follows, it will be assumed that the current date is `2025-10-01` and that the _scheduled datetime_ for the illustrative reminder is
230
+
231
+ @s 2025-10-21 10:00am
232
+
233
+ #### extent
234
+
235
+ The entry `@e 2h30m` would set the _extent_ for the reminder to two hours and 30 minutes.
236
+
237
+ If the reminder were an _event_, this would schedule the "busy time" for the event to _extend_ from 10am until 12:30pm.
238
+
239
+ For a task, this same entry would indicate that attention to completing the task should begin no later than 10am and that 2 hours and 30 minutes is the _estimate_ of the time required for completion. The period from 10am until 12:30pm is not displayed as a busy time, however, since the task could be begun before or after 10am and could take more or less than 2 hours and 30 minutes to complete. For a task, both `@s` and `@e` are best regarded as _estimates_.
240
+
241
+ For a project, this same entry would similarly indicate that attention to completing the project should begin no later than 10am and that two hours and 30 minutes is estimated for completion subject to additional times specified in the jobs. A job entry containing `&s 2d &e 3h`, for example, would set the scheduled time for this job to be two days _after_ the `@s` entry for the project and would add three hours to the estimate of total time required for the project.
242
+
243
+ #### notice
244
+
245
+ The entry `@n I` where `I` is a _positive_ interval specifies that a notice for the reminder should begin on the date in which `scheduled - I` falls. For the example, adding `@b 1d12h` would set _notice_ to the date corresponding to
246
+
247
+ 2025-10-21 10am - 1d12h = 2025-10-19 10pm
248
+
249
+ so notices would begin on `2025-10-19`.
250
+
251
+ If the reminder is an event, then the agenda view would display an notice for the event beginning on `25-10-19` and continuing on the `25-10-20`, i.e., from the date of the notice through the date before the scheduled datetime. For an _event_ think of this notice as a visual alert of the proximity of the event.
252
+
253
+ If the reminder is a task, then the task would _not_ appear in the agenda view until `25-10-19`, i.e., it would be hidden before that date.
254
+
255
+ #### wrap
256
+
257
+ The entry `@w BEFORE, AFTER`, where `BEFORE` and `AFTER` are _intervals_, can be used to wrap the _scheduled_ datetime of a reminder. Possible entries and the resulting values of BEFORE and AFTER are illustrated below:
258
+
259
+ | entry | before | after |
260
+ | ---------- | ------ | ---------- |
261
+ | @w 1h, 30m | 1 hour | 30 minutes |
262
+ | @w 1h, | 1 hour | None |
263
+ | @w , 30m | None | 30 minutes |
264
+
265
+ Consider an event with `@s 2025-10-21 10am @e 2h30m`, which starts at 10am and ends at 12:30pm and suppose that it will take an hour to travel to the location of the event and 30 minutes to travel from the event to the next location. The entry `@w 1h, 30m` could be used to indicate these travel periods from 9am until 10am before the event begins and from 12:30pm until 1pm after the event ends.
266
+
267
+ #### alert
268
+
269
+ An alert is specified using `@a <list of invervals> : <list of commands>`. An `@s <datetime>` is required and the result is to execute the commands in `<list of commands>` at the datetimes resulting from subtracting the intervals in `<list of intervals>` from `<datetime>`. E.g., with `@s 17:00 fri` and `@a 1h, -15m: c, d`, the commands `c` and `d` would each be executed at `17:00 - 1h = 16:00` and `17:00 + 15m = 17:15` on Friday.
270
+
271
+ A command such as `d` in the example must be specified in the user configuration file. This is the relevant section:
272
+
273
+ ```
274
+ [alerts]
275
+ # dict[str, str]: character -> command_str.
276
+ # E.g., this entry
277
+ # d: '/usr/bin/say -v Alex "[[volm 0.5]] {subject}, {when}"'
278
+ # would, on my macbook, invoke the system voice to speak the subject
279
+ # of the reminder and the time remaining until the scheduled datetime.
280
+ # The character "d" would be associated with this command so that, e.g.,
281
+ # the alert entry "@a 30m, 15m: d" would trigger this command 30
282
+ # minutes before and again 15 minutes before the scheduled datetime.
283
+ ```
284
+
285
+ ### Recurrence
286
+
287
+ #### @r and, by requirement, @s are given
288
+
289
+ When an item is specified with an `@r` entry, an `@s` entry is required and is used as the `DTSTART` entry in the recurrence rule. E.g.,
290
+
291
+ ```python
292
+ * datetime repeating @s 2025-11-06 14:00 @r d &i 2
293
+ ```
294
+
295
+ With this entry, the `@s 2025-11-06 14:00` and `@r d &i 2` parts would be combined by _tklr_ to generate this _rruleset_:
296
+
297
+ ```python
298
+ "rruleset": "DTSTART:20251106T1900Z\nRRULE:FREQ=DAILY;INTERVAL=2"
299
+ ```
300
+
301
+ Two aspects of this _rruleset_ are worth emphasizing
302
+
303
+ 1. "DTSTART:20251106T1900Z\nRRULE:FREQ=DAILY;INTERVAL=2" is a string and can therefore be stored without conversion in SQLite3 - the database used for _tklr_.
304
+ 2. Even though it is only 50 characters long, it actually represents an infinite number of datetimes - every datetime matching the recurrence rule which occurs on or after 2025-11-06 19:00 UTC.
305
+
306
+ In the hands of the wonderful _python_ library _dateutil_, this _rruleset_ string can be asked a variety of useful questions which will be answered almost instantly. E.g, What datetimes does it represent which lie between 2025-06-23 08:00 and 2026-01-01 00:00?, What is the first datetime after 2025-10-15 00:00? What is the last datetime before 2025-12-15 00:00? And so forth.
307
+
308
+ **For every reminder in tklr which involves datetimes, a rruleset is used to represent all of those datetimes.**
309
+
310
+ **Note**: The datetimes generated by the _rruleset_ correspond to datetimes matching the specification of `@r` which occur **on or after** the datetime specified by `@s`. The datetime corresponding to `@s` itself will only be generated if it matches the specification of `@r`.
311
+
312
+ #### @s is given but not @r
313
+
314
+ On the other hand, if an `@s` entry is specified, but `@r` is not, then the `@s` entry would be stored as an `RDATE` in the recurrence rule. E.g.,
315
+
316
+ ```python
317
+ * datetime only @s 2025-11-06 14:00
318
+ ```
319
+
320
+ would be serialized (stored) as
321
+
322
+ ```python
323
+ "rruleset": "RDATE:20251106T1900Z"
324
+ ```
325
+
326
+ The datetime corresponding to `@s` itself is, of course, generated in this case.
327
+
328
+ #### @+ is specified, with or without @r
329
+
330
+ When `@s` is specified, an `@+` entry can be used to specify one or more, comma separated datetimes. When `@r` is given, these datetimes are added to those generated by the `@r` specification. Otherwise, they are added to the datetime specified by `@s`. E.g., is a special case. It is used to specify a datetime that is relative to the current datetime. E.g.,
331
+
332
+ ```python
333
+ ... @s 2025-11-06 14:00 @+ 2025-11-13 21:00
334
+ ```
335
+
336
+ would be serialized (stored) as
337
+
338
+ ```python
339
+ "rruleset": "RDATE:20251106T1900Z, 20251114T0200Z"
340
+ ```
341
+
342
+ This option is particularly useful for irregular recurrences such as annual doctor visits. After the initial visit, subsequent visits can simply be added to the `@+` entry of the existing event once the new appointment is made.
343
+
344
+ **Note**: Without `@r`, the `@s` datetime is included in the datetimes generated but with `@r`, it is only used to set the beginning of the recurrence and otherwise ignored.
345
+
346
+ ### Timezone considerations
347
+
348
+ When a datetime is specified without an `z` component, the timezone is assumed to be aware and in the local timezone. The datetime is converted to UTC for storage in the database. When a datetime is displayed, it is displayed using the local timezone of the computer.
349
+
350
+ This remains true with _recurrence_ and _daylight savings time_ but is a little more complicated. As always, the recurrence rules are stored in UTC and the datetimes generated by the rules are also in UTC. When these datetimes are displayed, they are converted to the local timezone.
351
+
352
+ ```python
353
+ ... @s 2025-10-31 14:00 @r d &i 1 &c 4
354
+ ```
355
+
356
+ With this entry, the rruleset and datetimes generated show the effect of the transition from daylight to standard time:
357
+
358
+ ```python
359
+ "rruleset": "DTSTART:20251031T1800Z\nRRULE:FREQ=DAILY;INTERVAL=1;COUNT=4"
360
+
361
+ Fri 2025-10-31 14:00 EDT -0400
362
+ Sat 2025-11-01 14:00 EDT -0400
363
+ Sun 2025-11-02 13:00 EST -0500
364
+ Mon 2025-11-03 13:00 EST -0500
365
+ ```
366
+
367
+ ### Urgency
368
+
369
+ Since urgency values are used ultimately to give an ordinal ranking of tasks, all that matters is the relative values used to compute the urgency scores. Accordingly, all urgency scores are constrained to fall within the interval from -1.0 to 1.0. The default urgency is 0.0 for a task with no urgency components.
370
+
371
+ There are some situations in which a task will _not_ be displayed in the "urgency list" and there is no need, therefore, to compute its urgency:
372
+
373
+ - Completed tasks are not displayed.
374
+ - Hidden tasks are not displayed. The task is hidden if it has an `@s` entry and an `@b` entry and the date corresponding to `@s - @b` falls sometime after the current date.
375
+ - Waiting tasks are not displayed. A task is waiting if it belongs to a project and has unfinished prerequisites.
376
+ - Only the first _unfinished_ instance of a repeating task is displayed. Subsequent instances are not displayed.
377
+
378
+ There is one other circumstance in which urgency need not be computed. When the _pinned_ status of the task is toggled on in the user interface, the task is treated as if the computed urgency were equal to `1.0` without any actual computations.
379
+
380
+ All other tasks will be displayed and ordered by their computed urgency scores. Many of these computations involve datetimes and/or intervals and it is necessary to understand both are represented by integer numbers of seconds - datetimes by the integer number of seconds _since the epoch_ (1970-01-01 00:00:00 UTC) and intervals by the integer numbers of seconds it spans. E.g., for the datetime "2025-01-01 00:00 UTC" this would be `1735689600` and for the interval "1w" this would be the number of seconds in 1 week, `7*24*60*60 = 604800`. This means that an interval can be subtracted from a datetime to obtain another datetime which is "interval" earlier or added to get a datetime "interval" later. One datetime can also be subtracted from another to get the "interval" between the two, with the sign indicating whether the first is later (positive) or earlier (negative). (Adding datetimes, on the other hand, is meaningless.)
381
+
382
+ Briefly, here is the essence of this method used to compute the urgency scores using "due" as an example. Here is the relevant section from config.toml with the default values:
383
+
384
+ ```toml
385
+ [urgency.due]
386
+ # The "due" urgency increases from 0.0 to "max" as now passes from
387
+ # due - interval to due.
388
+ interval = "1w"
389
+ max = 8.0
390
+ ```
391
+
392
+ The "due" urgency of a task with an `@s` entry is computed from _now_ (the current datetime), _due_ (the datetime specified by `@s`) and the _interval_ and _max_ settings from _urgency.due_. The computation returns:
393
+
394
+ - `0.0`
395
+ if `now < due - interval`
396
+ - `max * (1.0 - (now - due) / interval)`
397
+ if `due - interval < now <= due`
398
+ - `max`
399
+ if `now > due`
400
+
401
+ For a task without an `@s` entry, the "due" urgency is 0.0.
402
+
403
+ Other contributions of the task to urgency are computed similarly. Depending on the configuration settings and the characteristics of the task, the value can be either positive or negative or 0.0 when missing the requisite characteristic(s).
404
+
405
+ Once all the contributions of a task have been computed, they are aggregated into a single urgency value in the following way. The process begins by setting the initial values of variables `Wn = 1.0` and `Wp = 1.0`. Then for each of the urgency contributions, `v`, the value is added to `Wp` if `v > 0` or `abs(v)` is added to `Wn` if `v` negative. Thus either `Wp` or `Wn` is increased by each addition unless `v = 0`. When each contribution has been added, the urgency value of the task is computed as follows:
406
+
407
+ ```python
408
+ urgency = (Wp - Wn) / (Wp + Wn)
409
+ ```
410
+
411
+ Equivalently, urgency can be regarded as a weighted average of `-1.0` and `1.0` with `Wn/(Wn + Wp)` and `Wp/(Wn + Wp)` as the weights:
412
+
413
+ ```python
414
+ urgency = -1.0 * Wn / (Wn + Wp) + 1.0 * Wp / (Wn + Wp) = (Wp - Wn) / (Wn + Wp)
415
+ ```
416
+
417
+ Observations from the weighted average perspective and the fact that `Wn >= 1` and `Wp >= 1`:
418
+
419
+ - `-1.0 < urgency < 1`
420
+ - `urgency = 0.0` if and only if `Wn = Wp`
421
+ - `urgency` is _always increasing_ in `Wp` and _always decreasing_ in `Wn`
422
+ - `urgency` approaches `1.0` as `Wn/Wp` approaches `0.0` - as `Wp` increases relative to `Wn`
423
+ - `urgency` approaches `-1.0` as `Wp/Wn` approaches `0.0` - as `Wn` increases relative to `Wp`
424
+
425
+ Thus positive contributions _always_ increase urgency and negative contributions _always_ decrease urgency. The fact that the urgency derived from contributions is always less than `1.0` means that _pinned_ tasks with `urgency = 1` will always be listed first.
426
+
427
+ ## Getting Started
428
+
429
+ ### Developer Install Guide
430
+
431
+ This guide walks you through setting up a development environment for `tklr` using [`uv`](https://github.com/astral-sh/uv) and a local virtual environment. Eventually the normal python installation procedures using pip or pipx will be available.
432
+
433
+ ### ✅ Step 1: Clone the repository
434
+
435
+ This step will create a directory named _tklr-dgrham_ in your current working directory that contains a clone of the github repository for _tklr_.
436
+
437
+ ```bash
438
+ git clone https://github.com/dagraham/tklr-dgraham.git
439
+ cd tklr-dgraham
440
+ ```
441
+
442
+ ### ✅ Step 2: Install uv (if needed)
443
+
444
+ ```bash
445
+ which uv || curl -LsSf https://astral.sh/uv/install.sh | sh
446
+ ```
447
+
448
+ ### ✅ Step 3: Create a virtual environment with `uv`
449
+
450
+ This will create a `.venv/` directory inside your project to hold all the relevant imports.
451
+
452
+ ```bash
453
+ uv venv
454
+ ```
455
+
456
+ ### ✅ Step 4: Install the project in editable mode
457
+
458
+ ```bash
459
+ uv pip install -e .
460
+ ```
461
+
462
+ ### ✅ Step 5: Use the CLI
463
+
464
+ You have two options for activating the virtual environment for the CLI:
465
+
466
+ #### ☑️ Option 1: Manual activation (every session)
467
+
468
+ ```bash
469
+ source .venv/bin/activate
470
+ ```
471
+
472
+ Then you can run:
473
+
474
+ ```bash
475
+ tklr --version
476
+ tklr add "- test task @s 2025-08-01"
477
+ tklr ui
478
+ ```
479
+
480
+ To deactivate:
481
+
482
+ ```bash
483
+ deactivate
484
+ ```
485
+
486
+ #### ☑️ Option 2: Automatic activation with `direnv` (recommended)
487
+
488
+ ##### 1. Install `direnv`
489
+
490
+ ```bash
491
+ brew install direnv # macOS
492
+ sudo apt install direnv # Ubuntu/Debian
493
+ ```
494
+
495
+ ##### 2. Add the shell hook to your `~/.zshrc` or `~/.bashrc`
496
+
497
+ ```sh
498
+ eval "$(direnv hook zsh)" # or bash
499
+ ```
500
+
501
+ Restart your shell or run `source ~/.zshrc`.
502
+
503
+ ##### 3. In the project directory, create a `.envrc` file
504
+
505
+ ```bash
506
+ echo 'export PATH="$PWD/.venv/bin:$PATH"' > .envrc
507
+ ```
508
+
509
+ ##### 4. Allow it
510
+
511
+ ```bash
512
+ direnv allow
513
+ ```
514
+
515
+ Now every time you `cd` into the project, your environment is activated automatically and, as with the manual option, test your setup with
516
+
517
+ ```bash
518
+ tklr --version
519
+ tklr add "- test task @s 2025-08-01"
520
+ tklr ui
521
+ ```
522
+
523
+ You're now ready to develop, test, and run `tklr` locally with full CLI and UI support.
524
+
525
+ ### ✅ Step 6: Updating your repository
526
+
527
+ To update your local copy of **Tklr** to the latest version:
528
+
529
+ ```bash
530
+ # Navigate to your project directory
531
+ cd ~/Projects/tklr-dgraham # adjust this path as needed
532
+
533
+ # Pull the latest changes from GitHub
534
+ git pull origin master
535
+
536
+ # Reinstall in editable mode (picks up new code and dependencies)
537
+ uv pip install -e .
538
+ ```
539
+
540
+ ### Starting tklr for the first time
541
+
542
+ **Tklr** needs a _home_ directory to store its files - most importantly these two:
543
+
544
+ - _config.toml_: An editable file that holds user configuration settings
545
+ - _tkrl.db_: An _SQLite3_ database file that holds all the records for events, tasks and other reminders created when using _tklr_
546
+
547
+ Any directory can be used for _home_. These are the options:
548
+
549
+ 1. If started using the command `tklr --home <path_to_home>` and the directory `<path_to_home>` exists then _tklr_ will use this directory and, if necessary, create the files `config.toml` and `tklr.db` in this directory.
550
+ 2. If the `--home <path_to_home>` is not passed to _tklr_ then the _home_ will be selected in this order:
551
+
552
+ - If the current working directory contains files named `config.toml` and `tklr.db` then it will be used as _home_
553
+ - Else if the environmental variable `TKLR_HOME` is set and specifies a path to an existing directory then it will be used as _home_
554
+ - Else if the environmental variable `XDG_CONFIG_HOME` is set, and specifies a path to an existing directory which contains a directory named `tklr`, then that directory will be used.
555
+ - Else the directory `~/.config/tklr` will be used.
556
+
557
+ ### Configuration
558
+
559
+ These are the default settings in _config.toml_:
560
+
561
+ <!-- BEGIN CONFIG -->
562
+
563
+ ```toml
564
+ # DO NOT EDIT TITLE
565
+ title = "Tklr Configuration"
566
+
567
+ [ui]
568
+ # theme: str = 'dark' | 'light'
569
+ theme = "dark"
570
+
571
+ # ampm: bool = true | false
572
+ # Use 12 hour AM/PM when true else 24 hour
573
+ ampm = false
574
+
575
+ # dayfirst and yearfirst settings
576
+ # These settings are used to resolve ambiguous date entries involving
577
+ # 2-digit components. E.g., the interpretation of the date "12-10-11"
578
+ # with the various possible settings for dayfirst and yearfirst:
579
+ #
580
+ # dayfirst yearfirst date interpretation standard
581
+ # ======== ========= ======== ============== ========
582
+ # True True 12-10-11 2012-11-10 Y-D-M ??
583
+ # True False 12-10-11 2011-10-12 D-M-Y EU
584
+ # False True 12-10-11 2012-10-11 Y-M-D ISO 8601
585
+ # False False 12-10-11 2011-12-10 M-D-Y US
586
+ #
587
+ # The defaults:
588
+ # dayfirst = false
589
+ # yearfirst = true
590
+ # correspond to the Y-M-D ISO 8601 standard.
591
+
592
+ # dayfirst: bool = true | false
593
+ dayfirst = false
594
+
595
+ # yearfirst: bool = true | false
596
+ yearfirst = true
597
+
598
+ [alerts]
599
+ # dict[str, str]: character -> command_str.
600
+ # E.g., this entry
601
+ # d: '/usr/bin/say -v Alex "[[volm 0.5]] {subject}, {when}"'
602
+ # would, on my macbook, invoke the system voice to speak the subject
603
+ # of the reminder and the time remaining until the scheduled datetime.
604
+ # The character "d" would be associated with this command so that, e.g.,
605
+ # the alert entry "@a 30m, 15m: d" would trigger this command 30
606
+ # minutes before and again 15 minutes before the scheduled datetime.
607
+
608
+
609
+ # ─── Urgency Configuration ─────────────────────────────────────
610
+
611
+ [urgency.due]
612
+ # The "due" urgency increases from 0.0 to "max" as now passes from
613
+ # due - interval to due.
614
+ interval = "1w"
615
+ max = 8.0
616
+
617
+ [urgency.pastdue]
618
+ # The "pastdue" urgency increases from 0.0 to "max" as now passes
619
+ # from due to due + interval.
620
+ interval = "2d"
621
+ max = 2.0
622
+
623
+ [urgency.recent]
624
+ # The "recent" urgency decreases from "max" to 0.0 as now passes
625
+ # from modified to modified + interval.
626
+ interval = "2w"
627
+ max = 4.0
628
+
629
+ [urgency.age]
630
+ # The "age" urgency increases from 0.0 to "max" as now increases
631
+ # from modified to modified + interval.
632
+ interval = "26w"
633
+ max = 10.0
634
+
635
+ [urgency.extent]
636
+ # The "@e extent" urgency increases from 0.0 when extent = "0m" to "max"
637
+ # when extent >= interval.
638
+ interval = "12h"
639
+ max = 4.0
640
+
641
+ [urgency.blocking]
642
+ # The "blocking" urgency increases from 0.0 when blocked = 0 to "max"
643
+ # when blocked >= count. Blocked is the integer count of tasks in a project for which the given task is an unfinished prerequisite.
644
+ count = 3
645
+ max = 6.0
646
+
647
+ [urgency.tags]
648
+ # The "tags" urgency increases from 0.0 when tags = 0 to "max" when
649
+ # when tags >= count. Tags is the count of "@t" entries given in the task.
650
+ count = 3
651
+ max = 3.0
652
+
653
+ [urgency.priority]
654
+ # The "priority" urgency corresponds to the value from "1" to "5" of `@p`
655
+ # specified in the task. E.g, with "@p 3", the value would correspond to
656
+ # the "3" entry below. Absent an entry for "@p", the value would be 0.0.
657
+
658
+ "1" = -5.0
659
+
660
+ "2" = 2.0
661
+
662
+ "3" = 5.0
663
+
664
+ "4" = 8.0
665
+
666
+ "5" = 10.0
667
+
668
+
669
+ # In the default settings, a priority of "1" is the only one that yields
670
+ # a negative value, `-5`, and thus reduces the urgency of the task.
671
+
672
+ [urgency.description]
673
+ # The "description" urgency equals "max" if the task has an "@d" entry and
674
+ # 0.0 otherwise.
675
+ max = 2.0
676
+
677
+ [urgency.project]
678
+ # The "project" urgency equals "max" if the task belongs to a project and
679
+ # 0.0 otherwise.
680
+ max = 3.0
681
+ ```
682
+
683
+ <!-- END CONFIG -->
684
+
685
+ ## Keyboard Shortcuts
686
+
687
+ | Key | Context | Action |
688
+ | --------- | --------------- | ---------------------------- |
689
+ | `a` … `z` | Agenda/List | Show details for tagged item |
690
+ | `ctrl+e` | Details | Edit selected item |
691
+ | `ctrl+f` | Details (task) | Finish task / occurrence |
692
+ | `ctrl+n` | Anywhere | Create new item |
693
+ | `ctrl+r` | Details (recur) | Show repetitions |
694
+ | `ctrl+p` | Details (task) | Toggle pin |
695
+ | `ctrl+t` | Details | Touch (update modified time) |
696
+ | `escape` | Any | Back / Close screen |
697
+ | `?` | Any | Show help |