Things 3
Drive Things 3 (tasks, projects, areas) through whichever modality fits the environment. Prefer the things CLI whenever there's command-line access to Things — it reads and writes the local database directly, so it's fast and needs no network round-trip. Where there's no CLI access, the fallbacks are: a connected Things MCP (full read + write that syncs across devices, from anywhere), the things:/// URL scheme (any device with Things, write-only), and email-to-Things (unattended, Inbox-only capture).
A Things MCP — a self-hosted server, or an official one Cultured Code may ship — plays the role the CLI plays locally, but reachable from anywhere, so it's the first fallback when there's no command-line access. Match its tools by their things_* base names; the MCP host prepends its own namespace, so the full identifier varies by client.
Pick a modality first
| Modality |
Read state? |
Schedule & update? |
Unattended? |
From phone? |
CLI (things binary) |
Yes |
Yes |
On the Mac only |
No |
URL scheme (things:///) |
No |
Yes (update/json need auth token) |
No — someone must open the URL |
Yes |
| Email-to-Things |
No |
No (Inbox capture only) |
Yes |
Yes (send an email) |
The real axis is attended vs unattended, not headless. A headless session someone is watching can still use the URL scheme — the agent prints the link, the user opens it. Only a routine with no one to act on its output is limited to email.
Decision rules:
- Command-line access to Things (a terminal with the local Things database) → use the CLI. The default: reads (Inbox, Today, search, projects, areas, tags) and the full write surface, all against the local DB.
- No CLI access, but a Things MCP is connected → use it. Some setups expose Things through an MCP server (self-hosted, or a future official one); its
things_* tools give full read + write that syncs across devices, with no Mac or device interaction required. The path for headless, remote, or cloud-routine environments.
- No terminal, but the user can act on the output → build a URL. You construct the
things:///... link; the user (or an iOS Shortcut) opens it on a device with Things to execute. This holds even when the agent itself is headless — being headless doesn't rule out the URL scheme; only the absence of anyone to open the link does. The mobile and attended-remote write/schedule path.
- Fully unattended (a remote/NAS routine with no one watching) → email-to-Things. The only path that needs neither a Mac nor a human. Lands in the Inbox only — no scheduling, no project placement, no updates. Good for dropping raw captures that get triaged later on desktop.
Credentials
Two values are specific to the user's Things setup:
- Auth token — needed only for commands that modify existing data (
update, update-project, json with an update op); plain add / add-project / add-area don't. Things → Settings → General → Enable Things URLs → Manage.
- Mail-to-Things address — the capture address for the email modality. Things → Settings → Mail to Things.
Resolve each at use time: env var first (THINGS_AUTH_TOKEN, THINGS_EMAIL), then private hosted configuration, then ask. Never invent one; never echo either into output, commits, or logs.
In a shell, these belong in the shell profile so an update can't overwrite them; the things CLI reads THINGS_AUTH_TOKEN on its own:
export THINGS_AUTH_TOKEN=…
export THINGS_EMAIL=…@things.email
In hosted environments, check for things.local.md in the skill folder and apply any configuration it contains before asking the user for missing values. This file belongs only in the user's personal copy of the skill; do not commit, share, or redistribute it.
1. CLI (things binary) — desktop, full read + write
Default modality on a Mac. Reads the local Things SQLite DB for queries; writes go through the URL scheme under the hood. Reading Things STATE is only possible here.
Setup (skip if which things returns a path): brew install ossianhempel/tap/things3-cli. The DB lives in the Things app sandbox; the terminal may need Full Disk Access to read it. Override the DB path with THINGSDB or --db=PATH if needed.
Read (queries the local DB)
- Built-in lists:
things inbox, things today, things upcoming, things anytime, things someday, things logbook, things deadlines, things trash
- Time-scoped:
things logtoday (completed today), things createdtoday, things completed, things canceled
- Overview:
things all (Inbox, Today, Upcoming, Anytime, Someday, Logbook, No Area, Areas in one shot)
- Structure:
things projects, things areas, things tags
- Search:
things search "query" — title/notes match; flags --status=incomplete|completed|canceled|any, --project=, --area=, --tag=, --limit=, --all (include completed/canceled/trashed), --json
- List todos with filters:
things tasks --project "Travel", things tasks --area "Work", things tasks --tag focus --search "draft"
- Inspect one item (exact match):
things show "Project Name", things show --id=<ID> --json
- Most read commands accept
--json and --no-header. Get an item's ID from --json output or things search "x" --json.
Write
Preview any write without executing: prepend --dry-run (prints the URL, does not open Things). Add --foreground to bring Things to the front.
- Add todo:
things add "Buy milk" --notes "2% + bananas" --when today --deadline 2026-07-01 --tags "health,phone"
- Scheduling via
--when: today, tomorrow, evening (This Evening), anytime, someday, a date (2026-07-01), or a datetime ("2026-07-01 18:00" → adds a reminder).
- Into a list/heading:
--list "Travel" --heading "Before" (or --list-id=<ID>).
- Checklist: repeat
--checklist-item "Passport" --checklist-item "Tickets" (max 100).
- Multiple at once:
--titles "Milk,Beer,Cheese" (other flags apply to all).
- From STDIN (first line = title, rest = notes):
printf 'Title\nNote line\n' | things add -
- Update todo (needs auth token):
things update --id=<ID> "New title" --when today --append-notes "..." --add-tags "urgent"
- Notes:
--notes replaces; --prepend-notes / --append-notes add. Same pattern for --checklist-item (replace) vs --prepend-checklist-item / --append-checklist-item.
- Tags:
--tags replaces; --add-tags adds.
- Move:
--list "Travel" --heading "Before". Complete/cancel: --completed / --canceled.
--duplicate updates a copy and leaves the original untouched.
- Areas & projects:
things add-area "Health"; things add-project "New Site" --area "Work" --notes "..." --todo "Task A" --todo "Task B"
things update-project --id=<ID> --auth-token=$THINGS_AUTH_TOKEN "New Title" --when tomorrow --add-tags Important (same notes/tags/scheduling flags as update, plus --area to move, --todo to append todos).
things update-area --id=<ID> --add-tags Focus (AppleScript-based; only updates tags; may prompt for automation permission).
Notes on the CLI write surface: update/update-project/update-area only modify existing items. Repeating todos/projects reject --when, --deadline, --completed, --canceled. There is no real "delete" — complete or cancel instead.
2. URL scheme (things:///...) — any device, write-only
Use when building something to open on an iPhone (or any device without terminal access). You construct the URL; it executes only when opened on a device that has Things installed. Cannot read state.
Form: things:///command?param1=value1¶m2=value2. All values are percent-encoded (space → %20, newline → %0a, comma stays literal in tag/list lists). add needs no token; update, update-project, and json-with-changes need auth-token=$THINGS_AUTH_TOKEN.
Quick examples:
- Capture to Inbox:
things:///add?title=Buy%20milk¬es=Low%20fat
- Schedule with a tag:
things:///add?title=Call%20doctor&when=next%20monday&tags=Errand (natural-language dates must be English)
- Into a project with a checklist:
things:///add?title=Trip%20prep&list=Travel&checklist-items=Passport%0aTickets
- Several todos:
things:///add?titles=Milk%0aBeer%0aCheese&list=Shopping
- Reschedule an existing todo:
things:///update?id=<ID>&auth-token=$THINGS_AUTH_TOKEN&when=today
- Show a built-in list:
things:///show?id=today (ids: inbox, today, anytime, upcoming, someday, logbook, deadlines, etc.)
The json batch command builds a whole project — headings, todos, notes, checklists — in one URL. For the full per-parameter reference, the json schema, date-string formats, and show/search ids, read references/url-scheme.md before constructing anything beyond a simple add.
When the agent is on the Mac, prefer generating these URLs via things --dry-run <command> ..., which prints a correctly-encoded URL you can hand off. Otherwise encode by hand or with a small script.
3. Email-to-Things — headless capture, Inbox only
The only modality that needs neither a Mac nor any device interaction, so it is the one a remote/NAS routine uses. Send an email to the mail-to-Things address ($THINGS_EMAIL):
- Subject → to-do title. Body → notes.
- Lands in the Inbox only. No scheduling, no tags, no project/area placement, no updates. Triage happens later on desktop.
Use it for unattended capture (a cron job dropping raw items); reach for the CLI or URL scheme whenever scheduling or placement matters.
Gotchas
CLI is macOS-only. It needs the Things app and its local DB present.
Reading state (Inbox/Today/search/projects/areas/tags) only works via the CLI against the local DB. URL scheme and email cannot read anything back.
update / update-project / json-with-changes require the auth token. Plain add does not.
A things:/// URL does nothing until opened on a device with Things. Constructing it is not executing it.
Email is Inbox-only — no scheduling, no placement, no updates.
Preview CLI writes with --dry-run before running anything destructive or unfamiliar.
Natural-language dates (next monday, in 3 days) must be in English, regardless of device language. yyyy-mm-dd and today/tomorrow always work.
No true delete — complete (--completed) or cancel (--canceled) instead. The CLI's --when=someday and --later (This Evening) are the scheduling aliases.
Renaming/deleting areas and deleting projects aren't in the things CLI or URL scheme — but osascript (AppleScript) does all of them. update-area exposes tags only, and there's no delete-area/delete-project, so via those two modalities you can only create areas/projects and reassign todos (update --list "..."). The fourth path is AppleScript against the scriptable Things app — use it on the Mac for exactly these structural edits:
- Rename an area:
osascript -e 'tell application "Things3" to set name of area "Hobby" to "Side"'
- Delete an area:
osascript -e 'tell application "Things3" to delete area "Strata"'
- Delete a project (its to-dos go with it):
osascript -e 'tell application "Things3" to delete project "Old Project"'
- List/identify first:
osascript -e 'tell application "Things3" to get name of every area'
Deletes move items to Things' Trash (recoverable until emptied), not a hard purge. The first osascript call may trigger a one-time macOS automation-permission prompt. This is Mac-only and needs no auth token. (The CLI's update-project --id=<ID> --auth-token=$THINGS_AUTH_TOKEN --canceled is a softer alternative that sends a project to the Logbook instead of deleting.)
Clearing a value via URL: include the param with an empty value, e.g. &deadline= removes a deadline.
1---2name: things-app3description: Read, capture, schedule, and update tasks and projects in Things 3. Prefer the `things` CLI whenever there's command-line access to Things; otherwise use a connected Things MCP for headless or remote environments (full read + write, any device), the `things:///` URL scheme (any device with Things, write-only), or email-to-Things for unattended capture. Use to add todos, build a project, show Today/Inbox, find tagged tasks, or schedule from desktop, phone, or a serverless routine.4---5
6# Things 3
7
8Drive Things 3 (tasks, projects, areas) through whichever modality fits the environment. Prefer the **`things` CLI** whenever there's command-line access to Things — it reads and writes the local database directly, so it's fast and needs no network round-trip. Where there's no CLI access, the fallbacks are: a connected **Things MCP** (full read + write that syncs across devices, from anywhere), the `things:///` URL scheme (any device with Things, write-only), and email-to-Things (unattended, Inbox-only capture).
9
10A **Things MCP** — a self-hosted server, or an official one Cultured Code may ship — plays the role the CLI plays locally, but reachable from anywhere, so it's the first fallback when there's no command-line access. Match its tools by their `things_*` base names; the MCP host prepends its own namespace, so the full identifier varies by client.
11
12## Pick a modality first
13
14| Modality | Read state? | Schedule & update? | Unattended? | From phone? |
15|----------|:-----------:|:------------------:|:-----------:|:-----------:|
16| **CLI** (`things` binary) | Yes | Yes | On the Mac only | No |
17| **URL scheme** (`things:///`) | No | Yes (`update`/`json` need auth token) | No — someone must open the URL | Yes |
18| **Email-to-Things** | No | No (Inbox capture only) | Yes | Yes (send an email) |
19
20The real axis is **attended vs unattended**, not headless. A headless session someone is watching can still use the URL scheme — the agent prints the link, the user opens it. Only a routine with no one to act on its output is limited to email.
21
22Decision rules:
23
24- **Command-line access to Things (a terminal with the local Things database) → use the CLI.** The default: reads (Inbox, Today, search, projects, areas, tags) and the full write surface, all against the local DB.
25- **No CLI access, but a Things MCP is connected → use it.** Some setups expose Things through an MCP server (self-hosted, or a future official one); its `things_*` tools give full read + write that syncs across devices, with no Mac or device interaction required. The path for headless, remote, or cloud-routine environments.
26- **No terminal, but the user can act on the output → build a URL.** You construct the `things:///...` link; the user (or an iOS Shortcut) opens it on a device with Things to execute. This holds even when the agent itself is headless — being headless doesn't rule out the URL scheme; only the absence of anyone to open the link does. The mobile and attended-remote write/schedule path.
27- **Fully unattended (a remote/NAS routine with no one watching) → email-to-Things.** The only path that needs neither a Mac nor a human. Lands in the Inbox only — no scheduling, no project placement, no updates. Good for dropping raw captures that get triaged later on desktop.
28
29## Credentials
30
31Two values are specific to the user's Things setup:
32
33- **Auth token** — needed only for commands that modify existing data (`update`, `update-project`, `json` with an `update` op); plain `add` / `add-project` / `add-area` don't. Things → Settings → General → Enable Things URLs → **Manage**.
34- **Mail-to-Things address** — the capture address for the email modality. Things → Settings → **Mail to Things**.
35
36Resolve each at use time: env var first (`THINGS_AUTH_TOKEN`, `THINGS_EMAIL`), then private hosted configuration, then ask. Never invent one; never echo either into output, commits, or logs.
37
38In a shell, these belong in the shell profile so an update can't overwrite them; the `things` CLI reads `THINGS_AUTH_TOKEN` on its own:
39
40 export THINGS_AUTH_TOKEN=…
41 export THINGS_EMAIL=…@things.email
42
43In hosted environments, check for `things.local.md` in the skill folder and apply any configuration it contains before asking the user for missing values. This file belongs only in the user's personal copy of the skill; do not commit, share, or redistribute it.
44
45---
46
47## 1. CLI (`things` binary) — desktop, full read + write
48
49Default modality on a Mac. Reads the local Things SQLite DB for queries; writes go through the URL scheme under the hood. Reading Things STATE is **only** possible here.
50
51Setup (skip if `which things` returns a path): `brew install ossianhempel/tap/things3-cli`. The DB lives in the Things app sandbox; the terminal may need Full Disk Access to read it. Override the DB path with `THINGSDB` or `--db=PATH` if needed.
52
53### Read (queries the local DB)
54
55- Built-in lists: `things inbox`, `things today`, `things upcoming`, `things anytime`, `things someday`, `things logbook`, `things deadlines`, `things trash`
56- Time-scoped: `things logtoday` (completed today), `things createdtoday`, `things completed`, `things canceled`
57- Overview: `things all` (Inbox, Today, Upcoming, Anytime, Someday, Logbook, No Area, Areas in one shot)
58- Structure: `things projects`, `things areas`, `things tags`
59- Search: `things search "query"` — title/notes match; flags `--status=incomplete|completed|canceled|any`, `--project=`, `--area=`, `--tag=`, `--limit=`, `--all` (include completed/canceled/trashed), `--json`
60- List todos with filters: `things tasks --project "Travel"`, `things tasks --area "Work"`, `things tasks --tag focus --search "draft"`
61- Inspect one item (exact match): `things show "Project Name"`, `things show --id=<ID> --json`
62- Most read commands accept `--json` and `--no-header`. Get an item's ID from `--json` output or `things search "x" --json`.
63
64### Write
65
66Preview any write without executing: prepend `--dry-run` (prints the URL, does not open Things). Add `--foreground` to bring Things to the front.
67
68- **Add todo:** `things add "Buy milk" --notes "2% + bananas" --when today --deadline 2026-07-01 --tags "health,phone"`
69 - Scheduling via `--when`: `today`, `tomorrow`, `evening` (This Evening), `anytime`, `someday`, a date (`2026-07-01`), or a datetime (`"2026-07-01 18:00"` → adds a reminder).
70 - Into a list/heading: `--list "Travel" --heading "Before"` (or `--list-id=<ID>`).
71 - Checklist: repeat `--checklist-item "Passport" --checklist-item "Tickets"` (max 100).
72 - Multiple at once: `--titles "Milk,Beer,Cheese"` (other flags apply to all).
73 - From STDIN (first line = title, rest = notes): `printf 'Title\nNote line\n' | things add -`
74- **Update todo (needs auth token):** `things update --id=<ID> "New title" --when today --append-notes "..." --add-tags "urgent"`
75 - Notes: `--notes` replaces; `--prepend-notes` / `--append-notes` add. Same pattern for `--checklist-item` (replace) vs `--prepend-checklist-item` / `--append-checklist-item`.
76 - Tags: `--tags` replaces; `--add-tags` adds.
77 - Move: `--list "Travel" --heading "Before"`. Complete/cancel: `--completed` / `--canceled`.
78 - `--duplicate` updates a copy and leaves the original untouched.
79- **Areas & projects:** `things add-area "Health"`; `things add-project "New Site" --area "Work" --notes "..." --todo "Task A" --todo "Task B"`
80 - `things update-project --id=<ID> --auth-token=$THINGS_AUTH_TOKEN "New Title" --when tomorrow --add-tags Important` (same notes/tags/scheduling flags as `update`, plus `--area` to move, `--todo` to append todos).
81 - `things update-area --id=<ID> --add-tags Focus` (AppleScript-based; only updates tags; may prompt for automation permission).
82
83Notes on the CLI write surface: `update`/`update-project`/`update-area` only modify existing items. Repeating todos/projects reject `--when`, `--deadline`, `--completed`, `--canceled`. There is no real "delete" — complete or cancel instead.
84
85---
86
87## 2. URL scheme (`things:///...`) — any device, write-only
88
89Use when building something to open on an **iPhone** (or any device without terminal access). You construct the URL; it executes only when opened on a device that has Things installed. Cannot read state.
90
91Form: `things:///command?param1=value1¶m2=value2`. All values are **percent-encoded** (space → `%20`, newline → `%0a`, comma stays literal in tag/list lists). `add` needs no token; `update`, `update-project`, and `json`-with-changes need `auth-token=$THINGS_AUTH_TOKEN`.
92
93Quick examples:
94
95- Capture to Inbox: `things:///add?title=Buy%20milk¬es=Low%20fat`
96- Schedule with a tag: `things:///add?title=Call%20doctor&when=next%20monday&tags=Errand` (natural-language dates must be English)
97- Into a project with a checklist: `things:///add?title=Trip%20prep&list=Travel&checklist-items=Passport%0aTickets`
98- Several todos: `things:///add?titles=Milk%0aBeer%0aCheese&list=Shopping`
99- Reschedule an existing todo: `things:///update?id=<ID>&auth-token=$THINGS_AUTH_TOKEN&when=today`
100- Show a built-in list: `things:///show?id=today` (ids: `inbox`, `today`, `anytime`, `upcoming`, `someday`, `logbook`, `deadlines`, etc.)
101
102The `json` batch command builds a whole project — headings, todos, notes, checklists — in one URL. For the full per-parameter reference, the `json` schema, date-string formats, and `show`/`search` ids, **read `references/url-scheme.md`** before constructing anything beyond a simple `add`.
103
104When the agent is on the Mac, prefer generating these URLs via `things --dry-run <command> ...`, which prints a correctly-encoded URL you can hand off. Otherwise encode by hand or with a small script.
105
106---
107
108## 3. Email-to-Things — headless capture, Inbox only
109
110The only modality that needs neither a Mac nor any device interaction, so it is the one a remote/NAS routine uses. Send an email to the mail-to-Things address (`$THINGS_EMAIL`):
111
112- **Subject → to-do title. Body → notes.**
113- Lands in the **Inbox only.** No scheduling, no tags, no project/area placement, no updates. Triage happens later on desktop.
114
115Use it for unattended capture (a cron job dropping raw items); reach for the CLI or URL scheme whenever scheduling or placement matters.
116
117---
118
119## Gotchas
120
121- **CLI is macOS-only.** It needs the Things app and its local DB present.
122- **Reading state (Inbox/Today/search/projects/areas/tags) only works via the CLI** against the local DB. URL scheme and email cannot read anything back.
123- **`update` / `update-project` / `json`-with-changes require the auth token.** Plain `add` does not.
124- **A `things:///` URL does nothing until opened on a device with Things.** Constructing it is not executing it.
125- **Email is Inbox-only** — no scheduling, no placement, no updates.
126- **Preview CLI writes with `--dry-run`** before running anything destructive or unfamiliar.
127- **Natural-language dates (`next monday`, `in 3 days`) must be in English**, regardless of device language. `yyyy-mm-dd` and `today`/`tomorrow` always work.
128- **No true delete** — complete (`--completed`) or cancel (`--canceled`) instead. The CLI's `--when=someday` and `--later` (This Evening) are the scheduling aliases.
129- **Renaming/deleting areas and deleting projects aren't in the `things` CLI or URL scheme — but `osascript` (AppleScript) does all of them.** `update-area` exposes tags only, and there's no `delete-area`/`delete-project`, so via those two modalities you can only *create* areas/projects and *reassign* todos (`update --list "..."`). The fourth path is AppleScript against the scriptable Things app — use it on the Mac for exactly these structural edits:
130 - Rename an area: `osascript -e 'tell application "Things3" to set name of area "Hobby" to "Side"'`
131 - Delete an area: `osascript -e 'tell application "Things3" to delete area "Strata"'`
132 - Delete a project (its to-dos go with it): `osascript -e 'tell application "Things3" to delete project "Old Project"'`
133 - List/identify first: `osascript -e 'tell application "Things3" to get name of every area'`
134
135 Deletes move items to Things' Trash (recoverable until emptied), not a hard purge. The first `osascript` call may trigger a one-time macOS automation-permission prompt. This is Mac-only and needs no auth token. (The CLI's `update-project --id=<ID> --auth-token=$THINGS_AUTH_TOKEN --canceled` is a softer alternative that sends a project to the Logbook instead of deleting.)
136- **Clearing a value via URL:** include the param with an empty value, e.g. `&deadline=` removes a deadline.