Skip to content

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 21:45
· 98 commits to main since this release
v0.10.0

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

  • fix: render task timestamps in local time by @ryanlewis in #283
  • fix(output): stop escaping &, < and > in --json output by @ryanlewis in #286
  • fix(open): match --tag and --area names case-insensitively by @ryanlewis in #284
  • fix(db): ignore case beyond ASCII in name and title filters by @ryanlewis in #287
  • fix(list): list anytime tasks due later in upcoming, as the app does by @ryanlewis in #288

Other Changes

  • fix(output): fit task rows to narrow terminals by @ryanlewis in #298
  • fix(output): wrap things show detail values with a hanging indent by @ryanlewis in #299
  • feat(output): show a task's start date beside its deadline by @ryanlewis in #300
  • fix(output): fit the project list and hint line to a narrow terminal by @ryanlewis in #301
  • fix(output): keep the project marker when a title is cut, fit group headers by @ryanlewis in #302
  • refactor(hint): let one terminal check decide the hint and its width by @ryanlewis in #303
  • test(output): pin the terminal check so no test reads the real stdout by @ryanlewis in #304
  • feat(output): cut over-long titles before dropping a column by @ryanlewis in #305
  • feat: add things update to update the CLI the way it was installed by @ryanlewis in #306
  • fix(output): end each row at its last filled cell by @ryanlewis in #307
  • fix(update): never downgrade, and run install.sh from the release tag by @ryanlewis in #308
  • feat(list): show checklist progress on task rows by @ryanlewis in #309
  • fix(output): wrap tabbed and list-like note lines under their text by @ryanlewis in #310
  • fix(output): drop checklist and start columns before cutting a title by @ryanlewis in #311
  • feat(output): switch dates to a compact form before dropping them by @ryanlewis in #312
  • feat(output): shorten tags to the first and a count before dropping them by @ryanlewis in #313
  • test(output): pin piped, wide-rune, dim-cut and tab/newline title rendering by @ryanlewis in #314
  • docs: add a changelog page built from the GitHub release notes by @ryanlewis in #315
  • refactor(output): measure table cells once and fit columns in one flat pass by @ryanlewis in #316
  • refactor(output): pass one layout to every printer, split task rows from headers by @ryanlewis in #317
  • fix(output): print tab and line-break titles on one line in listings by @ryanlewis in #318
  • feat(output): offer common next actions in the listing hint by @ryanlewis in #319
  • refactor(cmd): share the auth token read and warn on its errors by @ryanlewis in #320
  • refactor(db): finish the view table by @ryanlewis in #321
  • refactor(output): replace Print with typed printers and drop go-isatty by @ryanlewis in #322
  • refactor(things): share update params and URL building in UpdateCommon by @ryanlewis in #323
  • refactor(cmd): route prompts and warnings through Deps by @ryanlewis in #324
  • refactor(cmd): share complete/cancel in runStatusChange and the read-back loop in pollUntil by @ryanlewis in #326
  • test: export the db fixture via dbtest by @ryanlewis in #325
  • fix(db): trim surrounding spaces in list and projects name filters by @ryanlewis in #327
  • refactor(cmd): use one shell-quoting rule for printed commands by @ryanlewis in #328
  • refactor(cmd): share edit and project edit flags and Run in runEdit by @ryanlewis in #329
  • fix(edit): treat tags Things does not have as no change in the no-op check by @ryanlewis in #330
  • refactor(cmd): print show and confirmed edit output through printItem by @ryanlewis in #331
  • feat(add): read back the created item and print it as show does by @ryanlewis in #332
  • fix: retry a missing row in verifyStatuses, and keep stdin between prompts by @ryanlewis in #333
  • fix(edit): confirm a checklist-only edit by the checklist itself by @ryanlewis in #334
  • fix(list): show to-dos closed today in a named project under --include-completed by @ryanlewis in #335
  • fix(upcoming): show to-dos closed today under --include-completed by @ryanlewis in #336
  • fix(lookup): refuse a numeric ref from a listing of another database by @ryanlewis in #337
  • fix(add): tighten the read-back's unreadable, hint and warning messages by @ryanlewis in #339
  • fix(repeating): treat to-dos inside a repeating project template as repeating by @ryanlewis in #340
  • fix(list): prefer an exact-case --project title over case variants by @ryanlewis in #341
  • fix(today): list undated to-dos whose deadline has come by @ryanlewis in #342
  • fix(add): warn when --list or --heading will not resolve, accept a uuid, read tags once by @ryanlewis in #343
  • fix: read import's stdin and terminal check through Deps, update the no_verify comment, send area uuids as area-id by @ryanlewis in #344
  • fix(inbox): leave out undated to-dos whose deadline has come by @ryanlewis in #347
  • ci: pin govulncheck and let Renovate bump it by @ryanlewis in #348
  • fix(anytime): leave out to-dos of projects in Someday or scheduled later by @ryanlewis in #349
  • fix(cache): carry --config in a listing's re-run command by @ryanlewis in #350
  • fix(project): order a project listing the way its page in the app does by @ryanlewis in #351
  • fix(project): warn when project add --area names no area by @ryanlewis in #352
  • fix(anytime): list undated Inbox to-dos whose deadline has come by @ryanlewis in #354
  • feat(list): allow --include-completed on a bare --area listing by @ryanlewis in #353
  • fix(inbox): keep a to-do closed out of the Inbox there until it is logged by @ryanlewis in #356
  • fix(import): read back the items an import creates by @ryanlewis in #355
  • fix(logbook): follow the app's "Move completed items to Logbook" setting by @ryanlewis in #357
  • fix(list): prefer the exact-case name for --area and --tag by @ryanlewis in #358
  • fix(add): match --list and --heading titles untrimmed, as Things does by @ryanlewis in #359
  • chore(hooks): judge the main-branch guard by the repository each git command targets by @ryanlewis in #360
  • feat(db): fold names with full Unicode case folding by @ryanlewis in #361
  • fix(logbook): withhold every row Things has not logged yet by @ryanlewis in #362
  • fix(views): list a scheduled row Things has not moved yet under today by @ryanlewis in #366
  • fix(edit): warn when --list, --heading or --area will not match in Things by @ryanlewis in #367
  • fix(db): match names that differ only in Unicode normalisation by @ryanlewis in #368
  • chore(hooks): read the docs reminder's staged files from the repository the commit targets by @ryanlewis in #369
  • fix(json): report a scheduled row Things has not moved yet as anytime by @ryanlewis in #370
  • fix(db): match project and area titles across Unicode compatibility forms by @ryanlewis in #371
  • fix(edit): treat a no-op move as applied and warn on unknown ids by @ryanlewis in #372
  • fix(update): never install blind when the release check fails by @ryanlewis in #373
  • fix(import): do not confirm an item with a dated item's row by @ryanlewis in #374
  • fix(lookup): refuse an all-digit ref that is not a row in the last list by @ryanlewis in #378
  • test(today): record that a deadline move clears its suppression by @ryanlewis in #379
  • fix: pin or stop on odd release tags, run update script under bash, NFC read-back key by @ryanlewis in #380
  • fix(add): check a created item's destination in the read-back by @ryanlewis in #381
  • fix(edit): treat a --when or empty notes append Things ignores as applied by @ryanlewis in #382
  • fix(add): accept any list sharing the requested title in the read-back by @ryanlewis in #383
  • feat(list)!: show closed items Things still shows by default by @ryanlewis in #384
  • fix(today): order rows by reference date, then todayIndex, as the app does by @ryanlewis in #385
  • fix(hooks): ignore git commands inside quotes and heredoc bodies by @ryanlewis in #387
  • feat(config): add open_only to make --open-only the default for listings by @ryanlewis in #389
  • fix(update): send a GitHub token and fall back to the releases redirect by @ryanlewis in #386
  • fix(edit): guard --complete and --cancel on closed items like complete and cancel by @ryanlewis in #388
  • fix(lookup): refuse +N and #N refs instead of matching title fragments by @ryanlewis in #392
  • fix(import): refuse dates Things rejects, name shares-dated-title candidates, decode once by @ryanlewis in #391
  • fix(add): check the list Things picks when titles fold together by @ryanlewis in #390
  • fix(edit): check the --when bucket in the read-back, and tell case-twin headings apart by @ryanlewis in #393
  • fix(import): check destinations the way Things files them by @ryanlewis in #394
  • fix(lookup): refuse any number-like ref that is not a row or an exact title by @ryanlewis in #395
  • test(edit): pin --when today on a row Things has not moved past midnight by @ryanlewis in #398
  • fix(import): follow-ups to the destination and date checks by @ryanlewis in #397
  • fix(edit): leave a row Things has not moved into today where it is for --when today by @ryanlewis in #399
  • fix(db): read today once per query from one clock by @ryanlewis in #396
  • fix(add): confirm a to-do whose title had a newline by @ryanlewis in #404
  • feat(output): show a to-do's reminder time by @ryanlewis in #405
  • chore(go): bump toolchain to go1.26.9 for GO-2026-6617 by @ryanlewis in #411
  • fix: refuse writes to trashed items and print what complete/cancel closed by @ryanlewis in #407
  • fix(db): group area-less projects first and wait for the logbook fold by @ryanlewis in #410
  • fix: bring docs, skill and config template in line with the binary by @ryanlewis in #406
  • fix(edit): count a row in Today from an earlier day as filed today by @ryanlewis in #408
  • fix(import): refuse payloads Things rejects before sending by @ryanlewis in #412
  • test(import): pin the shares-dated-title clock by @ryanlewis in #414
  • fix(edit): refuse writes to a to-do whose project is in the Trash by @ryanlewis in #416
  • docs: fix drift between the docs, the skill, --help and the binary by @ryanlewis in #417
  • fix(import): refuse payloads Things saves differently from what they say by @ryanlewis in #418
  • fix(list): match the app's rows in today, upcoming, --tag, search and projects by @ryanlewis in #421
  • fix(list): match Things.app row order in today, anytime, someday, trash, logbook and --area/--tag by @ryanlewis in #420
  • fix(refs): stop task refs resolving to an item the user did not name by @ryanlewis in #422
  • fix: make write commands report what Things does by @ryanlewis in #423
  • fix(import): read back each created item's when as add does by @ryanlewis in #428
  • docs: add before/after screenshots of today for the v0.10.0 release notes by @ryanlewis in #429
  • docs(release): add v0.10.0 release notes header by @ryanlewis in #430

Full Changelog: v0.9.0...v0.10.0