things-api 0.18.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -9
- package/deputy/AppIcon.icns +0 -0
- package/deputy/VERSION +1 -1
- package/deputy/helpers-Info.plist +4 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Info.plist +3 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Resources/AppIcon.icns +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/_CodeSignature/CodeResources +15 -2
- package/deputy/prebuilt/Things API Helper.app/Contents/Info.plist +5 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Resources/AppIcon.icns +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/_CodeSignature/CodeResources +14 -2
- package/deputy/reader/Info.plist +2 -0
- package/deputy/reader/entitlements.plist +30 -0
- package/deputy/reader/main.swift +129 -40
- package/deputy/src/server.swift +25 -2
- package/deputy/src/tcc.swift +58 -0
- package/dist/audit/schema.d.ts +30 -1
- package/dist/audit/schema.js.map +1 -1
- package/dist/capability.d.ts +322 -0
- package/dist/capability.js +706 -0
- package/dist/capability.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +13 -1
- package/dist/cli/commands/doctor.js +141 -5
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/helpers.js +189 -51
- package/dist/cli/commands/helpers.js.map +1 -1
- package/dist/cli/commands/mcp.js +8 -0
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/op-result.js +6 -0
- package/dist/cli/commands/op-result.js.map +1 -1
- package/dist/cli/commands/reads.js +47 -1
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/repeat-flags.js +34 -2
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +9 -0
- package/dist/cli/commands/setup.js +76 -83
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/writes.d.ts +1 -1
- package/dist/cli/commands/writes.js +311 -142
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/dry-run.d.ts +1 -1
- package/dist/cli/dry-run.js +1 -1
- package/dist/cli/glyphs.d.ts +23 -4
- package/dist/cli/glyphs.js +46 -19
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +49 -27
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/interrupt.d.ts +54 -6
- package/dist/cli/interrupt.js +78 -12
- package/dist/cli/interrupt.js.map +1 -1
- package/dist/cli/main.js +8 -5
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/period.d.ts +3 -1
- package/dist/cli/period.js +1 -1
- package/dist/cli/period.js.map +1 -1
- package/dist/cli/read-driver.js +26 -1
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/render.d.ts +31 -4
- package/dist/cli/render.js +127 -15
- package/dist/cli/render.js.map +1 -1
- package/dist/client.d.ts +23 -3
- package/dist/client.js +36 -13
- package/dist/client.js.map +1 -1
- package/dist/contracts.d.ts +8 -4
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deputy/install.d.ts +234 -6
- package/dist/deputy/install.js +843 -31
- package/dist/deputy/install.js.map +1 -1
- package/dist/deputy/notices.js +2 -2
- package/dist/deputy/notices.js.map +1 -1
- package/dist/deputy/protocol.d.ts +59 -5
- package/dist/deputy/protocol.js +48 -11
- package/dist/deputy/protocol.js.map +1 -1
- package/dist/deputy/routing.d.ts +37 -0
- package/dist/deputy/routing.js +181 -27
- package/dist/deputy/routing.js.map +1 -1
- package/dist/deputy/wake.d.ts +61 -0
- package/dist/deputy/wake.js +149 -0
- package/dist/deputy/wake.js.map +1 -0
- package/dist/diagnose.d.ts +19 -4
- package/dist/diagnose.js +64 -8
- package/dist/diagnose.js.map +1 -1
- package/dist/direct-setup.d.ts +104 -0
- package/dist/direct-setup.js +658 -0
- package/dist/direct-setup.js.map +1 -0
- package/dist/host-access.d.ts +75 -0
- package/dist/host-access.js +177 -0
- package/dist/host-access.js.map +1 -0
- package/dist/index.d.ts +20 -5
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +27 -1
- package/dist/mcp/server.js +308 -109
- package/dist/mcp/server.js.map +1 -1
- package/dist/op-result.d.ts +9 -0
- package/dist/op-result.js +13 -1
- package/dist/op-result.js.map +1 -1
- package/dist/read/filter-contract.d.ts +1 -1
- package/dist/read/filter-contract.js +4 -0
- package/dist/read/filter-contract.js.map +1 -1
- package/dist/read/shape.js +4 -0
- package/dist/read/shape.js.map +1 -1
- package/dist/read/views.d.ts +29 -0
- package/dist/read/views.js +60 -0
- package/dist/read/views.js.map +1 -1
- package/dist/session-grant.d.ts +48 -0
- package/dist/session-grant.js +185 -0
- package/dist/session-grant.js.map +1 -0
- package/dist/surface-copy.d.ts +19 -3
- package/dist/surface-copy.js +19 -3
- package/dist/surface-copy.js.map +1 -1
- package/dist/wizard.d.ts +58 -0
- package/dist/wizard.js +174 -0
- package/dist/wizard.js.map +1 -0
- package/dist/write/availability.d.ts +1 -16
- package/dist/write/availability.js +10 -65
- package/dist/write/availability.js.map +1 -1
- package/dist/write/batch.d.ts +20 -0
- package/dist/write/batch.js +111 -21
- package/dist/write/batch.js.map +1 -1
- package/dist/write/capabilities.d.ts +9 -0
- package/dist/write/capabilities.js +2 -0
- package/dist/write/capabilities.js.map +1 -1
- package/dist/write/clear-reminder.js +1 -1
- package/dist/write/clear-reminder.js.map +1 -1
- package/dist/write/commands.d.ts +22 -7
- package/dist/write/commands.js +231 -254
- package/dist/write/commands.js.map +1 -1
- package/dist/write/failure-hints.d.ts +32 -7
- package/dist/write/failure-hints.js +9 -14
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/guards.js +22 -4
- package/dist/write/guards.js.map +1 -1
- package/dist/write/lock.d.ts +16 -13
- package/dist/write/lock.js +55 -0
- package/dist/write/lock.js.map +1 -1
- package/dist/write/make-repeating-project.js +9 -13
- package/dist/write/make-repeating-project.js.map +1 -1
- package/dist/write/move.js +9 -12
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +51 -29
- package/dist/write/operations.js +16 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +72 -14
- package/dist/write/opid.js +152 -8
- package/dist/write/opid.js.map +1 -1
- package/dist/write/param-schema.d.ts +80 -0
- package/dist/write/param-schema.js +754 -0
- package/dist/write/param-schema.js.map +1 -0
- package/dist/write/pipeline.d.ts +110 -5
- package/dist/write/pipeline.js +288 -14
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +38 -16
- package/dist/write/pre-state.js +117 -22
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/promote-clone.js +658 -444
- package/dist/write/promote-clone.js.map +1 -1
- package/dist/write/reorder.js +25 -8
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/repeat-collateral.d.ts +31 -0
- package/dist/write/repeat-collateral.js +153 -0
- package/dist/write/repeat-collateral.js.map +1 -0
- package/dist/write/repeat-rule.d.ts +28 -1
- package/dist/write/repeat-rule.js +54 -4
- package/dist/write/repeat-rule.js.map +1 -1
- package/dist/write/resolution-timestamps.d.ts +11 -0
- package/dist/write/resolution-timestamps.js +108 -40
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/reversibility.js +4 -0
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/template-mutation.d.ts +137 -0
- package/dist/write/template-mutation.js +598 -0
- package/dist/write/template-mutation.js.map +1 -0
- package/dist/write/undo.js +10 -20
- package/dist/write/undo.js.map +1 -1
- package/dist/write/update-fields.d.ts +151 -0
- package/dist/write/update-fields.js +427 -0
- package/dist/write/update-fields.js.map +1 -0
- package/dist/write/vectors/applescript.js +6 -10
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/registry.d.ts +1 -1
- package/dist/write/vectors/registry.js +10 -2
- package/dist/write/vectors/registry.js.map +1 -1
- package/dist/write/vectors/shortcuts.js +2 -2
- package/dist/write/vectors/shortcuts.js.map +1 -1
- package/dist/write/vectors/simulator.js +15 -4
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +268 -1
- package/dist/write/vectors/ui-certification.js +63 -13
- package/dist/write/vectors/ui-certification.js.map +1 -1
- package/dist/write/vectors/ui-chord.d.ts +144 -0
- package/dist/write/vectors/ui-chord.js +428 -0
- package/dist/write/vectors/ui-chord.js.map +1 -0
- package/dist/write/vectors/ui-drag.d.ts +8 -4
- package/dist/write/vectors/ui-drag.js +2 -0
- package/dist/write/vectors/ui-drag.js.map +1 -1
- package/dist/write/vectors/ui-recipes.d.ts +70 -3
- package/dist/write/vectors/ui-recipes.js +482 -44
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +280 -10
- package/dist/write/vectors/ui.js +1068 -77
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/vectors/url-scheme.js +3 -0
- package/dist/write/vectors/url-scheme.js.map +1 -1
- package/dist/write/verify/delta.d.ts +62 -0
- package/dist/write/verify/delta.js +36 -2
- package/dist/write/verify/delta.js.map +1 -1
- package/dist/write/verify/poller.d.ts +4 -1
- package/dist/write/verify/poller.js +14 -0
- package/dist/write/verify/poller.js.map +1 -1
- package/package.json +2 -1
- package/schema/envelope.schema.json +6 -4
- package/scripts/build-helpers.sh +5 -0
- package/skills/things-cli/SKILL.md +5 -3
- package/skills/things-cli/references/contracts.md +1 -0
- package/skills/things-cli/references/data-model.md +3 -0
- package/skills/things-cli/references/errors.md +1 -1
- package/skills/things-cli/references/gui.md +1 -0
- package/skills/things-cli/references/repeating.md +6 -0
- package/skills/things-cli/references/tag-cleanup.md +64 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The instance-scoped app-data grant marker (docs/design/permissions-doctrine.md,
|
|
3
|
+
* Article III).
|
|
4
|
+
*
|
|
5
|
+
* macOS has a consent class beneath Full Disk Access — "would like to access
|
|
6
|
+
* data from other apps" (`kTCCServiceSystemPolicyAppData`) — that covers the
|
|
7
|
+
* Things group container. It is MEASURED allow-once-per-responsible-process-
|
|
8
|
+
* instance: the grant is pinned to the running host app, and it dies when that
|
|
9
|
+
* app quits. APDP1 (docs/lab/apdp1-grant-pinning.md) settled the axis this
|
|
10
|
+
* marker rests on — TCC writes the row against the RESPONSIBLE APP (its bundle
|
|
11
|
+
* id, pinned to that instance's pid + pid_version), never against the pid that
|
|
12
|
+
* opened the file, so every process under one host-app instance shares a single
|
|
13
|
+
* grant and a relaunch of the host app re-arms the prompt. It is also, by
|
|
14
|
+
* TCC's ask-on-access design, undetectable without
|
|
15
|
+
* FDA: the only way to learn whether you hold it is to open the file, and the
|
|
16
|
+
* open is what raises the modal. That is precisely what Article I forbids
|
|
17
|
+
* outside a ceremony.
|
|
18
|
+
*
|
|
19
|
+
* So the grant is not detected — it is WITNESSED. `things setup` deliberately
|
|
20
|
+
* provokes the modal, and if the container open then succeeds it writes this
|
|
21
|
+
* marker recording WHICH host-app instance the grant was minted for. A later
|
|
22
|
+
* invocation reads the marker and re-derives that instance's identity; if it
|
|
23
|
+
* still matches, the grant is necessarily still live and the container may be
|
|
24
|
+
* opened without risk of a dialog.
|
|
25
|
+
*
|
|
26
|
+
* The marker is NOT an "onboarded" flag and cannot become one: it records a
|
|
27
|
+
* live-instance fact (pid + that pid's start time), so it self-invalidates the
|
|
28
|
+
* moment the host app quits — because the grant it describes dies at the same
|
|
29
|
+
* instant. Nothing here can outlive what it describes.
|
|
30
|
+
*
|
|
31
|
+
* The one honest hole: a `tccutil reset` performed by the user mid-session
|
|
32
|
+
* revokes the grant while the app instance keeps running, so the marker stays
|
|
33
|
+
* valid and the next container open re-prompts. That is user-caused, visible,
|
|
34
|
+
* and recorded in the doctrine's enforcement inventory rather than papered over.
|
|
35
|
+
*/
|
|
36
|
+
import { execFileSync } from "node:child_process";
|
|
37
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
38
|
+
import { join } from "node:path";
|
|
39
|
+
import { stateDir } from "./paths.js";
|
|
40
|
+
/**
|
|
41
|
+
* How far apart two `kern.boottime` readings may sit and still describe the
|
|
42
|
+
* same boot. MEASURED 2026-08-24: macOS re-derives boot time from the current
|
|
43
|
+
* clock after sleep/wake, so the value drifts by minutes on a laptop that
|
|
44
|
+
* suspends — an exact match would invalidate the marker on every lid close.
|
|
45
|
+
* The pid + start-time pair is what actually carries the guarantee; boot time
|
|
46
|
+
* is only a coarse guard against pid reuse across a restart, and a restart
|
|
47
|
+
* moves it by far more than this.
|
|
48
|
+
*/
|
|
49
|
+
const BOOT_TIME_TOLERANCE_SEC = 3600;
|
|
50
|
+
export function sessionGrantPath(env = process.env) {
|
|
51
|
+
return join(stateDir(env), "session-grant.json");
|
|
52
|
+
}
|
|
53
|
+
function hostPidDefault(bundleId) {
|
|
54
|
+
try {
|
|
55
|
+
// LaunchServices answers for RUNNING apps only, which is exactly the
|
|
56
|
+
// question: a bundle id with no pid means that instance is gone.
|
|
57
|
+
//
|
|
58
|
+
// This asks about the RESPONSIBLE APP (the bundle id this process inherited
|
|
59
|
+
// from it), never about this process — the CLI's own pid is irrelevant to
|
|
60
|
+
// the grant and would expire the marker on every command. It is also not a
|
|
61
|
+
// process-tree walk, so it stays correct inside tmux, where the responsible
|
|
62
|
+
// app is the one that started the tmux server and the ppid chain leads only
|
|
63
|
+
// to launchd. Where the bundle id names an app that quit and relaunched,
|
|
64
|
+
// this returns the NEW instance's pid, which fails the marker's pid +
|
|
65
|
+
// start-time comparison — the marker expires, which is the conservative
|
|
66
|
+
// answer and the true one: the grant died with the old instance.
|
|
67
|
+
const out = execFileSync("lsappinfo", ["info", "-only", "pid", bundleId], {
|
|
68
|
+
encoding: "utf8",
|
|
69
|
+
timeout: 3000,
|
|
70
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
71
|
+
});
|
|
72
|
+
const match = /"pid"\s*=\s*(\d+)/.exec(out);
|
|
73
|
+
const pid = match?.[1] !== undefined ? Number(match[1]) : Number.NaN;
|
|
74
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function processStartDefault(pid) {
|
|
81
|
+
try {
|
|
82
|
+
const out = execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
83
|
+
encoding: "utf8",
|
|
84
|
+
timeout: 3000,
|
|
85
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
86
|
+
});
|
|
87
|
+
const trimmed = out.trim();
|
|
88
|
+
return trimmed === "" ? null : trimmed;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// A dead pid exits nonzero — the instance is gone, which is an answer.
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function bootTimeDefault() {
|
|
96
|
+
try {
|
|
97
|
+
const out = execFileSync("sysctl", ["-n", "kern.boottime"], {
|
|
98
|
+
encoding: "utf8",
|
|
99
|
+
timeout: 3000,
|
|
100
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
101
|
+
});
|
|
102
|
+
const match = /sec\s*=\s*(\d+)/.exec(out);
|
|
103
|
+
return match?.[1] !== undefined ? Number(match[1]) : null;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** The live identity of the host app instance, or null when it is not running. */
|
|
110
|
+
function currentInstance(bundleId, deps) {
|
|
111
|
+
const pid = (deps.hostPid ?? hostPidDefault)(bundleId);
|
|
112
|
+
if (pid === null)
|
|
113
|
+
return null;
|
|
114
|
+
const start = (deps.processStart ?? processStartDefault)(pid);
|
|
115
|
+
return start === null ? null : { pid, start };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Record that a container open succeeded for the CURRENT host-app instance.
|
|
119
|
+
* Called by `things setup` only, and only after the open actually worked —
|
|
120
|
+
* writing this on any other occasion would turn a live-instance fact into the
|
|
121
|
+
* stored "onboarded" flag the doctrine forbids.
|
|
122
|
+
*/
|
|
123
|
+
export function witnessSessionGrant(bundleId, deps = {}) {
|
|
124
|
+
const env = deps.env ?? process.env;
|
|
125
|
+
const instance = currentInstance(bundleId, deps);
|
|
126
|
+
if (instance === null)
|
|
127
|
+
return null;
|
|
128
|
+
const marker = {
|
|
129
|
+
hostBundleId: bundleId,
|
|
130
|
+
hostPid: instance.pid,
|
|
131
|
+
hostStart: instance.start,
|
|
132
|
+
bootTime: (deps.bootTime ?? bootTimeDefault)() ?? 0,
|
|
133
|
+
witnessedAt: (deps.now ?? (() => new Date()))().toISOString(),
|
|
134
|
+
};
|
|
135
|
+
const path = sessionGrantPath(env);
|
|
136
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
137
|
+
writeFileSync(path, `${JSON.stringify(marker, null, 2)}\n`, "utf8");
|
|
138
|
+
return marker;
|
|
139
|
+
}
|
|
140
|
+
/** Forget the marker (a ceremony that finds the grant gone clears the stale claim). */
|
|
141
|
+
export function clearSessionGrant(env = process.env) {
|
|
142
|
+
rmSync(sessionGrantPath(env), { force: true });
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Is a witnessed app-data grant still live for THIS host-app instance?
|
|
146
|
+
*
|
|
147
|
+
* Costs a marker read plus, only when a marker exists, two small process
|
|
148
|
+
* queries. Never runs at all when Full Disk Access already answers the
|
|
149
|
+
* question, which is the common direct-mode case.
|
|
150
|
+
*/
|
|
151
|
+
export function sessionGrantValid(bundleId, deps = {}) {
|
|
152
|
+
const env = deps.env ?? process.env;
|
|
153
|
+
if (bundleId === null) {
|
|
154
|
+
return { valid: false, reason: "this process has no host application identity" };
|
|
155
|
+
}
|
|
156
|
+
let marker;
|
|
157
|
+
try {
|
|
158
|
+
marker = JSON.parse(readFileSync(sessionGrantPath(env), "utf8"));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return { valid: false, reason: "no app-data grant has been witnessed on this machine" };
|
|
162
|
+
}
|
|
163
|
+
if (marker.hostBundleId !== bundleId) {
|
|
164
|
+
return {
|
|
165
|
+
valid: false,
|
|
166
|
+
reason: `the witnessed grant belongs to ${marker.hostBundleId}, not ${bundleId}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const instance = currentInstance(bundleId, deps);
|
|
170
|
+
if (instance === null) {
|
|
171
|
+
return { valid: false, reason: "the application that held the grant is no longer running" };
|
|
172
|
+
}
|
|
173
|
+
if (instance.pid !== marker.hostPid || instance.start !== marker.hostStart) {
|
|
174
|
+
return {
|
|
175
|
+
valid: false,
|
|
176
|
+
reason: "the application that held the grant has been restarted since",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const boot = (deps.bootTime ?? bootTimeDefault)();
|
|
180
|
+
if (boot !== null && Math.abs(boot - marker.bootTime) > BOOT_TIME_TOLERANCE_SEC) {
|
|
181
|
+
return { valid: false, reason: "the machine has restarted since the grant was witnessed" };
|
|
182
|
+
}
|
|
183
|
+
return { valid: true, marker };
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=session-grant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-grant.js","sourceRoot":"","sources":["../src/session-grant.ts"],"names":[],"mappings":"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"}
|
package/dist/surface-copy.d.ts
CHANGED
|
@@ -13,12 +13,28 @@ export declare const DATE_FORMAT = "YYYY-MM-DD";
|
|
|
13
13
|
export declare const REMINDER_FORMAT = "HH:mm (24-hour)";
|
|
14
14
|
/**
|
|
15
15
|
* Resolution-timestamp value format (`--created-at`/`--completed-at`,
|
|
16
|
-
* `created_at`/`completed_at`):
|
|
17
|
-
*
|
|
16
|
+
* `created_at`/`completed_at`): a date or a datetime whose date and time are
|
|
17
|
+
* joined by `T` or by a space — both spellings resolve to the same instant. A
|
|
18
|
+
* bare date normalizes to noon in the effective zone (resolution-timestamp
|
|
19
|
+
* plan §5).
|
|
18
20
|
*/
|
|
19
|
-
export declare const RESOLUTION_DATE_FORMAT = "
|
|
21
|
+
export declare const RESOLUTION_DATE_FORMAT = "YYYY-MM-DD, or YYYY-MM-DD HH:mm with T or a space between date and time; a date is noon in the effective zone";
|
|
22
|
+
/**
|
|
23
|
+
* The same grammar as {@link RESOLUTION_DATE_FORMAT}, phrased as the "expected"
|
|
24
|
+
* half of a refusal ("expected … — received …"). Every layer that parses a
|
|
25
|
+
* resolution timestamp names the accepted spellings with this one string.
|
|
26
|
+
*/
|
|
27
|
+
export declare const RESOLUTION_TIMESTAMP_EXPECTED = "a date (YYYY-MM-DD) or datetime (YYYY-MM-DDTHH:mm or YYYY-MM-DD HH:mm)";
|
|
20
28
|
/** How projects, areas, and tags may be referenced. */
|
|
21
29
|
export declare const REF_FORMAT = "uuid or unique name";
|
|
30
|
+
/**
|
|
31
|
+
* The notes-field format, shared by every `--notes`/`notes` surface: notes is a
|
|
32
|
+
* markdown field with multi-line support, not a plain text blob. Things renders
|
|
33
|
+
* the common markdown constructs while leaving the syntax characters in place,
|
|
34
|
+
* and links a bare URL on its own — so `[label](url)` stays literal text.
|
|
35
|
+
* Evidence: docs/lab/notesmd-results.md.
|
|
36
|
+
*/
|
|
37
|
+
export declare const NOTES_FORMAT = "markdown, multi-line; a bare URL becomes a link, [label](url) stays literal";
|
|
22
38
|
/**
|
|
23
39
|
* Time-window bound grammar shared by `--since`/`--until` flags: relative
|
|
24
40
|
* periods counted from today (`2w`/`3m`/`1y`) or whole calendar periods
|
package/dist/surface-copy.js
CHANGED
|
@@ -13,12 +13,28 @@ export const DATE_FORMAT = "YYYY-MM-DD";
|
|
|
13
13
|
export const REMINDER_FORMAT = "HH:mm (24-hour)";
|
|
14
14
|
/**
|
|
15
15
|
* Resolution-timestamp value format (`--created-at`/`--completed-at`,
|
|
16
|
-
* `created_at`/`completed_at`):
|
|
17
|
-
*
|
|
16
|
+
* `created_at`/`completed_at`): a date or a datetime whose date and time are
|
|
17
|
+
* joined by `T` or by a space — both spellings resolve to the same instant. A
|
|
18
|
+
* bare date normalizes to noon in the effective zone (resolution-timestamp
|
|
19
|
+
* plan §5).
|
|
18
20
|
*/
|
|
19
|
-
export const RESOLUTION_DATE_FORMAT = "
|
|
21
|
+
export const RESOLUTION_DATE_FORMAT = "YYYY-MM-DD, or YYYY-MM-DD HH:mm with T or a space between date and time; a date is noon in the effective zone";
|
|
22
|
+
/**
|
|
23
|
+
* The same grammar as {@link RESOLUTION_DATE_FORMAT}, phrased as the "expected"
|
|
24
|
+
* half of a refusal ("expected … — received …"). Every layer that parses a
|
|
25
|
+
* resolution timestamp names the accepted spellings with this one string.
|
|
26
|
+
*/
|
|
27
|
+
export const RESOLUTION_TIMESTAMP_EXPECTED = "a date (YYYY-MM-DD) or datetime (YYYY-MM-DDTHH:mm or YYYY-MM-DD HH:mm)";
|
|
20
28
|
/** How projects, areas, and tags may be referenced. */
|
|
21
29
|
export const REF_FORMAT = "uuid or unique name";
|
|
30
|
+
/**
|
|
31
|
+
* The notes-field format, shared by every `--notes`/`notes` surface: notes is a
|
|
32
|
+
* markdown field with multi-line support, not a plain text blob. Things renders
|
|
33
|
+
* the common markdown constructs while leaving the syntax characters in place,
|
|
34
|
+
* and links a bare URL on its own — so `[label](url)` stays literal text.
|
|
35
|
+
* Evidence: docs/lab/notesmd-results.md.
|
|
36
|
+
*/
|
|
37
|
+
export const NOTES_FORMAT = "markdown, multi-line; a bare URL becomes a link, [label](url) stays literal";
|
|
22
38
|
/**
|
|
23
39
|
* Time-window bound grammar shared by `--since`/`--until` flags: relative
|
|
24
40
|
* periods counted from today (`2w`/`3m`/`1y`) or whole calendar periods
|
package/dist/surface-copy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface-copy.js","sourceRoot":"","sources":["../src/surface-copy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,yCAAyC;AACzC,MAAM,CAAC,MAAM,WAAW,GAAG,kDAAkD,CAAC;AAE9E,gEAAgE;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;AAExC,6CAA6C;AAC7C,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"surface-copy.js","sourceRoot":"","sources":["../src/surface-copy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,yCAAyC;AACzC,MAAM,CAAC,MAAM,WAAW,GAAG,kDAAkD,CAAC;AAE9E,gEAAgE;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;AAExC,6CAA6C;AAC7C,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GACjC,+GAA+G,CAAC;AAElH;;;;GAIG;AACH,MAAM,CAAC,MAAM,6BAA6B,GACxC,wEAAwE,CAAC;AAE3E,uDAAuD;AACvD,MAAM,CAAC,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAEhD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GACvB,6EAA6E,CAAC;AAEhF;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,oEAAoE,CAAC;AACjG,MAAM,CAAC,MAAM,YAAY,GAAG,+DAA+D,CAAC;AAE5F,sEAAsE;AACtE,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,UAAU,GAAG,kCAAkC,kBAAkB,GAAG,CAAC;AAElF,6DAA6D;AAC7D,MAAM,CAAC,MAAM,QAAQ,GAAG,oCAAoC,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,sDAAsD,CAAC;AAEhF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAAG,mGAAmG,kBAAkB,gCAAgC,CAAC;AAErL,+DAA+D;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,4DAA4D,qBAAqB,sCAAsC,CAAC;AAE1J,kFAAkF;AAClF,MAAM,CAAC,MAAM,gBAAgB,GAAG,oDAAoD,CAAC;AAErF,oFAAoF;AACpF,MAAM,CAAC,MAAM,gBAAgB,GAAG,sEAAsE,UAAU,yCAAyC,CAAC;AAE1J;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAC1B,qJAAqJ,CAAC;AAExJ;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GACxB,obAAob,CAAC;AAEvb;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,MAAsD;IACnF,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,IAAI;YACP,OAAO,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,OAAO;gBACL,wEAAwE;oBACtE,8CAA8C;aACjD,CAAC;QACJ,KAAK,iBAAiB;YACpB,OAAO;gBACL,6EAA6E;oBAC3E,8CAA8C;aACjD,CAAC;IACN,CAAC;AACH,CAAC"}
|
package/dist/wizard.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The human stopped the ceremony — Ctrl-D at a gate, a `\x03`/`\x04` arriving
|
|
3
|
+
* as data, or a closed terminal. Every leg is resumable, so this is a clean
|
|
4
|
+
* stop and not a failure: the CLI prints the stopped line and exits nonzero.
|
|
5
|
+
*/
|
|
6
|
+
export declare class CeremonyStopped extends Error {
|
|
7
|
+
constructor(why?: string);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Run a ceremony's synchronous span with the process's DEFAULT SIGINT/SIGTERM
|
|
11
|
+
* disposition, so Ctrl-C really does stop it (see the module note above).
|
|
12
|
+
*
|
|
13
|
+
* The lifted listeners are restored on the way out. A `once` listener comes
|
|
14
|
+
* back as a plain one — the CLI's handler exits the process, so it cannot fire
|
|
15
|
+
* twice, and a ceremony that returns normally never reaches it at all.
|
|
16
|
+
*/
|
|
17
|
+
export declare function withDefaultInterrupts<T>(run: () => T): T;
|
|
18
|
+
export interface WizardDeps {
|
|
19
|
+
/**
|
|
20
|
+
* Is a human sitting here? Default: stdin AND stderr are both terminals —
|
|
21
|
+
* stdin because the gate has to be answerable, stderr because the explainers
|
|
22
|
+
* have to be visible even when stdout is a pipe carrying `--json`.
|
|
23
|
+
*/
|
|
24
|
+
interactive?: boolean;
|
|
25
|
+
/** Where explainers and questions are printed. Default: stderr. */
|
|
26
|
+
say?: (line: string) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Read one line from the human. Default: one line off /dev/tty. `null` means
|
|
29
|
+
* end of input — Ctrl-D, or a terminal that went away — and stops the
|
|
30
|
+
* ceremony rather than reading as a bare Enter.
|
|
31
|
+
*/
|
|
32
|
+
readLine?: () => string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface Wizard {
|
|
35
|
+
/** True when this run is a guided sitting rather than strict mode. */
|
|
36
|
+
readonly interactive: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Explain a dialog that is about to be raised, then wait for the human to say
|
|
39
|
+
* they are ready. A no-op in strict mode — there the upfront banner has
|
|
40
|
+
* already counted the dialogs and nobody is here to pace them.
|
|
41
|
+
*/
|
|
42
|
+
explain(lines: string[]): void;
|
|
43
|
+
/**
|
|
44
|
+
* Ask a yes/no question. `fallback` is the answer in strict mode (and the
|
|
45
|
+
* answer a bare Enter gives), so a non-interactive run is never blocked on
|
|
46
|
+
* one; the flag that decides it non-interactively is the caller's business.
|
|
47
|
+
*/
|
|
48
|
+
ask(question: string, fallback: boolean): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Offer a leg's alternatives: `lines` describes them, Enter takes the first
|
|
51
|
+
* one, and typing any of `keys` takes that one. Returns the chosen key, or
|
|
52
|
+
* `""` for Enter — which is also strict mode's silent answer, so the default
|
|
53
|
+
* has to be the choice an absent human would want.
|
|
54
|
+
*/
|
|
55
|
+
choose(lines: string[], keys: string[]): string;
|
|
56
|
+
}
|
|
57
|
+
/** Build the wizard for one ceremony run. Fully injectable for tests. */
|
|
58
|
+
export declare function createWizard(deps?: WizardDeps): Wizard;
|
package/dist/wizard.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ceremony wizard — Article V's mode-aware half
|
|
3
|
+
* (docs/design/permissions-doctrine.md).
|
|
4
|
+
*
|
|
5
|
+
* Both setup ceremonies (`things setup` and `things helpers setup`) share this
|
|
6
|
+
* machinery, because a human sitting at either one needs the same two things:
|
|
7
|
+
* to be told what is about to appear on screen BEFORE it appears, and to be
|
|
8
|
+
* allowed to get there at their own pace.
|
|
9
|
+
*
|
|
10
|
+
* At a TTY the ceremony is a WIZARD: each prompt-raising leg is preceded by one
|
|
11
|
+
* plain-language explainer naming the dialog by its actual words and the button
|
|
12
|
+
* to click, and then the ceremony waits for Enter. Off a TTY it is STRICT mode,
|
|
13
|
+
* exactly as before: the upfront banner counts the dialogs, waits are bounded,
|
|
14
|
+
* and an unanswered leg fails the run. The tier question is asked interactively
|
|
15
|
+
* when nothing else decided it, and answered by the flag when it did.
|
|
16
|
+
*
|
|
17
|
+
* TTY-ness is the ONLY signal used, and it selects wizard-vs-strict *inside* a
|
|
18
|
+
* ceremony only — never behavior anywhere else. There is deliberately no
|
|
19
|
+
* env-based agent sniffing anywhere in the package (Article V): Article I makes
|
|
20
|
+
* it unnecessary, because no ordinary command can raise a dialog for an agent
|
|
21
|
+
* to hang on in the first place.
|
|
22
|
+
*
|
|
23
|
+
* ## Why a ceremony runs with the DEFAULT signal disposition
|
|
24
|
+
*
|
|
25
|
+
* MEASURED 2026-08-24 (macOS 24.6, node under a pty): a ceremony's gates and
|
|
26
|
+
* bounded waits are SYNCHRONOUS — a blocking `read(2)` on /dev/tty, or
|
|
27
|
+
* `Atomics.wait` between polls — so they hold the event loop for their whole
|
|
28
|
+
* duration. The CLI used to install `process.once("SIGINT", …)` at startup
|
|
29
|
+
* (../cli/interrupt.ts), and a registered JS listener replaces the kernel's
|
|
30
|
+
* default disposition with a libuv watcher that can only run a handler ON the
|
|
31
|
+
* event loop. With the loop held, the signal is queued and never dispatched,
|
|
32
|
+
* libuv restarts the EINTR'd read, and — because the line discipline keeps
|
|
33
|
+
* ISIG on, so ^C is consumed as a signal and never arrives as a byte — Ctrl-C
|
|
34
|
+
* is swallowed COMPLETELY: no handler, no exit, no input. Reproduced both at a
|
|
35
|
+
* gate and inside a poll; without the listener the same ^C kills node at once.
|
|
36
|
+
*
|
|
37
|
+
* The startup install is GONE (the same starvation hit ordinary commands, which
|
|
38
|
+
* now keep the kernel's disposition and are armed only for the span of a write),
|
|
39
|
+
* so a ceremony reached from a bare CLI invocation already has nothing to lift.
|
|
40
|
+
* {@link withDefaultInterrupts} stays as the ceremony's own guarantee: it holds
|
|
41
|
+
* whatever the process arrives with, and is what makes the promise independent
|
|
42
|
+
* of who called it.
|
|
43
|
+
*
|
|
44
|
+
* The fix is {@link withDefaultInterrupts}: for the span of the ceremony the JS
|
|
45
|
+
* listeners are lifted, so the kernel terminates the process on ^C exactly as
|
|
46
|
+
* the copy promises, at the conventional 130. Deliberately NOT fixed by putting
|
|
47
|
+
* the terminal in raw mode to read ^C as `\x03`: raw mode is what would disable
|
|
48
|
+
* ISIG and *cause* this class of bug, and it leaves a terminal to restore on
|
|
49
|
+
* every exit path — including the ones a signal never lets us reach. The gate
|
|
50
|
+
* stays in canonical mode, so there is no terminal state to restore, and a
|
|
51
|
+
* `\x03`/`\x04` that does arrive as data (a harness feeding the gate, or
|
|
52
|
+
* Ctrl-D) is treated as a stop.
|
|
53
|
+
*/
|
|
54
|
+
import { closeSync, openSync, readSync } from "node:fs";
|
|
55
|
+
/** The controlling terminal, so a piped stdout/stdin cannot swallow the gate. */
|
|
56
|
+
const TTY_DEVICE = "/dev/tty";
|
|
57
|
+
/** Signals whose default disposition a ceremony restores while it blocks. */
|
|
58
|
+
const CEREMONY_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
59
|
+
/**
|
|
60
|
+
* The human stopped the ceremony — Ctrl-D at a gate, a `\x03`/`\x04` arriving
|
|
61
|
+
* as data, or a closed terminal. Every leg is resumable, so this is a clean
|
|
62
|
+
* stop and not a failure: the CLI prints the stopped line and exits nonzero.
|
|
63
|
+
*/
|
|
64
|
+
export class CeremonyStopped extends Error {
|
|
65
|
+
constructor(why = "stopped at the keyboard") {
|
|
66
|
+
super(`the setup ceremony was ${why}`);
|
|
67
|
+
this.name = "CeremonyStopped";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Run a ceremony's synchronous span with the process's DEFAULT SIGINT/SIGTERM
|
|
72
|
+
* disposition, so Ctrl-C really does stop it (see the module note above).
|
|
73
|
+
*
|
|
74
|
+
* The lifted listeners are restored on the way out. A `once` listener comes
|
|
75
|
+
* back as a plain one — the CLI's handler exits the process, so it cannot fire
|
|
76
|
+
* twice, and a ceremony that returns normally never reaches it at all.
|
|
77
|
+
*/
|
|
78
|
+
export function withDefaultInterrupts(run) {
|
|
79
|
+
const lifted = CEREMONY_SIGNALS.map((signal) => {
|
|
80
|
+
const listeners = process.listeners(signal);
|
|
81
|
+
for (const listener of listeners)
|
|
82
|
+
process.removeListener(signal, listener);
|
|
83
|
+
return { signal, listeners };
|
|
84
|
+
});
|
|
85
|
+
try {
|
|
86
|
+
return run();
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
for (const { signal, listeners } of lifted) {
|
|
90
|
+
for (const listener of listeners)
|
|
91
|
+
process.on(signal, listener);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function readLineDefault() {
|
|
96
|
+
let fd;
|
|
97
|
+
try {
|
|
98
|
+
fd = openSync(TTY_DEVICE, "r");
|
|
99
|
+
const buffer = Buffer.alloc(256);
|
|
100
|
+
const read = readSync(fd, buffer, 0, buffer.length, null);
|
|
101
|
+
// A zero-byte read is EOF, not an empty answer.
|
|
102
|
+
return read === 0 ? null : buffer.toString("utf8", 0, read).trim();
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// No controlling terminal after all (a ceremony started under a harness
|
|
106
|
+
// that reported isTTY and then detached). Nobody can answer, so stop.
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
if (fd !== undefined) {
|
|
111
|
+
try {
|
|
112
|
+
closeSync(fd);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Nothing here can change the answer we already have.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function interactiveDefault() {
|
|
121
|
+
return process.stdin.isTTY === true && process.stderr.isTTY === true;
|
|
122
|
+
}
|
|
123
|
+
/** Build the wizard for one ceremony run. Fully injectable for tests. */
|
|
124
|
+
export function createWizard(deps = {}) {
|
|
125
|
+
const interactive = deps.interactive ?? interactiveDefault();
|
|
126
|
+
const say = deps.say ?? ((line) => process.stderr.write(`${line}\n`));
|
|
127
|
+
const readLine = deps.readLine ?? readLineDefault;
|
|
128
|
+
/**
|
|
129
|
+
* One gate: block for the human, with the default signal disposition in
|
|
130
|
+
* force so Ctrl-C stops the ceremony. Throws {@link CeremonyStopped} on end
|
|
131
|
+
* of input, or on a `\x03`/`\x04` that reached us as data.
|
|
132
|
+
*/
|
|
133
|
+
const gate = () => {
|
|
134
|
+
const answer = withDefaultInterrupts(readLine);
|
|
135
|
+
if (answer === null)
|
|
136
|
+
throw new CeremonyStopped("stopped — no more input");
|
|
137
|
+
if (answer.includes("\u0003") || answer.includes("\u0004"))
|
|
138
|
+
throw new CeremonyStopped();
|
|
139
|
+
return answer.trim();
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
interactive,
|
|
143
|
+
explain(lines) {
|
|
144
|
+
if (!interactive)
|
|
145
|
+
return;
|
|
146
|
+
say("");
|
|
147
|
+
for (const line of lines)
|
|
148
|
+
say(line);
|
|
149
|
+
say(" press Enter when you are ready — Ctrl-C stops here, and rerunning resumes here");
|
|
150
|
+
gate();
|
|
151
|
+
},
|
|
152
|
+
ask(question, fallback) {
|
|
153
|
+
if (!interactive)
|
|
154
|
+
return fallback;
|
|
155
|
+
say("");
|
|
156
|
+
say(`${question} [${fallback ? "Y/n" : "y/N"}]`);
|
|
157
|
+
const answer = gate().toLowerCase();
|
|
158
|
+
if (answer === "")
|
|
159
|
+
return fallback;
|
|
160
|
+
return answer.startsWith("y");
|
|
161
|
+
},
|
|
162
|
+
choose(lines, keys) {
|
|
163
|
+
if (!interactive)
|
|
164
|
+
return "";
|
|
165
|
+
say("");
|
|
166
|
+
for (const line of lines)
|
|
167
|
+
say(line);
|
|
168
|
+
say(` press Enter, or type ${keys.join(" / ")} then Enter — Ctrl-C stops here, and rerunning resumes here`);
|
|
169
|
+
const answer = gate().toLowerCase();
|
|
170
|
+
return keys.find((key) => answer.startsWith(key)) ?? "";
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=wizard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../src/wizard.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
/** The proxy shortcuts the Shortcuts surface is driven through (lab/shortcuts/). */
|
|
2
2
|
export declare const EXPECTED_PROXIES: readonly ["things-proxy-find-items", "things-proxy-create-heading", "things-proxy-edit-title", "things-proxy-set-detail", "things-proxy-delete-items", "things-proxy-delete-items-permanently"];
|
|
3
|
-
export interface UrlSchemeState {
|
|
4
|
-
/** true = on, false = off, null = not determinable from disk. */
|
|
5
|
-
enabled: boolean | null;
|
|
6
|
-
detail: string;
|
|
7
|
-
}
|
|
8
3
|
export interface ShortcutsState {
|
|
9
4
|
present: string[];
|
|
10
5
|
missing: string[];
|
|
11
6
|
detail: string;
|
|
12
7
|
}
|
|
13
8
|
export interface AvailabilityDeps {
|
|
14
|
-
/** Test seam: plist path override. */
|
|
15
|
-
plistPath?: string;
|
|
16
|
-
/** Test seam: raw `plutil -extract` runner; return the raw value or throw. */
|
|
17
|
-
extract?: (plistBytes: Buffer) => string;
|
|
18
9
|
/** Test seam: `shortcuts list` runner; return stdout or throw. */
|
|
19
10
|
listShortcuts?: () => string;
|
|
20
11
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Read the "Enable Things URLs" state from disk. `1`/`0` when the toggle has
|
|
23
|
-
* ever been set; the key is absent on an untouched install (the app then
|
|
24
|
-
* holds the first URL write behind an enable dialog).
|
|
25
|
-
*/
|
|
26
|
-
export declare function readUrlSchemeEnabled(deps?: AvailabilityDeps): UrlSchemeState;
|
|
27
|
-
/** Which proxy shortcuts are installed (drives the Shortcuts surface + `setup shortcuts`). */
|
|
12
|
+
/** Which proxy shortcuts are installed (drives the Shortcuts surface + `setup`). */
|
|
28
13
|
export declare function readShortcutProxies(deps?: AvailabilityDeps): ShortcutsState;
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Static (no-consent-prompt) availability signals per write surface.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* Static (no-consent-prompt) availability signals per write surface.
|
|
3
|
+
*
|
|
4
|
+
* The URL scheme's own switch used to live here too. It now lives with the
|
|
5
|
+
* other prompt-free verdicts, as `urlSchemeCapability()` in ../capability.ts:
|
|
6
|
+
* it gates a dispatch exactly the way the app-control and GUI-driving standings
|
|
7
|
+
* do, and reading it has a consent story of its own (the plist is inside the
|
|
8
|
+
* Things group container) that belongs next to the rest of the doctrine.
|
|
9
9
|
*/
|
|
10
|
-
import { execFileSync } from "node:child_process";
|
|
11
|
-
import { homedir } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
import { readContainerFileSync } from "../deputy/files.js";
|
|
14
10
|
import { shortcutsListSync } from "../deputy/shortcuts-exec.js";
|
|
15
|
-
const PREFS_PLIST = join("Library/Group Containers/JLMPQHK86H.com.culturedcode.ThingsMac", "Library/Preferences/JLMPQHK86H.com.culturedcode.ThingsMac.plist");
|
|
16
11
|
/** The proxy shortcuts the Shortcuts surface is driven through (lab/shortcuts/). */
|
|
17
12
|
export const EXPECTED_PROXIES = [
|
|
18
13
|
"things-proxy-find-items",
|
|
@@ -22,61 +17,11 @@ export const EXPECTED_PROXIES = [
|
|
|
22
17
|
"things-proxy-delete-items",
|
|
23
18
|
"things-proxy-delete-items-permanently",
|
|
24
19
|
];
|
|
25
|
-
function defaultExtract(plistBytes) {
|
|
26
|
-
return execFileSync("plutil", ["-extract", "uriSchemeEnabled", "raw", "-o", "-", "--", "-"], {
|
|
27
|
-
input: plistBytes,
|
|
28
|
-
encoding: "utf8",
|
|
29
|
-
timeout: 5000,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
20
|
function defaultListShortcuts() {
|
|
33
21
|
// Deputy-routed when active (src/deputy/shortcuts-exec.ts); direct otherwise.
|
|
34
22
|
return shortcutsListSync(10000);
|
|
35
23
|
}
|
|
36
|
-
/**
|
|
37
|
-
* Read the "Enable Things URLs" state from disk. `1`/`0` when the toggle has
|
|
38
|
-
* ever been set; the key is absent on an untouched install (the app then
|
|
39
|
-
* holds the first URL write behind an enable dialog).
|
|
40
|
-
*/
|
|
41
|
-
export function readUrlSchemeEnabled(deps = {}) {
|
|
42
|
-
const path = deps.plistPath ?? join(homedir(), PREFS_PLIST);
|
|
43
|
-
let bytes;
|
|
44
|
-
try {
|
|
45
|
-
// Deputy-routed when active (the prefs plist lives in the TCC-protected
|
|
46
|
-
// group container); plutil below parses the bytes locally, consent-free.
|
|
47
|
-
bytes = readContainerFileSync(path);
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return {
|
|
51
|
-
enabled: null,
|
|
52
|
-
detail: "the app's preferences plist is not readable on this machine — the 'Enable Things " +
|
|
53
|
-
"URLs' state is unknown (check Things > Settings > General)",
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
let raw;
|
|
57
|
-
try {
|
|
58
|
-
raw = (deps.extract ?? defaultExtract)(bytes).trim();
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return {
|
|
62
|
-
enabled: null,
|
|
63
|
-
detail: "'Enable Things URLs' has never been toggled on this machine — the app holds the " +
|
|
64
|
-
"first URL command behind an enable dialog (Things > Settings > General)",
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
if (raw === "1" || raw === "true") {
|
|
68
|
-
return { enabled: true, detail: "'Enable Things URLs' is on" };
|
|
69
|
-
}
|
|
70
|
-
if (raw === "0" || raw === "false") {
|
|
71
|
-
return {
|
|
72
|
-
enabled: false,
|
|
73
|
-
detail: "'Enable Things URLs' is OFF (Things > Settings > General) — the app holds URL " +
|
|
74
|
-
"commands behind an enable dialog instead of executing them",
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
return { enabled: null, detail: `unexpected uriSchemeEnabled value: ${raw}` };
|
|
78
|
-
}
|
|
79
|
-
/** Which proxy shortcuts are installed (drives the Shortcuts surface + `setup shortcuts`). */
|
|
24
|
+
/** Which proxy shortcuts are installed (drives the Shortcuts surface + `setup`). */
|
|
80
25
|
export function readShortcutProxies(deps = {}) {
|
|
81
26
|
let listing;
|
|
82
27
|
try {
|
|
@@ -102,8 +47,8 @@ export function readShortcutProxies(deps = {}) {
|
|
|
102
47
|
? "all proxy shortcuts are installed"
|
|
103
48
|
: present.length === 0
|
|
104
49
|
? "no proxy shortcuts are installed — heading creation and dated-reminder clearing " +
|
|
105
|
-
"need them (run `things setup
|
|
106
|
-
: `missing: ${missing.join(", ")} (run \`things setup
|
|
50
|
+
"need them (run `things setup`)"
|
|
51
|
+
: `missing: ${missing.join(", ")} (run \`things setup\`)`,
|
|
107
52
|
};
|
|
108
53
|
}
|
|
109
54
|
//# sourceMappingURL=availability.js.map
|