things-api 0.11.0 → 0.12.0
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 +16 -5
- package/dist/audit/schema.d.ts +7 -0
- package/dist/audit/schema.js.map +1 -1
- package/dist/cli/commands/area.d.ts +18 -11
- package/dist/cli/commands/area.js +55 -67
- package/dist/cli/commands/area.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +1 -2
- package/dist/cli/commands/doctor.js +11 -1
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/install-skill.d.ts +19 -4
- package/dist/cli/commands/install-skill.js +101 -52
- package/dist/cli/commands/install-skill.js.map +1 -1
- package/dist/cli/commands/mcp.js +9 -0
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/project.d.ts +1 -0
- package/dist/cli/commands/project.js +20 -28
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/reads.js +100 -28
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/show.js +32 -8
- package/dist/cli/commands/show.js.map +1 -1
- package/dist/cli/commands/todo.js +1 -1
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +769 -143
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/did-you-mean.d.ts +14 -3
- package/dist/cli/did-you-mean.js +26 -3
- package/dist/cli/did-you-mean.js.map +1 -1
- package/dist/cli/glyphs.d.ts +16 -2
- package/dist/cli/glyphs.js +60 -12
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.js +67 -12
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +1 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/move-hint.d.ts +3 -2
- package/dist/cli/move-hint.js +2 -2
- package/dist/cli/move-hint.js.map +1 -1
- package/dist/cli/read-driver.d.ts +43 -5
- package/dist/cli/read-driver.js +81 -15
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/render.d.ts +19 -8
- package/dist/cli/render.js +59 -25
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/resolve-invocation.d.ts +2 -2
- package/dist/cli/resolve-invocation.js +3 -3
- package/dist/cli/resolve-invocation.js.map +1 -1
- package/dist/cli/skill-check.d.ts +12 -6
- package/dist/cli/skill-check.js +31 -14
- package/dist/cli/skill-check.js.map +1 -1
- package/dist/cli/skill.d.ts +8 -0
- package/dist/cli/skill.js +10 -0
- package/dist/cli/skill.js.map +1 -1
- package/dist/cli/verb-hint.js +1 -1
- package/dist/cli/verb-hint.js.map +1 -1
- package/dist/client.d.ts +140 -19
- package/dist/client.js +253 -38
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +66 -2
- package/dist/config.js +120 -12
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +176 -28
- package/dist/contracts.js +22 -1
- package/dist/contracts.js.map +1 -1
- package/dist/diagnose.d.ts +34 -2
- package/dist/diagnose.js +36 -1
- package/dist/diagnose.js.map +1 -1
- package/dist/index.d.ts +19 -8
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.js +733 -304
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/entities.d.ts +46 -3
- package/dist/model/entities.js.map +1 -1
- package/dist/model/mappers.d.ts +2 -0
- package/dist/model/mappers.js +41 -2
- package/dist/model/mappers.js.map +1 -1
- package/dist/model/recurrence.d.ts +8 -1
- package/dist/model/recurrence.js.map +1 -1
- package/dist/read/area-filter.d.ts +76 -0
- package/dist/read/area-filter.js +59 -0
- package/dist/read/area-filter.js.map +1 -0
- package/dist/read/area-view.d.ts +11 -11
- package/dist/read/area-view.js +50 -28
- package/dist/read/area-view.js.map +1 -1
- package/dist/read/detail.js +15 -8
- package/dist/read/detail.js.map +1 -1
- package/dist/read/predicates.d.ts +18 -0
- package/dist/read/predicates.js +19 -0
- package/dist/read/predicates.js.map +1 -1
- package/dist/read/project-view.d.ts +31 -15
- package/dist/read/project-view.js +72 -39
- package/dist/read/project-view.js.map +1 -1
- package/dist/read/pseudo-area.d.ts +29 -0
- package/dist/read/pseudo-area.js +27 -0
- package/dist/read/pseudo-area.js.map +1 -0
- package/dist/read/queries.d.ts +105 -18
- package/dist/read/queries.js +185 -30
- package/dist/read/queries.js.map +1 -1
- package/dist/read/scope.d.ts +126 -0
- package/dist/read/scope.js +162 -0
- package/dist/read/scope.js.map +1 -0
- package/dist/read/search-rank.d.ts +2 -5
- package/dist/read/shape.d.ts +162 -0
- package/dist/read/shape.js +686 -0
- package/dist/read/shape.js.map +1 -0
- package/dist/read/show-target.d.ts +9 -1
- package/dist/read/show-target.js +45 -4
- package/dist/read/show-target.js.map +1 -1
- package/dist/read/stage.d.ts +199 -0
- package/dist/read/stage.js +125 -0
- package/dist/read/stage.js.map +1 -0
- package/dist/read/truncation.d.ts +7 -5
- package/dist/read/truncation.js +33 -8
- package/dist/read/truncation.js.map +1 -1
- package/dist/read/views.d.ts +49 -9
- package/dist/read/views.js +193 -40
- package/dist/read/views.js.map +1 -1
- package/dist/surface-copy.d.ts +9 -0
- package/dist/surface-copy.js +9 -0
- package/dist/surface-copy.js.map +1 -1
- package/dist/write/batch.d.ts +49 -10
- package/dist/write/batch.js +423 -71
- package/dist/write/batch.js.map +1 -1
- package/dist/write/commands.js +266 -55
- package/dist/write/commands.js.map +1 -1
- package/dist/write/guards.d.ts +1 -1
- package/dist/write/guards.js +71 -10
- package/dist/write/guards.js.map +1 -1
- package/dist/write/heading.d.ts +10 -1
- package/dist/write/heading.js +35 -5
- package/dist/write/heading.js.map +1 -1
- package/dist/write/make-repeating-project.d.ts +2 -2
- package/dist/write/make-repeating-project.js +9 -9
- package/dist/write/make-repeating-project.js.map +1 -1
- package/dist/write/move.d.ts +130 -0
- package/dist/write/move.js +1533 -0
- package/dist/write/move.js.map +1 -0
- package/dist/write/operations.d.ts +105 -19
- package/dist/write/operations.js +56 -7
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +31 -0
- package/dist/write/opid.js +30 -0
- package/dist/write/opid.js.map +1 -0
- package/dist/write/pipeline.d.ts +70 -3
- package/dist/write/pipeline.js +198 -33
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +162 -8
- package/dist/write/pre-state.js +385 -20
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/reorder.d.ts +23 -3
- package/dist/write/reorder.js +1871 -85
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/reversibility.js +19 -7
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/scope-guard.d.ts +31 -0
- package/dist/write/scope-guard.js +162 -0
- package/dist/write/scope-guard.js.map +1 -0
- package/dist/write/undo.js +120 -13
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/applescript.js +16 -7
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/shortcuts.js +1 -1
- package/dist/write/vectors/shortcuts.js.map +1 -1
- package/dist/write/vectors/simulator.js +83 -27
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +11 -0
- package/dist/write/vectors/ui-certification.d.ts +3 -3
- package/dist/write/vectors/ui-certification.js +41 -5
- package/dist/write/vectors/ui-certification.js.map +1 -1
- package/dist/write/vectors/ui-drag.d.ts +21 -1
- package/dist/write/vectors/ui-drag.js +138 -27
- package/dist/write/vectors/ui-drag.js.map +1 -1
- package/dist/write/vectors/ui-recipes.d.ts +21 -1
- package/dist/write/vectors/ui-recipes.js +166 -7
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +25 -1
- package/dist/write/vectors/ui.js +142 -33
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/verify/delta.d.ts +17 -1
- package/dist/write/verify/delta.js +106 -29
- package/dist/write/verify/delta.js.map +1 -1
- package/package.json +7 -1
- package/schema/envelope.schema.json +383 -0
- package/skills/things-cli/SKILL.md +34 -17
- package/skills/things-cli/references/banner.md +35 -0
- package/skills/things-cli/references/contracts.md +30 -9
- package/skills/things-cli/references/errors.md +49 -0
- package/skills/things-cli/references/gui.md +1 -1
- package/skills/things-cli/references/model.md +15 -6
- package/skills/things-cli/references/ordering.md +71 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://github.com/mikegreiling/things-api/schema/envelope.schema.json",
|
|
3
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
|
+
"$ref": "#/definitions/WireEnvelope",
|
|
5
|
+
"definitions": {
|
|
6
|
+
"WireEnvelope": {
|
|
7
|
+
"anyOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "#/definitions/WireOkEnvelope"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "#/definitions/ErrorEnvelope"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"description": "The machine-readable ROOT the envelope JSON Schema is generated from (`schema/envelope.schema.json`, produced by `npm run schema:gen`): the discriminated union — on the boolean `ok` — of a successful envelope and an {@link ErrorEnvelope } , exactly as written to stdout by the CLI `--json` surface and inherited by the MCP server. This is the same grammar docs/contract.md describes in prose; the schema is its generated, testable rendering. NB: the streaming commands (`batch`, `undo`) emit JSON Lines, a documented exception NOT covered by this envelope type."
|
|
16
|
+
},
|
|
17
|
+
"WireOkEnvelope": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"apiVersion": {
|
|
21
|
+
"type": "number",
|
|
22
|
+
"const": 1
|
|
23
|
+
},
|
|
24
|
+
"ok": {
|
|
25
|
+
"type": "boolean",
|
|
26
|
+
"const": true
|
|
27
|
+
},
|
|
28
|
+
"kind": {
|
|
29
|
+
"$ref": "#/definitions/WireOkKind",
|
|
30
|
+
"description": "The payload class — see {@link WireOkKind } ."
|
|
31
|
+
},
|
|
32
|
+
"data": {
|
|
33
|
+
"$ref": "#/definitions/WireData",
|
|
34
|
+
"description": "Command-specific payload; open at the schema layer (see {@link WireData } )."
|
|
35
|
+
},
|
|
36
|
+
"meta": {
|
|
37
|
+
"$ref": "#/definitions/EnvelopeMeta"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"required": ["apiVersion", "ok", "kind", "data", "meta"],
|
|
41
|
+
"additionalProperties": false,
|
|
42
|
+
"description": "A successful `--json` envelope, exactly as emitted on stdout."
|
|
43
|
+
},
|
|
44
|
+
"WireOkKind": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"enum": [
|
|
47
|
+
"inbox",
|
|
48
|
+
"upcoming",
|
|
49
|
+
"logbook",
|
|
50
|
+
"trash",
|
|
51
|
+
"changes",
|
|
52
|
+
"search",
|
|
53
|
+
"projects",
|
|
54
|
+
"areas",
|
|
55
|
+
"tags",
|
|
56
|
+
"today",
|
|
57
|
+
"anytime",
|
|
58
|
+
"someday",
|
|
59
|
+
"area-view",
|
|
60
|
+
"project-view",
|
|
61
|
+
"detail",
|
|
62
|
+
"mutation-result",
|
|
63
|
+
"move-result",
|
|
64
|
+
"mutation-plan",
|
|
65
|
+
"move-plan",
|
|
66
|
+
"project-reopen",
|
|
67
|
+
"doctor",
|
|
68
|
+
"capabilities",
|
|
69
|
+
"config",
|
|
70
|
+
"legend",
|
|
71
|
+
"setup-shortcuts",
|
|
72
|
+
"install-skill"
|
|
73
|
+
],
|
|
74
|
+
"description": "The envelope `kind` discriminators a SUCCESSFUL response carries today (the error envelope's `kind` is always the literal `\"error\"`). This union is the schema's record of the known payload classes — the flat-list reads (`data.items`), the sectioned reads (`data.sections`), the composite cards (`data.view`), the single-entity detail (`data.item`), the mutations/plans (flat `data` fields), and the diagnostic payloads.\n\nPer the compatibility covenant (docs/contract.md) a consumer MUST tolerate an UNKNOWN `kind`: adding a new kind is additive / non-breaking. So this union is \"the kinds known to THIS build\", not a closed set a generic reader may assume complete — the JSON Schema pins it as an enum so drift is caught, but a forward-compatible consumer routes on the wrapper it finds in `data`."
|
|
75
|
+
},
|
|
76
|
+
"WireData": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"additionalProperties": {},
|
|
79
|
+
"description": "The `data` payload of a successful envelope. Its CONCRETE shape is command-specific — one of the R1/R2 read wrappers (`item` | `view` | `items` | `sections`) or a mutation's flat result fields — and, crucially, entity payloads are omit-empty pruned on the wire (docs/design/contracts.md).\n\nCOVERAGE BOUNDARY: this schema does NOT model the per-kind payload shapes. It pins the ENVELOPE layer exactly (`apiVersion`, `ok`, `kind`, `meta`, and the whole `error` object) and treats `data` as an open JSON object. Fully typing every kind's payload against the omit-empty wire shape is a separate, larger effort; until then `src/contracts.ts` and `docs/contract.md` remain the authoritative description of what each `kind`'s `data` contains."
|
|
80
|
+
},
|
|
81
|
+
"EnvelopeMeta": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"dbVersion": {
|
|
85
|
+
"type": ["number", "null"],
|
|
86
|
+
"description": "Things database schema version (`Meta.databaseVersion`), null when no DB was opened."
|
|
87
|
+
},
|
|
88
|
+
"fingerprint": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"enum": ["ok", "drift", "user-accepted", "unknown"],
|
|
91
|
+
"description": "Schema fingerprint status at the time of the command."
|
|
92
|
+
},
|
|
93
|
+
"elapsedMs": {
|
|
94
|
+
"type": "number",
|
|
95
|
+
"description": "Wall-clock duration of the command in milliseconds."
|
|
96
|
+
},
|
|
97
|
+
"truncation": {
|
|
98
|
+
"$ref": "#/definitions/Truncation",
|
|
99
|
+
"description": "The read's completeness metadata (the single truncation shape). Present on any read that could drop rows — flat views (row `limit`), the Today split (`sections`), and grouped views (anytime/someday/`area show`, `blocks`). `meta.truncation.truncated` is the universal \"did I see everything\" check."
|
|
100
|
+
},
|
|
101
|
+
"resolvedCommand": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": "The canonical `things …` command a sugar invocation normalized to (bare noun, keyword-in-show, uuid/share-link routing). Present only on routed reads reached via a sugar form; absent for canonical invocations."
|
|
104
|
+
},
|
|
105
|
+
"warnings": {
|
|
106
|
+
"type": "array",
|
|
107
|
+
"items": {
|
|
108
|
+
"type": "string"
|
|
109
|
+
},
|
|
110
|
+
"description": "Non-blocking advisories about this read (ADDITIVE). Present only when there is something to flag — currently a one-line note when the Things database schema no longer matches the version this build was validated against (reads stay best-effort; run `things doctor`). Absent means none."
|
|
111
|
+
},
|
|
112
|
+
"clock": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"properties": {
|
|
115
|
+
"timezone": {
|
|
116
|
+
"type": "string"
|
|
117
|
+
},
|
|
118
|
+
"today": {
|
|
119
|
+
"type": "string"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"required": ["timezone", "today"],
|
|
123
|
+
"additionalProperties": false,
|
|
124
|
+
"description": "The effective clock this response's date boundaries were computed for (ADDITIVE honesty field). Present ONLY when a consumer zone (`THINGS_TZ` / the MCP `tz` argument) or a pinned `THINGS_NOW` is in effect; ABSENT on the host clock, so the wire shape is unchanged for ordinary consumers."
|
|
125
|
+
},
|
|
126
|
+
"filter": {
|
|
127
|
+
"type": "object",
|
|
128
|
+
"properties": {
|
|
129
|
+
"area": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"properties": {
|
|
132
|
+
"uuid": {
|
|
133
|
+
"type": "string"
|
|
134
|
+
},
|
|
135
|
+
"title": {
|
|
136
|
+
"type": "string"
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"required": ["uuid", "title"],
|
|
140
|
+
"additionalProperties": false
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"required": ["area"],
|
|
144
|
+
"additionalProperties": false,
|
|
145
|
+
"description": "The active content filter this response was scoped to (ADDITIVE). Present ONLY when a scope was applied — currently `area` (the `--area` view filter), carrying the resolved area's uuid + title; ABSENT otherwise, so the wire shape is unchanged for unscoped reads (the `meta.clock` precedent)."
|
|
146
|
+
},
|
|
147
|
+
"scope": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"properties": {
|
|
150
|
+
"kind": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"enum": ["area", "project"]
|
|
153
|
+
},
|
|
154
|
+
"uuid": {
|
|
155
|
+
"type": "string"
|
|
156
|
+
},
|
|
157
|
+
"title": {
|
|
158
|
+
"type": "string"
|
|
159
|
+
},
|
|
160
|
+
"source": {
|
|
161
|
+
"type": "string",
|
|
162
|
+
"enum": ["flag", "env", "config"]
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"required": ["kind", "uuid", "title", "source"],
|
|
166
|
+
"additionalProperties": false,
|
|
167
|
+
"description": "The active container scope this response was jailed to (ADDITIVE). Present ONLY when a scope is in force (the MCP `--scope` flag / `THINGS_API_SCOPE` / a stored `scope`), naming the container + where the scope came from; ABSENT otherwise, so the wire shape is unchanged for unscoped reads. Lets an agent know its own jail — which is not an oracle for what lies outside it (the `meta.clock` / `meta.filter` additive precedent)."
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"required": ["dbVersion", "fingerprint", "elapsedMs"],
|
|
171
|
+
"additionalProperties": false
|
|
172
|
+
},
|
|
173
|
+
"Truncation": {
|
|
174
|
+
"type": "object",
|
|
175
|
+
"properties": {
|
|
176
|
+
"shown": {
|
|
177
|
+
"type": "number"
|
|
178
|
+
},
|
|
179
|
+
"total": {
|
|
180
|
+
"type": "number"
|
|
181
|
+
},
|
|
182
|
+
"limit": {
|
|
183
|
+
"type": ["number", "null"]
|
|
184
|
+
},
|
|
185
|
+
"truncated": {
|
|
186
|
+
"type": "boolean"
|
|
187
|
+
},
|
|
188
|
+
"sections": {
|
|
189
|
+
"type": "array",
|
|
190
|
+
"items": {
|
|
191
|
+
"$ref": "#/definitions/SectionCount"
|
|
192
|
+
},
|
|
193
|
+
"description": "Per-section shown/total breakdown for a split flat view (Today); absent otherwise."
|
|
194
|
+
},
|
|
195
|
+
"blocks": {
|
|
196
|
+
"type": "array",
|
|
197
|
+
"items": {
|
|
198
|
+
"$ref": "#/definitions/GroupBlock"
|
|
199
|
+
},
|
|
200
|
+
"description": "Per-block nesting for a grouped view (anytime/someday/area card); absent otherwise."
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"required": ["shown", "total", "limit", "truncated"],
|
|
204
|
+
"additionalProperties": false,
|
|
205
|
+
"description": "The single truncation-metadata shape for every read (the `meta.truncation` field). `shown` items were returned of `total` that matched after all filters; `limit` is the effective cap (null when the caller asked for all rows, and always null on a grouped view whose caps are per-block); `truncated` is the UNIVERSAL completeness check — true exactly when anything was dropped (`shown < total`, or any block hid rows). The dropped remainder is `total - shown`.\n\nTwo optional per-shape breakdowns hang off it: `sections` for a split flat view (the Today split — per render-section shown/total), and `blocks` for a grouped view (anytime/someday/`area show` — the identity-carrying per-block nesting). Exactly one of them is present on the shapes that have it; both are absent on a plain flat view."
|
|
206
|
+
},
|
|
207
|
+
"SectionCount": {
|
|
208
|
+
"type": "object",
|
|
209
|
+
"properties": {
|
|
210
|
+
"key": {
|
|
211
|
+
"type": "string",
|
|
212
|
+
"enum": ["today", "evening"]
|
|
213
|
+
},
|
|
214
|
+
"shown": {
|
|
215
|
+
"type": "number"
|
|
216
|
+
},
|
|
217
|
+
"total": {
|
|
218
|
+
"type": "number"
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
"required": ["key", "shown", "total"],
|
|
222
|
+
"additionalProperties": false,
|
|
223
|
+
"description": "Per-section counts for a SPLIT flat view — currently only `today`'s Today / This-Evening split. One entry per section IN RENDER ORDER, each reporting the rows `shown` of the `total` that matched after all filters. Lets a renderer keep a downstream section (This Evening) honest under a single global cap without a pre-truncation copy of the view."
|
|
224
|
+
},
|
|
225
|
+
"GroupBlock": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"properties": {
|
|
228
|
+
"kind": {
|
|
229
|
+
"type": "string",
|
|
230
|
+
"enum": ["loose", "area", "project", "projects"]
|
|
231
|
+
},
|
|
232
|
+
"ref": {
|
|
233
|
+
"type": ["string", "null"],
|
|
234
|
+
"description": "Container reference (area or project uuid); null for the loose block."
|
|
235
|
+
},
|
|
236
|
+
"title": {
|
|
237
|
+
"type": ["string", "null"],
|
|
238
|
+
"description": "Container title; null for the loose block."
|
|
239
|
+
},
|
|
240
|
+
"shown": {
|
|
241
|
+
"type": "number"
|
|
242
|
+
},
|
|
243
|
+
"total": {
|
|
244
|
+
"type": "number"
|
|
245
|
+
},
|
|
246
|
+
"limit": {
|
|
247
|
+
"type": ["number", "null"],
|
|
248
|
+
"description": "The cap that applied to THIS block (null = uncapped)."
|
|
249
|
+
},
|
|
250
|
+
"totalProjects": {
|
|
251
|
+
"type": "number",
|
|
252
|
+
"description": "Type split for blocks that mix project rows and to-dos (someday's loose/area blocks; projects always list first, so the hidden split is `totalProjects - min(shown, totalProjects)` projects, remainder to-dos). Absent on single-type blocks."
|
|
253
|
+
},
|
|
254
|
+
"totalTodos": {
|
|
255
|
+
"type": "number"
|
|
256
|
+
},
|
|
257
|
+
"children": {
|
|
258
|
+
"type": "array",
|
|
259
|
+
"items": {
|
|
260
|
+
"$ref": "#/definitions/GroupBlock"
|
|
261
|
+
},
|
|
262
|
+
"description": "Nested project blocks (anytime item-lists / someday active-project groups). Absent when none."
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
"required": ["kind", "ref", "title", "shown", "total", "limit"],
|
|
266
|
+
"additionalProperties": false,
|
|
267
|
+
"description": "One identity-carrying block of a grouped catalogue (anytime/someday) or a sectioned detail view (`area show`). Every header/section is always rendered; only the innermost item lists are capped. Emitted for every block that has rows to cap (`total > 0`) — including a block whose rows were ALL dropped (`shown: 0`), so no truncated header is untraceable. A block with no cappable rows of its own (`total: 0`) is omitted UNLESS it wraps truncated `children` — an area whose only capped content is its project item-lists still appears as their container.\n\nBlocks are NESTED: an area/loose block carries its project blocks in `children` — in anytime the project item-lists inside the area, in someday the active-project child groups found in that section. The `area show` `projects`/`area` blocks are siblings of one area and stay top-level."
|
|
268
|
+
},
|
|
269
|
+
"ErrorEnvelope": {
|
|
270
|
+
"type": "object",
|
|
271
|
+
"properties": {
|
|
272
|
+
"apiVersion": {
|
|
273
|
+
"type": "number",
|
|
274
|
+
"const": 1
|
|
275
|
+
},
|
|
276
|
+
"ok": {
|
|
277
|
+
"type": "boolean",
|
|
278
|
+
"const": false
|
|
279
|
+
},
|
|
280
|
+
"kind": {
|
|
281
|
+
"type": "string",
|
|
282
|
+
"const": "error"
|
|
283
|
+
},
|
|
284
|
+
"error": {
|
|
285
|
+
"type": "object",
|
|
286
|
+
"properties": {
|
|
287
|
+
"code": {
|
|
288
|
+
"$ref": "#/definitions/ErrorCode",
|
|
289
|
+
"description": "Stable machine-readable code from the {@link ErrorCode } registry (mirrors the exit-code family, e.g. \"verify-failed\", \"blocked:H-UNKNOWN-TAG\")."
|
|
290
|
+
},
|
|
291
|
+
"message": {
|
|
292
|
+
"type": "string"
|
|
293
|
+
},
|
|
294
|
+
"likelyCause": {
|
|
295
|
+
"type": "string",
|
|
296
|
+
"description": "Advisory attribution when failure signals point somewhere: e.g. \"permission-denied\", \"permission-pending\", \"feature-disabled\", \"app-updated\", \"schema-drift\", \"app-behavior-change\"."
|
|
297
|
+
},
|
|
298
|
+
"remediation": {
|
|
299
|
+
"type": "string",
|
|
300
|
+
"description": "Actionable next step for the caller, when one exists."
|
|
301
|
+
},
|
|
302
|
+
"detail": {
|
|
303
|
+
"type": "object",
|
|
304
|
+
"properties": {
|
|
305
|
+
"expected": {},
|
|
306
|
+
"observed": {
|
|
307
|
+
"anyOf": [
|
|
308
|
+
{
|
|
309
|
+
"type": "object",
|
|
310
|
+
"additionalProperties": {}
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"type": "null"
|
|
314
|
+
}
|
|
315
|
+
]
|
|
316
|
+
},
|
|
317
|
+
"candidates": {
|
|
318
|
+
"type": "array",
|
|
319
|
+
"items": {}
|
|
320
|
+
},
|
|
321
|
+
"suggestions": {
|
|
322
|
+
"type": "array",
|
|
323
|
+
"items": {
|
|
324
|
+
"type": "string"
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
"considered": {
|
|
328
|
+
"type": "array",
|
|
329
|
+
"items": {
|
|
330
|
+
"type": "object",
|
|
331
|
+
"properties": {
|
|
332
|
+
"vector": {
|
|
333
|
+
"type": "string"
|
|
334
|
+
},
|
|
335
|
+
"why": {
|
|
336
|
+
"type": "string"
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
"required": ["vector", "why"],
|
|
340
|
+
"additionalProperties": false
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
"placed": {
|
|
344
|
+
"type": "array",
|
|
345
|
+
"items": {
|
|
346
|
+
"type": "string"
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
"remaining": {
|
|
350
|
+
"type": "array",
|
|
351
|
+
"items": {
|
|
352
|
+
"type": "string"
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
"cause": {},
|
|
356
|
+
"failed": {},
|
|
357
|
+
"completed": {
|
|
358
|
+
"type": "array",
|
|
359
|
+
"items": {}
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
"additionalProperties": false,
|
|
363
|
+
"description": "The SINGLE structured, machine-readable failure-context object (there is no separate `details` field — the two were reconciled into this one). Each key is present only for the failures that produce it: - `expected` / `observed` — a verify-failed mutation's expected delta and the observed post-write state. - `candidates` — the disambiguation list for a not-found / ambiguous resolution (or a show/bare-noun did-you-mean): the ONE fixed, flag- invariant candidate shape (`CandidateRef`; see docs/contract.md), a LIVE-scoped pool capped at 8, so an agent can self-correct without another round-trip. - `suggestions` — for a bare mutation verb (`things update <ref>`), the concrete namespaced command(s) to run instead. - `considered` — the vectors weighed (and why each was rejected) for an unsupported operation. - `placed` / `remaining` / `cause` — a bounce reorder that aborted part-way: the items already placed, those not yet placed, and the cause. - `failed` / `completed` — a multi-leg move that failed mid-way: the leg that failed and the legs already completed before it."
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
"required": ["code", "message"],
|
|
367
|
+
"additionalProperties": false
|
|
368
|
+
},
|
|
369
|
+
"meta": {
|
|
370
|
+
"$ref": "#/definitions/EnvelopeMeta"
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
"required": ["apiVersion", "ok", "kind", "error", "meta"],
|
|
374
|
+
"additionalProperties": false
|
|
375
|
+
},
|
|
376
|
+
"ErrorCode": {
|
|
377
|
+
"type": "string",
|
|
378
|
+
"description": "The registry of stable machine-readable error codes an error envelope can carry — the compiler IS the registry. Every `error.code` value the surfaces emit is a member of this union, and the human-readable meaning of each is frozen at v1.0 (new codes may still be ADDED after v1.0 — that is non-breaking; a documented code's MEANING never changes). The canonical per-code table (meaning + the `detail` keys each may carry) lives in `docs/contract.md` (The error-code registry).\n\nTwo members are template-literal families rather than fixed strings, because their suffix is minted in the write layer and the core deliberately never depends on it (see {@link blockedCode } / {@link verifyFailedCode } ): - `verify-failed:${reason}` — a single mutation executed but read-after-write verification failed; the suffix is the reason (`timeout` | `mismatch` | `silent-noop`). The bare `verify-failed` (no suffix) is the multi-leg move/reorder failure. - `blocked:${suffix}` — a mutation refused before touching the app; the suffix is the specific hazard id (`H-…`) when one is named, else the block reason (`drift` | `disruption-tier` | `lock` | `environment` | `clock` | `scope`). The bare `blocked` is a policy refusal from the move planner.\n\n`blocked:drift` maps to exit code 5 (DriftBlocked); every other `blocked:*` maps to exit code 4 (Blocked)."
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
"title": "things-api envelope",
|
|
382
|
+
"description": "JSON Schema for the things-api --json response envelope (the discriminated union of a success envelope and an error envelope), generated from the WireEnvelope type in src/contracts.ts. See docs/contract.md (The machine-readable schema). The envelope layer is modeled exactly; the command-specific `data` payload is an open object (coverage boundary)."
|
|
383
|
+
}
|
|
@@ -1,53 +1,70 @@
|
|
|
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.12.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Things CLI
|
|
8
8
|
|
|
9
|
-
`things` is a command-line interface to the user's Things 3 task database. Reads are instant SQL queries; writes go through the app itself and are
|
|
9
|
+
`things` is a command-line interface to the user's Things 3 task database. Reads are instant SQL queries; writes go through the app itself and are checked after they land. Use `things` when it is on your PATH; otherwise — or when `things --version` reports below **0.12.0** — substitute `npx -y things-api@latest` in every command (identical subcommands and flags), which always pairs the current commands with their current help.
|
|
10
10
|
|
|
11
|
-
`things --help` is the one-screen index; `things <group> --help` lists a group's verbs and flags (always current for the binary you invoke); `things help <topic>` opens a contract guide — topics: `agent`, `filters`, `ids`, `output`, `repeating`, `writes`.
|
|
11
|
+
`things --help` is the one-screen index; `things <group> --help` lists a group's verbs and flags (always current for the binary you invoke); `things help <topic>` opens a contract guide — topics: `agent`, `filters`, `ids`, `move`, `output`, `repeating`, `writes`.
|
|
12
12
|
|
|
13
13
|
## Data model (read this first)
|
|
14
14
|
|
|
15
15
|
- **To-do** — the basic item: title, notes, an optional **checklist** (sub-steps), tags, schedule, deadline, reminder. Has at most ONE container — loose in an **area**, directly in a **project**, or under a **heading** inside a project — or **none at all** (standalone to-dos are normal, like standalone projects).
|
|
16
16
|
- **Inbox** — not a container but the *untriaged state*: an inbox to-do has no container and no schedule. Filing it into a container or scheduling it moves it out of the Inbox; moving something back TO the Inbox un-files AND un-schedules it.
|
|
17
17
|
- **Project** — a goal-sized container of to-dos, optionally divided by **headings** (section labels; a heading belongs to one project and cannot hold projects). Projects live in an area or stand alone. Projects can also have their own notes, tags, schedule, and deadline.
|
|
18
|
-
- **Area** — a top-level bucket (e.g. a sphere of responsibility) holding projects and loose to-dos. Areas have tags but no dates.
|
|
18
|
+
- **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.
|
|
19
19
|
- **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.
|
|
20
20
|
- **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`.
|
|
21
|
-
- **Scheduling vocabulary**: an item's *when* is `today | evening | anytime | someday | YYYY-MM-DD
|
|
21
|
+
- **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*.
|
|
22
|
+
|
|
23
|
+
## Reading position from JSON: `stage` and `when`
|
|
24
|
+
|
|
25
|
+
Reads decompose an item's position onto two derived, presence-keyed words (they REPLACED the old `start`/`logged`/`trashed`/`todaySection` fields):
|
|
26
|
+
|
|
27
|
+
- **`stage`** — the sidebar BUCKET: `inbox | upcoming | anytime | someday | logbook | trash`. Read view membership off it directly (a completed row is `logbook`, a trashed row `trash`, regardless of any other hint). Dropped inside a section that already states it; present in mixed lists, `search`, `changes`, and `detail`.
|
|
28
|
+
- **`when`** — the TIME POSITION: `today | evening | a future ISO date`, or absent. `evening` implies today; someday is a bucket (→ `stage`), never a `when`. A due deadline pulls an undated row into Today (`when: "today"`); the app re-files it into Anytime, so it derives `stage: "anytime"` (it leaves the Inbox/Someday list).
|
|
29
|
+
- **`provisional: true`** marks a Today member the app has not yet materialized (the "N new to-dos" banner / `•` pip); see [references/banner.md](references/banner.md).
|
|
30
|
+
|
|
31
|
+
**Absence is meaningful.** In the compact list tier a field at its default is omitted (no `status` = open, no `when` = not in Today), and inside a single-container node an item omits ancestry the node already states (absent `project`/`area` there = inherited). Full rules and the compact/full tiers: [references/model.md](references/model.md).
|
|
22
32
|
|
|
23
33
|
## Referring to items
|
|
24
34
|
|
|
25
|
-
Commands take a `<ref>`: a UUID, a unique UUID prefix, or a (unique) title. Ambiguous refs fail with the candidates listed — pick one and retry. Discover UUIDs via any read command; add `--json` for stable machine output (UUIDs are in `.uuid
|
|
35
|
+
Commands take a `<ref>`: a UUID, a unique UUID prefix (≥ 6 chars), a Things share link, or a (unique) title. Ambiguous refs fail with the candidates listed — pick one and retry ([references/errors.md](references/errors.md) has the candidate/dead-row/hazard contract). Discover UUIDs via any read command; add `--json` for stable machine output (UUIDs are in `.uuid`; emitted UUIDs are always full).
|
|
26
36
|
|
|
27
37
|
## Stable contracts
|
|
28
38
|
|
|
29
39
|
These hold regardless of the binary version; see [references/contracts.md](references/contracts.md) for the full text.
|
|
30
40
|
|
|
31
|
-
- **JSON envelope**: every `--json` response is `{ ok, data, meta }`. Read results from `.data` (
|
|
32
|
-
- **Exit codes**: `0` landed and
|
|
33
|
-
- **Previews, undo &
|
|
41
|
+
- **JSON envelope**: every `--json` response is `{ apiVersion, ok, kind, data, meta }`. Read results from `.data` (`.data.items`/`.data.sections`/`.data.item`/`.data.view` per `kind`), 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.
|
|
42
|
+
- **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.
|
|
43
|
+
- **Previews, undo & idempotency**: `--dry-run` shows any write's 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`).
|
|
34
44
|
- **Preconditions**: referenced containers and tags must already exist — create nested structures outside-in and reuse each returned UUID.
|
|
35
|
-
-
|
|
36
|
-
- **View reasoning from JSON**: `start:"inbox"`→Inbox, `start:"someday"`→Someday, `start:"active"` with no `startDate`→Anytime; a dated open item is Today when dated for `meta.clock.today`, else Upcoming. `todaySection` only marks placement within Today and is NOT evidence an undated item is in Today. Completed/canceled → Logbook, trashed → Trash, regardless of a stale `logged` field.
|
|
37
|
-
- If the user requests a JSON reply schema, return exactly that object after the read or verified write.
|
|
45
|
+
- If the user requests a JSON reply schema, return exactly that object after the read or checked write.
|
|
38
46
|
|
|
39
47
|
## Reading
|
|
40
48
|
|
|
41
|
-
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>`. Filters (`--tag`, `--untagged`, `--overdue`, `--limit N`, …) compose with AND — see `things help filters`
|
|
49
|
+
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>`. 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.
|
|
42
50
|
|
|
43
51
|
## Writing
|
|
44
52
|
|
|
45
|
-
Namespaced verb families — run `things <group> --help` for the verbs and `things <group> <verb> --help` for exact flags: `things todo …` (add/update/complete/cancel/reopen/move/delete/restore/tags/checklist/make-repeating), `things project
|
|
53
|
+
Namespaced verb families — run `things <group> --help` for the verbs and `things <group> <verb> --help` for exact flags: `things todo …` (add/update/complete/cancel/reopen/move/delete/restore/tags/checklist/make-repeating), `things project …` (add/update/move/complete/… plus the heading verbs), `things area …`, `things tag …`, plus `things batch` (JSONL — chain created uuids across lines with `tempId`/`$ref`, retry safely with `opId`, undo the whole run with its `undoToken`), `things undo`, and `things reorder`.
|
|
54
|
+
|
|
55
|
+
**Scheduling is an update, not a move**: `things todo update <ref> --when today|evening|anytime|someday|YYYY-MM-DD` schedules or parks an item; `move` changes its CONTAINER only.
|
|
56
|
+
|
|
57
|
+
**Move vs reorder — keep them apart** (`things help move`). MOVE changes WHAT an item belongs to: `things todo move <refs…> --to-project|--to-heading|--to-area <ref>`, or the detach family `--no-heading` / `--loose` / `--inbox`; `things project move <refs…> --to-area <ref>|--no-area`. REORDER changes only ARRANGEMENT in place, never membership: `things reorder <refs…> [--first|--last|--before <ref>|--after <ref>] [--in <target>]`. Both are variadic (selection order = landing order). An anchor POSITIONS but never MIGRATES — a cross-container/bucket anchor fails closed. A Today/Evening member sits on two axes (its Today slot and its container slot); a set coherent on both is REFUSED until you pass `--in` (`today|evening|anytime|someday|inbox`, or a project/area/heading ref). Every non-template item is sortable; the axes, gates, and automatic fallbacks are in [references/ordering.md](references/ordering.md). (MCP exposes these as the `reorder` and `reorder_areas` tools.)
|
|
58
|
+
|
|
59
|
+
**Guarded writes** surface their consequence and require an explicit flag: deleting a NON-EMPTY area needs `--allow-non-empty`, a permanent delete needs `--dangerously-permanent`, and a UI-driving op (e.g. `area reorder`) needs `--dangerously-drive-gui` plus `things config set ui-enabled true`. The full acknowledgment contract is in [references/errors.md](references/errors.md).
|
|
46
60
|
|
|
47
|
-
**
|
|
61
|
+
**Quick skeletons**: `things todo add "T1" "T2" "T3" [shared flags]` creates several to-dos in one call (every shared flag — `--project`/`--area`/`--when`/`--tags`/… — applies to each; `--id-only` prints the new uuids one per line for chaining; one `undoToken` removes the set). To stand up a new project with children, `things project add "<title>" --todo "T1" --todo "T2" …` (repeatable). For richer per-item metadata or cross-item references, use `things batch`.
|
|
48
62
|
|
|
49
63
|
## Going deeper
|
|
50
64
|
|
|
51
|
-
- [references/model.md](references/model.md) — the full data model, view
|
|
52
|
-
- [references/contracts.md](references/contracts.md) — the JSON envelope, exit codes, safety/undo/
|
|
65
|
+
- [references/model.md](references/model.md) — the full data model, `stage`/`when` derivation, the compact/full tiers, view membership, and filters.
|
|
66
|
+
- [references/contracts.md](references/contracts.md) — the JSON envelope, exit codes, safety/undo/idempotency, batch chaining, and recurrence.
|
|
67
|
+
- [references/ordering.md](references/ordering.md) — move vs reorder in depth: axes, gates, caps, automatic fallbacks, placement guarantees, and the one dead class (templates).
|
|
68
|
+
- [references/errors.md](references/errors.md) — the error contract: the candidate shape, dead-row hints, hazard acknowledgments, and the error-code registry.
|
|
69
|
+
- [references/banner.md](references/banner.md) — the Today "new to-dos" banner, the provisional `•` pip, reminder/evening liveness, and what a watcher sees.
|
|
53
70
|
- [references/gui.md](references/gui.md) — how the user sees Things in the app (where results appear, what list rows show).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# The Today banner, provisional pips, and liveness (deep reference)
|
|
2
|
+
|
|
3
|
+
Things renders three presentation-layer facts that are NOT stored as their own bytes — the "new to-dos" banner, the evening section, and the reminder bell. The reader mirrors the app faithfully rather than reporting the raw columns. This page is the model behind the `provisional` field, the `•` pip, and reminder/evening liveness.
|
|
4
|
+
|
|
5
|
+
## The provisional `•` pip and the "N new to-dos" banner
|
|
6
|
+
|
|
7
|
+
When items arrive in Today on their own, Things shows a yellow "You have N new to-dos" banner and a yellow `•` pip on each such row. Those rows are **provisional** — Today members the app has not yet materialized.
|
|
8
|
+
|
|
9
|
+
- The wire emits presence-keyed **`provisional: true`** on exactly those rows, on every tier, and never drops it (the banner is not a section). The banner's `N` equals the count of `provisional` Today members, exactly — **the banner as data.**
|
|
10
|
+
- **What "new" means:** the flagged rows are the AUTONOMOUS entrants — a scheduled arrival (lands on its startDate), a repeat-instance spawn, or a due-deadline pull (no startDate yet). A USER-placed entrant (`add`/`update --when today`, which lands materialized) is never flagged. The wire hands you `provisional` directly, so you never compute it; the underlying law (what a raw-DB watcher would evaluate) is `start` is not `1` (active) OR `startDate IS NULL`.
|
|
11
|
+
- `provisional` reuses the same Today markers `when` does, so it can never disagree with the ★ the row would render.
|
|
12
|
+
- **The pip is Today-view-only.** In the app's Anytime list the same provisional row shows the ordinary Today ★ and no pip. So the TTY renders `•` only in `things today` (Today and This Evening sections); the wire `provisional` field is present on every surface, but the `•` glyph is not.
|
|
13
|
+
|
|
14
|
+
**A provisional row is already a full Today member** — visible, sortable, and mutable through every ordinary write. Model it as `stage: "anytime"` + `when: "today"` + `provisional: true`: at pull time the app re-files a deadline-pulled Inbox/Someday row into Anytime (R13/BANNER1b), so it is EXCLUDED from the Inbox/Someday lists and INCLUDED in Anytime even though the user never chose to leave those buckets — the derived stage reflects where the app actually shows it.
|
|
15
|
+
|
|
16
|
+
## There is no `today ok` — dismissing the banner is a data mutation
|
|
17
|
+
|
|
18
|
+
`things today ok` (acknowledge the banner / clear the pips) is a DECIDED not-to-implement (⛔). Reasons worth knowing:
|
|
19
|
+
|
|
20
|
+
- Clicking **OK** in the app does exactly one thing: it MATERIALIZES the provisional rows — `start := 1` and `startDate := ` the cohort/deadline date where it was NULL — and writes nothing else, in no other table. There is no separate "reviewed" marker anywhere; acknowledgment IS the row mutation.
|
|
21
|
+
- That mutation is **not byte-reproducible** through a supported write (`--when today` no-ops on a provisional row; the OK'd `startDate` is a past date every URL write clamps; `--when anytime` is the only leg that flips the bucket but it ejects the row from Today), and it silently **de-inboxes / de-somedays** an untriaged pulled item (`start 0→1` / `2→1`). Approximating it would diverge silently from the app's own bytes, so it is not offered.
|
|
22
|
+
|
|
23
|
+
**The per-item dismiss you CAN drive (deadline-pulled rows):** rescheduling an overdue-deadline row out of Today to a no-startDate bucket — `things todo update <ref> --when someday` (or `anytime`) — auto-stamps the app's own suppression so the past deadline stops re-pulling it; or `things todo update <ref> --clear-deadline` removes the deadline outright. There is no dedicated "dismiss deadline" affordance in the app either — this reschedule side effect is it.
|
|
24
|
+
|
|
25
|
+
## What a watcher sees
|
|
26
|
+
|
|
27
|
+
A process polling the database must tolerate the app rewriting provisional rows' schedule bytes out from under it: when a human clicks OK (on this or any synced device), every listed row's `start`/`startDate` change (`start:=1`, `startDate:=` the cohort date). Nothing else moves. So a watcher should key "new/unreviewed" off the same derivation the reader uses (`start != 1 OR startDate IS NULL`), NOT off a stored marker (there is none), and expect those bytes to settle once the user acknowledges.
|
|
28
|
+
|
|
29
|
+
## Reminder and evening liveness (§9n)
|
|
30
|
+
|
|
31
|
+
The app shows a reminder bell and a "This Evening" placement only while the row's `startDate` is TODAY (or future, for a reminder). Once `startDate` goes strictly past, the app discards both at the presentation layer while leaving the raw columns in the database forever — nothing clears them, not a day-rollover, not the banner OK. The reader mirrors this:
|
|
32
|
+
|
|
33
|
+
- **Reminders.** A today- or future-dated reminder is reported (`reminder` present); a strictly-past one is OMITTED on every surface (list rows, detail, JSON, search, and the TTY `◷` chip). The stored byte is never touched. On the WRITE side a bare `--when` re-schedule auto-preserves a LIVE reminder but never RESURRECTS a stale one (so `--when evening` on a weeks-past row no longer re-arms an 18:00 bell the user believed gone).
|
|
34
|
+
- **Evening.** An arrived evening row whose `startDate` has gone past collapses to a plain Today member — `when` reads `"today"`, not `"evening"`. The evening section expires daily.
|
|
35
|
+
- **Reminder format:** the JSON wire carries `HH:MM` (24-hour); the human TTY chip renders 12-hour. Same fact, two renderings.
|
|
@@ -4,31 +4,52 @@ The apiVersion-stable contracts that hold regardless of which binary version you
|
|
|
4
4
|
|
|
5
5
|
## JSON envelope
|
|
6
6
|
|
|
7
|
-
Every `--json` response is an envelope `{ ok, data, meta }`:
|
|
7
|
+
Every `--json` response is an envelope `{ apiVersion, ok, kind, data, meta }`:
|
|
8
8
|
|
|
9
|
-
- Results are in `.data` (
|
|
10
|
-
- Check `meta.truncation
|
|
11
|
-
- List/search rows are **summaries**: their `tags` field is not necessarily the complete effective set, and placement can be partial. Use `things show <ref> --json` when notes, checklist, placement, or inherited/effective tags matter.
|
|
9
|
+
- Results are in `.data`, in exactly one of four wrappers named by `kind`: `.data.item` (one entity), `.data.view` (an area/project card), `.data.items` (a flat list), or `.data.sections` (a list split into named sections). Never `.items` at the top level. Item UUIDs are in `.uuid`, not `.id`; emitted UUIDs are always full.
|
|
10
|
+
- Check `meta.truncation.truncated` before concluding "no match" or "that's everything": it is `true` exactly when any row was hidden (`shown < total`, or any grouped block capped). Raise `--limit`/`--all` or narrow the filter rather than assuming you saw everything.
|
|
11
|
+
- List/search rows are compact **summaries**: their `tags` field is not necessarily the complete effective set, and placement can be partial. Use `things show <ref> --json` when notes, checklist, placement, or inherited/effective tags matter.
|
|
12
12
|
|
|
13
13
|
## Exit codes (writes are verified after they land)
|
|
14
14
|
|
|
15
|
+
A value once assigned keeps its meaning forever — the codes are never renumbered.
|
|
16
|
+
|
|
15
17
|
- `0` — the change landed and was verified.
|
|
18
|
+
- `1` — unexpected (an internal error / bug): stop and report.
|
|
16
19
|
- `2` — usage error: fix your invocation and retry.
|
|
17
20
|
- `3` — verify-failed: the change did NOT stick; the message carries the reason and usually the remediation.
|
|
18
|
-
-
|
|
21
|
+
- `4` — blocked: refused before it touched the app (a guard/hazard, scope, lock, …); the message names the flag or fix.
|
|
22
|
+
- `5` — drift-blocked: the database schema fingerprint no longer matches; writes are held until re-certified.
|
|
23
|
+
- `6` — unsupported: no available write surface performs this operation.
|
|
24
|
+
- `7` — environment: database not found, Things not installed, or a permission problem.
|
|
19
25
|
|
|
20
|
-
A nonzero exit is informative, not a dead end — it means the write did not silently half-apply, so you are never left guessing whether it took. Read the message; it usually names the fix.
|
|
26
|
+
A nonzero exit is informative, not a dead end — it means the write did not silently half-apply, so you are never left guessing whether it took. Read the message; it usually names the fix. The error `code`, the candidate/dead-row contract, and the hazard acknowledgments are in [errors.md](errors.md).
|
|
21
27
|
|
|
22
28
|
## Safety & recovery
|
|
23
29
|
|
|
24
30
|
- `--dry-run` previews the exact plan (operation, target, expected change) for ANY write without executing — use it for anything destructive, bulk, or unfamiliar.
|
|
25
31
|
- `things undo` reverses recent changes made through this tool (its own audit trail, not arbitrary app history). Prefer a targeted fix when you know it; undo is the safety net.
|
|
26
|
-
-
|
|
27
|
-
- Ambiguous refs FAIL with the candidates listed — retry with a UUID or a unique prefix. Never guess between candidates for a destructive action; inspect details or ask.
|
|
32
|
+
- To-do/project deletes are TRASHES: `todo delete` moves to Trash and is restorable (`todo restore`). Emptying the trash is permanent and requires explicit user intent — don't do it unless asked. AREAS are the exception — an area does not go to the Trash, so deleting one is permanent (`--dangerously-permanent`), and a non-empty area also needs `--allow-non-empty` (see [errors.md](errors.md)).
|
|
33
|
+
- Ambiguous refs FAIL with the candidates listed — retry with a UUID or a unique prefix. Never guess between candidates for a destructive action; inspect details or ask. The full candidate shape and dead-row hints are in [errors.md](errors.md).
|
|
28
34
|
- Referenced containers and tags must already exist. Create nested structures **outside-in** (area → project → heading → to-do), and prefer each newly returned UUID as the next reference so duplicate titles cannot redirect placement.
|
|
29
|
-
- Some operations are disruptive
|
|
35
|
+
- Some operations are disruptive and require an explicit flag, **including their dry runs**: `--allow-disruptive` permits an op that briefly steals window focus; an op that visibly DRIVES the Things UI needs both `--allow-disruptive` and `--allow-very-disruptive` (the two-key gate). `things capabilities` lists each operation's support and any preconditions.
|
|
30
36
|
- If a request needs a capability the tool reports as unsupported, say so plainly rather than improvising through unrelated commands.
|
|
31
37
|
|
|
38
|
+
## Bulk creation (contract summary)
|
|
39
|
+
|
|
40
|
+
- **Several to-dos at once**: `things todo add "T1" "T2" "T3" [shared flags]`. Every shared flag (`--project`/`--area`/`--heading`/`--when`/`--tags`/`--deadline`/…) applies to each title; titles land in argument order. `--stdin` reads newline-delimited titles from stdin (blank lines skipped) instead of positional args (the two are mutually exclusive). `--id-only` prints exactly one uuid per line in creation order — pipe it to chain follow-up commands (mutually exclusive with `--json`).
|
|
41
|
+
- **One undo for the whole set**: a multi-title add runs as one unit. `--json` streams a per-line result plus a trailing `summary` line carrying a single `undoToken`; `things undo --txn <undoToken>` removes the whole skeleton at once. A single-title `add` is unchanged — it still returns the ordinary single mutation-result envelope.
|
|
42
|
+
- **New project with children**: `things project add "<title>" --todo "T1" --todo "T2" …` (`--todo` repeatable) seeds a project skeleton in one call.
|
|
43
|
+
- **Richer batches**: for per-item metadata that differs, or cross-item references (`tempId`/`$ref`), use `things batch` (JSONL) — same one-`undoToken` undo semantics.
|
|
44
|
+
|
|
45
|
+
## Batch (many changes at once)
|
|
46
|
+
|
|
47
|
+
`things batch` runs a JSONL script (one `{"op","params",…}` per line) sequentially and independently — no transactions; a failure does not roll back earlier lines. Three fields make multi-step work reliable:
|
|
48
|
+
|
|
49
|
+
- **`tempId` (chaining):** a line that CREATES something (a to-do, project, area, heading, repeater — never `tag.add`) can carry `"tempId":"proj1"`; a LATER line references that new uuid as `"$proj1"` in any id/container field. Dotted forms reach a repeater's parts: `"$proj1.instance"` (the visible occurrence), `"$proj1.replaced"` (the original). 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; an unknown or forward `$ref` fails just that line.
|
|
50
|
+
- **`opId` (safe retry):** carry a stable `"opId"` per line so resubmitting a batch after an ambiguous failure does not double-create — a line matching an earlier success is reported `already-applied`, not re-run. 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`.
|
|
51
|
+
- **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.
|
|
52
|
+
|
|
32
53
|
## Recurrence (contract summary)
|
|
33
54
|
|
|
34
55
|
Full rule vocabulary and worked examples: **`things help repeating`**. The stable contract:
|