things-api 0.18.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -9
- package/deputy/AppIcon.icns +0 -0
- package/deputy/VERSION +1 -1
- package/deputy/helpers-Info.plist +4 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Info.plist +3 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Resources/AppIcon.icns +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/_CodeSignature/CodeResources +15 -2
- package/deputy/prebuilt/Things API Helper.app/Contents/Info.plist +5 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Resources/AppIcon.icns +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/_CodeSignature/CodeResources +14 -2
- package/deputy/reader/Info.plist +2 -0
- package/deputy/reader/entitlements.plist +30 -0
- package/deputy/reader/main.swift +129 -40
- package/deputy/src/server.swift +25 -2
- package/deputy/src/tcc.swift +58 -0
- package/dist/audit/schema.d.ts +30 -1
- package/dist/audit/schema.js.map +1 -1
- package/dist/capability.d.ts +322 -0
- package/dist/capability.js +706 -0
- package/dist/capability.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +13 -1
- package/dist/cli/commands/doctor.js +141 -5
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/helpers.js +189 -51
- package/dist/cli/commands/helpers.js.map +1 -1
- package/dist/cli/commands/mcp.js +8 -0
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/op-result.js +6 -0
- package/dist/cli/commands/op-result.js.map +1 -1
- package/dist/cli/commands/reads.js +47 -1
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/repeat-flags.js +34 -2
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +9 -0
- package/dist/cli/commands/setup.js +76 -83
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/writes.d.ts +1 -1
- package/dist/cli/commands/writes.js +311 -142
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/dry-run.d.ts +1 -1
- package/dist/cli/dry-run.js +1 -1
- package/dist/cli/glyphs.d.ts +23 -4
- package/dist/cli/glyphs.js +46 -19
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +49 -27
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/interrupt.d.ts +54 -6
- package/dist/cli/interrupt.js +78 -12
- package/dist/cli/interrupt.js.map +1 -1
- package/dist/cli/main.js +8 -5
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/period.d.ts +3 -1
- package/dist/cli/period.js +1 -1
- package/dist/cli/period.js.map +1 -1
- package/dist/cli/read-driver.js +26 -1
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/render.d.ts +31 -4
- package/dist/cli/render.js +127 -15
- package/dist/cli/render.js.map +1 -1
- package/dist/client.d.ts +23 -3
- package/dist/client.js +36 -13
- package/dist/client.js.map +1 -1
- package/dist/contracts.d.ts +8 -4
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deputy/install.d.ts +234 -6
- package/dist/deputy/install.js +843 -31
- package/dist/deputy/install.js.map +1 -1
- package/dist/deputy/notices.js +2 -2
- package/dist/deputy/notices.js.map +1 -1
- package/dist/deputy/protocol.d.ts +59 -5
- package/dist/deputy/protocol.js +48 -11
- package/dist/deputy/protocol.js.map +1 -1
- package/dist/deputy/routing.d.ts +37 -0
- package/dist/deputy/routing.js +181 -27
- package/dist/deputy/routing.js.map +1 -1
- package/dist/deputy/wake.d.ts +61 -0
- package/dist/deputy/wake.js +149 -0
- package/dist/deputy/wake.js.map +1 -0
- package/dist/diagnose.d.ts +19 -4
- package/dist/diagnose.js +64 -8
- package/dist/diagnose.js.map +1 -1
- package/dist/direct-setup.d.ts +104 -0
- package/dist/direct-setup.js +658 -0
- package/dist/direct-setup.js.map +1 -0
- package/dist/host-access.d.ts +75 -0
- package/dist/host-access.js +177 -0
- package/dist/host-access.js.map +1 -0
- package/dist/index.d.ts +20 -5
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +27 -1
- package/dist/mcp/server.js +308 -109
- package/dist/mcp/server.js.map +1 -1
- package/dist/op-result.d.ts +9 -0
- package/dist/op-result.js +13 -1
- package/dist/op-result.js.map +1 -1
- package/dist/read/filter-contract.d.ts +1 -1
- package/dist/read/filter-contract.js +4 -0
- package/dist/read/filter-contract.js.map +1 -1
- package/dist/read/shape.js +4 -0
- package/dist/read/shape.js.map +1 -1
- package/dist/read/views.d.ts +29 -0
- package/dist/read/views.js +60 -0
- package/dist/read/views.js.map +1 -1
- package/dist/session-grant.d.ts +48 -0
- package/dist/session-grant.js +185 -0
- package/dist/session-grant.js.map +1 -0
- package/dist/surface-copy.d.ts +19 -3
- package/dist/surface-copy.js +19 -3
- package/dist/surface-copy.js.map +1 -1
- package/dist/wizard.d.ts +58 -0
- package/dist/wizard.js +174 -0
- package/dist/wizard.js.map +1 -0
- package/dist/write/availability.d.ts +1 -16
- package/dist/write/availability.js +10 -65
- package/dist/write/availability.js.map +1 -1
- package/dist/write/batch.d.ts +20 -0
- package/dist/write/batch.js +111 -21
- package/dist/write/batch.js.map +1 -1
- package/dist/write/capabilities.d.ts +9 -0
- package/dist/write/capabilities.js +2 -0
- package/dist/write/capabilities.js.map +1 -1
- package/dist/write/clear-reminder.js +1 -1
- package/dist/write/clear-reminder.js.map +1 -1
- package/dist/write/commands.d.ts +22 -7
- package/dist/write/commands.js +231 -254
- package/dist/write/commands.js.map +1 -1
- package/dist/write/failure-hints.d.ts +32 -7
- package/dist/write/failure-hints.js +9 -14
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/guards.js +22 -4
- package/dist/write/guards.js.map +1 -1
- package/dist/write/lock.d.ts +16 -13
- package/dist/write/lock.js +55 -0
- package/dist/write/lock.js.map +1 -1
- package/dist/write/make-repeating-project.js +9 -13
- package/dist/write/make-repeating-project.js.map +1 -1
- package/dist/write/move.js +9 -12
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +51 -29
- package/dist/write/operations.js +16 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +72 -14
- package/dist/write/opid.js +152 -8
- package/dist/write/opid.js.map +1 -1
- package/dist/write/param-schema.d.ts +80 -0
- package/dist/write/param-schema.js +754 -0
- package/dist/write/param-schema.js.map +1 -0
- package/dist/write/pipeline.d.ts +110 -5
- package/dist/write/pipeline.js +288 -14
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +38 -16
- package/dist/write/pre-state.js +117 -22
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/promote-clone.js +658 -444
- package/dist/write/promote-clone.js.map +1 -1
- package/dist/write/reorder.js +25 -8
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/repeat-collateral.d.ts +31 -0
- package/dist/write/repeat-collateral.js +153 -0
- package/dist/write/repeat-collateral.js.map +1 -0
- package/dist/write/repeat-rule.d.ts +28 -1
- package/dist/write/repeat-rule.js +54 -4
- package/dist/write/repeat-rule.js.map +1 -1
- package/dist/write/resolution-timestamps.d.ts +11 -0
- package/dist/write/resolution-timestamps.js +108 -40
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/reversibility.js +4 -0
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/template-mutation.d.ts +137 -0
- package/dist/write/template-mutation.js +598 -0
- package/dist/write/template-mutation.js.map +1 -0
- package/dist/write/undo.js +10 -20
- package/dist/write/undo.js.map +1 -1
- package/dist/write/update-fields.d.ts +151 -0
- package/dist/write/update-fields.js +427 -0
- package/dist/write/update-fields.js.map +1 -0
- package/dist/write/vectors/applescript.js +6 -10
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/registry.d.ts +1 -1
- package/dist/write/vectors/registry.js +10 -2
- package/dist/write/vectors/registry.js.map +1 -1
- package/dist/write/vectors/shortcuts.js +2 -2
- package/dist/write/vectors/shortcuts.js.map +1 -1
- package/dist/write/vectors/simulator.js +15 -4
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +268 -1
- package/dist/write/vectors/ui-certification.js +63 -13
- package/dist/write/vectors/ui-certification.js.map +1 -1
- package/dist/write/vectors/ui-chord.d.ts +144 -0
- package/dist/write/vectors/ui-chord.js +428 -0
- package/dist/write/vectors/ui-chord.js.map +1 -0
- package/dist/write/vectors/ui-drag.d.ts +8 -4
- package/dist/write/vectors/ui-drag.js +2 -0
- package/dist/write/vectors/ui-drag.js.map +1 -1
- package/dist/write/vectors/ui-recipes.d.ts +70 -3
- package/dist/write/vectors/ui-recipes.js +482 -44
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +280 -10
- package/dist/write/vectors/ui.js +1068 -77
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/vectors/url-scheme.js +3 -0
- package/dist/write/vectors/url-scheme.js.map +1 -1
- package/dist/write/verify/delta.d.ts +62 -0
- package/dist/write/verify/delta.js +36 -2
- package/dist/write/verify/delta.js.map +1 -1
- package/dist/write/verify/poller.d.ts +4 -1
- package/dist/write/verify/poller.js +14 -0
- package/dist/write/verify/poller.js.map +1 -1
- package/package.json +2 -1
- package/schema/envelope.schema.json +6 -4
- package/scripts/build-helpers.sh +5 -0
- package/skills/things-cli/SKILL.md +5 -3
- package/skills/things-cli/references/contracts.md +1 -0
- package/skills/things-cli/references/data-model.md +3 -0
- package/skills/things-cli/references/errors.md +1 -1
- package/skills/things-cli/references/gui.md +1 -0
- package/skills/things-cli/references/repeating.md +6 -0
- package/skills/things-cli/references/tag-cleanup.md +64 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: things-cli
|
|
3
3
|
description: Read and manage a user's tasks in the Things 3 app (macOS) through the `things` CLI — list views like Today/Inbox/Upcoming, search, and create, edit, schedule, complete, move, or organize to-dos, projects, areas, headings, and tags. Use whenever the user asks about their tasks, to-dos, projects, or anything in Things.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Things CLI
|
|
@@ -20,6 +20,7 @@ version: 0.18.0
|
|
|
20
20
|
- **Area** — a top-level bucket (e.g. a sphere of responsibility) holding projects and loose to-dos. Areas have tags but no dates, and never go to the Trash — deleting one is permanent.
|
|
21
21
|
- **Tags** — form a hierarchy, and are **inherited downward**: a to-do effectively carries its own tags plus those of its project and area. Headings carry no tags, but inheritance flows through them from project to to-do.
|
|
22
22
|
- **Views** are queries over this model, matching the app's sidebar: `inbox` (unsorted), `today` (scheduled for today, incl. This Evening), `upcoming` (future-dated), `anytime` (all active), `someday` (kept without a date), `logbook` (completed/canceled), `trash`.
|
|
23
|
+
- **Notes** — a *markdown field with multi-line support*, not a plain text blob. Newlines are kept exactly as written, and `**bold**`, `_italic_`, `` `code` ``, fenced code blocks, `#` headings, `-` bullets, and numbered lists all render (their syntax characters stay visible alongside the formatting). A bare URL becomes a link by itself, so never wrap one in `[label](url)` — that spelling stays literal text. Also literal, not rendered: `> blockquotes`, `---` rules, `*` bullets, and `- [ ]`/`- [x]` checkboxes (a real checklist is a separate feature — `things todo checklist`). To pass several lines from a shell, `--notes $'first line\nsecond line'`; to pipe a whole body in (heredoc, `< notes.md`), `--notes -`.
|
|
23
24
|
- **Scheduling vocabulary**: an item's *when* is `today | evening | anytime | someday | YYYY-MM-DD`. A **deadline** is a separate due date; a **reminder** is a separate time-of-day alert — never write `date@time` into *when*.
|
|
24
25
|
|
|
25
26
|
## Reading position from JSON: `stage` and `when`
|
|
@@ -45,14 +46,14 @@ These hold regardless of the binary version; see [references/contracts.md](refer
|
|
|
45
46
|
- **JSON envelope**: every `--json` response is `{ apiVersion, ok, kind, data, meta }`. Read results from `.data` (`.data.items`/`.data.sections`/`.data.item`/`.data.view`/`.data.children` per `kind`; `today` is `.data.children.{today,evening}.items` with its count on `meta.counts`; the `logbook` view carries the log-move cadence on `meta.logging = {cadence, lastLoggedAt?}` — CC's Settings words `Immediately|Daily|Manually`, `lastLoggedAt` under Daily and Manually), never `.items`; UUIDs are `.uuid`, not `.id`. Check `meta.truncation.truncated` before concluding "no match" or "that's everything". List/search rows are compact summaries whose `tags` may be incomplete — use `things show <ref> --json` for effective tags, checklist, notes, and placement.
|
|
46
47
|
- **Exit codes**: `0` landed and checked · `2` usage · `3` verify-failed · `4` blocked · `5` drift-blocked · `6` unsupported · `7` environment (`1` is an internal bug). Nonzero means the change did NOT stick; the message names the fix.
|
|
47
48
|
- **Previews, undo & idempotency**: `--dry-run` is universal — accepted by every command, it guarantees nothing changes: on a read it returns the normal output unchanged, on a write it shows the exact plan without executing; `things undo` reverses recent changes made through this tool (each reversible write returns an `undoToken`); a single mutation may carry `--op-id <key>` so an ambiguous resubmission is recognized as already applied (the variadic `move`/`reorder` refuse it — use `things batch` with a per-line `opId`).
|
|
48
|
-
- **Timeline-silent mutations** (`--preserve-modified`, MCP `preserve_modified`): a universal write flag that keeps a change off the modification-date timeline. Some writes (applying/removing a tag, editing a field, resolving an item) re-stamp each edited item's modification date — the key `things changes --since <moment>` and any watch/sync process sort on. `--preserve-modified` captures each PRE-EXISTING edited item's modification date before the write and restores it (to the whole second) afterward, so the edit does not surface as a new change. It is the enabler for a mass retro-tag or bulk cleanup that must not flood `changes
|
|
49
|
+
- **Timeline-silent mutations** (`--preserve-modified`, MCP `preserve_modified`): a universal write flag that keeps a change off the modification-date timeline. Some writes (applying/removing a tag, editing a field, resolving an item) re-stamp each edited item's modification date — the key `things changes --since <moment>` and any watch/sync process sort on. `--preserve-modified` captures each PRE-EXISTING edited item's modification date before the write and restores it (to the whole second) afterward, so the edit does not surface as a new change. It is the enabler for a mass retro-tag or bulk cleanup that must not flood `changes` — the worked recipe is [references/tag-cleanup.md](references/tag-cleanup.md). `things batch` takes it too: at the RUN level it defaults every line, and a line's own `"options": {"preserveModified": …}` outranks that. A pure create (`add`) is a no-op; the restore is best-effort (a failure is reported per item, the change itself stands) and the result carries `preservedModified: <n>`. **Safe against a synced (Things Cloud) store** — the restored modification date syncs to the other devices and survives the round-trip, so the item stays off the timeline on every device (SYNC2B); the only edge is a genuinely concurrent edit to the same item on another device, which re-dates it via Things Cloud's per-attribute merge (fails safe — the edit resurfaces, never silently hidden).
|
|
49
50
|
- **Completion hints**: a successful `todo complete`/`todo cancel` result carries a `context` object (`data.context` under `--json`) reporting the OPEN work remaining in the to-do's container(s) — `context.project = {uuid, title, remainingOpen}` when it was in a project (heading children included), `context.today = {remainingOpen}` when it was in Today. Use it to notice an emptied container: `remainingOpen: 0` on the project means every open task there is now done, so you might offer to complete the project itself — it only informs, it never auto-acts. Absent when the to-do was in neither.
|
|
50
51
|
- **Preconditions**: referenced containers and tags must already exist — create nested structures outside-in and reuse each returned UUID.
|
|
51
52
|
- If the user requests a JSON reply schema, return exactly that object after the read or checked write.
|
|
52
53
|
|
|
53
54
|
## Reading
|
|
54
55
|
|
|
55
|
-
Views and lookups — pass `--json` whenever you will act on the output: `things today | inbox | upcoming | anytime | someday | logbook | trash`, `things show <ref>` (full detail incl. notes + checklist + effective tags), `things projects [ref]`, `things areas [ref]`, `things tags`, `things search <words>`, `things changes --since <moment>`. `things deadlines` is the deadline horizon — every item (to-dos AND projects) that HAS a deadline, in deadline order, most-overdue first; it is a derived query view, NOT reorderable, and repeating items appear at their next occurrence's projected deadline. Scope it with `--today` (only what's due in Today), `--overdue` (only items already past their deadline), and `--project`/`--area`/`--tag`. Compact rows carry `hasNotes`, `checklist:{open,total}` (to-dos), and `todos:{open,total}` (projects); `--full` (or `show`) adds the full `notes`, `startDate`, and checklist `items`. A `search` hit carries `match:{field,text}` provenance when it matched something other than the title. Filters (`--tag`, `--untagged`, `--overdue`, `--limit N`, `--all`, …) compose with AND — see `things help filters`. The reserved read-only ref `loose` addresses the area-less items as a pseudo-area (`areas loose`, `area show loose`, `--area loose`) and wins over any real area named "Loose"; every write verb refuses it.
|
|
56
|
+
Views and lookups — pass `--json` whenever you will act on the output: `things today | inbox | upcoming | anytime | someday | logbook | trash`, `things show <ref>` (full detail incl. notes + checklist + effective tags), `things projects [ref]`, `things areas [ref]`, `things tags`, `things search <words>`, `things changes --since <moment>`. `things deadlines` is the deadline horizon — every item (to-dos AND projects) that HAS a deadline, in deadline order, most-overdue first; it is a derived query view, NOT reorderable, and repeating items appear at their next occurrence's projected deadline. Scope it with `--today` (only what's due in Today), `--overdue` (only items already past their deadline), and `--project`/`--area`/`--tag`. `things repeaters` is the repeating-series catalogue — every repeating to-do AND project in the library, each row carrying its decoded rule under `repeating.rule`, ordered by next occurrence (paused, ended, and after-completion series are included and come last, since they project no date). It is the ONLY view that lists the series themselves: every other view shows the occurrences a series spawns, and search matches those occurrences but never the series behind them — so this is where a series' uuid and rule come from before you `reschedule-repeat`, `pause-repeat`, or `show` it. Compact rows carry `hasNotes`, `checklist:{open,total}` (to-dos), and `todos:{open,total}` (projects); `--full` (or `show`) adds the full `notes`, `startDate`, and checklist `items`. A `search` hit carries `match:{field,text}` provenance when it matched something other than the title. Filters (`--tag`, `--untagged`, `--overdue`, `--limit N`, `--all`, …) compose with AND — see `things help filters`. The reserved read-only ref `loose` addresses the area-less items as a pseudo-area (`areas loose`, `area show loose`, `--area loose`) and wins over any real area named "Loose"; every write verb refuses it.
|
|
56
57
|
|
|
57
58
|
## Writing
|
|
58
59
|
|
|
@@ -83,3 +84,4 @@ Never construct `things:///` URLs, AppleScript (`tell application "Things3" …`
|
|
|
83
84
|
- [references/errors.md](references/errors.md) — the error contract: the candidate shape, dead-row hints, hazard acknowledgments, and the error-code registry.
|
|
84
85
|
- [references/banner.md](references/banner.md) — the Today "new to-dos" banner, the provisional `•` pip, reminder/evening liveness, and what a watcher sees.
|
|
85
86
|
- [references/gui.md](references/gui.md) — how the user sees Things in the app (where results appear, what list rows show).
|
|
87
|
+
- [references/tag-cleanup.md](references/tag-cleanup.md) — the mass tag-cleanup recipe: which moves are free on the modification-date timeline (rename, delete) and which need `--preserve-modified` (applying a tag), plus retiring an archived area.
|
|
@@ -57,6 +57,7 @@ A verified write is committed to the LOCAL Things database immediately — `ok`/
|
|
|
57
57
|
- **Stop-on-failure is the DEFAULT:** a runtime per-line failure HALTS the batch — the failed line is reported and every later line is reported not-run. Pass `--continue-on-error` (MCP `continue_on_error`) to run past failures instead (the old proceed-past behavior). The summary states how many lines did not run and whether a verbatim resubmission is safe to resume.
|
|
58
58
|
- **`tempId` (chaining):** a line that CREATES something (a to-do, project, area, heading — never `tag.add`, and NOT `make-repeating`/`add-repeating`) can carry `"tempId":"proj1"`; a LATER line references that new uuid as `"$proj1"` in any id/container field. This is how you "create a project, then file to-dos into it" in one submission without knowing the uuid up front. Handles are `[A-Za-z0-9_-]{1,32}` and unique per batch.
|
|
59
59
|
- **`opId` (safe retry + RESUME):** carry a stable `"opId"` per line so resubmitting a batch after a failure does not double-create — a line matching an earlier success is reported `already-applied`, not re-run. **Put an `opId` on EVERY line** so a batch that stopped mid-way can be resubmitted VERBATIM to resume: already-applied lines are skipped and the run continues from the failure. Without opIds, a verbatim resubmission RE-RUNS the lines that already committed (the summary's resume guidance names those indices). The single-op analogue is `--op-id <key>` (MCP `op_id`) on ONE mutation: a resubmission with a matched key returns the original success (`alreadyApplied: true`, the original `uuid`/`undoToken`) instead of running again. The variadic `move`/`reorder` are multi-leg compounds and REFUSE `--op-id` — express their idempotency as `things batch` with a per-line `opId`.
|
|
60
|
+
- **`preserveModified` (timeline-silent bulk):** the universal write flag reaches a batch in two shapes — the run-level `--preserve-modified` (MCP `preserve_modified`), which applies to EVERY line, and a per-line `"options": {"preserveModified": true|false}`, whose explicit value outranks the run level (so `false` opts one line back onto the timeline). Each line's result then carries `preservedModified` (and `preserveFailures` for a restore that did not land), exactly as the per-item verb does. This is the vehicle for a mass re-tag that must not flood `changes` — see the tag-cleanup recipe.
|
|
60
61
|
- **Undo the whole batch:** the trailing summary line returns `tempIdMapping` (handle → uuid) and `undoToken`; `things undo --txn <undoToken>` reverses the entire submission as one unit.
|
|
61
62
|
|
|
62
63
|
## Recurrence (contract summary)
|
|
@@ -22,6 +22,7 @@ Entities, relationships, and how the sidebar views are computed over them, as ex
|
|
|
22
22
|
- **Status**: open → completed or canceled (both land in the Logbook) or trashed (Trash; restorable until emptied). Reopen brings a logged item back.
|
|
23
23
|
- **when** (`today | evening | anytime | someday | YYYY-MM-DD`) controls which view an item appears in; **deadline** is an independent due date shown alongside the item; **reminder** is a time-of-day alert attached to a dated when.
|
|
24
24
|
- "Overdue" = open with a deadline strictly before today (a deadline of today is "due", not overdue).
|
|
25
|
+
- **notes is a markdown field with multi-line support**, on both to-dos and projects — never treat it as a flat text blob. Newlines survive verbatim: give the body inline with `--notes $'first line\nsecond line'`, or pass `--notes -` to read the whole body from stdin (a heredoc, a pipe, or `< notes.md`; one trailing newline is dropped, and a terminal stdin is refused rather than left hanging). Things renders the formatting while KEEPING the syntax characters visible, so the stored text and the rendered card carry the same characters: `#`/`##` headings, `_italic_`, `*italic*`, `**bold**`, `__bold__`, `` `code` ``, ```` ``` ```` fenced blocks, `-` bullets (nesting preserved), and `1.` numbered lists all render. A bare URL — with or without `<angle brackets>` — becomes a clickable link on its own, which makes `[label](url)` pure noise: the label and brackets stay literal and only the raw URL inside links. Inert, stored and shown as literal characters: `> blockquote`, the `---` rule, `*` used as a bullet, and `- [ ]`/`- [x]` checkboxes (`- [x]` lines are additionally drawn dimmed, which is cosmetic only — nothing is checked off). Real sub-steps are the checklist (`things todo checklist`), a separate structure from the notes body.
|
|
25
26
|
|
|
26
27
|
## Views (queries over the model)
|
|
27
28
|
|
|
@@ -37,6 +38,8 @@ Entities, relationships, and how the sidebar views are computed over them, as ex
|
|
|
37
38
|
|
|
38
39
|
Beyond the sidebar mirror, **`things deadlines`** is a derived query view (not an app sidebar list): one flat `data.items[]` of every LIVE item — to-dos AND projects — that carries a deadline, ordered `deadline` ASC (most-overdue first), tie-broken by Today order then uuid. Its `stage` is KEPT (stage-mixed, like `search`/`changes`). It is presentation-ordered and **NON-SCOPE** — deadline order IS the view, so it is not reorderable (`reorder --in deadlines` is refused). A dismissed deadline nag does NOT hide a row here (dismissal only affects the Today pull, not the deadline). Deadline-bearing repeating items are PROJECTED at their next occurrence's deadline (the same projection the `upcoming` view uses); a deadline-less or unprojectable template is left out. Filters: `--today` (only current Today members, This-Evening included), `--overdue` (only open items already past today — never projections), `--project`/`--area`/`--tag`, `--limit`/`--all`. MCP: `read_view deadlines` with `today`/`overdue`/`project`/`area`/`tag` params.
|
|
39
40
|
|
|
41
|
+
**`things repeaters`** is the other derived query view: one flat `data.items[]` of every LIVE repeating TEMPLATE — to-do and project alike — each row carrying its decoded rule under `repeating.rule` (a shape no other list row has; ordinarily the rule rides a detail read only). It exists because templates are invisible everywhere else — every other view lists the OCCURRENCES a series spawns, `search` matches those occurrences but never the series behind them, and the app hides templates too — so a series' uuid, the handle every repeat write needs, had nowhere to come from. Paused, ended, and after-completion series are INCLUDED (a catalogue that hid them would hide exactly what a reader is asking about); each row's chip states which. Ordered by next occurrence ASC, the ones that project nowhere last (title, then uuid). Filters: `--tag`/`--exact-tag`/`--untagged`, `--limit`/`--all`; `--overdue` does not apply (a template's own deadline column is an app sentinel, never a real due date). MCP: `read_view repeaters` with the `tag` params.
|
|
42
|
+
|
|
40
43
|
`things projects`/`areas`/`tags` list containers; `things projects <ref>` / `things areas <ref>` / `things show <ref>` show one item's full detail — notes, checklist, effective tags — which the compact list rows do NOT display.
|
|
41
44
|
|
|
42
45
|
### Reading view membership from JSON — `stage` and `when`
|
|
@@ -31,7 +31,7 @@ Some writes have a cascading or permanent effect. Each is refused BEFORE touchin
|
|
|
31
31
|
|
|
32
32
|
- **Delete a NON-EMPTY area** → `--allow-non-empty`. Deleting an area sends its to-dos AND its projects (with their children) to the Trash and destroys the area row permanently. A delete pre-counts live members and refuses with the counts ("the area is not empty — it contains 3 projects and 12 to-dos"), naming both remediations: empty the area first, or pass `--allow-non-empty`. An empty area is unaffected (still just `--dangerously-permanent`). Code: `blocked:H-AREA-NOT-EMPTY`.
|
|
33
33
|
- **Permanent delete** (an area; emptying the Trash) → `--dangerously-permanent`. There is no inverse; `undo` names the manual one rather than pretending. Code: `blocked:H-PERMANENT-DELETE`.
|
|
34
|
-
- **Drive the live UI** (the ops the app offers nowhere else — e.g. `area reorder`, and some heading/repeat ops) → the two-key gate: the `--dangerously-drive-gui` flag AND `things config set ui-enabled true
|
|
34
|
+
- **Drive the live UI** (the ops the app offers nowhere else — e.g. `area reorder`, and some heading/repeat ops) → the two-key gate: the `--dangerously-drive-gui` flag AND `things config set ui-enabled true`, plus the GUI-driving permissions, which are granted to the helper pair alone via `things helpers setup --gui` (never to the terminal). These visibly drive the Things window. Code: `blocked:H-UI-DRIVE` for the missing flag, `blocked:environment` for the missing permissions — the latter names exactly which grant is absent. (Separately, the `--allow-disruptive` / `--allow-very-disruptive` flags raise the process's disruption ceiling for any op that steals focus or drives the UI.)
|
|
35
35
|
- **Reopen or reuse a resolved project** — moving or adding an OPEN child into a completed/canceled project reopens it via the app; that is acknowledged (`blocked:H-REOPEN-RESOLVED-PROJECT`) so it is never a silent side effect.
|
|
36
36
|
- Other guards name their own consequence the same way (heading cascades, backdating an open item, checklist replacement, repeat scheduling, tag-subtree delete, …).
|
|
37
37
|
|
|
@@ -7,5 +7,6 @@ Curated rendering facts — the bench loop may compress or relocate these but mu
|
|
|
7
7
|
- **Sidebar**: top-level views (Inbox, Today, Upcoming, Anytime, Someday, Logbook, Trash), then each **area** with its active **projects** nested beneath it — in the same order this CLI/API reports them. Reordering areas or projects changes what the user sees there.
|
|
8
8
|
- **List rows are compact**: a to-do in any list shows its checkbox/status, title, tags, deadline, and small marker chips — the Today ★, This Evening ⏾, and (in the Today view only) the provisional `•` "new item" pip, plus has-notes / reminder / checklist glyphs (`things legend` names them all). The **notes TEXT and checklist CONTENTS are invisible until the user opens the item**. Put must-see-at-a-glance information in the title; supporting detail belongs in notes. Projects carry the same Today ★ / This Evening ⏾ pips as to-dos. Why some chips vanish once a row's date goes stale — the reminder bell, the This-Evening section, and the provisional pip — is in [banner.md](banner.md).
|
|
9
9
|
- **Project notes** are likewise visible only when the project itself is opened in project view — in lists, a project is just its title (and progress ring).
|
|
10
|
+
- **An open card renders the notes as markdown, with the syntax characters left in place** — headings, bold/italic, code spans, fenced blocks, and `-`/numbered lists take on their formatting while the user still sees the `#`, `**`, backticks, and dashes that produced it. Write notes the user will read as source AND as formatting: no huge heading stacks, and no `[label](url)` (a bare URL links itself; the label form stays literal). See [data-model.md](data-model.md) for the full construct list.
|
|
10
11
|
- **Today** shows the day's scheduled items, with **This Evening** as a separate section beneath. **Upcoming** is a forward-looking date-ordered calendar of scheduled items. **Logbook** is where completed/canceled items go — after completing something for the user, that's where they'll find it.
|
|
11
12
|
- When telling the user where something landed, name the container and view ("in project P under area A"; "it'll show in Today this evening").
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A repeating item is a **template** plus the **instances** it spawns. The template holds the rule (how often, on what calendar day, with what deadline/reminder/end bound); each occurrence the app materializes is a separate to-do (or project) you complete normally. Completing an instance never touches the template; the template keeps spawning the next occurrence on schedule.
|
|
4
4
|
|
|
5
|
+
**Telling the two halves apart in output.** On a TTY both wear the `↻` glyph, in different slots: a TEMPLATE keeps an ordinary checkbox and carries a **blue `↻` in front of its title** (`[ ] ↻ Water plants ‹Jul 8›`, `( ) ↻ Weekly review`); an INSTANCE is an ordinary checkable row with a **muted `↻` after the title**, in the marker cluster beside `≡`/`◷`/`≔` (`[ ] Water plants ↻ ≡`). In `--json` the same split is presence-keyed: a template carries a `repeating` object, an instance carries a flat `instanceOf: <templateUuid>` (the template's uuid — pass it to the repeat verbs), and a plain row carries neither. `things legend` lists both marks.
|
|
6
|
+
|
|
7
|
+
## Finding a series in the first place
|
|
8
|
+
|
|
9
|
+
`things repeaters` lists every repeating series in the library — to-dos and projects — with its uuid, its next occurrence, its container, and its decoded rule. Reach for it before any repeat write, because a template is invisible to every other read: the list views show the OCCURRENCES a series spawns rather than the series itself, and `search` matches those occurrences but never the template behind them. Paused and ended series are listed too (each row says which), which is usually the fastest answer to "why has this stopped appearing?". `--json` carries the rule structurally under `repeating.rule`; the uuid it gives you is what `reschedule-repeat`, `pause-repeat`, `resume-repeat`, and `show` all take.
|
|
10
|
+
|
|
5
11
|
## The verbs
|
|
6
12
|
|
|
7
13
|
- `things todo make-repeating <ref> --frequency … --interval …` — turn an existing plain to-do into a repeating series. This **REPLACES the item**: the original to-do disappears and a fresh template takes its place, so the uuid you get back is the TEMPLATE's, not the original's. Cannot be undone through the normal path (the original is moved to the Trash; `things undo` removes the new series and restores it).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Mass tag cleanup — reshaping a tag vocabulary without flooding the timeline
|
|
2
|
+
|
|
3
|
+
A tag cleanup ("we have 40 tags, six of them mean the same thing") touches hundreds of items, and the naive version re-dates every one of them: `things changes --since <moment>` — and any watcher or sync-driven review keyed on the modification date — then reports the whole library as freshly changed, burying whatever really happened that day.
|
|
4
|
+
|
|
5
|
+
It does not have to. **Two of the three moves cost nothing on the timeline, and the third has a flag.** Work in that order.
|
|
6
|
+
|
|
7
|
+
## The three moves, cheapest first
|
|
8
|
+
|
|
9
|
+
| Move | Command | Timeline cost |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| **Rename a tag** (merge a synonym into the survivor's name, fix casing, re-word) | `things tag update <ref> --title "<new>"` | **none** — the assignment stores the tag's identity, not its text, so no member item is touched. Existing assignments follow the rename. |
|
|
12
|
+
| **Delete a tag** (retire it everywhere, including its whole nested subtree) | `things tag delete <ref> --dangerously-permanent [--acknowledge-subtree]` | **none** — the tag is removed from every item without re-dating any of them. Permanent: tags do not go to the Trash. |
|
|
13
|
+
| **Apply or remove a tag on items** (`--set` replaces the tag set, `--add` merges) | `things todo tags <uuid> --add "<tag>"` / `things project tags <ref> --set "<a>,<b>"` | **one re-date per item** — this is the only move that lands on the timeline, and the only one that wants `--preserve-modified`. |
|
|
14
|
+
|
|
15
|
+
So: **reshape the vocabulary first, re-tag items last.** A rename that turns `errands`, `Errand`, and `errand-run` into one surviving tag costs nothing; every item you can leave alone by renaming instead of re-tagging is an item that never reaches the timeline.
|
|
16
|
+
|
|
17
|
+
## The recipe
|
|
18
|
+
|
|
19
|
+
1. **Look before you write.** `things tags` lists the hierarchy; `things anytime --tag <name> --all --json` (or `things search`) enumerates what a tag actually holds. Decide which tag survives.
|
|
20
|
+
2. **Rename the survivor into the name you want** (`tag update`), rather than creating a new tag and re-tagging into it. Free.
|
|
21
|
+
3. **Fold the synonyms.** For each doomed tag, the items that carry it and NOT the survivor need the survivor added — this is the only pass that re-dates anything, so run it with `--preserve-modified`. One item at a time:
|
|
22
|
+
```sh
|
|
23
|
+
things todo tags <uuid> --add "errand" --preserve-modified
|
|
24
|
+
```
|
|
25
|
+
For hundreds of them, `things batch` is the vehicle: it takes `--preserve-modified` at the RUN level, applying it to every line, and the whole submission comes back under one `undoToken`. The batch op is `todo.set-tags`, which REPLACES the tag set (there is no per-line merge — `--add` is CLI sugar over a read), so build each line's full set from the item's current tags:
|
|
26
|
+
```sh
|
|
27
|
+
# anytime is a SECTIONED view (data.sections[].items[]); item tags are
|
|
28
|
+
# {title} objects, and todo.set-tags wants a flat list of titles.
|
|
29
|
+
things anytime --tag "Errand" --all --json \
|
|
30
|
+
| jq -c '.data.sections[].items[]
|
|
31
|
+
| select(.type == "to-do")
|
|
32
|
+
| {op: "todo.set-tags",
|
|
33
|
+
params: {uuid, tags: ([(.tags // [])[].title] - ["errand"] + ["errand"])}}' \
|
|
34
|
+
| things batch --preserve-modified
|
|
35
|
+
```
|
|
36
|
+
(Project rows carry their own tags — filter them out as above and re-tag them with `project.set-tags` in a second pass, or drop the `select` and branch the `op` on `.type`.)
|
|
37
|
+
Each line's result carries `preservedModified` (and `preserveFailures` for any restore that did not land), so you can see the pass stayed silent. A single line can opt back onto the timeline with its own `"options": {"preserveModified": false}` — the per-line value always outranks the run flag. Add an `"opId"` per line if you want a stopped batch to be resubmittable verbatim.
|
|
38
|
+
4. **Delete the doomed tags** (`tag delete … --dangerously-permanent`, plus `--acknowledge-subtree` when the tag has children — deleting a parent deletes its whole subtree). Free, and it also removes the tag from anything you missed in step 3, so a stray assignment does not survive as a ghost.
|
|
39
|
+
5. **Check the timeline you were protecting**: `things changes --since <the moment you started>` should show only what you meant to surface.
|
|
40
|
+
|
|
41
|
+
## `--preserve-modified` in one paragraph
|
|
42
|
+
|
|
43
|
+
It captures each pre-existing edited item's modification date before the write and restores it (to the whole second) afterwards, so the edit does not surface in `changes`. It is universal — every write verb takes it — and a no-op on a pure create. The restore is best-effort: a failure is reported per item and the change itself still stands, so a cleanup never half-applies because a restore missed. It is **safe against a synced library**: the restored date propagates to your other devices and survives the round-trip, so the item stays off the timeline everywhere; the one edge is a genuinely concurrent edit to the same item on another device, which re-dates it through Things Cloud's per-attribute merge — the edit resurfaces rather than being silently hidden (SYNC2B).
|
|
44
|
+
|
|
45
|
+
Full contract: the `--preserve-modified` bullet in [../SKILL.md](../SKILL.md); per-operation detail in `things <verb> --help`.
|
|
46
|
+
|
|
47
|
+
## The adjacent cleanup: retiring an area
|
|
48
|
+
|
|
49
|
+
The same "make the free move first" logic applies to an `(archived)`-style area you want to dissolve. **Deleting an area treats its members by status:**
|
|
50
|
+
|
|
51
|
+
- an **open** direct member (to-do or project) is moved to the **Trash** and re-dated;
|
|
52
|
+
- a **logged** member (completed or canceled) is merely **detached** — its area link is cleared, it stays live in the Logbook, and it is **not** re-dated.
|
|
53
|
+
|
|
54
|
+
So deleting a long-dead area full of finished work is mostly free: the history stays in the Logbook, unlinked, off the timeline. Only the open remnants move, and those are exactly the ones you should look at first. Deleting an area is **permanent** (areas do not go to the Trash) and a non-empty one is refused unless you pass `--allow-non-empty`:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
things area delete "Old Client (archived)" --dangerously-permanent --allow-non-empty --preserve-modified
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Preview it first — `--dry-run` reports the plan — and if you want the open remnants kept, move them out before deleting rather than restoring them from the Trash afterwards.
|
|
61
|
+
|
|
62
|
+
## What this rests on
|
|
63
|
+
|
|
64
|
+
The laws above are measured, not assumed: the rename/delete/apply modification-date footprint (TAGMOD), the area-delete status-dependent trash-vs-detach split (TAGMOD-T4, refining AREADEL), and the sync round-trip that makes the restore safe on a real Things Cloud account (SYNC2B). The evidence lives with the project's lab documentation, indexed by those names.
|