things-api 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/deputy/AppIcon.icns +0 -0
- package/deputy/VERSION +1 -0
- package/deputy/helpers-Info.plist +27 -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 +22 -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/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 +128 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Info.plist +27 -0
- 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 +137 -0
- package/deputy/reader/Info.plist +22 -0
- package/deputy/reader/entitlements.plist +45 -0
- package/deputy/reader/main.swift +580 -0
- package/deputy/src/main.swift +133 -0
- package/deputy/src/osascript.swift +102 -0
- package/deputy/src/server.swift +373 -0
- package/deputy/src/sqlite.swift +121 -0
- package/deputy/src/tcc.swift +58 -0
- package/dist/audit/schema.d.ts +13 -1
- package/dist/audit/schema.js.map +1 -1
- package/dist/capability.d.ts +160 -0
- package/dist/capability.js +416 -0
- package/dist/capability.js.map +1 -0
- package/dist/cli/commands/doctor.js +181 -2
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/helpers.d.ts +17 -0
- package/dist/cli/commands/helpers.js +275 -0
- package/dist/cli/commands/helpers.js.map +1 -0
- package/dist/cli/commands/op-result.d.ts +15 -0
- package/dist/cli/commands/op-result.js +45 -0
- package/dist/cli/commands/op-result.js.map +1 -0
- package/dist/cli/commands/reads.js +47 -1
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +9 -0
- package/dist/cli/commands/setup.js +63 -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 +262 -109
- 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 +53 -27
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/helpers-check.d.ts +10 -0
- package/dist/cli/helpers-check.js +59 -0
- package/dist/cli/helpers-check.js.map +1 -0
- package/dist/cli/main.js +22 -1
- 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 +22 -2
- package/dist/client.js +52 -15
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +31 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +6 -2
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/db/baselines/db-v26.js +8 -4
- package/dist/db/baselines/db-v26.js.map +1 -1
- package/dist/db/baselines/db-v27.d.ts +42 -0
- package/dist/db/baselines/db-v27.js +47 -0
- package/dist/db/baselines/db-v27.js.map +1 -0
- package/dist/db/baselines/index.js +2 -1
- package/dist/db/baselines/index.js.map +1 -1
- package/dist/db/locate.d.ts +4 -1
- package/dist/db/locate.js +3 -1
- package/dist/db/locate.js.map +1 -1
- package/dist/db/schema.d.ts +1 -1
- package/dist/db/schema.js +9 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/deputy/bridge-worker.d.ts +1 -0
- package/dist/deputy/bridge-worker.js +79 -0
- package/dist/deputy/bridge-worker.js.map +1 -0
- package/dist/deputy/bridge.d.ts +14 -0
- package/dist/deputy/bridge.js +70 -0
- package/dist/deputy/bridge.js.map +1 -0
- package/dist/deputy/client.d.ts +19 -0
- package/dist/deputy/client.js +104 -0
- package/dist/deputy/client.js.map +1 -0
- package/dist/deputy/db-facade.d.ts +18 -0
- package/dist/deputy/db-facade.js +71 -0
- package/dist/deputy/db-facade.js.map +1 -0
- package/dist/deputy/files.d.ts +1 -0
- package/dist/deputy/files.js +16 -0
- package/dist/deputy/files.js.map +1 -0
- package/dist/deputy/install.d.ts +319 -0
- package/dist/deputy/install.js +1239 -0
- package/dist/deputy/install.js.map +1 -0
- package/dist/deputy/notice.d.ts +6 -0
- package/dist/deputy/notice.js +28 -0
- package/dist/deputy/notice.js.map +1 -0
- package/dist/deputy/notices.d.ts +31 -0
- package/dist/deputy/notices.js +94 -0
- package/dist/deputy/notices.js.map +1 -0
- package/dist/deputy/osa.d.ts +21 -0
- package/dist/deputy/osa.js +72 -0
- package/dist/deputy/osa.js.map +1 -0
- package/dist/deputy/protocol.d.ts +140 -0
- package/dist/deputy/protocol.js +143 -0
- package/dist/deputy/protocol.js.map +1 -0
- package/dist/deputy/routing.d.ts +84 -0
- package/dist/deputy/routing.js +422 -0
- package/dist/deputy/routing.js.map +1 -0
- package/dist/deputy/shortcuts-exec.d.ts +10 -0
- package/dist/deputy/shortcuts-exec.js +50 -0
- package/dist/deputy/shortcuts-exec.js.map +1 -0
- package/dist/diagnose.d.ts +57 -2
- package/dist/diagnose.js +105 -3
- package/dist/diagnose.js.map +1 -1
- package/dist/direct-setup.d.ts +78 -0
- package/dist/direct-setup.js +484 -0
- package/dist/direct-setup.js.map +1 -0
- package/dist/host-access.d.ts +63 -0
- package/dist/host-access.js +165 -0
- package/dist/host-access.js.map +1 -0
- package/dist/index.d.ts +31 -5
- package/dist/index.js +23 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +27 -1
- package/dist/mcp/server.js +300 -97
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/mappers.d.ts +12 -3
- package/dist/model/mappers.js +6 -1
- package/dist/model/mappers.js.map +1 -1
- package/dist/model/recurrence.d.ts +5 -1
- package/dist/model/recurrence.js +19 -1
- package/dist/model/recurrence.js.map +1 -1
- package/dist/model/template-projection.d.ts +26 -0
- package/dist/model/template-projection.js +113 -0
- package/dist/model/template-projection.js.map +1 -0
- package/dist/op-result.d.ts +55 -0
- package/dist/op-result.js +161 -0
- package/dist/op-result.js.map +1 -0
- package/dist/read/detail.js +7 -1
- package/dist/read/detail.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/queries.js +8 -1
- package/dist/read/queries.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 +35 -4
- package/dist/read/views.js +120 -18
- package/dist/read/views.js.map +1 -1
- package/dist/session-grant.d.ts +48 -0
- package/dist/session-grant.js +170 -0
- package/dist/session-grant.js.map +1 -0
- package/dist/surface-copy.d.ts +8 -0
- package/dist/surface-copy.js +8 -0
- package/dist/surface-copy.js.map +1 -1
- package/dist/sync-health.js +6 -2
- package/dist/sync-health.js.map +1 -1
- package/dist/wizard.d.ts +58 -0
- package/dist/wizard.js +167 -0
- package/dist/wizard.js.map +1 -0
- package/dist/write/accessibility-probe.js +4 -2
- package/dist/write/accessibility-probe.js.map +1 -1
- package/dist/write/automation-probe.js +5 -1
- package/dist/write/automation-probe.js.map +1 -1
- package/dist/write/availability.d.ts +1 -1
- package/dist/write/availability.js +10 -6
- package/dist/write/availability.js.map +1 -1
- package/dist/write/batch.d.ts +20 -0
- package/dist/write/batch.js +94 -15
- 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 +12 -1
- package/dist/write/commands.js +170 -237
- package/dist/write/commands.js.map +1 -1
- package/dist/write/experimental.d.ts +19 -0
- package/dist/write/experimental.js +50 -0
- package/dist/write/experimental.js.map +1 -1
- package/dist/write/failure-hints.d.ts +3 -1
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/guards.js +19 -2
- 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 +14 -6
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +64 -25
- package/dist/write/operations.js +7 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +14 -0
- package/dist/write/opid.js +22 -0
- package/dist/write/opid.js.map +1 -1
- package/dist/write/param-schema.d.ts +124 -0
- package/dist/write/param-schema.js +747 -0
- package/dist/write/param-schema.js.map +1 -0
- package/dist/write/pipeline.d.ts +78 -2
- package/dist/write/pipeline.js +249 -4
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +8 -0
- package/dist/write/pre-state.js +62 -17
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/promote-clone.js +635 -367
- package/dist/write/promote-clone.js.map +1 -1
- package/dist/write/reorder.js +95 -29
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/repeat-anchor.d.ts +30 -0
- package/dist/write/repeat-anchor.js +166 -1
- package/dist/write/repeat-anchor.js.map +1 -1
- package/dist/write/repeat-rule.d.ts +28 -1
- package/dist/write/repeat-rule.js +64 -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 +83 -38
- 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 +451 -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 +10 -16
- 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/session-reachability.d.ts +55 -0
- package/dist/write/vectors/session-reachability.js +56 -8
- package/dist/write/vectors/session-reachability.js.map +1 -1
- package/dist/write/vectors/shortcuts.js +7 -16
- package/dist/write/vectors/shortcuts.js.map +1 -1
- package/dist/write/vectors/simulator.d.ts +16 -1
- package/dist/write/vectors/simulator.js +68 -12
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +124 -1
- package/dist/write/vectors/ui-recipes.d.ts +19 -0
- package/dist/write/vectors/ui-recipes.js +158 -38
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +89 -0
- package/dist/write/vectors/ui.js +504 -49
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/verify/delta.d.ts +21 -0
- package/dist/write/verify/delta.js.map +1 -1
- package/dist/write/verify/poller.d.ts +13 -0
- package/dist/write/verify/poller.js +38 -0
- package/dist/write/verify/poller.js.map +1 -1
- package/package.json +9 -1
- package/schema/envelope.schema.json +5 -3
- package/scripts/build-helpers.sh +109 -0
- package/scripts/deputy-cert-setup.sh +60 -0
- package/skills/things-cli/SKILL.md +12 -5
- 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 +5 -1
- package/skills/things-cli/references/gui.md +1 -0
- package/skills/things-cli/references/repeating.md +75 -0
- package/skills/things-cli/references/tag-cleanup.md +64 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only SQLite executor. Mirrors the library's WAL policy
|
|
3
|
+
* (docs/design/architecture.md §0): SQLITE_OPEN_READONLY, never immutable, a
|
|
4
|
+
* fresh WAL read snapshot per statement outside any transaction — the deputy
|
|
5
|
+
* never opens a transaction, so the CLI's verification poller sees fresh state
|
|
6
|
+
* on every poll exactly as it does with a local connection. An authorizer
|
|
7
|
+
* denies ATTACH so the read-only handle cannot be aimed at other files.
|
|
8
|
+
*/
|
|
9
|
+
import Foundation
|
|
10
|
+
import SQLite3
|
|
11
|
+
|
|
12
|
+
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
|
13
|
+
|
|
14
|
+
struct SqliteError: Error {
|
|
15
|
+
let message: String
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
final class SqliteReader {
|
|
19
|
+
private var handle: OpaquePointer?
|
|
20
|
+
|
|
21
|
+
init(path: String) throws {
|
|
22
|
+
var h: OpaquePointer?
|
|
23
|
+
let rc = sqlite3_open_v2(path, &h, SQLITE_OPEN_READONLY, nil)
|
|
24
|
+
guard rc == SQLITE_OK, let opened = h else {
|
|
25
|
+
let message = h.map { String(cString: sqlite3_errmsg($0)) } ?? "sqlite rc \(rc)"
|
|
26
|
+
sqlite3_close(h)
|
|
27
|
+
throw SqliteError(message: message)
|
|
28
|
+
}
|
|
29
|
+
handle = opened
|
|
30
|
+
sqlite3_busy_timeout(opened, 2000)
|
|
31
|
+
sqlite3_set_authorizer(
|
|
32
|
+
opened,
|
|
33
|
+
{ _, action, _, _, _, _ in
|
|
34
|
+
action == SQLITE_ATTACH ? SQLITE_DENY : SQLITE_OK
|
|
35
|
+
}, nil)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
deinit {
|
|
39
|
+
if let h = handle { sqlite3_close_v2(h) }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func query(sql: String, params: [Any]) throws -> [[String: Any]] {
|
|
43
|
+
guard let h = handle else { throw SqliteError(message: "connection closed") }
|
|
44
|
+
var stmt: OpaquePointer?
|
|
45
|
+
// The tail pointer aims into the C string handed to prepare, so it MUST be
|
|
46
|
+
// read inside withCString — Swift's implicit bridging buffer is deallocated
|
|
47
|
+
// the moment the call returns (a read-after-free otherwise).
|
|
48
|
+
var trailingStatement = false
|
|
49
|
+
let rc = sql.withCString { (cSql: UnsafePointer<CChar>) -> Int32 in
|
|
50
|
+
var tail: UnsafePointer<CChar>? = nil
|
|
51
|
+
let result = sqlite3_prepare_v2(h, cSql, -1, &stmt, &tail)
|
|
52
|
+
if let t = tail {
|
|
53
|
+
trailingStatement = !String(cString: t)
|
|
54
|
+
.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
55
|
+
}
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
58
|
+
guard rc == SQLITE_OK, let prepared = stmt else {
|
|
59
|
+
throw SqliteError(message: String(cString: sqlite3_errmsg(h)))
|
|
60
|
+
}
|
|
61
|
+
defer { sqlite3_finalize(prepared) }
|
|
62
|
+
if trailingStatement {
|
|
63
|
+
throw SqliteError(message: "only a single SQL statement is accepted per request")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (index, param) in params.enumerated() {
|
|
67
|
+
let slot = Int32(index + 1)
|
|
68
|
+
let rc: Int32
|
|
69
|
+
switch param {
|
|
70
|
+
case is NSNull:
|
|
71
|
+
rc = sqlite3_bind_null(prepared, slot)
|
|
72
|
+
case let text as String:
|
|
73
|
+
rc = sqlite3_bind_text(prepared, slot, text, -1, SQLITE_TRANSIENT)
|
|
74
|
+
case let number as NSNumber:
|
|
75
|
+
if CFNumberIsFloatType(number) {
|
|
76
|
+
rc = sqlite3_bind_double(prepared, slot, number.doubleValue)
|
|
77
|
+
} else {
|
|
78
|
+
rc = sqlite3_bind_int64(prepared, slot, number.int64Value)
|
|
79
|
+
}
|
|
80
|
+
default:
|
|
81
|
+
throw SqliteError(message: "unsupported parameter type at index \(index)")
|
|
82
|
+
}
|
|
83
|
+
guard rc == SQLITE_OK else {
|
|
84
|
+
throw SqliteError(message: String(cString: sqlite3_errmsg(h)))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var rows: [[String: Any]] = []
|
|
89
|
+
let columnCount = sqlite3_column_count(prepared)
|
|
90
|
+
while true {
|
|
91
|
+
let rc = sqlite3_step(prepared)
|
|
92
|
+
if rc == SQLITE_DONE { break }
|
|
93
|
+
guard rc == SQLITE_ROW else {
|
|
94
|
+
throw SqliteError(message: String(cString: sqlite3_errmsg(h)))
|
|
95
|
+
}
|
|
96
|
+
var row: [String: Any] = [:]
|
|
97
|
+
for col in 0..<columnCount {
|
|
98
|
+
let name = String(cString: sqlite3_column_name(prepared, col))
|
|
99
|
+
switch sqlite3_column_type(prepared, col) {
|
|
100
|
+
case SQLITE_INTEGER:
|
|
101
|
+
row[name] = sqlite3_column_int64(prepared, col)
|
|
102
|
+
case SQLITE_FLOAT:
|
|
103
|
+
row[name] = sqlite3_column_double(prepared, col)
|
|
104
|
+
case SQLITE_TEXT:
|
|
105
|
+
row[name] = String(cString: sqlite3_column_text(prepared, col))
|
|
106
|
+
case SQLITE_BLOB:
|
|
107
|
+
let bytes = sqlite3_column_bytes(prepared, col)
|
|
108
|
+
let data =
|
|
109
|
+
bytes > 0
|
|
110
|
+
? Data(bytes: sqlite3_column_blob(prepared, col), count: Int(bytes))
|
|
111
|
+
: Data()
|
|
112
|
+
row[name] = ["$b64": data.base64EncodedString()]
|
|
113
|
+
default:
|
|
114
|
+
row[name] = NSNull()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
rows.append(row)
|
|
118
|
+
}
|
|
119
|
+
return rows
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt-free TCC introspection, plus the one call that deliberately DOES
|
|
3
|
+
* prompt (Accessibility).
|
|
4
|
+
*
|
|
5
|
+
* The deputy is the process macOS attributes every AppleEvent and every
|
|
6
|
+
* Accessibility action to, so it — and only it — can answer "am I allowed to
|
|
7
|
+
* drive Things?" truthfully. Both answers ride the `hello` response, which
|
|
8
|
+
* makes `things helpers status` and the onboarding ceremony able to SKIP a leg
|
|
9
|
+
* that is already granted instead of re-firing a dialog at the user.
|
|
10
|
+
*
|
|
11
|
+
* `AEDeterminePermissionToAutomateTarget(…, askUserIfNeeded: false)` is the
|
|
12
|
+
* documented non-prompting probe: it answers from the existing TCC record and
|
|
13
|
+
* returns errAEEventWouldRequireUserConsent (-1744) when no record exists yet
|
|
14
|
+
* — reported here as "unknown", which is exactly the state the ceremony then
|
|
15
|
+
* resolves by sending a real (prompting) AppleEvent.
|
|
16
|
+
*/
|
|
17
|
+
import ApplicationServices
|
|
18
|
+
import Foundation
|
|
19
|
+
|
|
20
|
+
/// One target's Automation state: "granted" | "denied" | "not-running" | "unknown".
|
|
21
|
+
func automationStatus(bundleID: String) -> String {
|
|
22
|
+
var target = AEAddressDesc()
|
|
23
|
+
let bytes = Array(bundleID.utf8)
|
|
24
|
+
let created = bytes.withUnsafeBufferPointer { buf in
|
|
25
|
+
AECreateDesc(typeApplicationBundleID, buf.baseAddress, buf.count, &target)
|
|
26
|
+
}
|
|
27
|
+
guard created == noErr else { return "unknown" }
|
|
28
|
+
defer { AEDisposeDesc(&target) }
|
|
29
|
+
// askUserIfNeeded: false — a status probe must never raise a dialog.
|
|
30
|
+
let status = AEDeterminePermissionToAutomateTarget(&target, typeWildCard, typeWildCard, false)
|
|
31
|
+
switch status {
|
|
32
|
+
case noErr:
|
|
33
|
+
return "granted"
|
|
34
|
+
case -1743: // errAEEventNotPermitted — the user (or MDM) said no.
|
|
35
|
+
return "denied"
|
|
36
|
+
case -600: // procNotFound — the target is not running, so TCC has no answer.
|
|
37
|
+
return "not-running"
|
|
38
|
+
default:
|
|
39
|
+
// -1744 errAEEventWouldRequireUserConsent lands here: never asked yet.
|
|
40
|
+
return "unknown"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Accessibility trust for THIS process, prompt-free.
|
|
45
|
+
func accessibilityTrusted() -> Bool {
|
|
46
|
+
AXIsProcessTrusted()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Ask macOS to show the Accessibility prompt for this process. The dialog is
|
|
51
|
+
* fire-and-forget (it offers a Settings deep-link; the actual grant is a
|
|
52
|
+
* toggle the user flips in System Settings), so this returns the CURRENT trust
|
|
53
|
+
* state immediately rather than waiting for one that can only arrive later.
|
|
54
|
+
*/
|
|
55
|
+
func primeAccessibility() -> Bool {
|
|
56
|
+
let key = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String
|
|
57
|
+
return AXIsProcessTrustedWithOptions([key: true] as CFDictionary)
|
|
58
|
+
}
|
package/dist/audit/schema.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OccurrenceResolution } from "../write/verify/delta.ts";
|
|
1
2
|
export interface AuditRecord {
|
|
2
3
|
v: 1;
|
|
3
4
|
ts: string;
|
|
@@ -41,13 +42,24 @@ export interface AuditRecord {
|
|
|
41
42
|
* bumped) without a separate read. Absent on writes made without the flag.
|
|
42
43
|
*/
|
|
43
44
|
preModDates?: Record<string, number | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Template-target composite disclosure (ADDITIVE): which occurrence a
|
|
47
|
+
* `complete`/`cancel`/`update --exception` aimed at a repeating series
|
|
48
|
+
* actually wrote, and whether the composite minted it. Recorded on the
|
|
49
|
+
* composite's SUMMARY record only — the one record its `opId` keys — so an
|
|
50
|
+
* idempotency replay can hand the caller the same two uuids the original
|
|
51
|
+
* call returned without re-reading the database.
|
|
52
|
+
*/
|
|
53
|
+
occurrence?: OccurrenceResolution;
|
|
44
54
|
/** Normalized requested delta (params as given, post-normalization). */
|
|
45
55
|
requested: Record<string, unknown>;
|
|
46
56
|
/** Asserted-field subset of the pre-state (null when target didn't exist). */
|
|
47
57
|
pre: Record<string, unknown> | null;
|
|
48
58
|
/** Post-verify observation (best-effort on failure). */
|
|
49
59
|
observed: Record<string, unknown> | null;
|
|
50
|
-
result: "intent" | "ok" | "verify-failed:timeout" | "verify-failed:mismatch" | "verify-failed:silent-noop"
|
|
60
|
+
result: "intent" | "ok" | "verify-failed:timeout" | "verify-failed:mismatch" | "verify-failed:silent-noop"
|
|
61
|
+
/** A GUI drive stopped because the Things window was unreachable/unresponsive (#512). */
|
|
62
|
+
| "verify-failed:ui-unreachable" | `blocked:${string}` | "unsupported";
|
|
51
63
|
verify: {
|
|
52
64
|
attempts: number;
|
|
53
65
|
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;AA8EzC;;;;;;;;;;;;;;;;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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { type HostAccessDeps, type HostApp } from "./host-access.ts";
|
|
2
|
+
export { fdaGranted, type FdaVerdict, type HostApp, hostApp, hostDisplayName, tccDbPath, } from "./host-access.ts";
|
|
3
|
+
/** The Things application's bundle identifier — the Automation grant's target. */
|
|
4
|
+
export declare const THINGS_BUNDLE_ID = "com.culturedcode.ThingsMac";
|
|
5
|
+
/** How reads may reach the live library, if at all. */
|
|
6
|
+
export type ReadCapabilityMode =
|
|
7
|
+
/** A caller-supplied database path — outside the doctrine entirely (Article VI). */
|
|
8
|
+
"explicit-db"
|
|
9
|
+
/** Reads ride the sandboxed reader; the read itself is the capability check. */
|
|
10
|
+
| "helpers"
|
|
11
|
+
/** The helpers are expected here but cannot serve — refuse, never fall back. */
|
|
12
|
+
| "helpers-unavailable"
|
|
13
|
+
/** No helpers, but the host app holds Full Disk Access. */
|
|
14
|
+
| "direct-fda"
|
|
15
|
+
/** A ceremony witnessed an app-data grant that is still live for this app instance. */
|
|
16
|
+
| "session-grant"
|
|
17
|
+
/** Nothing can open the container — the caller must run a setup ceremony. */
|
|
18
|
+
| "none";
|
|
19
|
+
/** How GUI-driving may be delivered, if at all (Article IV). */
|
|
20
|
+
export type UiCapabilityMode =
|
|
21
|
+
/** The helper pair holds Accessibility + Automation→System Events. */
|
|
22
|
+
"helpers"
|
|
23
|
+
/** The lab's documented in-guest escape (see {@link UI_DIRECT_ESCAPE_ENV}). */
|
|
24
|
+
| "direct-escape"
|
|
25
|
+
/** GUI-driving is switched off in config — nothing has been asked for yet. */
|
|
26
|
+
| "config-disabled"
|
|
27
|
+
/** No deputy answers, so there is no identity that could hold the grants. */
|
|
28
|
+
| "helpers-missing"
|
|
29
|
+
/** The deputy answers but the `--gui` tier is incomplete. */
|
|
30
|
+
| "tier-incomplete";
|
|
31
|
+
/** How app automation may be delivered, if at all. */
|
|
32
|
+
export type WriteCapabilityMode =
|
|
33
|
+
/** The deputy is onboarded; Apple Events are sent under the helper identity. */
|
|
34
|
+
"deputy"
|
|
35
|
+
/** macOS records an Automation grant for the host app against Things. */
|
|
36
|
+
| "direct-granted"
|
|
37
|
+
/** macOS records a REFUSAL. It will not re-ask; the human must re-arm it. */
|
|
38
|
+
| "direct-denied"
|
|
39
|
+
/** No record either way, or none readable — never resolved by prompting. */
|
|
40
|
+
| "direct-unknown";
|
|
41
|
+
export interface Capability<Mode> {
|
|
42
|
+
mode: Mode;
|
|
43
|
+
/** One sentence of provenance, for `doctor` and for refusal copy. */
|
|
44
|
+
detail: string;
|
|
45
|
+
/**
|
|
46
|
+
* The remediation lines a refusal prints, in the order they should be
|
|
47
|
+
* offered. Empty when the capability is present.
|
|
48
|
+
*/
|
|
49
|
+
remediation: string[];
|
|
50
|
+
/** The identity a direct-mode grant would attach to. */
|
|
51
|
+
host: HostApp;
|
|
52
|
+
}
|
|
53
|
+
export type ReadCapability = Capability<ReadCapabilityMode>;
|
|
54
|
+
export type WriteCapability = Capability<WriteCapabilityMode>;
|
|
55
|
+
export type UiCapability = Capability<UiCapabilityMode>;
|
|
56
|
+
/** True when this verdict permits opening the live container. */
|
|
57
|
+
export declare function readAllowed(capability: ReadCapability): boolean;
|
|
58
|
+
/** True when this verdict permits driving the Things window. */
|
|
59
|
+
export declare function uiAllowed(capability: UiCapability): boolean;
|
|
60
|
+
/** True when app automation may be dispatched. */
|
|
61
|
+
export declare function writeAllowed(capability: WriteCapability): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Injection seams. Every one of these defaults to a real, prompt-free probe;
|
|
64
|
+
* tests replace them so no test ever touches the host's TCC state.
|
|
65
|
+
*/
|
|
66
|
+
export interface CapabilityDeps extends HostAccessDeps {
|
|
67
|
+
/** Are this process's database reads actually being served by the reader? */
|
|
68
|
+
helpersServing?: () => boolean;
|
|
69
|
+
/** Are the helpers expected on this machine (enabled, and installed under auto)? */
|
|
70
|
+
helpersExpected?: () => boolean;
|
|
71
|
+
/** Why the helpers are not serving, for the loud refusal. */
|
|
72
|
+
helpersReason?: () => string | null;
|
|
73
|
+
/** The deputy's `automation.things` standing, from its handshake. */
|
|
74
|
+
deputyAutomation?: () => string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* The deputy's GUI-driving standing, from the same handshake: whether it is
|
|
77
|
+
* Accessibility-trusted and where its Automation→System Events grant stands.
|
|
78
|
+
* `null` means no deputy answered at all.
|
|
79
|
+
*/
|
|
80
|
+
deputyGuiStanding?: () => {
|
|
81
|
+
axTrusted: boolean | undefined;
|
|
82
|
+
systemEvents: string | undefined;
|
|
83
|
+
} | null;
|
|
84
|
+
/** Is GUI-driving switched on in config (`ui-enabled`)? */
|
|
85
|
+
uiEnabled?: () => boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Read one Automation row out of TCC.db. Returns the raw `auth_value`, or
|
|
88
|
+
* null when there is no row / the file cannot be read.
|
|
89
|
+
*/
|
|
90
|
+
automationAuthValue?: (client: string, target: string) => number | null;
|
|
91
|
+
/** Resolve a running app's display name from its bundle id (LaunchServices). */
|
|
92
|
+
lookupAppName?: (bundleId: string) => string | null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Can this process read the live Things library, and on whose authority?
|
|
96
|
+
*
|
|
97
|
+
* Stateless: every call re-derives the verdict, because a grant can appear or
|
|
98
|
+
* vanish between one invocation and the next and a cached "yes" would be a
|
|
99
|
+
* stored onboarding flag by another name. The common case costs one `open(2)`.
|
|
100
|
+
*/
|
|
101
|
+
export declare function readCapability(options?: {
|
|
102
|
+
dbPath?: string;
|
|
103
|
+
}, deps?: CapabilityDeps): ReadCapability;
|
|
104
|
+
/** Thrown when a read is refused for want of capability (Article II). */
|
|
105
|
+
export declare class ReadCapabilityError extends Error {
|
|
106
|
+
readonly remediation: string[];
|
|
107
|
+
readonly capability: ReadCapability;
|
|
108
|
+
constructor(capability: ReadCapability);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* May this process drive Things over Apple Events, and on whose authority?
|
|
112
|
+
*
|
|
113
|
+
* The deputy wins when it is onboarded (its own handshake reports the grant it
|
|
114
|
+
* holds). Otherwise the host app's own Automation record is read out of TCC —
|
|
115
|
+
* granted, denied, or, when there is no record at all, `direct-unknown`. That
|
|
116
|
+
* last state is deliberately NOT resolved here: resolving it means sending a
|
|
117
|
+
* real Apple Event, which is what raises the dialog, and Article I reserves
|
|
118
|
+
* that for `things setup`.
|
|
119
|
+
*/
|
|
120
|
+
export declare function writeCapability(deps?: CapabilityDeps): WriteCapability;
|
|
121
|
+
/** Thrown when app automation is refused for want of capability (Article II). */
|
|
122
|
+
export declare class WriteCapabilityError extends Error {
|
|
123
|
+
readonly remediation: string[];
|
|
124
|
+
readonly capability: WriteCapability;
|
|
125
|
+
constructor(capability: WriteCapability);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The LAB's escape hatch, and deliberately not consumer surface. The VM lab and
|
|
129
|
+
* the guest e2e bundle drive the UI vector DIRECT — the in-guest Accessibility
|
|
130
|
+
* grant is held by the runner's own processes (the AXVM1 layer), there is no
|
|
131
|
+
* helper bundle in a disposable clone, and there is nobody to answer a dialog
|
|
132
|
+
* either. Setting this to `1` restores direct UI-vector availability for that
|
|
133
|
+
* one situation. It is documented in docs/lab/harness.md and exported by the
|
|
134
|
+
* lab's guest environment; nothing consumer-facing mentions it, and it does not
|
|
135
|
+
* bypass `ui.enabled` — a lab clone still sets that key explicitly.
|
|
136
|
+
*/
|
|
137
|
+
export declare const UI_DIRECT_ESCAPE_ENV = "THINGS_API_UI_DIRECT";
|
|
138
|
+
/**
|
|
139
|
+
* May this process drive the Things WINDOW, and on whose authority?
|
|
140
|
+
*
|
|
141
|
+
* Article IV admits exactly one provenance: the helper pair. Accessibility on a
|
|
142
|
+
* general-purpose host app (a terminal, an agent harness, an MCP host) has a
|
|
143
|
+
* blast radius far beyond Things, churns with every host update, and has no
|
|
144
|
+
* sane story at all over ssh — so direct AX is unsupported, and a refusal here
|
|
145
|
+
* names the config knob and `things helpers setup --gui` rather than raising an
|
|
146
|
+
* Accessibility prompt against whatever happens to be running us.
|
|
147
|
+
*
|
|
148
|
+
* Every answer is prompt-free: the config key is a file read, and the deputy's
|
|
149
|
+
* `hello` carries `AXIsProcessTrusted()` plus its own `AEDeterminePermission`
|
|
150
|
+
* verdict for System Events.
|
|
151
|
+
*/
|
|
152
|
+
export declare function uiCapability(deps?: CapabilityDeps): UiCapability;
|
|
153
|
+
/** Thrown when GUI-driving is refused for want of capability (Article IV). */
|
|
154
|
+
export declare class UiCapabilityError extends Error {
|
|
155
|
+
readonly remediation: string[];
|
|
156
|
+
readonly capability: UiCapability;
|
|
157
|
+
constructor(capability: UiCapability);
|
|
158
|
+
}
|
|
159
|
+
/** Test seam: forget the one memo this module keeps (the host's display name). */
|
|
160
|
+
export declare function resetCapabilityForTests(): void;
|