Docs
Reference

Changelog

What changed in each release, newest first. These are the release notes published with each version on GitHub, where the downloads are.

v0.11.0

· Downloads on GitHub

things-cli v0.11.0 — smaller JSON for agents

JSON listings are now sized for agents, and there is a new things doctor for checking database access. Most of the rest is a refactor of the write and import code, with no change in behaviour, plus a set of fixes to how --when times are sent and checked.

Behaviour changes

  • -j listings print a default set of fields. list views, search and projects used to print every field of every row. They now print the fields an agent needs to pick an item and act on it: uuid, title, type, status, start, startBucket, dates, reminder, project, area and heading titles, tags, repeating and checklist progress. Notes, parent uuids, sort indexes and the creation date are left out. On a 94-task list the output drops from 90 KB to 29 KB. --fields all prints the full record exactly as before, and things show <uuid> -j is unchanged. (#454)
  • --fields (-f) picks the keys yourself. things anytime -j --fields uuid,title,deadline prints only those keys, in that order. Unknown names are refused with the unknown field token and the list of valid names. (#451, #459)
  • Weekdays and evening times are sent and checked. friday and friday@9pm are sent as the date Things gives them (the next such day after today, so saturday@9pm on a Saturday is next week) and read back. evening@6pm is sent as evening@18:00 and checked to be in This Evening today. import accepts and resolves them too. (#458)
  • A --when the CLI cannot check is reported as not confirmed. An abbreviation such as fri or a phrase such as next friday that Things understood used to print the item as if confirmed. add, project add, edit and import now report it as not confirmed (when not checked) (JSON confirmed: false, reason: when), exit 0. (#453, #458)
  • Dated --when times are sent as a date. today@6pm, tomorrow@9am and <date>@<time> are sent to Things as YYYY-MM-DD@HH:MM and read back, in add, edit and now import, so a time Things misfiles is caught. A bare 12-hour time such as 9:30pm is sent as 21:30. (#443, #446, #449)
  • Project refs say “project not found”. open --project and project edit refuse a ref that names a to-do, and every kind of miss (title, row number, uuid) now reports the project kind. (#448, #444, #445)

New

  • things doctor checks that the Things database can be found and opened, and reports a permission denial (Full Disk Access) as such. (#447)

Also

  • A padded --list, --area or --heading name is sent trimmed when only the trimmed name matches. A title that really has the spaces still matches as typed. import sends names as given. (#456)
  • The missing-area and “–heading needs a list” warnings use one wording across add, edit and import, and a list given by uuid is named by its title. (#450)
  • Large refactors of the import, edit and database code, each checked against the previous binary with a comparison runner now in tools/compare/. (#431–#455)
  • The README puts Install first. (#457)
  • The bundled skill and the docs at things.rlew.io are updated. Run things skill install after upgrading.

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.10.0...v0.11.0

v0.10.0

· Downloads on GitHub

things-cli v0.10.0 — the CLI shows what Things shows

A big release, this one: three headline changes and a long list of fixes. Everything since v0.9.0, over a hundred PRs.

The CLI shows what Things shows. Every view was compared against the app row by row. today, anytime, upcoming, someday and the project, area and tag pages now list the same rows in the same order as the app: a deadline that has arrived pulls an item into Today, a scheduled Someday item appears in Upcoming, This Evening sits last, and a to-do you completed stays where it was until Things moves it to the Logbook. That last one changes the default output, so --open-only and the open_only config key give the old lists back.

Writes behave the same way every time. A lot of this release went on validating that each command does one thing and says what it did. Every write reads its result back from Things, and the refusals and --json error tokens are shared across add, edit and import. Behaviour changed in a few places, mainly so that an agent driving the CLI gets a deterministic answer rather than a silent miss: an item that landed somewhere else, a --when Things ignored, or a title that happened to match a different task.

Listings fit the terminal. Rows adapt to the width they are printed at. Long titles are cut before any column is dropped, dates switch to a compact form (due:Tue, 21 Oct) when the full date will not fit, tags shorten to the first plus a count, and checklist progress and start dates appear on rows when there is room. Piped output is byte-identical to before, so scripts and agents see no change. (#298–#319) Here is things today at 80 columns on v0.9.0:

v0.9.0: rows overflow and wrap, tags and dates dropped

and on v0.10.0:

v0.10.0: titles cut, tags and compact dates kept

Behaviour changes

  • Default views include closed items Things still shows. inbox, today, anytime, upcoming, someday, bare things, bare --project/--area listings and show --agent on a project now list to-dos completed or cancelled but not yet moved to the Logbook, as the app does. --open-only gives the old output, and the new open_only config key makes that the default. --include-completed is accepted but does nothing. (#384, #389)
  • add and project add print the created item, in the same form as things show, after reading it back. They exit non-zero if the item never appears, or appears somewhere other than where it was sent. Under --json a misfiled item is reported as one error object with the token misfiled, its uuid and where it landed. (#332, #381, #393)
  • Refs that look like a number but are not a row are refused. +5, #5, # 12, (#12), No. 12, 12. and an all-digit ref that is not a row in the last list now match only an exact title or a uuid. They used to match any title containing them, so things complete '#12' could close the wrong task. Dates, times, amounts and version numbers still match part of a title. (#378, #392, #395)
  • import refuses payloads Things would reject. A creation-date or completion-date without a full date, time and offset, or a non-text list, list-id, heading, heading-id, area or area-id, makes Things reject the whole payload with an error sheet. The CLI now refuses it before sending anything, as import refused with a reason per item. (#391, #394, #397)
  • --json no longer escapes &, < and >. (#286)
  • Plain show prints Created: in local time. JSON timestamps are unchanged (UTC, RFC 3339). (#283)
  • complete, cancel and edit refuse an item that is in the Trash. A row number or uuid that now points at a trashed item used to be completed, cancelled or edited silently with exit 0. It is now refused with exit 1 and the --json token trashed; show still opens it and says Status: Open (in Trash). complete and cancel now print the item they closed, like edit --complete. Switching a closed item between completed and cancelled returns the token already closed. (#407, #406)
  • Reminder times are shown. Task JSON gains reminderTime (HH:MM, omitted when unset); show and show --agent put it on the start line, and list rows show it before the title, as the app does. (#405)
  • import refuses more of what Things rejects, and never drops an item from the read-back. Malformed items (invalid-item), blank titles (blank-title), wrong JSON kinds for any attribute (invalid-type), impossible calendar dates such as month 13 (invalid-date), and payloads that create more than 200 items (too-many-items, since Things shows a confirmation dialog above that) are refused before sending. Two date forms Things accepts (+01:00:00, trailing space) are accepted. A same-titled dated/undated pair no longer fails when both landed. (#412)
  • import refuses what Things would silently mangle. Duplicate keys, titles and notes over Things’ length limits, items on a to-do, creation or completion dates in the future, and a when or deadline Things would misread are all refused before anything is sent, each with its own reason. The full list is on the commands page. (#418)
  • A title that belongs only to a closed or trashed to-do no longer reaches a different open one. complete "Pay rent" with “Pay rent” completed and “Re: Pay rent deposit” open used to close the deposit task via the substring fallback; it now fails with already closed or trashed, lists the matches, and touches nothing. Refs are stricter in the same way: a leading zero or a bare 0 is never a row number, a uuid-shaped ref never matches part of a title, and an empty ref is refused. (#422)
  • edit --heading "" clears the heading and edit --list "" unfiles the to-do, and both are read back; they used to print the old location while Things moved the item. Blank titles are refused everywhere, and a --when that cannot be a real date or time is refused before anything is sent, while a free phrase Things ignored is reported as such instead of waiting five seconds. (#423)
  • Deadlines promote items the way the app does. Any undated open to-do or project whose deadline has arrived is in today and anytime and leaves someday, and one with a later deadline is in upcoming; Inbox items stay in the Inbox. --tag and projects list closed-but-unlogged rows like the other views, projects --open-only hides them, and search skips to-dos inside a trashed project. (#421)
  • Row order matches the app. today puts This Evening last under its own header and orders project groups by schedule; anytime, someday, --area and --tag list projects before loose to-dos and follow heading order inside a project; trash is most-recently-trashed first; plain logbook has a date header per day. (#420)
  • Docs match the binary. --deadline takes English phrases (not today/tomorrow), --when 9:30PM works, RFC 3339 offsets are ignored in favour of the wall clock, open and log print nothing on success, ~ in --db/--config/the db key follows $HOME. (#417)
  • import reads back where each to-do landed. A when Things did not apply (a keyword or date that landed elsewhere, such as today with a past deadline, which Things files in the Inbox) is reported confirmed: false, reason: misfiled with landed, and the import exits non-zero; a free phrase Things ignored is reason: when, exit 0, as add does. (#428)

Listings match the app

  • today orders rows by the day they were placed in Today, newest first, then by their position that day. It also lists undated to-dos whose deadline has come. (#342, #385)
  • inbox, anytime and upcoming follow the app’s rules for deadlines, Someday and scheduled projects, and rows Things has not moved into Today yet. (#288, #347, #349, #354, #356, #366, #370)
  • logbook follows the “Move completed items to Logbook” setting and holds back everything Things has not logged. (#357, #362)
  • A --project listing is in the order of the project’s page in the app. (#351)
  • To-dos of a project with no area sit in the no-area group at the top of Today, Anytime and Someday, not after every area. The Logbook no longer hides the logged to-dos of a project that is completed but not yet logged. (#410)
  • Names match the way Things matches them: exact case first, then full Unicode case folding, normalisation, and compatibility forms for project and area titles. (#284, #287, #327, #341, #358, #361, #368, #371)

Writes are checked

  • add --project/--list and project add --area accept a uuid. When several projects or areas share a title, the CLI picks the one Things files into (the smallest uuid; a project beats an area) and says so on stderr. It also notes when the target is completed, cancelled or in the Trash. (#343, #344, #390)
  • add, edit and project add/edit warn when a list, heading or area will not resolve, using Things’ own title rules. (#352, #359, #367, #372)
  • The read-back checks the When bucket (Today, This Evening, Anytime, Someday or a date) as well as the list and heading. A --when or move Things ignores, or a reminder the item already has, returns at once instead of waiting. (#372, #382, #393, #398, #399)
  • complete, cancel, edit --complete and edit --cancel on an item that is already in that state do nothing and exit 0. Switching an item between completed and cancelled is refused. (#384, #388)
  • import reads back every item it creates, checks it landed where Things files it (following Things’ measured rules for list, list-id, heading, heading-id, area and nested items), checks a completion-date was kept, and exits non-zero naming each item that did not. (#355, #374, #391, #394, #397)
  • Checklist-only edits confirm. (#334)
  • add confirms a to-do whose title had a newline (Things stores it as a space), and the search hint it prints on failure is safe to paste. (#404)
  • edit --tags/--add-tags naming a tag Things does not have treats that tag as no change, so an edit that would otherwise be a no-op returns at once instead of waiting out the read-back and failing. (#330)
  • A row number from a listing of another database (--db <backup> then complete 2) is refused, and the re-run command printed with a stale-row error keeps --config. (#337, #350)
  • To-dos inside a repeating project template count as repeating, so the writes Things drops silently on them are refused before anything is sent. (#340)
  • The read-back accepts a new item in any open project or area sharing the requested title, instead of failing a correct add or import when titles collide. (#383)
  • A missing row during a status check is retried until the deadline rather than reported at once, and a second prompt in one command no longer loses the input the first one read ahead. (#333)
  • edit --when today on an item still in Today from an earlier day is a no-op that returns at once, and --when <today's date> on it is no longer reported as misfiled. Things keeps such an item’s original start date; the docs now say so. A dropped write on one of these rows that still has its reminder is reported as not applied. (#408)

things update

  • New command that updates the CLI the way it was installed (Homebrew, go install or the release binary). It installs only the release it checked and stops if that check fails. It uses GH_TOKEN or GITHUB_TOKEN when set, and falls back to the github.com releases redirect when the API check fails. (#306, #308, #373, #380, #386)

Also

  • “Today” is read once per query from one clock, so a listing that runs across midnight judges every row by the same day, and the day’s bounds are right in zones where midnight is skipped or repeated. (#396)
  • Docs: --include-completed is documented only where it is accepted; the config template’s example db path now has the ThingsData-XXXXX/ segment; the database lookup is derived from $HOME and --db/db is the only override. (#406)
  • The docs site has a Changelog page built from the GitHub release notes, linked from the version on the home page. (#315)
  • Go toolchain 1.26.9 (GO-2026-6617). (#411)
  • The bundled skill and the docs at things.rlew.io are updated for all of the above. Run things skill install after upgrading.

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

🐛 Bug Fixes

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.9.0...v0.10.0

v0.9.0

· Downloads on GitHub

things-cli v0.9.0 — edits confirm what happened

things edit and things project edit used to exit 0 with no output, so an agent had to run things show to learn whether the change had landed. They now wait for Things to record the write and print the item, in the same form as things show, so one call does both. How long the CLI waits for that confirmation can now be configured. Two behaviour changes, below.

Behaviour changes

  • edit and project edit print the edited item. The output is the same as things show for that item, plain or --json. Anything that expected these commands to print nothing needs updating. (#280)
  • An edit Things drops now fails. The CLI waits for the item’s modification date to change and exits non-zero with edit did not apply: … if it never does. Before, a dropped edit reported success. An edit that only re-sets values the item already has is caught before the wait when it uses --title, --notes, --tags, --add-tags or a --deadline date. Any other flag, --when included, still waits and reports the same error, because Things does not record a no-op as a change. The error says to check with things show <uuid> before retrying. (#280)

Also

  • --no-verify and --duplicate edits print a line saying the edit was sent but not confirmed, with "confirmed": false and a reason under --json, rather than printing nothing. (#280)
  • The read-back wait is configurable, with --verify-timeout or the verify_timeout config key, and defaults to 5s. It applies to every write that is read back: complete, cancel, edit, project edit, import and tag add. In v0.8.0, complete, cancel, import and tag add waited 10s before reporting a change as not applied; they now give up after 5s unless configured otherwise. (#281)
  • The bundled skill tells agents that exit 0 with the item printed means the edit is confirmed, and to put several changes to one item in a single edit. Run things skill install after upgrading. (#280, #281)

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.8.0...v0.9.0

v0.8.0

· Downloads on GitHub

things-cli v0.8.0 — parity with the app’s lists

Every list in Things.app now has a CLI view that returns the same rows in the same order, projects included. Until now a project scheduled for Today was visible in the app and absent from things today, so an agent driving the CLI never saw it. Every view was measured against the app’s own lists by uuid, and every one of them now matches. Two JSON fields became words, listings are deterministic, agents are steered to UUIDs, and the code behind the views was reorganised so the next parity fix is one table entry. Two breaking changes, below.

Breaking changes: type and start are strings

type and start on task rows in every --json output were Things’ internal integers. They are now words, so a caller can tell a project from a task, or a deferred item from a scheduled one, without a lookup table. Anything matching on type == 1 or start == 2 must change. On input, the legacy integers are still accepted. start also appears on things projects -j rows. (#208, #214, #218, #223, #241, #251)

BeforeAfter
"type": 0"type": "task"
"type": 1"type": "project"
"type": 2"type": "heading" (never returned by any command)
"start": 0"start": "inbox"
"start": 1"start": "anytime" (Today when it carries a date)
"start": 2"start": "someday" (Upcoming when it carries a date)

The word for a to-do is task everywhere the CLI speaks, in JSON values, error payloads, the agent brief and the docs. The one exception is the things import payload, which keeps Things’ own spelling to-do. Run things skill install after upgrading so agents pick up the new skill, which documents all of this.

Projects appear where the app shows them

  • today, upcoming, someday, logbook, trash and deadlines include projects. A project scheduled, deferred, closed, trashed or due is a row with "type": "project" in JSON and a (project) marker in plain text. anytime is the exception, as in the app: it lists a project’s tasks under the project as a group header rather than as a row. The bare --area and --tag listings include projects too; --project X never matches a project itself. (#201, #205, #206, #209, #212, #216, #213, #220, #217, #236, #222, #232)
  • logbook includes cancelled items, with status telling them apart. (#210, #225)
  • logbook and trash fold a closed or trashed project’s tasks into the project’s row, as the app does; the same fold applies under --include-completed. Ask for the project instead: things --project <uuid> on a closed project returns its tasks whatever their status, and things show <uuid> --agent lists them with their real status marks instead of claiming the project has no open tasks. Logbook went from 1334 rows to the app’s 910 on the author’s data, row for row. (#229, #235, #249, #252, #253, #260)
  • Closed items file by calendar day. An item you close stays in Today and Anytime, struck through in the app, until midnight, then moves to the logbook. things today --include-completed and the new things anytime --include-completed show them until then; --include-completed used to keep everything closed since the last “Log Completed Now”. (#230, #233, #238, #247)
  • someday no longer surfaces tasks that live inside a project. The app keeps a deferred task inside its project, whichever list the project is in; measured with a probe project created and deleted through the app. things someday --project X is now rejected like the other impossible filters. (#211, #228)
  • Naming a trashed project with --project <uuid> reaches its contents in every view. The app returns a trashed project’s untrashed children when asked for them directly, and so does the CLI now, in today, anytime, logbook and the rest, not only in the bare things --project form. Trash keeps its fold, so a task trashed on its own account inside a later-trashed project stays where the app keeps it. (#263, #273)
  • Repeating templates, headings and trashed items stay excluded from these views exactly as before.

Listings are ordered as the app orders them, and deterministically

  • today, anytime and someday arrange rows as the app does: unfiled items first, then areas in area order, and inside an area its own tasks before those of its projects. upcoming is ordered by start date. Closed items shown under --include-completed stay where they were rather than sinking to the end. (#217, #236, #237, #239)
  • Every view, search and lookup ordering ends in a total tiebreak, so the same list run twice numbers the same items the same way and things complete 3 acts on the row you read. Deadlines and logbook had no tiebreak at all. Numbers still move as items are added, closed or rescheduled. (#221, #226)

Agents: use UUIDs, and --json no longer touches the row cache

  • A --json listing no longer writes the numbered-row cache. JSON output never showed row numbers, so a JSON listing could only move the numbers a person had just read, and two agents on one machine could race each other into completing the wrong task. Numeric references keep resolving against the last plain-text listing. The bundled skill and https://things.rlew.io/agents/ now tell agents to use --json and act on uuid, never on row numbers, with every example resolving a UUID first. Thanks to @davidolrik for the report. (#246, #261)
  • things skill install honours each agent’s config-directory variable: $CLAUDE_CONFIG_DIR for Claude Code (thanks to @davidolrik for the fix), $CODEX_HOME for Codex and $PI_CODING_AGENT_DIR for Pi, each with that tool’s own rule for tilde expansion. (#227, #234, #250, #254)
  • things projects -j reports start, startDate and deadline, and its taskCount and openCount are documented, with a recipe for finding projects with no open tasks and the caveats that go with it. (#202, #204, #203, #207)
  • References and filters match literally. A task reference, a search query and the --project, --area and --tag filters used to be SQL LIKE patterns, so things complete '20_30 review' could resolve to and complete 20:30 review, and things search '50%' matched everything. % and _ are now characters to find. (#262, #266, #267, #268)
  • Row numbers expire after four hours. The numbered-row cache now records when the listing ran and which command printed it, and a bare number against a listing older than four hours is refused with an error that names the listing to re-run, or the UUID to use instead, under the stable token stale list cache. A cache written by an older release counts as stale, so the first numeric reference after upgrading asks for a fresh listing. (#265, #271)
  • A title shared by a project and a task is an ambiguity, not a coin toss. things project edit Chores used to resolve the first row with that title, land on the task and recommend a retry that edited the wrong item. An exact title that matches more than one open item now returns the existing ambiguous task error listing each candidate with its type and UUID, so the caller picks by UUID. (#194, #270)
  • The jq select examples in the agent docs were missing .[] | and failed against the JSON array; all are fixed. (#225)

Under the hood

Each list view is now described once, in one table, with a golden test that pins the exact SQL for every view, filter and flag combination; the four copies of the column renderer became one; main.go was split by command group; the status, type and start codecs share one implementation. None of this changes output: it was verified byte for byte against the previous binary across hundreds of live invocations. (#240, #242, #243, #248, #258, #259, #264)

Also

  • edit and project edit refuse the wrong kind of reference with the same structured error instead of opening a things:///update URL for the wrong kind of item. (#190, #193, #219, #231)
  • An empty listing that names a repeating project template says so. things --project <template> prints a one-line note on stderr that the template’s tasks are not listed, instead of an unexplained empty result. (#174, #272)
  • The reference site at https://things.rlew.io moved to Hugo with a new landing page, an agents page, a social preview image and analytics, and rebuilds on each release. Local builds stamp version, commit and date. (#183, #185, #186, #188, #192, #195, #198)

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

Other Changes

New Contributors

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.7.0...v0.8.0

v0.7.0

· Downloads on GitHub

things-cli v0.7.0 — a config file, tag creation, and output an agent can trust

A TOML config file for persistent defaults, tag creation from the CLI, a things show --agent hand-off format, a repeating view, machine-readable --json errors, and fixes to what lookups and views return.

Settings live in a file now

  • ~/.config/things-cli/config.toml supplies defaults for json, color, db, no_verify, strict_tags, create_tags, assume_yes and hints. Flags still win per run. --config PATH or THINGS_CLI_CONFIG point at another file, $XDG_CONFIG_HOME is honoured, and the file is optional: without one, things runs on its built-in defaults. things config path, config show and config init (writes a commented template) round it out. Unknown keys and malformed TOML fail with one line naming the file. Full reference at https://things.rlew.io/configuration/. (#159, #160)
  • assume_yes is scoped to complete and cancel; it cannot reach skill uninstall, whose --yes means delete. (#158, #169)

Tags can be created from the CLI

  • things tag add <name>... creates tags over AppleScript, skipping names that already exist (case-insensitive) and reading the tag list back to confirm. --create-tags on add, edit, project add, project edit and import creates the unknown tags before the write instead of warning. --create-tags with --strict-tags is rejected. Tag names are escaped for AppleScript. (#144, #150)

Handing a to-do to an agent

  • things show <ref> --agent prints a self-contained Markdown brief: the to-do, its notes and checklist, and the exact UUID-based commands to close it out, ready for things show 3 --agent | claude -p "action this". Plain list and search output on a terminal ends with a one-line hint; --no-hints or hints = false turns it off. Notes are fenced and labelled as content so a to-do cannot forge the closing-out section. (#153, #172)
  • --json is now non-interactive and its errors are JSON. An ambiguous or missing ref no longer opens the picker or prints help text to stdout; you get one object with a stable error token (ambiguous task, not found, import refused, import partially applied, or error), the message, the query and the candidates or per-item details. Exit codes match the plain path. (#152, #157, #161, #166, #164, #168)
  • --yes / -y on complete and cancel skips the project confirmation, so things --json complete <project> --yes works from a script. (#158, #169)

Lookups and views return what they claim

  • Project headings never come back as tasks from show, edit, complete, cancel, open, search or the numeric index cache. A heading with the same title as a to-do no longer makes the lookup ambiguous. (#146, #149)
  • Repeating templates have their own view. things repeating lists to-do and project templates (projects marked (project)), and they are gone from someday, the catch-all project view and things projects. Generated instances were never affected and still list in today and upcoming. (#147, #154, #165, #170)
  • A title shared by a template and its generated to-do resolves to the to-do, so things complete "Water plants" no longer hits the repeating refusal while an actionable instance exists. The template is still reachable by UUID or from things repeating. (#156, #163)
  • To-dos under a trashed project are hidden in inbox, today, upcoming, anytime, someday and deadlines, not just the filtered views. trash and logbook still show them. (#155, #162)
  • To-dos inside a repeating project template are hidden from the same views, since the app only shows the generated copies. (#171, #173)
  • import with operation: update goes through the repeating check and the read-back. A payload that sets when, deadline, completed or canceled on a repeating item is refused whole, with the path of each offending item, before anything is sent. Status changes are read back after the write with one shared timeout. completed: false and canceled: false count as status changes, and canceled takes priority over completed, as the Things docs specify. (#145, #151)
  • The things tag add suggestion printed for unknown tags is now shell-quoted, and --json=1, -jv and other flag spellings render parse errors as JSON. (#164, #168)

Also

  • Import checks resolve every item in one query per round instead of one per item. (#167, #175)
  • The bundled agent skill was reorganised and audited against every --help; open is no longer described as a write, and the skill, config and tag commands are covered. Run things skill install again to pick it up. (#176)

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.6.0...v0.7.0

v0.6.0

· Downloads on GitHub

things-cli v0.6.0 — filters that tell the truth, writes that fail loudly

Four fixes from recent use, three of them about the CLI quietly reporting the wrong thing. Two new flags.

Filters no longer under-report

  • --project, --area and --tag now search all open tasks by default instead of the Today view. Naming a project previously returned only its Today slice while the header just printed the project name; one project showed 8 tasks where there were 59. An explicit view still wins and is now labelled in the header when combined with a filter. (#140, #142)
  • Tasks nested under a project heading are included in project and area filters, and show reports their project and area. They were invisible before because Things stores the project on the heading row, not the task. (#139, #142)
  • Tasks left open under a trashed project no longer leak into filtered output.
  • Behaviour change: things today "Chores" now means today within Chores. It used to silently switch to the whole-project view.

Complete and cancel now verify the write landed

  • Repeating to-dos and projects are refused up front. Things silently drops status, when and deadline changes on repeating items through both the URL scheme and AppleScript, so cancel used to exit 0 and change nothing. The CLI now checks for a recurrence rule and refuses with a message naming the blocked attributes. show prints a Repeats: line and JSON carries "repeating": true. (#129, #143)
  • Every complete and cancel reads the status back from the database and exits non-zero if it did not change. A new global --no-verify skips the check. edit --complete --cancel together is now rejected. (#143)

Unknown tags are no longer dropped silently

  • Things ignores tags that do not already exist. add, edit, project add, project edit and import now warn on stderr for each unknown tag, and --strict-tags refuses to write at all. Matching is case-insensitive, as Things treats tag names. Creating tags from the CLI is still open as #144. (#102, #141)

Also

  • Dependency bumps: modernc sqlite v1.57, kong v1.16.1, lipgloss v2.0.6. (#118)
  • Release pipeline egress allowlist narrowed to the Apple notary bucket. (#138)

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

📦 Dependencies

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.5.3...v0.6.0

v0.5.3

· Downloads on GitHub

things-cli v0.5.3 — signed and notarized binaries

Infra release: no CLI changes. The binaries themselves are the feature.

Release pipeline

  • Darwin binaries are now Developer ID–signed and notarized by Apple. Both architectures are signed and notarized before archiving, so the tarballs, checksums, and provenance attestations all cover the signed Mach-O. Gatekeeper accepts the binary without any quarantine workaround — the cask’s xattr -d com.apple.quarantine hack is gone. (#130)
  • Release job egress is now blocked by default with an explicit endpoint allowlist (previously audit-only), limiting what a compromised dependency or action could exfiltrate — the job holds signing keys now. (#130)

One caveat: bare Mach-O binaries can’t have the notarization ticket stapled, so Gatekeeper does an online check on first run. Fine for normal installs; only bites a fully-offline first run.

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.5.2...v0.5.3

v0.5.2

· Downloads on GitHub

things-cli v0.5.2 — timestamps back from 2057

Patch release: one bug fix.

Fixes

  • creationDate and stopDate are no longer 31 years in the future. Things stores its absolute timestamps (task creation, task and checklist completion) as Unix-epoch seconds, but they were decoded against Apple’s Core Data reference date (2001-01-01) — so a task created on 2026-08-09 reported "creationDate": "2057-08-09T…" in --json output. Anything sorting or filtering on those fields downstream would misbehave. Scheduling fields (startDate, deadline) use a different encoding and were always correct. (#127)

No CLI surface changes — if you parse creationDate/stopDate from --json output, the values are simply right now.

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.5.1...v0.5.2

v0.5.1

· Downloads on GitHub

things-cli v0.5.1 — cancel projects, honest empty JSON

Patch release: two bug fixes with a supply-chain hardening pass riding underneath.

Fixes

  • things cancel now works on projects. It previously routed every reference through the task-only AppleScript path, so cancelling a project failed with a raw osascript error. Projects now cancel properly, behind the same interactive confirmation complete uses — cancelling a project cascades to its tasks. (#125)
  • Empty --json results emit [], not null. All list-shaped commands (list, search, projects, areas, tags) returned null for an empty result, which breaks jq '.[]' pipelines — including the ones documented in the bundled agent skill. (#124)
  • Date filters on someday are rejected instead of silently matching nothing. Someday items have no start date, so --on/--from/--to could never match; the CLI now says so instead of returning empty output, and the README no longer advertises the impossible combination. (#124)

Small improvements

  • show, complete, and cancel help text now documents the numeric-index reference form (things list today; things complete 3) — it always worked, but only edit admitted it. (#125)
  • things projects gains the -a short flag for --area, matching list and open. (#125)

Supply chain

  • Release artifacts now carry build provenance attestations. install.sh verifies them automatically when the GitHub CLI is present (and refuses to install on a failed verification); check any artifact yourself with gh attestation verify things_<version>_darwin_arm64.tar.gz -R ryanlewis/things-cli. (#122, #123)
  • CI hardening: govulncheck (gating + weekly Security-tab scan), CodeQL, dependency review, and a locked-down release pipeline. (#122)

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

📦 Dependencies

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.5.0...v0.5.1

v0.5.0

· Downloads on GitHub

things-cli v0.5.0 — clearer JSON status, leaner today

Minor release. Two behaviour changes worth a look before you upgrade any automation, plus dependency maintenance.

Behaviour changes

  • JSON status is now a string. Task, Project, and ChecklistItem status fields render as "open" / "cancelled" / "completed" in JSON instead of the raw Things integers (0 / 2 / 3). The bare ints were an implementation detail that read like a rank. Plain-text output is unchanged. The decoder still accepts both the string name and the legacy integer, and any unrecognised code round-trips as its number — but consumers that parse our JSON status as a number will need to read it as a string. (#107, closes #105)
  • things today excludes completed tasks by default. It now returns only open tasks; completed/cancelled items Things still shows in Today are available behind a new --include-completed flag (UI-parity). For automation, “in today” now means “still to do”. (#109, closes #106)

Maintenance

  • Bumped lipgloss, golang.org/x/term, golang.org/x/sys, and modernc.org/sqlite; actions/checkout → v7 in CI. (#100, #108)

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

📦 Dependencies

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.4.2...v0.5.0

v0.4.2

· Downloads on GitHub

things-cli v0.4.2 — fix --when rejecting weekday names

Patch release. things add --when monday was incorrectly rejected with “did you mean today?”. The --when typo detector flags any value within Levenshtein distance 2 of a keyword, and monday is exactly distance 2 from today — so it was mistaken for a typo despite being a valid natural-language date Things accepts.

Fix

  • Weekday names and their abbreviations (monday…sunday, plus mon, tue, wed, thu, fri, sat, sun and common variants) now bypass the typo detector and pass through to Things verbatim. Verified against Things3 that each resolves to the correct upcoming scheduled date.
  • Genuine typos (tommorrow, evning, …) are still rejected as before.

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.4.1...v0.4.2

v0.4.1

· Downloads on GitHub

things-cli v0.4.1 — fix Homebrew completion install

Patch release. v0.4.0 added shell completions, but the Homebrew cask passed the shell name as --shell=bash (Homebrew’s :arg format) while things completions takes the shell as a positional argument. The mismatch made brew install print “Failed to generate … completions” warnings and write no completion files.

Fix

  • The cask now calls things completions bash|zsh|fish with the shell as a bare positional argument (Homebrew’s default when shell_parameter_format is omitted), matching the CLI. brew install / brew upgrade now generates the bash, zsh, and fish completion files as intended.

If you installed v0.4.0 from Homebrew, run brew update && brew reinstall ryanlewis/tap/things to pick up working completions.

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.4.0...v0.4.1

v0.4.0

· Downloads on GitHub

things-cli v0.4.0 — shell completions

things <TAB> now completes subcommands, flags, and enum values in bash, zsh, and fish. Homebrew sets it up for you; everyone else runs one line.

Headline

  • Shell completions for bash, zsh, and fish (#94, #95). The Homebrew cask generates them on install, so brew install ryanlewis/tap/things gives you things <TAB> with no extra steps. On other install paths, load them yourself — source <(things completions bash), source <(things completions zsh), or things completions fish | source. Completions are driven from the live command tree, so they never drift from the CLI: subcommand names, flag names, and enum values like --color auto|always|never.

Under the hood

  • Migrated styling to lipgloss v2 (charm.land/lipgloss/v2) (#93). No visible change to list/show output; JSON (-j) stays script-stable.
  • Routine dependency updates (#83, #91, #92).

Docs

  • The bundled agent skill (things skill show / installed SKILL.md) now spells out every things open flag (-p/-a/-t/-q/--filter/--background) and the edit / project edit prepend/append/checklist/*-id flags instead of trailing ... (#89). Reinstall it (things skill install claude, …codex, …pi) to pick up the new text.

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

📦 Dependencies

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.3.1...v0.4.0

v0.3.1

· Downloads on GitHub

things-cli v0.3.1 — clean uninstall

Patch release: the Homebrew cask now ships a zap stanza so a full uninstall cleans up after itself.

Change

  • Cask zap trash: now lists ~/Library/Caches/things-cli (the last-list cache that backs things complete 1-style indices), so brew uninstall --zap things leaves no orphan state behind (#86)

Plain brew uninstall things still leaves the cache alone — zap only fires with --zap (or brew zap things separately), so a routine uninstall + reinstall keeps your indices intact.

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.3.0...v0.3.1

v0.3.0

· Downloads on GitHub

things-cli v0.3.0 — Homebrew tap, date filters, polished output

Install with brew install ryanlewis/tap/things and start slicing your lists by date — plus a fresh coat of lipgloss styling on list and show.

Headline

  • Homebrew cask — brew install ryanlewis/tap/things (auto-published from each release into ryanlewis/homebrew-tap)
  • Date-range list filters — narrow any list with --on, --from, and --to (#81)
  • Styled output — list and show now render with lipgloss; JSON output (-j) is unaffected and remains script-stable

Fixes

  • things today keeps completed items visible until you log them, matching the Things app (#74)
  • ThingsDate marshals as YYYY-MM-DD in JSON output (was a packed integer) (#79)

Docs

  • Install page surfaces Homebrew as the recommended option
  • Skill docs cover bulk reschedule, the auth-token flow, and weekday --when parsing (#80)

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

📦 Dependencies

Other Changes

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.2.0...v0.3.0

v0.2.0

· Downloads on GitHub

things-cli v0.2.0 — more writes, safer writes

A bigger surface area for writing back to Things3, plus client-side validation so the URL scheme can’t silently swallow bad input. The docs now live at things.rlew.io.

New commands

  • things edit — update existing tasks (title, notes, when, deadline, tags, checklist, project, area) via the URL scheme
  • things open — reveal a task, project, or area in the Things3 app
  • things import — batch-create items from a JSON payload using the things:///add-json scheme
  • things project add / things project edit — create and update projects
  • things skill — install the bundled Claude Code skill (also supports Codex and Pi agents) so an AI assistant can drive things-cli correctly

Quality of life

  • --when and --deadline are validated and normalized before being sent to Things
  • URL-scheme size and field limits are checked client-side, with clear errors instead of a silently truncated task
  • things today ordering now matches the Things app (projects and area-only items grouped by area index)
  • --todos / --checklist correctly expand literal \n in arguments

Docs

  • Project site published at things.rlew.io — install guide, command reference, about page
  • README rewritten with grouped command tables and capabilities above the fold

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

📦 Dependencies

Other Changes

New Contributors

Full Changelog: https://github.com/ryanlewis/things-cli/compare/v0.1.0...v0.2.0

v0.1.0

· Downloads on GitHub

🎉 First release — things-cli v0.1.0

A command-line interface for Things3 on macOS. Reads tasks, projects, areas and tags directly from the Things3 SQLite database (read-only) and writes via the official things:/// URL scheme and AppleScript — so Things stays the source of truth and your data never leaves the machine.

Highlights

  • Read anything — things today, things inbox, things upcoming, project/area/tag filters, full-text things search, and things show with numeric references into the last list
  • Write safely — things add creates tasks via the Things URL scheme; things complete / things cancel / things log drive Things via AppleScript
  • JSON everywhere — -j on any command emits structured output for piping into jq
  • Pure Go — single static binary, no cgo, no external SQLite dependency (modernc.org/sqlite)
  • Installable three ways
    • curl -fsSL https://raw.githubusercontent.com/ryanlewis/things-cli/main/install.sh | sh (verifies checksums)
    • go install github.com/ryanlewis/things-cli/cmd/things@latest
    • Download a tarball from this release and drop the binary onto your PATH

Requirements

macOS with Things3 installed. Binaries for Apple Silicon (darwin_arm64) and Intel (darwin_amd64).


What’s Changed

📦 Dependencies

New Contributors

Full Changelog: https://github.com/ryanlewis/things-cli/commits/v0.1.0