things-api 0.19.4 → 0.20.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.
Files changed (131) hide show
  1. package/README.md +1 -1
  2. package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
  3. package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
  4. package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
  5. package/dist/audit/schema.d.ts +28 -10
  6. package/dist/audit/schema.js +21 -0
  7. package/dist/audit/schema.js.map +1 -1
  8. package/dist/cli/commands/doctor.js +3 -1
  9. package/dist/cli/commands/doctor.js.map +1 -1
  10. package/dist/cli/commands/op-result.js +15 -9
  11. package/dist/cli/commands/op-result.js.map +1 -1
  12. package/dist/cli/commands/repeat-flags.d.ts +19 -1
  13. package/dist/cli/commands/repeat-flags.js +71 -0
  14. package/dist/cli/commands/repeat-flags.js.map +1 -1
  15. package/dist/cli/commands/rescue.d.ts +12 -0
  16. package/dist/cli/commands/rescue.js +83 -0
  17. package/dist/cli/commands/rescue.js.map +1 -0
  18. package/dist/cli/commands/writes.js +35 -25
  19. package/dist/cli/commands/writes.js.map +1 -1
  20. package/dist/cli/help.js +25 -25
  21. package/dist/cli/help.js.map +1 -1
  22. package/dist/cli/main.js +12 -2
  23. package/dist/cli/main.js.map +1 -1
  24. package/dist/cli/resolve-invocation.d.ts +9 -1
  25. package/dist/cli/resolve-invocation.js +71 -0
  26. package/dist/cli/resolve-invocation.js.map +1 -1
  27. package/dist/cli/unknown-command.d.ts +26 -0
  28. package/dist/cli/unknown-command.js +135 -0
  29. package/dist/cli/unknown-command.js.map +1 -0
  30. package/dist/contracts.d.ts +4 -3
  31. package/dist/contracts.js +1 -1
  32. package/dist/index.d.ts +4 -2
  33. package/dist/index.js +1 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/mcp/server.js +55 -10
  36. package/dist/mcp/server.js.map +1 -1
  37. package/dist/model/entities.d.ts +11 -0
  38. package/dist/model/entities.js.map +1 -1
  39. package/dist/model/mappers.js +26 -9
  40. package/dist/model/mappers.js.map +1 -1
  41. package/dist/model/today-placement.d.ts +65 -0
  42. package/dist/model/today-placement.js +59 -0
  43. package/dist/model/today-placement.js.map +1 -0
  44. package/dist/op-result.d.ts +15 -2
  45. package/dist/op-result.js +72 -20
  46. package/dist/op-result.js.map +1 -1
  47. package/dist/process-instance.d.ts +41 -0
  48. package/dist/process-instance.js +82 -0
  49. package/dist/process-instance.js.map +1 -0
  50. package/dist/rescue.d.ts +247 -0
  51. package/dist/rescue.js +811 -0
  52. package/dist/rescue.js.map +1 -0
  53. package/dist/session-grant.js +4 -15
  54. package/dist/session-grant.js.map +1 -1
  55. package/dist/ui-state.d.ts +8 -4
  56. package/dist/ui-state.js +8 -4
  57. package/dist/ui-state.js.map +1 -1
  58. package/dist/write/commands.d.ts +9 -0
  59. package/dist/write/commands.js +138 -51
  60. package/dist/write/commands.js.map +1 -1
  61. package/dist/write/disclosures.d.ts +24 -0
  62. package/dist/write/disclosures.js +24 -0
  63. package/dist/write/disclosures.js.map +1 -1
  64. package/dist/write/experimental.d.ts +16 -0
  65. package/dist/write/experimental.js +19 -0
  66. package/dist/write/experimental.js.map +1 -1
  67. package/dist/write/failure-hints.js +3 -2
  68. package/dist/write/failure-hints.js.map +1 -1
  69. package/dist/write/guards.d.ts +1 -1
  70. package/dist/write/guards.js +130 -8
  71. package/dist/write/guards.js.map +1 -1
  72. package/dist/write/lock.d.ts +85 -1
  73. package/dist/write/lock.js +121 -6
  74. package/dist/write/lock.js.map +1 -1
  75. package/dist/write/move.js +33 -6
  76. package/dist/write/move.js.map +1 -1
  77. package/dist/write/operations.d.ts +52 -6
  78. package/dist/write/operations.js +22 -0
  79. package/dist/write/operations.js.map +1 -1
  80. package/dist/write/opid.d.ts +69 -8
  81. package/dist/write/opid.js +111 -17
  82. package/dist/write/opid.js.map +1 -1
  83. package/dist/write/param-schema.js +14 -2
  84. package/dist/write/param-schema.js.map +1 -1
  85. package/dist/write/pipeline.d.ts +43 -2
  86. package/dist/write/pipeline.js +206 -43
  87. package/dist/write/pipeline.js.map +1 -1
  88. package/dist/write/planner.d.ts +7 -0
  89. package/dist/write/planner.js +12 -0
  90. package/dist/write/planner.js.map +1 -1
  91. package/dist/write/pre-state.d.ts +9 -9
  92. package/dist/write/pre-state.js +44 -38
  93. package/dist/write/pre-state.js.map +1 -1
  94. package/dist/write/preserve-modified.d.ts +17 -8
  95. package/dist/write/preserve-modified.js.map +1 -1
  96. package/dist/write/promote-clone.js +60 -17
  97. package/dist/write/promote-clone.js.map +1 -1
  98. package/dist/write/reorder.js +28 -7
  99. package/dist/write/reorder.js.map +1 -1
  100. package/dist/write/repeat-rule.d.ts +25 -1
  101. package/dist/write/repeat-rule.js +69 -0
  102. package/dist/write/repeat-rule.js.map +1 -1
  103. package/dist/write/resolution-timestamps.js +103 -80
  104. package/dist/write/resolution-timestamps.js.map +1 -1
  105. package/dist/write/reversibility.js +2 -2
  106. package/dist/write/reversibility.js.map +1 -1
  107. package/dist/write/template-mutation.js +14 -2
  108. package/dist/write/template-mutation.js.map +1 -1
  109. package/dist/write/undo.js +45 -5
  110. package/dist/write/undo.js.map +1 -1
  111. package/dist/write/vectors/simulator.js +48 -2
  112. package/dist/write/vectors/simulator.js.map +1 -1
  113. package/dist/write/vectors/types.d.ts +10 -0
  114. package/dist/write/vectors/ui-drag.d.ts +102 -0
  115. package/dist/write/vectors/ui-drag.js +425 -11
  116. package/dist/write/vectors/ui-drag.js.map +1 -1
  117. package/dist/write/vectors/ui-state.d.ts +22 -6
  118. package/dist/write/vectors/ui-state.js +1 -1
  119. package/dist/write/vectors/ui-state.js.map +1 -1
  120. package/dist/write/vectors/ui.d.ts +29 -1
  121. package/dist/write/vectors/ui.js +57 -5
  122. package/dist/write/vectors/ui.js.map +1 -1
  123. package/dist/write/vectors/url-scheme.js +26 -0
  124. package/dist/write/vectors/url-scheme.js.map +1 -1
  125. package/package.json +1 -1
  126. package/schema/envelope.schema.json +1 -2
  127. package/skills/things-cli/SKILL.md +4 -2
  128. package/skills/things-cli/references/repeating.md +1 -1
  129. package/dist/cli/commands/ui-state.d.ts +0 -11
  130. package/dist/cli/commands/ui-state.js +0 -27
  131. package/dist/cli/commands/ui-state.js.map +0 -1
