# Things App

> 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.

- Skill: `nweii/things-app` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add nweii/things-app`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nweii/things-app/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: nweii (https://skillmd.com/u/nweii)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/nweii/things-app

---


# 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&param2=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&notes=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.

