Skip to content

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 18:53
· 369 commits to main since this release
v0.8.0
8a87785

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)

Before After
"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

  • build: stamp version, commit and date into local builds by @ryanlewis in #183
  • docs: add an animated demo to the README by @ryanlewis in #184
  • docs: add a Working with agents page and make the site the reference by @ryanlewis in #185
  • ci(pages): build the site with Hugo by @ryanlewis in #186
  • ci(pages): let workflow_dispatch deploy again by @ryanlewis in #187
  • feat(site): replace Jekyll with a Hugo site and new landing page by @ryanlewis in #188
  • fix(edit): refuse a project reference instead of opening things:///update by @ryanlewis in #190
  • ci(site): derive the site version from tags and rebuild on release by @ryanlewis in #192
  • fix(project edit): refuse a to-do with the same structured error as edit by @ryanlewis in #193
  • feat(site): add Umami analytics by @ryanlewis in #195
  • chore(claude): remind agents to keep the site docs in step with the CLI by @ryanlewis in #196
  • ci(site): ignore pre-release tags for the site version by @ryanlewis in #197
  • feat(site): add a social preview image and structured data by @ryanlewis in #198
  • chore(og): render the social image with Go instead of Python by @ryanlewis in #199
  • build: pin the Go toolchain with a toolchain directive by @ryanlewis in #200
  • fix(projects): report start, startBucket, startDate and deadline by @ryanlewis in #204
  • fix(list): include scheduled projects in today, upcoming and anytime by @ryanlewis in #205
  • docs(projects): document taskCount and openCount by @ryanlewis in #207
  • fix(list): include projects in someday and logbook by @ryanlewis in #209
  • feat(output)!: render type as a string in JSON by @ryanlewis in #214
  • fix(list): include projects in trash by @ryanlewis in #216
  • fix(list): include projects in deadlines by @ryanlewis in #220
  • feat(output)!: spell the to-do type value task, not todo by @ryanlewis in #223
  • fix(list): include cancelled items in logbook by @ryanlewis in #225
  • fix(list): make view ordering deterministic by @ryanlewis in #226
  • fix(list): keep project to-dos out of someday by @ryanlewis in #228
  • chore(output): say task, not to-do, in the --json error kind by @ryanlewis in #231
  • fix(list): carry projects in the bare-filter catch-all view by @ryanlewis in #232
  • fix(list): file closed items by the calendar day, not manualLogDate by @ryanlewis in #233
  • fix(list): fold a closed or trashed project's to-dos into its row by @ryanlewis in #235
  • fix(list): match the app's anytime and upcoming arrangement by @ryanlewis in #236
  • fix(list): arrange today and someday the way the app does by @ryanlewis in #239
  • feat(list): show items closed today in anytime under --include-completed by @ryanlewis in #247
  • chore(cli): split main.go by command group by @ryanlewis in #248
  • feat(model)!: render start as a string and share one enum codec by @ryanlewis in #251
  • fix: Honour $CLAUDE_CONFIG_DIR by @davidolrik in #234
  • fix(list): fold closed-project to-dos out of the --include-completed views by @ryanlewis in #252
  • fix(skill): honour $CODEX_HOME and $PI_CODING_AGENT_DIR by @ryanlewis in #254
  • docs: state the project rule once, and call a task a task by @ryanlewis in #255
  • docs: say task for the CLI's concept across the site and README by @ryanlewis in #257
  • refactor(db): describe each list view in one table by @ryanlewis in #258
  • refactor(db): move the flag-validation maps into the view table by @ryanlewis in #259
  • fix(list): let a named project lift the closed-project fold by @ryanlewis in #260
  • fix(list): do not write the row cache under --json by @ryanlewis in #261
  • refactor(output): one column renderer, one kind word, one status text by @ryanlewis in #264
  • fix(list): match --project, --area and --tag values literally by @ryanlewis in #266
  • fix(lookup): match a task reference and a search query literally by @ryanlewis in #268
  • test(db): one fixture builder instead of fourteen per-view seeds by @ryanlewis in #269
  • fix(lookup): report an exact title that matches more than one row by @ryanlewis in #270
  • feat(cache): expire the numbered rows after four hours by @ryanlewis in #271
  • fix(list): reach a trashed project's children by naming it in any view by @ryanlewis in #273
  • feat(list): say why a repeating project template lists nothing by @ryanlewis in #272
  • docs(release): add v0.8.0 release notes header by @ryanlewis in #275
  • build(goreleaser): drop the deprecated homebrew_casks url.verified key by @ryanlewis in #276
  • docs(release): keep the v0.8.0 projects section as one list by @ryanlewis in #277

New Contributors

Full Changelog: v0.7.0...v0.8.0