things:url ยท diff
git:20261018.cc37a33 to git:20261030.03f7155
25 added, 64 removed. Audit A to A.
---
name: things:url
description: Write to Things 3 from this machine's CLI, including quick inbox captures. Prefer the things MCP tools, which reach the same writes from any machine. Use this skill for a raw json data= payload, or when that server is unreachable.
argument-hint: "<add | update | show | search | json | capture> [key=value ...]"
effort: low
allowed-tools:
- "Bash(bun ${CLAUDE_PLUGIN_ROOT}/scripts/url.ts:*)"
- "Bash(bun ${CLAUDE_PLUGIN_ROOT}/scripts/inbox.ts:*)"
- "Bash(bun ${CLAUDE_PLUGIN_ROOT}/scripts/reorder.ts:*)"
- Read
---
# Things URL Scheme
- Write operations for Things 3 via the `things:///` URL scheme.
+ Write to Things 3 through the `things:///` URL scheme. A write is done when the script prints its confirmation or link, or, on a silent run, when a `things:jxa` query shows the change.
## Arguments
- `$0` is the command (`add`, `add-project`, `update`, `update-project`, `show`, `search`, `json`); the rest are its `key=value` params. Pass both straight to `url.ts`. A command is required. With none, infer the operation from the request. `capture` routes to `inbox.ts` instead (see [Inbox Capture](#inbox-capture)).
+ `$0` is the command (`add`, `add-project`, `update`, `update-project`, `show`, `search`, `json`). The rest are its `key=value` params. Pass both straight to `url.ts`. With no command, infer the operation from the request. `capture` routes to `inbox.ts` instead (see [Inbox Capture](#inbox-capture)).
- ## Quick Start
+ ## Usage
- Use `url.ts` for most operations. It handles auth tokens and URL encoding.
+ `url.ts` handles the auth token ([1password.md](1password.md)) and URL encoding. Multiple `id=` params batch into one JSON command, which keeps 3+ items under the rate limit of 250 operations per 10 seconds.
```bash
bun ${CLAUDE_PLUGIN_ROOT}/scripts/url.ts --link-base "${user_config.link_base}" <command> [key=value ...]
-
- # Bulk update: pass multiple id= params to batch via JSON command
bun ${CLAUDE_PLUGIN_ROOT}/scripts/url.ts --link-base "${user_config.link_base}" update id=X id=Y id=Z when=tomorrow
```
- For raw URL scheme access: `open -g "things:///add?title=Buy%20milk&when=today"`. Use `-g` for data commands to run in background. Omit it for `show`/`search` to foreground Things.
-
- ## Commands
-
- | Command | Description | Auth required |
- |---------|-------------|:---:|
- | `add` | Create a todo | No |
- | `add-project` | Create a project with optional todos | No |
- | `update` | Modify a todo's properties | Yes |
- | `update-project` | Modify a project's properties | Yes |
- | `show` | Navigate to a list, todo, or project | No |
- | `search` | Open search with optional query | No |
- | `json` | Batch create/update via JSON payload | Yes (for updates) |
-
- Full parameters, JSON payload schema, and limits: [url-scheme.md](url-scheme.md). `url.ts` fetches the auth token automatically ([1password.md](1password.md)).
+ For raw access, `open -g "things:///add?title=Buy%20milk&when=today"`. Use `-g` for data commands so Things stays in the background, and omit it for `show`/`search`.
- `show` accepts built-in list IDs: `inbox`, `today`, `anytime`, `upcoming`, `someday`, `logbook`, `tomorrow`, `deadlines`, `repeating`, `all-projects`, `logged-projects`.
+ Parameters, the JSON payload schema, built-in list IDs, and limits: [url-scheme.md](url-scheme.md).
## Tags
- Things drops a tag it does not already hold and still reports success. `url.ts` resolves `tags` and `add-tags` against the stored tags first, so an unknown tag fails the call and names itself instead of vanishing from the write. Pass `--create-tags` to create the missing ones:
+ `url.ts` resolves `tags` and `add-tags` against the stored tags, case-insensitively, and fails naming any unknown tag, since Things silently drops tags it does not hold. Pass `--create-tags` to create the missing ones:
```bash
bun ${CLAUDE_PLUGIN_ROOT}/scripts/url.ts --link-base "${user_config.link_base}" add title="Fix login" tags=bug --create-tags
```
- Matching folds case, so `Bug` resolves to a stored `bug`. An empty `tags=` still clears a todo's tags. Tags inside a raw `json data=...` payload go through unchecked.
+ An empty `tags=` clears a todo's tags. Tags inside a raw `json data=...` payload go through unchecked.
## Reorder Items
```bash
bun ${CLAUDE_PLUGIN_ROOT}/scripts/reorder.ts [--list today|anytime|someday] <id1> <id2> <id3> ...
```
- Items appear at the top of the list in the order specified. Default list is `today`. Use the `--list` value matching the items' current scheduling state. See [Reordering Replaces a Specific Date](#reordering-replaces-a-specific-date).
+ Items land at the top of the list in the given order. `--list` defaults to `today`. Pass the list matching the items' current scheduling state.
+ `reorder.ts` reschedules each item out of the list and back, so an item with a specific date has that date replaced by the list. Order within a project is unaffected.
+
## Inbox Capture
- For quick captures to the inbox, use `inbox.ts`. It tags each todo `Claude` and appends session attribution, so prefer it over `url.ts add` when delegating a task mid-session.
+ Use `inbox.ts` for quick captures and for delegating a task mid-session. It tags each todo `Claude` and appends session attribution.
```bash
bun ${CLAUDE_PLUGIN_ROOT}/scripts/inbox.ts --link-base "${user_config.link_base}" --session-id ${CLAUDE_SESSION_ID} title="Buy milk"
```
- `title` captures one todo. `titles` (newline-separated) captures several at once. Add tags with `--tag` (repeatable). Other params: `notes` (max 10,000 chars), `tags` (comma-separated), `checklist-items` (newline-separated, max 100).
-
- On success it prints a confirmation. With the `x-callback-url` plugin, xcall returns the todo ID and the script prints a link to it. Present the printed link. Without xcall it prints `captured: <title>`.
-
- ## Callback
-
- When the `x-callback-url` plugin is installed, `url.ts` uses xcall to get a response from Things on stdout:
-
- - **Single todo** (`add`, `update`): prints a link to the todo. Present the printed link
- - **Batch** (`json`): prints `x-things-ids=["id1","id2"]`. Present a bulleted list with each todo's title and `${user_config.link_base}<id>` link
-
- Callback is enabled by default. Disable with `--callback=false` to fall back to fire-and-forget via `open -g`. If xcall is unavailable, the script falls back silently.
-
- ## Areas
-
- The `list` parameter only works with project names. To file a todo under an area (on create or move), use `list-id` with the area UUID, not `area-id`. Query area IDs via the `things:jxa` skill.
-
- ## Notes Formatting
-
- Things notes support [Markdown](https://culturedcode.com/things/support/articles/4651820/) plus Things-specific `::highlight::` syntax.
-
- ## Gotchas
-
- #### Silent Success
-
- `url.ts` prints only when xcall returns a result. On the fallback path it exits 0 with empty stdout after a successful write, so empty output says nothing about whether the change landed.
-
- Judge failure by a non-zero exit and read stderr for the cause. To confirm a write that printed nothing, query the todo with the `things:jxa` skill. Never retry blind: a repeated `add` creates duplicate todos.
+ `title` captures one todo, `titles` (newline-separated) several. Other params: `--tag` (repeatable), `notes` (max 10,000 chars), `tags` (comma-separated), `checklist-items` (newline-separated, max 100).
- `inbox.ts` and `reorder.ts` do print on success regardless of xcall, so silence from those is a genuine failure.
+ ## Output
- #### Reordering Replaces a Specific Date
+ With the `x-callback-url` plugin installed, the scripts read Things' response through xcall:
- `reorder.ts` reschedules each item out of the target list and back, because the URL scheme offers no other way to move an item to the top. An item carrying a specific date has that date replaced by the target list, and there is no workaround.
+ - `add`, `update`, and `inbox.ts`: a link to the todo. Present it.
+ - `json`: `x-things-ids=["id1","id2"]`. Present a bulleted list of each todo's title linked as `${user_config.link_base}<id>`.
- Order within a project is untouched, being separate from scheduling.
+ `--callback=false` switches `url.ts` to fire-and-forget `open -g`. Without xcall it falls back to that silently.
- #### Sandbox-blocked URL handoff
+ On the fallback path `url.ts` exits 0 with empty stdout after a successful write. Judge failure by a non-zero exit and stderr. Confirm a silent write with `things:jxa` before retrying, since a repeated `add` creates duplicates. `inbox.ts` (`captured: <title>`) and `reorder.ts` always print on success, so silence from them is a failure.
- If stderr mentions `procNotFound`, `-10810`, or `LSOpenURLsWithRole`, the macOS sandbox blocked the URL handoff to Things. `url.ts` and `inbox.ts` carry the `claude:dangerouslyDisableSandbox` marker so the `mac` plugin's sandbox hook runs them outside the sandbox. If it still happens, verify the `mac` plugin is installed so that hook is active.
+ When stderr mentions `procNotFound`, `-10810`, or `LSOpenURLsWithRole`, the sandbox blocked the handoff to Things. `url.ts` and `inbox.ts` carry the `claude:dangerouslyDisableSandbox` marker that the `mac` plugin's sandbox hook acts on, so check that the `mac` plugin is installed.
- ## Tips
+ ## Things Behavior
- - **Moving out of inbox**: Set `when=anytime` to move a todo out of inbox without assigning an area
- - **Rate limiting**: Max 250 operations per 10 seconds. For 3+ items, use multi-ID syntax (`id=X id=Y id=Z`) to batch into a single JSON command instead of individual calls.
- - **Repeating todos**: Cannot update `when` or `deadline` on repeating to-dos
+ - `list` takes project names only. File a todo under an area with `list-id` and the area UUID (from `things:jxa`).
+ - `when=anytime` moves a todo out of the inbox without assigning an area.
+ - `when` and `deadline` cannot be updated on repeating todos.
+ - Notes support [Markdown](https://culturedcode.com/things/support/articles/4651820/) plus `::highlight::`.