tidyline 0.2.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.
Files changed (40) hide show
  1. tidyline-0.2.0/LICENSE +21 -0
  2. tidyline-0.2.0/PKG-INFO +248 -0
  3. tidyline-0.2.0/README.md +223 -0
  4. tidyline-0.2.0/pyproject.toml +69 -0
  5. tidyline-0.2.0/pyproject.toml.orig +55 -0
  6. tidyline-0.2.0/src/tidyline/__init__.py +3 -0
  7. tidyline-0.2.0/src/tidyline/anonymise.py +301 -0
  8. tidyline-0.2.0/src/tidyline/checks/__init__.py +21 -0
  9. tidyline-0.2.0/src/tidyline/checks/active_project_no_open_tasks.py +38 -0
  10. tidyline-0.2.0/src/tidyline/checks/dated_in_someday_project.py +38 -0
  11. tidyline-0.2.0/src/tidyline/checks/finding.py +52 -0
  12. tidyline-0.2.0/src/tidyline/checks/leftover_templates.py +29 -0
  13. tidyline-0.2.0/src/tidyline/checks/open_in_trashed_project.py +35 -0
  14. tidyline-0.2.0/src/tidyline/checks/outside_heading.py +41 -0
  15. tidyline-0.2.0/src/tidyline/checks/possible_duplicates.py +60 -0
  16. tidyline-0.2.0/src/tidyline/checks/recurrence_not_recognised.py +27 -0
  17. tidyline-0.2.0/src/tidyline/checks/registry.py +70 -0
  18. tidyline-0.2.0/src/tidyline/checks/scope.py +52 -0
  19. tidyline-0.2.0/src/tidyline/checks/someday_in_active_project.py +35 -0
  20. tidyline-0.2.0/src/tidyline/checks/title_hygiene.py +50 -0
  21. tidyline-0.2.0/src/tidyline/checks/unfiled_open.py +32 -0
  22. tidyline-0.2.0/src/tidyline/cli.py +778 -0
  23. tidyline-0.2.0/src/tidyline/dates.py +71 -0
  24. tidyline-0.2.0/src/tidyline/db/__init__.py +0 -0
  25. tidyline-0.2.0/src/tidyline/db/locate.py +77 -0
  26. tidyline-0.2.0/src/tidyline/db/schema.py +180 -0
  27. tidyline-0.2.0/src/tidyline/db/snapshot.py +127 -0
  28. tidyline-0.2.0/src/tidyline/decode.py +101 -0
  29. tidyline-0.2.0/src/tidyline/llm/__init__.py +2 -0
  30. tidyline-0.2.0/src/tidyline/llm/audit_prompt.md +164 -0
  31. tidyline-0.2.0/src/tidyline/llm/prompt.py +214 -0
  32. tidyline-0.2.0/src/tidyline/model.py +157 -0
  33. tidyline-0.2.0/src/tidyline/read.py +323 -0
  34. tidyline-0.2.0/src/tidyline/recurrence.py +448 -0
  35. tidyline-0.2.0/src/tidyline/render/__init__.py +0 -0
  36. tidyline-0.2.0/src/tidyline/render/check_out.py +100 -0
  37. tidyline-0.2.0/src/tidyline/render/json_out.py +197 -0
  38. tidyline-0.2.0/src/tidyline/render/recurrence_out.py +144 -0
  39. tidyline-0.2.0/src/tidyline/render/text.py +234 -0
  40. tidyline-0.2.0/src/tidyline/status.py +67 -0
