Skip to main content

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.

  • light keeps 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.
  • strict replaces every title and name with a label such as Task k3f7az. It removes notes and replaces checklist items with a count. Only strict removes 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
  • doctor finds the database and checks permissions, the schema and versions.
  • report prints a text summary. It shows counts only, including counts of repeating templates. No title appears in it; recurrence shows the titles.
  • check runs the health checks.
  • recurrence lists every repeating template with its readable rule.
  • export writes a JSON export. Add --anonymise light or --anonymise strict to anonymise it.
  • prompt and decode are 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

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 no none option for prompt.
  • --context FILE is a plain text file with sections labelled Role:, Priorities: and Dates:. 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 DIR writes tidyline_payload.json (your open work with ready-made counts, anonymised) and tidyline_prompt.md (the filled prompt). --copy puts the prompt on the clipboard with pbcopy. 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.
  • prompt always 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 as k3f7az when 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_repeatingTemplate and rt1_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)

Source distribution for tidyline 0.2.1
File Size Uploaded
tidyline-0.2.1.tar.gz 59.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tidyline 0.2.1
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.2

2 release files

This release

0.2.1 This release

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page