tidyline
An offline, read-only command-line tool that audits a Things 3 database on macOS.
Unofficial. tidyline is not affiliated with or endorsed by Cultured Code. "Things" is a trademark of its owner. This project uses the name only to say which app it reads. It uses no logo or icon.
Your data is personal
The Things database, the JSON export, the text output of check and
recurrence, and any decoded file contain your personal data: task titles,
notes, checklists and dates. Never commit them to a repository, and never post
them in a public issue. report prints counts only, and check --json prints
ids and fixed messages only; neither shows titles.
Anonymised exports reduce the risk. They cannot remove all of it.
lightkeeps titles but replaces emails, phone numbers, URLs, money amounts and long digit runs with placeholders. It removes notes. It is a best-effort scrub. It cannot catch names, addresses or alphanumeric account numbers.strictreplaces every title and name with a label such asTask k3f7az. It removes notes and replaces checklist items with a count. Onlystrictremoves all titles.- At every level, ids are kept. Anyone who also has your database can map them back to titles. Dates, counts and findings stay too, and they reveal how you work.
What it does
tidyline reads your Things database and reports on it: counts, health checks, a decoded view of your repeating tasks, a JSON export, and an anonymised prompt you can give to an AI assistant yourself.
What it does not do:
- It never writes to the Things database.
- It never reads the database file directly. It makes a private copy (a snapshot) with the SQLite backup API and reads that, then deletes the copy. Things can stay open while it runs.
- It makes no network calls. None of the current commands needs a network.
- It never reads or exports the URL-scheme auth token.
- It never changes, completes or opens anything in Things.
Install
You need Python 3.10 or later and uv or pipx.
uv tool install tidyline
or
pipx install tidyline
Then check it works:
tidyline --version
To upgrade:
uv tool upgrade tidyline
From source (for development)
git clone https://github.com/rathodlaxman/tidyline.git
cd tidyline
uv sync
uv run tidyline --version
macOS may block Terminal from reading Things' data folder. If doctor says so,
open System Settings, Privacy & Security, Full Disk Access, turn it on for your
terminal app, then quit and reopen the terminal.
Quick start
These examples use the installed tidyline command. In a source checkout, run
uv run tidyline instead, from the folder you cloned. Write outputs outside any
git folder, so they cannot be committed by accident. The examples use
~/tidyline-out/, which must exist first:
mkdir -p -m 700 ~/tidyline-out
tidyline doctor
tidyline report
tidyline check
tidyline recurrence
tidyline export --out ~/tidyline-out/export.json
tidyline prompt --out ~/tidyline-out/prompt
tidyline decode ~/tidyline-out/answer.md --out ~/tidyline-out/answer-decoded.md
doctorfinds the database and checks permissions, the schema and versions.reportprints a text summary. It shows counts only, including counts of repeating templates. No title appears in it;recurrenceshows the titles.checkruns the health checks.recurrencelists every repeating template with its readable rule.exportwrites a JSON export. Add--anonymise lightor--anonymise strictto anonymise it.promptanddecodeare described below.
Every command accepts --db PATH for a database in a non-standard place. You
can also set the THINGSDB environment variable. Add --verbose before the
command for more detail when something fails.
Guides
- Getting started: a step-by-step guide for people who have never used a command line tool.
- Fixing the problems tidyline finds: what each health check means and how to fix it in Things 3.
Commands
| Command | What it does | Options |
|---|---|---|
doctor |
Finds the database and checks permissions, schema and versions. | --db |
report |
Prints a text summary, or writes it to a new file. | --out PATH, --include-completed, --no-color, --db |
check |
Runs the health checks. | --only ID [ID ...], --pattern REGEX [REGEX ...], --json, --db |
export |
Writes a JSON export to a new file. | --out PATH (required), --anonymise {none,light,strict}, --db |
recurrence |
Lists every repeating template with its readable rule. | --raw, --db |
prompt |
Writes an anonymised payload and a filled audit prompt. | --anonymise {light,strict}, --context FILE, --out DIR, --copy, --db |
decode |
Replaces strict labels in an answer with your real titles. |
ANSWER_FILE, --out FILE, --db |
Files written with --out get mode 0600, and an existing file is never
overwritten.
Exit codes:
| Code | Meaning |
|---|---|
| 0 | Success. For check: no warnings. |
| 1 | Only check uses this: at least one warning-level finding. |
| 2 | Usage error, such as a bad option, a bad regular expression or an output path that already exists. |
| 3 | The run failed: the database could not be found or read, or a file could not be written. |
Using an AI assistant without sending anything automatically
tidyline never sends your data anywhere. prompt prepares two files, and you
decide whether to attach or paste them into an assistant yourself.
tidyline prompt --context my-context.txt --out ~/tidyline-out/prompt
- The default is
--anonymise strict. There is nononeoption forprompt. --context FILEis a plain text file with sections labelledRole:,Priorities:andDates:. It is added to the prompt exactly as you wrote it. A section you leave out stays as a visible placeholder, and the preview warns you.--out DIRwritestidyline_payload.json(your open work with ready-made counts, anonymised) andtidyline_prompt.md(the filled prompt).--copyputs the prompt on the clipboard withpbcopy. With neither option, nothing is written.- The payload holds open work only. Completed, cancelled and trashed to-dos, and completed or trashed projects, appear only as counts in its summary.
promptalways prints a preview first: the level, the counts, what was removed or replaced, what was left out, and your context text. The last line says nothing has been sent anywhere.- The text report is never part of the output.
- Use a private chat (in Claude, an incognito chat) with memory switched off, and also check the assistant's profile and personal-preference settings. Claude's help says incognito chats do not use memory but can still use profile information such as custom styles and personal preferences (https://support.claude.com/en/articles/12260368-use-incognito-chats, read 2026-10-11). Clear or pause those settings if you do not want them to appear in the answer. Other assistants have similar settings; check yours.
At strict, titles are hidden, so the assistant cannot judge how your tasks are
worded. The prompt tells it to skip that part. Use --anonymise light if you
want task wording reviewed, and accept that light leaves titles in place.
To turn the answer back into real titles, save it to a file and run:
tidyline decode ~/tidyline-out/answer.md
decode recomputes each label from the current database, so it needs no
mapping file. It needs the same database the prompt was made from, and light
output needs no decoding.
- It replaces whole labels such as
Task k3f7az, and bare codes such ask3f7azwhen the code belongs to exactly one item. A code shared by several items is left as it is. - Titles go in double quotes, unless the answer already quoted the label or put it in backticks.
- Long titles are cut to 80 characters, and line breaks in titles become spaces.
- In a markdown table row, a
|inside a title is written\|, so the table keeps its shape. - When two open items share a title, each gets its project or area:
"Sort the mail (in Household)". - It reports how many labels and codes it replaced, and how many it left because they were ambiguous or matched nothing. The report never shows titles.
The decoded text contains real titles, so treat it as personal data.
Health checks
tidyline check exits with 1 if any warning is found. Info findings do not
change the exit code. The findings themselves carry ids and fixed messages.
The text output looks up the titles and shows them on your own screen, so do
not paste it into a public place. check --json prints ids and fixed messages
only, with no titles.
| Check | Severity | What it finds |
|---|---|---|
open_in_trashed_project |
warning | Open to-dos inside a trashed project. The other checks leave these out. |
unfiled_open |
warning | Open to-dos with no area and no project, outside the Inbox. |
title_hygiene |
warning | A stray · at either end, leading or trailing spaces, doubled spaces, and matches of your own --pattern regular expressions. |
possible_duplicates |
warning | Open to-dos whose titles match after Unicode normalisation, case folding, apostrophes removed and other punctuation treated as spaces. |
dated_in_someday_project |
warning | To-dos with a start date inside a Someday project. They will never surface. |
active_project_no_open_tasks |
warning | Active projects with no open to-dos and no live repeating template. |
recurrence_not_recognised |
warning | Repeating templates whose rule tidyline cannot read or does not recognise. |
leftover_templates |
info | Repeating templates whose project is trashed or missing. |
someday_in_active_project |
info | Someday to-dos inside active projects. |
outside_heading |
info | To-dos outside any heading in projects that use headings. |
A finding for one of your own patterns says only "matches user pattern N". The text output on your screen shows the pattern next to it; the JSON does not.
Things does not document its database
tidyline reads columns that Things does not document, because there is no public API for this. These include:
- the repeating-task columns
rt1_recurrenceRule,rt1_repeatingTemplateandrt1_instanceCreationPaused; startBucket, which marks This Evening;deadlineSuppressionDate, which keeps an overdue deadline out of Today.
Repeat rules are stored as property lists whose meaning was worked out by comparing them with what Things shows. A rule tidyline does not understand is reported as not recognised. It is never guessed.
Before reading, tidyline checks that the tables and columns it needs exist. If they do not, it stops with a message that names what is missing, the versions it was tested with, and what to do next. See docs/COMPATIBILITY.md for what has been tested and what has not.
Not yet built: the HTML report and the opt-in model integration.
Development
uv sync
uv run pytest
uv run ruff check .
uv run ruff format --check .
Tests use only a synthetic database built by tests/fixtures/build_fixture.py.
The real Things database is never used in tests, and no database, export or
report is ever committed. Network sockets are blocked while the tests run.
Golden files under tests/fixtures/ are regenerated with
TIDYLINE_UPDATE_GOLDEN=1 uv run pytest. Review the diff before committing.
The design and the decisions behind it are in docs/DESIGN.md. How repeat rules are read is in docs/RECURRENCE.md.
Licence
MIT. See LICENSE. tidyline depends on things.py, which is licensed under Apache 2.0.
Metadata
Release files for tidyline 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tidyline-0.2.1.tar.gz | 59.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tidyline-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 133.2 kB
Release files / tidyline-0.2.1.tar.gz
| Download URL | tidyline-0.2.1.tar.gz |
|---|---|
| Size | 59.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6780e4cc3cad2ba1ec89603348fc290b26cc6c9f1490c1596b67b3b206c6a909
|
|
BLAKE2b-256 checksum How to use checksums |
622ba0fa2d46cd0421131e9f75c7caea3ba3893a9ced27e47a4aa91f1050da50
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / tidyline-0.2.1-py3-none-any.whl
| Download URL | tidyline-0.2.1-py3-none-any.whl |
|---|---|
| Size | 73.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
52eb7b765a4e27933885032924793e08e5420b3cbd0530311d247327df55ab96
|
|
BLAKE2b-256 checksum How to use checksums |
e9417e2fabfcbf313448eb73501eb6c029f601cf9d4dbff4970a27a8c1efd3b1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|