things-api 0.19.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 +3 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
- 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/MacOS/things-deputy +0 -0
- package/dist/audit/schema.d.ts +19 -2
- package/dist/audit/schema.js.map +1 -1
- package/dist/capability.d.ts +166 -4
- package/dist/capability.js +303 -13
- package/dist/capability.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +13 -1
- package/dist/cli/commands/doctor.js +24 -14
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/helpers.js +14 -3
- 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/repeat-flags.js +34 -2
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/setup.js +16 -3
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/writes.js +102 -67
- package/dist/cli/commands/writes.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/client.d.ts +1 -1
- package/dist/contracts.d.ts +3 -3
- package/dist/contracts.js +1 -1
- package/dist/deputy/install.d.ts +15 -1
- package/dist/deputy/install.js +46 -6
- package/dist/deputy/install.js.map +1 -1
- package/dist/deputy/routing.d.ts +16 -0
- package/dist/deputy/routing.js +70 -0
- 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 +6 -4
- package/dist/diagnose.js +20 -4
- package/dist/diagnose.js.map +1 -1
- package/dist/direct-setup.d.ts +31 -5
- package/dist/direct-setup.js +200 -26
- package/dist/direct-setup.js.map +1 -1
- package/dist/host-access.d.ts +12 -0
- package/dist/host-access.js +12 -0
- package/dist/host-access.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +23 -9
- package/dist/mcp/server.js.map +1 -1
- package/dist/op-result.js +8 -1
- package/dist/op-result.js.map +1 -1
- package/dist/session-grant.js +16 -1
- package/dist/session-grant.js.map +1 -1
- package/dist/surface-copy.d.ts +11 -3
- package/dist/surface-copy.js +11 -3
- package/dist/surface-copy.js.map +1 -1
- package/dist/wizard.js +8 -1
- package/dist/wizard.js.map +1 -1
- package/dist/write/availability.d.ts +0 -15
- package/dist/write/availability.js +7 -62
- package/dist/write/availability.js.map +1 -1
- package/dist/write/batch.js +17 -6
- package/dist/write/batch.js.map +1 -1
- package/dist/write/commands.d.ts +10 -6
- package/dist/write/commands.js +120 -31
- package/dist/write/commands.js.map +1 -1
- package/dist/write/failure-hints.d.ts +29 -6
- package/dist/write/failure-hints.js +9 -14
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/guards.js +3 -2
- package/dist/write/guards.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 +15 -8
- package/dist/write/operations.js +9 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +67 -23
- package/dist/write/opid.js +141 -19
- package/dist/write/opid.js.map +1 -1
- package/dist/write/param-schema.d.ts +0 -44
- package/dist/write/param-schema.js +10 -3
- package/dist/write/param-schema.js.map +1 -1
- package/dist/write/pipeline.d.ts +38 -7
- package/dist/write/pipeline.js +104 -18
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +30 -16
- package/dist/write/pre-state.js +88 -20
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/promote-clone.js +132 -18
- package/dist/write/promote-clone.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/resolution-timestamps.js +25 -2
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/template-mutation.js +163 -16
- package/dist/write/template-mutation.js.map +1 -1
- package/dist/write/vectors/applescript.js +3 -10
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/simulator.js +8 -2
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +211 -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 +51 -3
- package/dist/write/vectors/ui-recipes.js +452 -44
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +267 -10
- package/dist/write/vectors/ui.js +1006 -64
- 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 +41 -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 +1 -1
- package/schema/envelope.schema.json +1 -1
- package/skills/things-cli/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
<img src="assets/things-api-icon-160x160.png" alt="" width="80" height="80">
|
|
2
|
+
|
|
1
3
|
# things-api
|
|
2
4
|
|
|
3
5
|
A typed TypeScript library + CLI (`things`) for programmatic interaction with [Things 3](https://culturedcode.com/things/) by Cultured Code.
|
|
4
6
|
|
|
5
|
-
**Status: read + write + MCP layers live and published to npm (v0.19.
|
|
7
|
+
**Status: read + write + MCP layers live and published to npm (v0.19.1 — see [CHANGELOG.md](CHANGELOG.md)).** Reads go straight to the local SQLite database (UI-exact Today ordering, sidebar-grouped Anytime/Someday with container-status cascade, decoded repeat rules, occurrence projections); writes run a verified pipeline over four write vectors — two lab-validated headless surfaces (the URL scheme + AppleScript) and two opt-in surfaces (Apple Shortcuts and an Accessibility-driven GUI vector) — with hazard guards, disruption-tier policy, a JSONL audit trail, batch mode, audit-replay undo (transactional across compound operations), full project lifecycle (complete/cancel/reopen/restore), heading rename/archive/unarchive with children policies, container detach, granular stateful checklists, tag hierarchy management incl. un-nesting, completion/creation backdating, Logbook imports, tiered fuzzy name resolution (uuid prefixes, `things:///show?id=` share links), and kind-neutral in-place reordering within any container or view (one `things reorder <refs…> [--start|--end|--before|--after] --in <target>` verb — Today/Evening, a project, an area, a heading's children, someday, inbox — plus `project move-heading` for the headings themselves and `area reorder` for the sidebar). The Apple Shortcuts surface is wired for the two headless capabilities nothing else can do: creating a heading in an existing project (`things project add-heading`) and clearing a reminder from a date-scheduled item (`things todo clear-reminder`). Both run through bundled proxy shortcuts (`things setup`) and are gated on their presence. The fourth vector — the Accessibility-driven GUI vector — is off by default and drives the local Things app to reach transforms that exist on no scriptable surface (repeat-rule editing on existing to-dos and projects, convert-to-project, sidebar area reorder); see [Accessibility GUI vector](#accessibility-gui-vector-optional-off-by-default) below. Single-item permanent delete stays interactive-only (its macOS consent has no always-allow) and is out of the headless pipeline. See [docs/design/](docs/design/) for the architecture and VM-lab design, [docs/lab/](docs/lab/harness.md) for the probe harness and campaign results the write layer is grounded in, and [docs/atlas/](docs/atlas/schema-v26.md) for the database↔UI map.
|
|
6
8
|
|
|
7
9
|
```sh
|
|
8
10
|
things today --json # read: your Today list, Evening split, UI order
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/audit/schema.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OccurrenceResolution } from "../write/verify/delta.ts";
|
|
1
|
+
import type { DeltaSpec, OccurrenceResolution } from "../write/verify/delta.ts";
|
|
2
2
|
export interface AuditRecord {
|
|
3
3
|
v: 1;
|
|
4
4
|
ts: string;
|
|
@@ -51,6 +51,18 @@ export interface AuditRecord {
|
|
|
51
51
|
* call returned without re-reading the database.
|
|
52
52
|
*/
|
|
53
53
|
occurrence?: OccurrenceResolution;
|
|
54
|
+
/**
|
|
55
|
+
* AMBIGUOUS-OUTCOME reconciliation key (ADDITIVE): the expected-state
|
|
56
|
+
* assertion this attempt was verifying, recorded on `verify-failed:timeout`
|
|
57
|
+
* records only — the one result class where the change may or may not have
|
|
58
|
+
* landed. A resubmission carrying the same `opId` re-evaluates THIS assertion
|
|
59
|
+
* against current state to decide whether the timed-out change is there
|
|
60
|
+
* (replay it as already-applied) or absent (execute normally), so the presence
|
|
61
|
+
* test is the attempt's OWN oracle rather than a per-operation guess
|
|
62
|
+
* (`src/write/opid.ts`). Absent on every other record — and an absent one is a
|
|
63
|
+
* refusal to guess, never an assumption either way.
|
|
64
|
+
*/
|
|
65
|
+
expected?: DeltaSpec;
|
|
54
66
|
/** Normalized requested delta (params as given, post-normalization). */
|
|
55
67
|
requested: Record<string, unknown>;
|
|
56
68
|
/** Asserted-field subset of the pre-state (null when target didn't exist). */
|
|
@@ -59,7 +71,12 @@ export interface AuditRecord {
|
|
|
59
71
|
observed: Record<string, unknown> | null;
|
|
60
72
|
result: "intent" | "ok" | "verify-failed:timeout" | "verify-failed:mismatch" | "verify-failed:silent-noop"
|
|
61
73
|
/** A GUI drive stopped because the Things window was unreachable/unresponsive (#512). */
|
|
62
|
-
| "verify-failed:ui-unreachable"
|
|
74
|
+
| "verify-failed:ui-unreachable"
|
|
75
|
+
/**
|
|
76
|
+
* The requested change landed, but a field the caller never named moved with it
|
|
77
|
+
* and nothing in the operation's vocabulary attributes the movement (CGRD1).
|
|
78
|
+
*/
|
|
79
|
+
| "verify-failed:collateral" | `blocked:${string}` | "unsupported";
|
|
63
80
|
verify: {
|
|
64
81
|
attempts: number;
|
|
65
82
|
elapsedMs: number;
|
package/dist/audit/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/audit/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/audit/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA+FzC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CACvB,MAA0E;IAE1E,IAAI,MAAM,CAAC,GAAG,EAAE,IAAI,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACzD,yEAAyE;IACzE,wEAAwE;IACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,MAAM,CAAC,EAAE;QACT,MAAM,CAAC,EAAE;QACT,MAAM,CAAC,KAAK;QACZ,MAAM,CAAC,IAAI;QACX,MAAM,CAAC,IAAI,IAAI,EAAE;KAClB,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChF,OAAO,KAAK,MAAM,EAAE,CAAC;AACvB,CAAC"}
|
package/dist/capability.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { type TargetWake } from "./deputy/wake.ts";
|
|
1
2
|
import { type HostAccessDeps, type HostApp } from "./host-access.ts";
|
|
2
3
|
export { fdaGranted, type FdaVerdict, type HostApp, hostApp, hostDisplayName, tccDbPath, } from "./host-access.ts";
|
|
3
4
|
/** The Things application's bundle identifier — the Automation grant's target. */
|
|
4
|
-
export
|
|
5
|
+
export { THINGS_BUNDLE_ID } from "./deputy/wake.ts";
|
|
5
6
|
/** How reads may reach the live library, if at all. */
|
|
6
7
|
export type ReadCapabilityMode =
|
|
7
8
|
/** A caller-supplied database path — outside the doctrine entirely (Article VI). */
|
|
@@ -26,14 +27,37 @@ export type UiCapabilityMode =
|
|
|
26
27
|
| "config-disabled"
|
|
27
28
|
/** No deputy answers, so there is no identity that could hold the grants. */
|
|
28
29
|
| "helpers-missing"
|
|
30
|
+
/** System Events is down and would not start — a liveness fault, not a grant one. */
|
|
31
|
+
| "target-unreachable"
|
|
29
32
|
/** The deputy answers but the `--gui` tier is incomplete. */
|
|
30
33
|
| "tier-incomplete";
|
|
34
|
+
/**
|
|
35
|
+
* Where Things' OWN in-app authorization for the URL scheme stands (URLEN1).
|
|
36
|
+
*
|
|
37
|
+
* This is not a macOS consent class at all — `open -g things:///…` is a
|
|
38
|
+
* LaunchServices dispatch that needs no grant. The gate is the app's: Settings
|
|
39
|
+
* ▸ General ▸ "Enable Things URLs", recorded as `uriSchemeEnabled` in the
|
|
40
|
+
* group-container preferences plist. Three states, all measured.
|
|
41
|
+
*/
|
|
42
|
+
export type UrlSchemeCapabilityMode =
|
|
43
|
+
/** `uriSchemeEnabled = 1` — URL commands execute. */
|
|
44
|
+
"enabled"
|
|
45
|
+
/** `uriSchemeEnabled = 0` — the app drops URL MUTATIONS with no dialog at all. */
|
|
46
|
+
| "disabled"
|
|
47
|
+
/** No key: nobody has answered the app's first-use dialog on this machine. */
|
|
48
|
+
| "never-asked"
|
|
49
|
+
/** The plist cannot be reached prompt-free — never resolved by dispatching. */
|
|
50
|
+
| "unreadable";
|
|
31
51
|
/** How app automation may be delivered, if at all. */
|
|
32
52
|
export type WriteCapabilityMode =
|
|
33
53
|
/** The deputy is onboarded; Apple Events are sent under the helper identity. */
|
|
34
54
|
"deputy"
|
|
55
|
+
/** Things is not running, so where the deputy's grant stands cannot be read. */
|
|
56
|
+
| "deputy-target-dormant"
|
|
35
57
|
/** macOS records an Automation grant for the host app against Things. */
|
|
36
58
|
| "direct-granted"
|
|
59
|
+
/** The lab's documented in-guest escape (see {@link WRITE_DIRECT_ESCAPE_ENV}). */
|
|
60
|
+
| "direct-escape"
|
|
37
61
|
/** macOS records a REFUSAL. It will not re-ask; the human must re-arm it. */
|
|
38
62
|
| "direct-denied"
|
|
39
63
|
/** No record either way, or none readable — never resolved by prompting. */
|
|
@@ -53,10 +77,23 @@ export interface Capability<Mode> {
|
|
|
53
77
|
export type ReadCapability = Capability<ReadCapabilityMode>;
|
|
54
78
|
export type WriteCapability = Capability<WriteCapabilityMode>;
|
|
55
79
|
export type UiCapability = Capability<UiCapabilityMode>;
|
|
80
|
+
export type UrlSchemeCapability = Capability<UrlSchemeCapabilityMode>;
|
|
56
81
|
/** True when this verdict permits opening the live container. */
|
|
57
82
|
export declare function readAllowed(capability: ReadCapability): boolean;
|
|
58
83
|
/** True when this verdict permits driving the Things window. */
|
|
59
84
|
export declare function uiAllowed(capability: UiCapability): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* True when a URL-scheme MUTATION may be dispatched.
|
|
87
|
+
*
|
|
88
|
+
* `unreadable` is permissive, and deliberately so — the asymmetry with the
|
|
89
|
+
* write gate is the point. There, an unknown standing is refused because
|
|
90
|
+
* resolving it means sending the Apple Event that IS the dialog. Here,
|
|
91
|
+
* dispatching costs nothing on a machine whose answer is already "enabled",
|
|
92
|
+
* which is every settled machine; only the two states we have positively READ
|
|
93
|
+
* as not-enabled are refused. What catches the unreadable case instead is the
|
|
94
|
+
* read-after-write verify plus its likely-cause hint (src/write/failure-hints.ts).
|
|
95
|
+
*/
|
|
96
|
+
export declare function urlSchemeAllowed(capability: UrlSchemeCapability): boolean;
|
|
60
97
|
/** True when app automation may be dispatched. */
|
|
61
98
|
export declare function writeAllowed(capability: WriteCapability): boolean;
|
|
62
99
|
/**
|
|
@@ -81,6 +118,25 @@ export interface CapabilityDeps extends HostAccessDeps {
|
|
|
81
118
|
axTrusted: boolean | undefined;
|
|
82
119
|
systemEvents: string | undefined;
|
|
83
120
|
} | null;
|
|
121
|
+
/**
|
|
122
|
+
* Start System Events and re-read its Automation standing, for the one state
|
|
123
|
+
* where the handshake reports liveness instead of authorization
|
|
124
|
+
* (`not-running`). Prompt-free — see ./deputy/wake.ts for why the launch must
|
|
125
|
+
* come before the determination.
|
|
126
|
+
*/
|
|
127
|
+
wakeSystemEvents?: () => TargetWake;
|
|
128
|
+
/**
|
|
129
|
+
* Start Things and re-read its Automation standing — the same liveness step
|
|
130
|
+
* for the AppleScript vector's own target (#617). Consulted ONLY on the
|
|
131
|
+
* dispatch path: a survey never launches the user's app.
|
|
132
|
+
*/
|
|
133
|
+
wakeThings?: () => TargetWake;
|
|
134
|
+
/**
|
|
135
|
+
* Report a woken target's real standing back to the routing layer, which
|
|
136
|
+
* defers its `auto` onboarding gate while Things is closed
|
|
137
|
+
* (./deputy/routing.ts, {@link settleDeputyAutomation}).
|
|
138
|
+
*/
|
|
139
|
+
settleDeputyAutomation?: (standing: string | undefined) => void;
|
|
84
140
|
/** Is GUI-driving switched on in config (`ui-enabled`)? */
|
|
85
141
|
uiEnabled?: () => boolean;
|
|
86
142
|
/**
|
|
@@ -90,6 +146,15 @@ export interface CapabilityDeps extends HostAccessDeps {
|
|
|
90
146
|
automationAuthValue?: (client: string, target: string) => number | null;
|
|
91
147
|
/** Resolve a running app's display name from its bundle id (LaunchServices). */
|
|
92
148
|
lookupAppName?: (bundleId: string) => string | null;
|
|
149
|
+
/**
|
|
150
|
+
* The read standing {@link urlSchemeCapability} consults before it touches
|
|
151
|
+
* the group container. Defaults to a live {@link readCapability} call.
|
|
152
|
+
*/
|
|
153
|
+
readStanding?: () => ReadCapability;
|
|
154
|
+
/** Raw bytes of the Things group-container preferences plist. Throws when unreachable. */
|
|
155
|
+
readPrefsPlist?: () => Buffer;
|
|
156
|
+
/** Pull `uriSchemeEnabled` out of those bytes. Throws when the key is absent. */
|
|
157
|
+
extractUriSchemeEnabled?: (plistBytes: Buffer) => string;
|
|
93
158
|
}
|
|
94
159
|
/**
|
|
95
160
|
* Can this process read the live Things library, and on whose authority?
|
|
@@ -107,6 +172,49 @@ export declare class ReadCapabilityError extends Error {
|
|
|
107
172
|
readonly capability: ReadCapability;
|
|
108
173
|
constructor(capability: ReadCapability);
|
|
109
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* The LAB's escape hatch for the AppleScript vector — the write-side twin of
|
|
177
|
+
* {@link UI_DIRECT_ESCAPE_ENV}, and just as deliberately not consumer surface.
|
|
178
|
+
*
|
|
179
|
+
* A guest shell in a golden clone descends from sshd, not from an application
|
|
180
|
+
* bundle, so `hostApp()` finds no `__CFBundleIdentifier` and macOS has no
|
|
181
|
+
* identity to have recorded an Automation grant against. The verdict is
|
|
182
|
+
* therefore `direct-unknown` in every clone, which blocks every AppleScript-
|
|
183
|
+
* vector verb and every composite carrying an AppleScript leg. What the clone
|
|
184
|
+
* actually has is an in-guest Automation grant on the runner's own processes
|
|
185
|
+
* (the same AXVM1 layer the ui escape leans on), so the block is an artefact of
|
|
186
|
+
* UNKNOWABILITY, not of a missing grant. Setting this to `1` says so.
|
|
187
|
+
*
|
|
188
|
+
* Bounded, and the bound is the point: it is consulted ONLY on the
|
|
189
|
+
* bundle-id-less path. A host that has an identity is answered from its TCC row
|
|
190
|
+
* as it always was, so the escape can never mask a recorded refusal
|
|
191
|
+
* (`direct-denied`) or manufacture a grant for a real user's terminal.
|
|
192
|
+
* Documented in docs/lab/harness.md and exported by the lab's guest
|
|
193
|
+
* environment; nothing consumer-facing mentions it.
|
|
194
|
+
*/
|
|
195
|
+
export declare const WRITE_DIRECT_ESCAPE_ENV = "THINGS_API_WRITE_DIRECT";
|
|
196
|
+
/**
|
|
197
|
+
* What a {@link writeCapability} verdict is FOR — the one thing that decides
|
|
198
|
+
* whether a dormant Things may be started while the verdict is taken (#617).
|
|
199
|
+
*
|
|
200
|
+
* The asymmetry with the GUI preflight is deliberate. System Events is a
|
|
201
|
+
* headless macOS component nobody sees, so {@link uiCapability} wakes it for
|
|
202
|
+
* every caller. Things is the user's own app: starting it is visible, so only a
|
|
203
|
+
* caller that is ABOUT TO DRIVE IT may do so.
|
|
204
|
+
*/
|
|
205
|
+
export interface WriteCapabilityOptions {
|
|
206
|
+
/**
|
|
207
|
+
* - `survey` (the default) — `doctor`, the MCP startup bake, `--dry-run`.
|
|
208
|
+
* Launches nothing and reports a closed Things as the liveness state
|
|
209
|
+
* `deputy-target-dormant`.
|
|
210
|
+
* - `dispatch` — the write gate and the two setup ceremonies, which are about
|
|
211
|
+
* to send Things an Apple Event anyway. A dormant target is started in the
|
|
212
|
+
* background first and its standing re-read, which is also what keeps the
|
|
213
|
+
* operation at tier 0/1: an Apple Event to a CLOSED Things auto-launches it
|
|
214
|
+
* WITH focus steal (A40/A41), a background pre-launch does not.
|
|
215
|
+
*/
|
|
216
|
+
purpose?: "survey" | "dispatch";
|
|
217
|
+
}
|
|
110
218
|
/**
|
|
111
219
|
* May this process drive Things over Apple Events, and on whose authority?
|
|
112
220
|
*
|
|
@@ -116,8 +224,21 @@ export declare class ReadCapabilityError extends Error {
|
|
|
116
224
|
* last state is deliberately NOT resolved here: resolving it means sending a
|
|
117
225
|
* real Apple Event, which is what raises the dialog, and Article I reserves
|
|
118
226
|
* that for `things setup`.
|
|
227
|
+
*
|
|
228
|
+
* LIVENESS BEFORE AUTHORIZATION (#617). The deputy's `not-running` is the
|
|
229
|
+
* ask-false determination having no answer for a CLOSED Things — a fact about
|
|
230
|
+
* the app's process, not about the grant. Two rules follow, and both matter:
|
|
231
|
+
*
|
|
232
|
+
* - while the deputy is standing, that value NEVER falls through to the direct
|
|
233
|
+
* host branch. A silent direct engagement would put consent back on the
|
|
234
|
+
* terminal (the routing doctrine's no-fallback rule), and on a machine with
|
|
235
|
+
* no host record it would refuse a fully onboarded user with "run
|
|
236
|
+
* `things setup`" — the #610 false-onboarding loop, one vector over;
|
|
237
|
+
* - a `dispatch` caller resolves it by STARTING Things (a background
|
|
238
|
+
* LaunchServices dispatch, never an Apple Event) and re-reading the
|
|
239
|
+
* standing. Only what comes back is an authorization fact.
|
|
119
240
|
*/
|
|
120
|
-
export declare function writeCapability(deps?: CapabilityDeps): WriteCapability;
|
|
241
|
+
export declare function writeCapability(options?: WriteCapabilityOptions, deps?: CapabilityDeps): WriteCapability;
|
|
121
242
|
/** Thrown when app automation is refused for want of capability (Article II). */
|
|
122
243
|
export declare class WriteCapabilityError extends Error {
|
|
123
244
|
readonly remediation: string[];
|
|
@@ -125,7 +246,8 @@ export declare class WriteCapabilityError extends Error {
|
|
|
125
246
|
constructor(capability: WriteCapability);
|
|
126
247
|
}
|
|
127
248
|
/**
|
|
128
|
-
* The LAB's escape hatch, and deliberately not consumer
|
|
249
|
+
* The LAB's escape hatch for the ui vector, and deliberately not consumer
|
|
250
|
+
* surface (its write-side twin is {@link WRITE_DIRECT_ESCAPE_ENV}). The VM lab and
|
|
129
251
|
* the guest e2e bundle drive the UI vector DIRECT — the in-guest Accessibility
|
|
130
252
|
* grant is held by the runner's own processes (the AXVM1 layer), there is no
|
|
131
253
|
* helper bundle in a disposable clone, and there is nobody to answer a dialog
|
|
@@ -147,14 +269,54 @@ export declare const UI_DIRECT_ESCAPE_ENV = "THINGS_API_UI_DIRECT";
|
|
|
147
269
|
*
|
|
148
270
|
* Every answer is prompt-free: the config key is a file read, and the deputy's
|
|
149
271
|
* `hello` carries `AXIsProcessTrusted()` plus its own `AEDeterminePermission`
|
|
150
|
-
* verdict for System Events.
|
|
272
|
+
* verdict for System Events. The one state that needs more than a read is a
|
|
273
|
+
* DORMANT System Events, which macOS reaps whenever it has been idle: the
|
|
274
|
+
* target is started in the background — never by sending it an event — and the
|
|
275
|
+
* determination re-read, so `not-running` resolves to the truth instead of
|
|
276
|
+
* masquerading as a missing grant (./deputy/wake.ts).
|
|
151
277
|
*/
|
|
152
278
|
export declare function uiCapability(deps?: CapabilityDeps): UiCapability;
|
|
279
|
+
/**
|
|
280
|
+
* Has Things been authorized to act on `things:///` commands, and how do we know?
|
|
281
|
+
*
|
|
282
|
+
* Stateless per invocation, like every other verdict here: the setting is the
|
|
283
|
+
* user's to flip at any moment, and a cached "yes" would be a stored onboarding
|
|
284
|
+
* flag by another name. The common case costs one file read plus one `plutil`.
|
|
285
|
+
*
|
|
286
|
+
* WHY THIS IS GATED ON THE READ STANDING. The plist lives inside the Things
|
|
287
|
+
* group container, which is the same `kTCCServiceSystemPolicyAppData` class as
|
|
288
|
+
* the database — so the open is itself what would raise the app-data modal on a
|
|
289
|
+
* machine that holds no standing. This function therefore never opens it
|
|
290
|
+
* speculatively: it asks {@link readCapability} first and reports `unreadable`
|
|
291
|
+
* unless the container is already reachable (helpers, FDA, or a live session
|
|
292
|
+
* grant). Where the helpers are serving, the read rides the reader's own
|
|
293
|
+
* security-scoped bookmark over the container, so the prefs plist is inside the
|
|
294
|
+
* granted subtree and no host grant is involved at all.
|
|
295
|
+
*
|
|
296
|
+
* MEASURED, all three states (URLEN1, golden-v4 / Things 3.23):
|
|
297
|
+
*
|
|
298
|
+
* - `1` — URL mutations execute.
|
|
299
|
+
* - `0` — every mutating verb (`add`, token-bearing `update`, the `json`
|
|
300
|
+
* batch) is dropped in TOTAL SILENCE: zero row delta, no dialog, no window
|
|
301
|
+
* of any kind, and nothing to wait for. Navigation URLs (`things:///show`)
|
|
302
|
+
* still work, so this gate covers mutations only.
|
|
303
|
+
* - absent — nobody has answered the app's own first-use "Things URL Scheme"
|
|
304
|
+
* dialog (Cancel / Enable). The dispatched request PARKS behind that dialog
|
|
305
|
+
* rather than being dropped, which is what #611 saw: with nobody at the
|
|
306
|
+
* machine, the verify window expired and the write reported a silent no-op.
|
|
307
|
+
*/
|
|
308
|
+
export declare function urlSchemeCapability(deps?: CapabilityDeps): UrlSchemeCapability;
|
|
153
309
|
/** Thrown when GUI-driving is refused for want of capability (Article IV). */
|
|
154
310
|
export declare class UiCapabilityError extends Error {
|
|
155
311
|
readonly remediation: string[];
|
|
156
312
|
readonly capability: UiCapability;
|
|
157
313
|
constructor(capability: UiCapability);
|
|
158
314
|
}
|
|
315
|
+
/** Thrown when a URL-scheme mutation is refused for want of the app's own authorization. */
|
|
316
|
+
export declare class UrlSchemeCapabilityError extends Error {
|
|
317
|
+
readonly remediation: string[];
|
|
318
|
+
readonly capability: UrlSchemeCapability;
|
|
319
|
+
constructor(capability: UrlSchemeCapability);
|
|
320
|
+
}
|
|
159
321
|
/** Test seam: forget the one memo this module keeps (the host's display name). */
|
|
160
322
|
export declare function resetCapabilityForTests(): void;
|