tidyline-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Laxman Rathod
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,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: tidyline
3
+ Version: 0.2.0
4
+ Summary: Offline, read-only audit of a Things 3 database on macOS.
5
+ Keywords: things,things3,gtd,audit,cli,macos,offline
6
+ Author: Laxman Rathod
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Utilities
19
+ Requires-Dist: things-py>=1.0.1
20
+ Requires-Python: >=3.10
21
+ Project-URL: Homepage, https://github.com/rathodlaxman/tidyline
22
+ Project-URL: Source, https://github.com/rathodlaxman/tidyline
23
+ Project-URL: Issues, https://github.com/rathodlaxman/tidyline/issues
24
+ Description-Content-Type: text/markdown
25
+
26
+ # tidyline
27
+
28
+ An offline, read-only command-line tool that audits a Things 3 database on macOS.
29
+
30
+ > **Unofficial.** tidyline is not affiliated with or endorsed by Cultured Code.
31
+ > "Things" is a trademark of its owner. This project uses the name only to say
32
+ > which app it reads. It uses no logo or icon.
33
+
34
+ ## Your data is personal
35
+
36
+ The Things database, the JSON export, the text report and any decoded file
37
+ contain your personal data: task titles, notes, checklists and dates. Never
38
+ commit them to a repository, and never post them in a public issue.
39
+
40
+ Anonymised exports reduce the risk. They cannot remove all of it.
41
+
42
+ - `light` keeps titles but replaces emails, phone numbers, URLs, money amounts
43
+ and long digit runs with placeholders. It removes notes. It is a best-effort
44
+ scrub. It cannot catch names, addresses or alphanumeric account numbers.
45
+ - `strict` replaces every title and name with a label such as `Task k3f9az`. It
46
+ removes notes and replaces checklist items with a count. Only `strict`
47
+ removes all titles.
48
+ - At every level, ids are kept. Anyone who also has your database can map them
49
+ back to titles. Dates, counts and findings stay too, and they reveal how you
50
+ work.
51
+
52
+ ## What it does
53
+
54
+ tidyline reads your Things database and reports on it: counts, health checks,
55
+ a decoded view of your repeating tasks, a JSON export, and an anonymised
56
+ prompt you can give to an AI assistant yourself.
57
+
58
+ What it does not do:
59
+
60
+ - It never writes to the Things database.
61
+ - It never reads the database file directly. It makes a private copy (a
62
+ snapshot) with the SQLite backup API and reads that, then deletes the copy.
63
+ Things can stay open while it runs.
64
+ - It makes no network calls. None of the current commands needs a network.
65
+ - It never reads or exports the URL-scheme auth token.
66
+ - It never changes, completes or opens anything in Things.
67
+
68
+ ## Install
69
+
70
+ From source:
71
+
72
+ ```
73
+ git clone https://github.com/rathodlaxman/tidyline.git
74
+ cd tidyline
75
+ uv sync
76
+ uv run tidyline --version
77
+ ```
78
+
79
+ You need [uv](https://docs.astral.sh/uv/) and Python 3.10 or later.
80
+
81
+ Once published: `pipx install tidyline` or `uv tool install tidyline`.
82
+
83
+ macOS may block Terminal from reading Things' data folder. If `doctor` says so,
84
+ open System Settings, Privacy & Security, Full Disk Access, turn it on for your
85
+ terminal app, then quit and reopen the terminal.
86
+
87
+ ## Quick start
88
+
89
+ Run these from the folder you cloned. Write outputs outside any git folder, so
90
+ they cannot be committed by accident. The examples use `~/tidyline-out/`, which
91
+ must exist first:
92
+
93
+ ```
94
+ mkdir -p -m 700 ~/tidyline-out
95
+ uv run tidyline doctor
96
+ uv run tidyline report
97
+ uv run tidyline check
98
+ uv run tidyline recurrence
99
+ uv run tidyline export --out ~/tidyline-out/export.json
100
+ uv run tidyline prompt --out ~/tidyline-out/prompt
101
+ uv run tidyline decode ~/tidyline-out/answer.md --out ~/tidyline-out/answer-decoded.md
102
+ ```
103
+
104
+ - `doctor` finds the database and checks permissions, the schema and versions.
105
+ - `report` prints a text summary. It shows counts only, except for repeating
106
+ templates, which are listed with their titles.
107
+ - `check` runs the health checks.
108
+ - `recurrence` lists every repeating template with its readable rule.
109
+ - `export` writes a JSON export. Add `--anonymise light` or `--anonymise strict`
110
+ to anonymise it.
111
+ - `prompt` and `decode` are described below.
112
+
113
+ Every command accepts `--db PATH` for a database in a non-standard place. You
114
+ can also set the `THINGSDB` environment variable. Add `--verbose` before the
115
+ command for more detail when something fails.
116
+
117
+ ## Commands
118
+
119
+ | Command | What it does | Options |
120
+ |---|---|---|
121
+ | `doctor` | Finds the database and checks permissions, schema and versions. | `--db` |
122
+ | `report` | Prints a text summary, or writes it to a new file. | `--out PATH`, `--include-completed`, `--no-color`, `--db` |
123
+ | `check` | Runs the health checks. | `--only ID [ID ...]`, `--pattern REGEX [REGEX ...]`, `--json`, `--db` |
124
+ | `export` | Writes a JSON export to a new file. | `--out PATH` (required), `--anonymise {none,light,strict}`, `--db` |
125
+ | `recurrence` | Lists every repeating template with its readable rule. | `--raw`, `--db` |
126
+ | `prompt` | Writes an anonymised payload and a filled audit prompt. | `--anonymise {light,strict}`, `--context FILE`, `--out DIR`, `--copy`, `--db` |
127
+ | `decode` | Replaces `strict` labels in an answer with your real titles. | `ANSWER_FILE`, `--out FILE`, `--db` |
128
+
129
+ Files written with `--out` get mode 0600, and an existing file is never
130
+ overwritten.
131
+
132
+ Exit codes:
133
+
134
+ | Code | Meaning |
135
+ |---|---|
136
+ | 0 | Success. For `check`: no warnings. |
137
+ | 1 | Only `check` uses this: at least one warning-level finding. |
138
+ | 2 | Usage error, such as a bad option, a bad regular expression or an output path that already exists. |
139
+ | 3 | The run failed: the database could not be found or read, or a file could not be written. |
140
+
141
+ ## Using an AI assistant without sending anything automatically
142
+
143
+ tidyline never sends your data anywhere. `prompt` prepares two files, and you
144
+ decide whether to attach or paste them into an assistant yourself.
145
+
146
+ ```
147
+ uv run tidyline prompt --context my-context.txt --out ~/tidyline-out/prompt
148
+ ```
149
+
150
+ - The default is `--anonymise strict`. There is no `none` option for `prompt`.
151
+ - `--context FILE` is a plain text file with sections labelled `Role:`,
152
+ `Priorities:` and `Dates:`. It is added to the prompt exactly as you wrote it.
153
+ A section you leave out stays as a visible placeholder, and the preview warns
154
+ you.
155
+ - `--out DIR` writes `tidyline_payload.json` (the anonymised export) and
156
+ `tidyline_prompt.md` (the filled prompt). `--copy` puts the prompt on the
157
+ clipboard with `pbcopy`. With neither option, nothing is written.
158
+ - `prompt` always prints a preview first: the level, the counts, what was
159
+ removed or replaced, and your context text. The last line says nothing has
160
+ been sent anywhere.
161
+ - The text report is never part of the output.
162
+
163
+ At `strict`, titles are hidden, so the assistant cannot judge how your tasks are
164
+ worded. The prompt tells it to skip that part. Use `--anonymise light` if you
165
+ want task wording reviewed, and accept that `light` leaves titles in place.
166
+
167
+ To turn the answer back into real titles, save it to a file and run:
168
+
169
+ ```
170
+ uv run tidyline decode ~/tidyline-out/answer.md
171
+ ```
172
+
173
+ `decode` recomputes each label from the current database, so it needs no
174
+ mapping file. Labels it cannot match stay as they are, and it reports how many.
175
+ It needs the same database the prompt was made from, and `light` output needs no
176
+ decoding. Titles go in exactly as stored, so a title containing `|` can break a
177
+ markdown table cell. The decoded text contains real titles, so treat it as
178
+ personal data.
179
+
180
+ ## Health checks
181
+
182
+ `tidyline check` exits with 1 if any warning is found. Info findings do not
183
+ change the exit code. Findings carry ids and fixed messages, never titles. The
184
+ text output shows titles on your own screen only. `check --json` prints ids
185
+ only.
186
+
187
+ | Check | Severity | What it finds |
188
+ |---|---|---|
189
+ | `open_in_trashed_project` | warning | Open to-dos inside a trashed project. The other checks leave these out. |
190
+ | `unfiled_open` | warning | Open to-dos with no area and no project, outside the Inbox. |
191
+ | `title_hygiene` | warning | A stray `·` at either end, leading or trailing spaces, doubled spaces, and matches of your own `--pattern` regular expressions. |
192
+ | `possible_duplicates` | warning | Open to-dos whose titles match after Unicode normalisation, case folding, apostrophes removed and other punctuation treated as spaces. |
193
+ | `dated_in_someday_project` | warning | To-dos with a start date inside a Someday project. They will never surface. |
194
+ | `active_project_no_open_tasks` | warning | Active projects with no open to-dos and no live repeating template. |
195
+ | `recurrence_not_recognised` | warning | Repeating templates whose rule tidyline cannot read or does not recognise. |
196
+ | `leftover_templates` | info | Repeating templates whose project is trashed or missing. |
197
+ | `someday_in_active_project` | info | Someday to-dos inside active projects. |
198
+ | `outside_heading` | info | To-dos outside any heading in projects that use headings. |
199
+
200
+ A finding for one of your own patterns says only "matches user pattern N".
201
+ The text output on your screen shows the pattern next to it; the JSON does not.
202
+
203
+ ## Things does not document its database
204
+
205
+ tidyline reads columns that Things does not document, because there is no
206
+ public API for this. These include:
207
+
208
+ - the repeating-task columns `rt1_recurrenceRule`, `rt1_repeatingTemplate` and
209
+ `rt1_instanceCreationPaused`;
210
+ - `startBucket`, which marks This Evening;
211
+ - `deadlineSuppressionDate`, which keeps an overdue deadline out of Today.
212
+
213
+ Repeat rules are stored as property lists whose meaning was worked out by
214
+ comparing them with what Things shows. A rule tidyline does not understand is
215
+ reported as not recognised. It is never guessed.
216
+
217
+ Before reading, tidyline checks that the tables and columns it needs exist. If
218
+ they do not, it stops with a message that names what is missing, the versions
219
+ it was tested with, and what to do next. See
220
+ [docs/COMPATIBILITY.md](https://github.com/rathodlaxman/tidyline/blob/main/docs/COMPATIBILITY.md) for what has been tested and
221
+ what has not.
222
+
223
+ Not yet built: the HTML report and the opt-in model integration.
224
+
225
+ ## Development
226
+
227
+ ```
228
+ uv sync
229
+ uv run pytest
230
+ uv run ruff check .
231
+ uv run ruff format --check .
232
+ ```
233
+
234
+ Tests use only a synthetic database built by `tests/fixtures/build_fixture.py`.
235
+ The real Things database is never used in tests, and no database, export or
236
+ report is ever committed. Network sockets are blocked while the tests run.
237
+
238
+ Golden files under `tests/fixtures/` are regenerated with
239
+ `TIDYLINE_UPDATE_GOLDEN=1 uv run pytest`. Review the diff before committing.
240
+
241
+ The design and the decisions behind it are in [docs/DESIGN.md](https://github.com/rathodlaxman/tidyline/blob/main/docs/DESIGN.md).
242
+ How repeat rules are read is in [docs/RECURRENCE.md](https://github.com/rathodlaxman/tidyline/blob/main/docs/RECURRENCE.md).
243
+
244
+ ## Licence
245
+
246
+ MIT. See [LICENSE](https://github.com/rathodlaxman/tidyline/blob/main/LICENSE). tidyline depends on
247
+ [things.py](https://github.com/thingsapi/things.py), which is licensed under
248
+ Apache 2.0.
@@ -0,0 +1,223 @@
1
+ # tidyline
2
+
3
+ An offline, read-only command-line tool that audits a Things 3 database on macOS.
4
+
5
+ > **Unofficial.** tidyline is not affiliated with or endorsed by Cultured Code.
6
+ > "Things" is a trademark of its owner. This project uses the name only to say
7
+ > which app it reads. It uses no logo or icon.
8
+
9
+ ## Your data is personal
10
+
11
+ The Things database, the JSON export, the text report and any decoded file
12
+ contain your personal data: task titles, notes, checklists and dates. Never
13
+ commit them to a repository, and never post them in a public issue.
14
+
15
+ Anonymised exports reduce the risk. They cannot remove all of it.
16
+
17
+ - `light` keeps titles but replaces emails, phone numbers, URLs, money amounts
18
+ and long digit runs with placeholders. It removes notes. It is a best-effort
19
+ scrub. It cannot catch names, addresses or alphanumeric account numbers.
20
+ - `strict` replaces every title and name with a label such as `Task k3f9az`. It
21
+ removes notes and replaces checklist items with a count. Only `strict`
22
+ removes all titles.
23
+ - At every level, ids are kept. Anyone who also has your database can map them
24
+ back to titles. Dates, counts and findings stay too, and they reveal how you
25
+ work.
26
+
27
+ ## What it does
28
+
29
+ tidyline reads your Things database and reports on it: counts, health checks,
30
+ a decoded view of your repeating tasks, a JSON export, and an anonymised
31
+ prompt you can give to an AI assistant yourself.
32
+
33
+ What it does not do:
34
+
35
+ - It never writes to the Things database.
36
+ - It never reads the database file directly. It makes a private copy (a
37
+ snapshot) with the SQLite backup API and reads that, then deletes the copy.
38
+ Things can stay open while it runs.
39
+ - It makes no network calls. None of the current commands needs a network.
40
+ - It never reads or exports the URL-scheme auth token.
41
+ - It never changes, completes or opens anything in Things.
42
+
43
+ ## Install
44
+
45
+ From source:
46
+
47
+ ```
48
+ git clone https://github.com/rathodlaxman/tidyline.git
49
+ cd tidyline
50
+ uv sync
51
+ uv run tidyline --version
52
+ ```
53
+
54
+ You need [uv](https://docs.astral.sh/uv/) and Python 3.10 or later.
55
+
56
+ Once published: `pipx install tidyline` or `uv tool install tidyline`.
57
+
58
+ macOS may block Terminal from reading Things' data folder. If `doctor` says so,
59
+ open System Settings, Privacy & Security, Full Disk Access, turn it on for your
60
+ terminal app, then quit and reopen the terminal.
61
+
62
+ ## Quick start
63
+
64
+ Run these from the folder you cloned. Write outputs outside any git folder, so
65
+ they cannot be committed by accident. The examples use `~/tidyline-out/`, which
66
+ must exist first:
67
+
68
+ ```
69
+ mkdir -p -m 700 ~/tidyline-out
70
+ uv run tidyline doctor
71
+ uv run tidyline report
72
+ uv run tidyline check
73
+ uv run tidyline recurrence
74
+ uv run tidyline export --out ~/tidyline-out/export.json
75
+ uv run tidyline prompt --out ~/tidyline-out/prompt
76
+ uv run tidyline decode ~/tidyline-out/answer.md --out ~/tidyline-out/answer-decoded.md
77
+ ```
78
+
79
+ - `doctor` finds the database and checks permissions, the schema and versions.
80
+ - `report` prints a text summary. It shows counts only, except for repeating
81
+ templates, which are listed with their titles.
82
+ - `check` runs the health checks.
83
+ - `recurrence` lists every repeating template with its readable rule.
84
+ - `export` writes a JSON export. Add `--anonymise light` or `--anonymise strict`
85
+ to anonymise it.
86
+ - `prompt` and `decode` are described below.
87
+
88
+ Every command accepts `--db PATH` for a database in a non-standard place. You
89
+ can also set the `THINGSDB` environment variable. Add `--verbose` before the
90
+ command for more detail when something fails.
91
+
92
+ ## Commands
93
+
94
+ | Command | What it does | Options |
95
+ |---|---|---|
96
+ | `doctor` | Finds the database and checks permissions, schema and versions. | `--db` |
97
+ | `report` | Prints a text summary, or writes it to a new file. | `--out PATH`, `--include-completed`, `--no-color`, `--db` |
98
+ | `check` | Runs the health checks. | `--only ID [ID ...]`, `--pattern REGEX [REGEX ...]`, `--json`, `--db` |
99
+ | `export` | Writes a JSON export to a new file. | `--out PATH` (required), `--anonymise {none,light,strict}`, `--db` |
100
+ | `recurrence` | Lists every repeating template with its readable rule. | `--raw`, `--db` |
101
+ | `prompt` | Writes an anonymised payload and a filled audit prompt. | `--anonymise {light,strict}`, `--context FILE`, `--out DIR`, `--copy`, `--db` |
102
+ | `decode` | Replaces `strict` labels in an answer with your real titles. | `ANSWER_FILE`, `--out FILE`, `--db` |
103
+
104
+ Files written with `--out` get mode 0600, and an existing file is never
105
+ overwritten.
106
+
107
+ Exit codes:
108
+
109
+ | Code | Meaning |
110
+ |---|---|
111
+ | 0 | Success. For `check`: no warnings. |
112
+ | 1 | Only `check` uses this: at least one warning-level finding. |
113
+ | 2 | Usage error, such as a bad option, a bad regular expression or an output path that already exists. |
114
+ | 3 | The run failed: the database could not be found or read, or a file could not be written. |
115
+
116
+ ## Using an AI assistant without sending anything automatically
117
+
118
+ tidyline never sends your data anywhere. `prompt` prepares two files, and you
119
+ decide whether to attach or paste them into an assistant yourself.
120
+
121
+ ```
122
+ uv run tidyline prompt --context my-context.txt --out ~/tidyline-out/prompt
123
+ ```
124
+
125
+ - The default is `--anonymise strict`. There is no `none` option for `prompt`.
126
+ - `--context FILE` is a plain text file with sections labelled `Role:`,
127
+ `Priorities:` and `Dates:`. It is added to the prompt exactly as you wrote it.
128
+ A section you leave out stays as a visible placeholder, and the preview warns
129
+ you.
130
+ - `--out DIR` writes `tidyline_payload.json` (the anonymised export) and
131
+ `tidyline_prompt.md` (the filled prompt). `--copy` puts the prompt on the
132
+ clipboard with `pbcopy`. With neither option, nothing is written.
133
+ - `prompt` always prints a preview first: the level, the counts, what was
134
+ removed or replaced, and your context text. The last line says nothing has
135
+ been sent anywhere.
136
+ - The text report is never part of the output.
137
+
138
+ At `strict`, titles are hidden, so the assistant cannot judge how your tasks are
139
+ worded. The prompt tells it to skip that part. Use `--anonymise light` if you
140
+ want task wording reviewed, and accept that `light` leaves titles in place.
141
+
142
+ To turn the answer back into real titles, save it to a file and run:
143
+
144
+ ```
145
+ uv run tidyline decode ~/tidyline-out/answer.md
146
+ ```
147
+
148
+ `decode` recomputes each label from the current database, so it needs no
149
+ mapping file. Labels it cannot match stay as they are, and it reports how many.
150
+ It needs the same database the prompt was made from, and `light` output needs no
151
+ decoding. Titles go in exactly as stored, so a title containing `|` can break a
152
+ markdown table cell. The decoded text contains real titles, so treat it as
153
+ personal data.
154
+
155
+ ## Health checks
156
+
157
+ `tidyline check` exits with 1 if any warning is found. Info findings do not
158
+ change the exit code. Findings carry ids and fixed messages, never titles. The
159
+ text output shows titles on your own screen only. `check --json` prints ids
160
+ only.
161
+
162
+ | Check | Severity | What it finds |
163
+ |---|---|---|
164
+ | `open_in_trashed_project` | warning | Open to-dos inside a trashed project. The other checks leave these out. |
165
+ | `unfiled_open` | warning | Open to-dos with no area and no project, outside the Inbox. |
166
+ | `title_hygiene` | warning | A stray `·` at either end, leading or trailing spaces, doubled spaces, and matches of your own `--pattern` regular expressions. |
167
+ | `possible_duplicates` | warning | Open to-dos whose titles match after Unicode normalisation, case folding, apostrophes removed and other punctuation treated as spaces. |
168
+ | `dated_in_someday_project` | warning | To-dos with a start date inside a Someday project. They will never surface. |
169
+ | `active_project_no_open_tasks` | warning | Active projects with no open to-dos and no live repeating template. |
170
+ | `recurrence_not_recognised` | warning | Repeating templates whose rule tidyline cannot read or does not recognise. |
171
+ | `leftover_templates` | info | Repeating templates whose project is trashed or missing. |
172
+ | `someday_in_active_project` | info | Someday to-dos inside active projects. |
173
+ | `outside_heading` | info | To-dos outside any heading in projects that use headings. |
174
+
175
+ A finding for one of your own patterns says only "matches user pattern N".
176
+ The text output on your screen shows the pattern next to it; the JSON does not.
177
+
178
+ ## Things does not document its database
179
+
180
+ tidyline reads columns that Things does not document, because there is no
181
+ public API for this. These include:
182
+
183
+ - the repeating-task columns `rt1_recurrenceRule`, `rt1_repeatingTemplate` and
184
+ `rt1_instanceCreationPaused`;
185
+ - `startBucket`, which marks This Evening;
186
+ - `deadlineSuppressionDate`, which keeps an overdue deadline out of Today.
187
+
188
+ Repeat rules are stored as property lists whose meaning was worked out by
189
+ comparing them with what Things shows. A rule tidyline does not understand is
190
+ reported as not recognised. It is never guessed.
191
+
192
+ Before reading, tidyline checks that the tables and columns it needs exist. If
193
+ they do not, it stops with a message that names what is missing, the versions
194
+ it was tested with, and what to do next. See
195
+ [docs/COMPATIBILITY.md](https://github.com/rathodlaxman/tidyline/blob/main/docs/COMPATIBILITY.md) for what has been tested and
196
+ what has not.
197
+
198
+ Not yet built: the HTML report and the opt-in model integration.
199
+
200
+ ## Development
201
+
202
+ ```
203
+ uv sync
204
+ uv run pytest
205
+ uv run ruff check .
206
+ uv run ruff format --check .
207
+ ```
208
+
209
+ Tests use only a synthetic database built by `tests/fixtures/build_fixture.py`.
210
+ The real Things database is never used in tests, and no database, export or
211
+ report is ever committed. Network sockets are blocked while the tests run.
212
+
213
+ Golden files under `tests/fixtures/` are regenerated with
214
+ `TIDYLINE_UPDATE_GOLDEN=1 uv run pytest`. Review the diff before committing.
215
+
216
+ The design and the decisions behind it are in [docs/DESIGN.md](https://github.com/rathodlaxman/tidyline/blob/main/docs/DESIGN.md).
217
+ How repeat rules are read is in [docs/RECURRENCE.md](https://github.com/rathodlaxman/tidyline/blob/main/docs/RECURRENCE.md).
218
+
219
+ ## Licence
220
+
221
+ MIT. See [LICENSE](https://github.com/rathodlaxman/tidyline/blob/main/LICENSE). tidyline depends on
222
+ [things.py](https://github.com/thingsapi/things.py), which is licensed under
223
+ Apache 2.0.
@@ -0,0 +1,69 @@
1
+ [project]
2
+ name = "tidyline"
3
+ version = "0.2.0"
4
+ description = "Offline, read-only audit of a Things 3 database on macOS."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.10"
9
+ keywords = [
10
+ "things",
11
+ "things3",
12
+ "gtd",
13
+ "audit",
14
+ "cli",
15
+ "macos",
16
+ "offline",
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 3 - Alpha",
20
+ "Environment :: Console",
21
+ "Intended Audience :: End Users/Desktop",
22
+ "Operating System :: MacOS",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Utilities",
29
+ ]
30
+ dependencies = ["things-py>=1.0.1"]
31
+
32
+ [[project.authors]]
33
+ name = "Laxman Rathod"
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/rathodlaxman/tidyline"
37
+ Source = "https://github.com/rathodlaxman/tidyline"
38
+ Issues = "https://github.com/rathodlaxman/tidyline/issues"
39
+
40
+ [project.scripts]
41
+ tidyline = "tidyline.cli:main"
42
+
43
+ [build-system]
44
+ requires = ["uv_build>=0.13.0,<0.14.0"]
45
+ build-backend = "uv_build"
46
+
47
+ [dependency-groups]
48
+ dev = [
49
+ "pytest>=9.1.1",
50
+ "pytest-socket>=0.8.1",
51
+ "ruff>=0.17.0",
52
+ ]
53
+
54
+ [tool.pytest.ini_options]
55
+ addopts = "--disable-socket"
56
+ testpaths = ["tests"]
57
+
58
+ [tool.ruff]
59
+ line-length = 88
60
+ target-version = "py310"
61
+
62
+ [tool.ruff.lint]
63
+ select = [
64
+ "E",
65
+ "F",
66
+ "I",
67
+ "UP",
68
+ "B",
69
+ ]
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = "tidyline"
3
+ version = "0.2.0"
4
+ description = "Offline, read-only audit of a Things 3 database on macOS."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.10"
9
+ authors = [{ name = "Laxman Rathod" }]
10
+ keywords = ["things", "things3", "gtd", "audit", "cli", "macos", "offline"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Environment :: Console",
14
+ "Intended Audience :: End Users/Desktop",
15
+ "Operating System :: MacOS",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: Utilities",
22
+ ]
23
+ dependencies = [
24
+ "things-py>=1.0.1",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/rathodlaxman/tidyline"
29
+ Source = "https://github.com/rathodlaxman/tidyline"
30
+ Issues = "https://github.com/rathodlaxman/tidyline/issues"
31
+
32
+ [project.scripts]
33
+ tidyline = "tidyline.cli:main"
34
+
35
+ [build-system]
36
+ requires = ["uv_build>=0.13.0,<0.14.0"]
37
+ build-backend = "uv_build"
38
+
39
+ [dependency-groups]
40
+ dev = [
41
+ "pytest>=9.1.1",
42
+ "pytest-socket>=0.8.1",
43
+ "ruff>=0.17.0",
44
+ ]
45
+
46
+ [tool.pytest.ini_options]
47
+ addopts = "--disable-socket"
48
+ testpaths = ["tests"]
49
+
50
+ [tool.ruff]
51
+ line-length = 88
52
+ target-version = "py310"
53
+
54
+ [tool.ruff.lint]
55
+ select = ["E", "F", "I", "UP", "B"]
@@ -0,0 +1,3 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version("tidyline")