@@ -0,0 +1,247 @@
1
+ /**
2
+ * `things rescue` — the headless emergency surface for un-sticking Things
3
+ * (issue #640).
4
+ *
5
+ * WHY THIS EXISTS. Things can reach a state where the app is up, answering, and
6
+ * unusable to automation, with no scripted way back. Three measured behaviours
7
+ * converge on it:
8
+ *
9
+ * - a standing modal sheet EMPTIES the app's top-level AppleScript collections
10
+ * (oddities §25 / MODALX1), so `delete` reports `-1728 Can't get to do id` on
11
+ * a row the database holds open — issue #620's "ghost clone", which has no
12
+ * second cause;
13
+ * - a standing sheet GATES Things Cloud sync entirely (oddities §24), so writes
14
+ * land locally and silently never leave the Mac;
15
+ * - a Repeat dialog opened while Things is BACKGROUNDED becomes a detached
16
+ * window that nothing dismisses (oddities §26 / DRVLAT1 §5) — not its own
17
+ * Cancel, not Escape, not ⌘W, not a real HID click at its Cancel frame.
18
+ *
19
+ * The shipped driver never creates the third state (every dialog recipe
20
+ * activates Things first, which is exactly why that step is permanent), but a
21
+ * killed client, a harness tool-timeout or a person walking away mid-dialog can
22
+ * all leave the first two. {@link rescueStatus} reports them; the other two
23
+ * verbs act on them. This is the single home for both halves — the read-only
24
+ * census that was once the top-level `ui-state` command lives here now.
25
+ *
26
+ * THE THREE VERBS, separated by what they can cost you:
27
+ *
28
+ * - {@link rescueStatus} free, ungated, read-only — the census + the lock
29
+ * table + a "we must not touch that" verdict;
30
+ * - {@link rescueDismiss} gated two ways — presses ONE dialog's own Cancel;
31
+ * - {@link rescueRelaunch} gated two ways — ends the process and starts it
32
+ * again, the only cure measured for oddities §26.
33
+ *
34
+ * PERMISSIONS DOCTRINE. Nothing here raises a macOS consent dialog. The screen
35
+ * is read behind the prompt-free capability verdict, exactly as `doctor
36
+ * --ui-state` reads it, and a machine that has not granted the access is TOLD so
37
+ * rather than
38
+ * prompted. {@link rescueRelaunch} is the deliberate exception to needing that
39
+ * grant at all — see its own note.
40
+ */
41
+ import { type AuditWriter } from "./audit/log.ts";
42
+ import { type UiCapability } from "./capability.ts";
43
+ import { type UiState } from "./write/vectors/ui-state.ts";
44
+ import { type UiRunner } from "./write/vectors/ui.ts";
45
+ /** What a relaunch could establish about the database it left behind. */
46
+ export interface RescueSchemaVerdict {
47
+ /** Did the database open and read as a shape this build understands? */
48
+ ok: boolean;
49
+ /** One sentence, for the report. */
50
+ detail: string;
51
+ }
52
+ /**
53
+ * Every side-effecting thing this module does, injectable. Production defaults
54
+ * are the real ones; the unit suite substitutes all of them, which is what makes
55
+ * the gating, the refusals and the kill ladder testable without an app.
56
+ */
57
+ export interface RescueDeps {
58
+ /** The prompt-free GUI capability verdict. */
59
+ uiCapability?: () => UiCapability;
60
+ /** The osascript / reveal dispatch seam — the same transport the drive uses. */
61
+ run?: UiRunner;
62
+ /** Process environment, for config, the lock path and the change-history directory. */
63
+ env?: NodeJS.ProcessEnv;
64
+ /** Where a rescue ACTION records what it did. */
65
+ audit?: AuditWriter;
66
+ now?: () => number;
67
+ pidAlive?: (pid: number) => boolean;
68
+ /** Which pids Things is running as, newest first; empty when it is not running. */
69
+ thingsPids?: () => Promise<number[]>;
70
+ /** Signal one pid. Never throws for a pid that has already gone. */
71
+ signal?: (pid: number, sig: NodeJS.Signals) => void;
72
+ /** Start Things in the BACKGROUND — we do not steal the screen to fix the screen. */
73
+ launch?: () => Promise<{
74
+ ok: boolean;
75
+ detail: string;
76
+ }>;
77
+ /** Read the database's own verdict, for the post-relaunch check. */
78
+ schemaStatus?: () => RescueSchemaVerdict;
79
+ sleep?: (ms: number) => Promise<void>;
80
+ }
81
+ /**
82
+ * A modal that is NOT ours. `inspectable: false` is the signature of a secure
83
+ * system modal — a macOS privacy/consent dialog, which belongs to no
84
+ * application's Accessibility tree and which no rescue verb may ever press a
85
+ * button on. It is reported so the operator knows to look at the screen, and so
86
+ * they know the refusal that follows is deliberate rather than a failure.
87
+ */
88
+ export interface RescueForeignModal {
89
+ /** The application that owns the screen, or null when even that could not be read. */
90
+ owner: string | null;
91
+ detail: string;
92
+ }
93
+ /** The mutation lock, as `rescue status` reports it. Read-only, always. */
94
+ export interface RescueLockView {
95
+ path: string;
96
+ held: boolean;
97
+ pid: number | null;
98
+ /** ISO instant the holder took it. */
99
+ since: string | null;
100
+ /** Human age ("4m 12s"), or null when it could not be computed. */
101
+ heldFor: string | null;
102
+ /** Does a process with that pid still exist? */
103
+ alive: boolean;
104
+ /** Alive AND older than any change takes — old enough to say so. */
105
+ suspect: boolean;
106
+ detail: string;
107
+ }
108
+ export interface RescueStatusReport {
109
+ /** Could the screen be read at all on this machine? */
110
+ screenReadable: boolean;
111
+ /** One sentence: the census summary, or the reason there is none. */
112
+ detail: string;
113
+ /** The census; null when it could not be read. */
114
+ state: UiState | null;
115
+ lock: RescueLockView;
116
+ /** Set when something we must not touch owns the screen. */
117
+ foreignModal: RescueForeignModal | null;
118
+ /** Consequences worth stating — the open-dialog sync gate. */
119
+ warnings: string[];
120
+ /** What to do next, including which rescue verb applies. */
121
+ remediation: string[];
122
+ }
123
+ /**
124
+ * Read everything a stuck machine can be asked about, without touching any of
125
+ * it. Never throws and never refuses: the cases this exists for are exactly the
126
+ * ones where something is already wrong, so every failure is a REPORTED state.
127
+ */
128
+ export declare function rescueStatus(deps?: RescueDeps): Promise<RescueStatusReport>;
129
+ export type RescueDismissOutcome =
130
+ /** Nothing was stranded — no dialog was open. */
131
+ "no-dialog"
132
+ /** Cancel was pressed and the dialog is confirmed gone. */
133
+ | "dismissed"
134
+ /** Cancel was pressed and the dialog is demonstrably still there (oddities §26). */
135
+ | "still-open"
136
+ /** Cancel was pressed and the screen could not be re-read to confirm either way. */
137
+ | "unverified"
138
+ /** Nothing was pressed. */
139
+ | "refused";
140
+ export interface RescueDismissResult {
141
+ outcome: RescueDismissOutcome;
142
+ /** Which press worked, when one did. */
143
+ how: "cancel-button" | "cancel-click" | null;
144
+ before: UiState | null;
145
+ after: UiState | null;
146
+ /** Dialogs still stacked after this one; null when it could not be re-read. */
147
+ levelsRemaining: number | null;
148
+ detail: string;
149
+ remediation: string[];
150
+ notes: string[];
151
+ warnings: string[];
152
+ }
153
+ /** The two keys `rescue dismiss` needs, spelled the way each surface spells them. */
154
+ export interface RescueDismissAck {
155
+ /** `--dangerously-dismiss-dialog`. */
156
+ dangerouslyDismissDialog?: boolean;
157
+ }
158
+ /**
159
+ * Close the dialog IN FRONT by pressing its own Cancel button — FGRD2's proven
160
+ * Cancel, generalized off the drive's abort path and onto a dialog no drive
161
+ * opened.
162
+ *
163
+ * EXACTLY ONE LIFO LEVEL PER INVOCATION. Sheets nest as `AXSheet` children of
164
+ * the sheet below and dismiss strictly last-in-first-out (MODALX1 §6). A loop
165
+ * that cleared a whole stack in one command would be a loop pressing an unknown
166
+ * number of buttons on an unknown number of dialogs; instead this closes the top
167
+ * one, re-reads, and reports how many remain so the caller decides.
168
+ *
169
+ * PROVEN, never assumed. The press reporting success is NOT closure: the
170
+ * detached editor of oddities §26 accepts an AXPress on its Cancel and stays
171
+ * exactly where it is. Closure is decided by a fresh census showing the stack one
172
+ * level shorter, and an unconfirmable press is reported as unconfirmed.
173
+ *
174
+ * WHAT IT CAN COST YOU: the dialog's own pending edits, and nothing else. The
175
+ * database is untouched, and a URL command parked behind a consent sheet is
176
+ * discarded with it (URLEN1).
177
+ */
178
+ export declare function rescueDismiss(ack?: RescueDismissAck, deps?: RescueDeps): Promise<RescueDismissResult>;
179
+ export type RescueRelaunchOutcome = "relaunched" | "refused" | "failed";
180
+ export interface RescueRelaunchResult {
181
+ outcome: RescueRelaunchOutcome;
182
+ /** Which rung actually ended the process. */
183
+ endedBy: "quit" | "sigterm" | "sigkill" | "not-running" | null;
184
+ before: UiState | null;
185
+ after: UiState | null;
186
+ /** The step-by-step account of the ladder, always present. */
187
+ ladder: string[];
188
+ detail: string;
189
+ remediation: string[];
190
+ notes: string[];
191
+ warnings: string[];
192
+ }
193
+ /** The keys `rescue relaunch` needs. The second one is profile-dependent. */
194
+ export interface RescueRelaunchAck {
195
+ /** `--yes`. */
196
+ yes?: boolean;
197
+ /** `--dangerously-force-quit`; required only under the `workstation` profile. */
198
+ dangerouslyForceQuit?: boolean;
199
+ }
200
+ /**
201
+ * End Things and start it again — the nuclear rung, and the ONLY cure this
202
+ * project has measured for the detached editor of oddities §26. DRVLAT1 §5 tried
203
+ * every other route against the same live dialog (its own Cancel backgrounded,
204
+ * foregrounded and after `AXRaise`; Escape; ⌘W then re-activate; a real HID click
205
+ * at the AX-resolved Cancel frame) and every one was inert.
206
+ *
207
+ * THE LADDER, each rung bounded:
208
+ *
209
+ * 1. ask Things to quit through its own scripting interface. Bounded, because
210
+ * a standing sheet swallows ⌘Q and the `quit` verb can block behind one.
211
+ * 2. SIGTERM, and wait for the process to go.
212
+ * 3. SIGKILL — the lab's canonical reset for a modal that will not clear.
213
+ * 4. start it again in the BACKGROUND: we do not steal the screen to fix the
214
+ * screen.
215
+ * 5. prove it: the process is answering, the database still reads as the shape
216
+ * this version expects, and no dialog is open.
217
+ *
218
+ * WHAT IS LOST: the Things database is write-ahead-logged, so everything already
219
+ * committed survives a kill unconditionally. The ceiling is what was never
220
+ * committed — the edits sitting in the dialog being destroyed.
221
+ *
222
+ * WHAT COMES BACK: sync. While a dialog stands, Things stops sending changes to
223
+ * Things Cloud (oddities §24); killing the dialog with the process releases that,
224
+ * and everything written on this Mac in the meantime goes out.
225
+ *
226
+ * DELIBERATELY NOT GATED ON THE GUI CAPABILITY. Every other verb that touches the
227
+ * app needs the GUI grant, because it synthesizes input into someone's window.
228
+ * This one signals a process the user already owns, which needs no grant at all —
229
+ * and the machines that need it most are exactly the ones where the Accessibility
230
+ * path is what broke. The quit rung and the census still run through the granted
231
+ * seam when it is available, and are skipped without complaint when it is not.
232
+ */
233
+ export declare function rescueRelaunch(ack?: RescueRelaunchAck, deps?: RescueDeps): Promise<RescueRelaunchResult>;
234
+ /**
235
+ * The human render of `things rescue status`.
236
+ *
237
+ * A ROW WHOSE PROBE DID NOT ANSWER SAYS SO (issue #629). Printing a field's
238
+ * unset default ("none", "unknown") beside rows that WERE measured is exactly
239
+ * what made a stalled inspection read as a clean screen in the field, so an
240
+ * unproven probe renders as "not established" rather than as its default, and
241
+ * the probes are named again in full on the `unproven:` row.
242
+ */
243
+ export declare function rescueStatusLines(report: RescueStatusReport): string[];
244
+ /** The human render of `things rescue dismiss`. */
245
+ export declare function rescueDismissLines(result: RescueDismissResult): string[];
246
+ /** The human render of `things rescue relaunch`. */
247
+ export declare function rescueRelaunchLines(result: RescueRelaunchResult): string[];