@hasna/recordings 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +349 -69
  2. package/bun.lock +3 -0
  3. package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
  4. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
  5. package/dist/cli/index.js +1728 -320
  6. package/dist/cli/macos-permissions.d.ts +13 -0
  7. package/dist/cli/macos-permissions.d.ts.map +1 -0
  8. package/dist/cli/options.d.ts +12 -0
  9. package/dist/cli/options.d.ts.map +1 -1
  10. package/dist/db/pg-migrations.d.ts.map +1 -1
  11. package/dist/db/recordings.d.ts +2 -1
  12. package/dist/db/recordings.d.ts.map +1 -1
  13. package/dist/db/remote-storage.d.ts +5 -1
  14. package/dist/db/remote-storage.d.ts.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +628 -147
  18. package/dist/lib/bun-runtime.d.ts +11 -0
  19. package/dist/lib/bun-runtime.d.ts.map +1 -0
  20. package/dist/lib/config.d.ts +8 -1
  21. package/dist/lib/config.d.ts.map +1 -1
  22. package/dist/lib/enhancer.d.ts +10 -2
  23. package/dist/lib/enhancer.d.ts.map +1 -1
  24. package/dist/lib/install-maintenance.d.ts +24 -0
  25. package/dist/lib/install-maintenance.d.ts.map +1 -0
  26. package/dist/lib/machine.d.ts +2 -0
  27. package/dist/lib/machine.d.ts.map +1 -0
  28. package/dist/lib/recorder.d.ts +2 -1
  29. package/dist/lib/recorder.d.ts.map +1 -1
  30. package/dist/lib/recording-create-identity.d.ts +13 -0
  31. package/dist/lib/recording-create-identity.d.ts.map +1 -0
  32. package/dist/lib/release-install-policy.d.ts +31 -0
  33. package/dist/lib/release-install-policy.d.ts.map +1 -0
  34. package/dist/lib/transcriber.d.ts.map +1 -1
  35. package/dist/mcp/index.d.ts.map +1 -1
  36. package/dist/mcp/index.js +977 -194
  37. package/dist/sdk/index.js +5 -1
  38. package/dist/sdk/v1.generated.d.ts +4 -0
  39. package/dist/sdk/v1.generated.d.ts.map +1 -1
  40. package/dist/server/cloud-config.d.ts +9 -0
  41. package/dist/server/cloud-config.d.ts.map +1 -0
  42. package/dist/server/cloud-readiness.d.ts +10 -0
  43. package/dist/server/cloud-readiness.d.ts.map +1 -0
  44. package/dist/server/cloud.d.ts +9 -18
  45. package/dist/server/cloud.d.ts.map +1 -1
  46. package/dist/server/index.js +1654 -304
  47. package/dist/server/migrate-command.d.ts +11 -0
  48. package/dist/server/migrate-command.d.ts.map +1 -0
  49. package/dist/server/openapi.d.ts +22 -0
  50. package/dist/server/openapi.d.ts.map +1 -1
  51. package/dist/server/repo.d.ts +8 -1
  52. package/dist/server/repo.d.ts.map +1 -1
  53. package/dist/server/serve.d.ts +6 -1
  54. package/dist/server/serve.d.ts.map +1 -1
  55. package/dist/server/v1.d.ts.map +1 -1
  56. package/dist/storage.js +515 -80
  57. package/dist/store.d.ts +3 -1
  58. package/dist/store.d.ts.map +1 -1
  59. package/dist/types/index.d.ts +10 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/dist/version.d.ts +1 -1
  62. package/package.json +7 -4
  63. package/packaging/macos/Empty.entitlements +5 -0
  64. package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
  65. package/packaging/macos/Verifier.entitlements +10 -0
  66. package/packaging/macos/artifact-verifier.sb +33 -0
  67. package/packaging/macos/build_release_pkg.sh +872 -0
  68. package/packaging/macos/managed_bootstrap.sh +623 -0
  69. package/packaging/macos/pkgutil_fingerprint.awk +37 -0
  70. package/packaging/macos/release_lifecycle.ts +463 -0
  71. package/packaging/macos/scripts/postinstall +565 -0
  72. package/packaging/macos/scripts/preinstall +297 -0
  73. package/scripts/build_companion_cli.sh +337 -0
  74. package/scripts/build_native_fs_guard.sh +59 -0
  75. package/scripts/generate-sdk.ts +19 -1
  76. package/scripts/install_macos_app.sh +1919 -98
  77. package/scripts/macos_artifact.ts +5316 -0
  78. package/scripts/migrate.ts +38 -10
  79. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
  80. package/scripts/native/recordings_fs_guard.c +1167 -0
  81. package/scripts/native_fs_guard.ts +158 -0
  82. package/scripts/release-guard.ts +20 -1
  83. package/scripts/resolve_tailscale_cli.sh +389 -0
  84. package/scripts/smoke_macos_app.sh +573 -0
  85. package/src/native/Recordings/App/ContentView.swift +83 -0
  86. package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
  87. package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
  88. package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
  89. package/src/native/Recordings/App/RecordingsApp.swift +367 -44
  90. package/src/native/Recordings/App/RecordingsListView.swift +130 -0
  91. package/src/native/Recordings/App/RecordingsStore.swift +206 -0
  92. package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
  93. package/src/native/Recordings/App/SidebarView.swift +212 -0
  94. package/src/native/Recordings/App/Theme.swift +87 -0
  95. package/src/native/Recordings/Package.resolved +5 -5
  96. package/src/native/Recordings/Package.swift +62 -2
  97. package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
  98. package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
  99. package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
  100. package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
  101. package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
  102. package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
  103. package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
  104. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
  105. package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
  106. package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
  107. package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
  108. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
  109. package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
  110. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
  111. package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
  112. package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
  113. package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
  114. package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
  115. package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
  116. package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
  117. package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
  118. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
  119. package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
  120. package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
  121. package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
  122. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
  123. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
  124. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
  125. package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
  126. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
  127. package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
  128. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
  129. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
  130. package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
  131. package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
  132. package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
  133. package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
  134. package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
  135. package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
  136. package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
  137. package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
  138. package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
  139. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
  140. package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
  141. package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
  142. package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
  143. package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
  144. package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
  145. package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
  146. package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
  147. package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
  148. package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
  149. package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
  150. package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
  151. package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
  152. package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
  153. package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
  154. package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
  155. package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
  156. package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
  157. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
  158. package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
  159. package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
  160. package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
  161. package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
  162. package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
  163. package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
  164. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
  165. package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
  166. package/src/native/Recordings/build.sh +1973 -236
  167. package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
@@ -0,0 +1,297 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ umask 077
4
+
5
+ export PATH=/usr/bin:/bin:/usr/sbin:/sbin
6
+ export LC_ALL=C
7
+ export LANG=C
8
+ export TZ=UTC0
9
+
10
+ PACKAGE_BOOTSTRAP_ID="__RECORDINGS_PACKAGE_BOOTSTRAP_ID__"
11
+ AUTHORIZATION_ROOT="/private/var/db/com.hasna.recordings.bootstrap-authorization"
12
+ AUTHORIZATION_JOURNAL="${AUTHORIZATION_ROOT}/journal"
13
+ AUTHORIZATION_NEXT="${AUTHORIZATION_ROOT}/.journal.next"
14
+ STATE="/var/db/com.hasna.recordings.updater/release-state.json"
15
+ VERIFIER_USER="_recordingsverify"
16
+ VERIFIER_GROUP="_recordingsverify"
17
+
18
+ [ "$(/usr/bin/id -u)" = "0" ] || {
19
+ echo "Recordings updater bootstrap must run as root through Installer or MDM." >&2
20
+ exit 1
21
+ }
22
+
23
+ path_exists_or_symlink() {
24
+ [ -e "$1" ] || [ -L "$1" ]
25
+ }
26
+
27
+ require_no_extended_acl() {
28
+ local path="$1"
29
+ local acl_listing acl_status
30
+ acl_listing="$(/bin/ls -lade "$path")" || {
31
+ echo "Recordings updater could not inspect target ACLs: $path" >&2
32
+ exit 1
33
+ }
34
+ if /usr/bin/printf '%s\n' "$acl_listing" | /usr/bin/awk '
35
+ NR == 1 && length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
36
+ NR > 1 && $1 ~ /^[0-9]+:$/ { found = 1 }
37
+ END { exit found ? 0 : 1 }
38
+ '; then
39
+ echo "Recordings updater target trust path has an extended ACL: $path" >&2
40
+ exit 1
41
+ else
42
+ acl_status=$?
43
+ [ "$acl_status" -eq 1 ] || {
44
+ echo "Recordings updater could not evaluate target ACLs: $path" >&2
45
+ exit 1
46
+ }
47
+ fi
48
+ }
49
+
50
+ require_tree_without_extended_acl() {
51
+ local tree="$1"
52
+ local acl_listing acl_status
53
+ require_no_extended_acl "$tree"
54
+ [ -d "$tree" ] || return 0
55
+ acl_listing="$(/bin/ls -laeR "$tree")" || {
56
+ echo "Recordings updater could not recursively inspect target ACLs: $tree" >&2
57
+ exit 1
58
+ }
59
+ if /usr/bin/printf '%s\n' "$acl_listing" | /usr/bin/awk '
60
+ length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
61
+ $1 ~ /^[0-9]+:$/ { found = 1 }
62
+ END { exit found ? 0 : 1 }
63
+ '; then
64
+ echo "Recordings updater target tree has an extended ACL: $tree" >&2
65
+ exit 1
66
+ else
67
+ acl_status=$?
68
+ [ "$acl_status" -eq 1 ] || {
69
+ echo "Recordings updater could not evaluate recursive target ACLs: $tree" >&2
70
+ exit 1
71
+ }
72
+ fi
73
+ }
74
+
75
+ require_root_owned_nonwritable_directory() {
76
+ local directory="$1"
77
+ local mode
78
+ [ -d "$directory" ] && [ ! -L "$directory" ] && \
79
+ [ "$(/usr/bin/stat -f '%u' "$directory")" = 0 ] || {
80
+ echo "Recordings updater target ancestry is missing, linked, or not root-owned: $directory" >&2
81
+ exit 1
82
+ }
83
+ mode="$(/usr/bin/stat -f '%Lp' "$directory")"
84
+ [ $((8#$mode & 8#022)) -eq 0 ] || {
85
+ echo "Recordings updater target ancestry is group/other writable: $directory" >&2
86
+ exit 1
87
+ }
88
+ require_no_extended_acl "$directory"
89
+ }
90
+
91
+ require_managed_applications_directory() {
92
+ local mode group_id
93
+ [ -d "/Applications" ] && [ ! -L "/Applications" ] && \
94
+ [ "$(/usr/bin/stat -f '%u' "/Applications")" = 0 ] || {
95
+ echo "Recordings updater requires a root-owned canonical /Applications directory." >&2
96
+ exit 1
97
+ }
98
+ require_no_extended_acl "/Applications"
99
+ mode="$(/usr/bin/stat -f '%Lp' "/Applications")"
100
+ group_id="$(/usr/bin/stat -f '%g' "/Applications")"
101
+ case "$mode:$group_id" in
102
+ 755:*|775:80) ;;
103
+ *) echo "Recordings updater requires ACL-free /Applications mode 0755 or root:admin mode 0775." >&2; exit 1 ;;
104
+ esac
105
+ }
106
+
107
+ require_expected_var_link() {
108
+ local var_target
109
+ [ -L "/var" ] || { echo "Recordings updater requires the canonical macOS /var system link." >&2; exit 1; }
110
+ var_target="$(/usr/bin/readlink "/var")" || { echo "Recordings updater could not inspect the macOS /var system link." >&2; exit 1; }
111
+ [ "$var_target" = "private/var" ] && \
112
+ [ "$(cd "/var" && /bin/pwd -P)" = "/private/var" ] || {
113
+ echo "Recordings updater found a noncanonical macOS /var system link." >&2
114
+ exit 1
115
+ }
116
+ }
117
+
118
+ read_journal_value() {
119
+ local key="$1"
120
+ /usr/bin/awk -F= -v key="$key" '
121
+ $1 == key { count += 1; value = substr($0, length(key) + 2) }
122
+ END { if (count != 1) exit 1; print value }
123
+ ' "$AUTHORIZATION_JOURNAL"
124
+ }
125
+
126
+ require_authorization_journal() {
127
+ path_exists_or_symlink "$AUTHORIZATION_ROOT" && \
128
+ [ -d "$AUTHORIZATION_ROOT" ] && [ ! -L "$AUTHORIZATION_ROOT" ] && \
129
+ [ "$(/usr/bin/stat -f '%u:%g:%Lp' "$AUTHORIZATION_ROOT")" = "0:0:700" ] || {
130
+ echo "Recordings updater authorization evidence is missing or unsafe." >&2
131
+ exit 1
132
+ }
133
+ [ -f "$AUTHORIZATION_JOURNAL" ] && [ ! -L "$AUTHORIZATION_JOURNAL" ] && \
134
+ [ "$(/usr/bin/stat -f '%u:%g:%Lp' "$AUTHORIZATION_JOURNAL")" = "0:0:400" ] && \
135
+ [ "$(/usr/bin/stat -f '%l' "$AUTHORIZATION_JOURNAL")" = "1" ] && \
136
+ [ "$(/usr/bin/wc -l <"$AUTHORIZATION_JOURNAL" | /usr/bin/tr -d ' ')" = "6" ] || {
137
+ echo "Recordings updater authorization journal is missing or unsafe." >&2
138
+ exit 1
139
+ }
140
+ require_no_extended_acl "$AUTHORIZATION_ROOT"
141
+ require_no_extended_acl "$AUTHORIZATION_JOURNAL"
142
+ [ "$(read_journal_value schema_version)" = "1" ] || { echo "Recordings updater authorization journal schema is invalid." >&2; exit 1; }
143
+ JOURNAL_PHASE="$(read_journal_value phase)"
144
+ JOURNAL_PACKAGE_SHA256="$(read_journal_value package_sha256)"
145
+ JOURNAL_PACKAGE_BOOTSTRAP_ID="$(read_journal_value package_bootstrap_id)"
146
+ JOURNAL_INSTALLER_TEAM_ID="$(read_journal_value installer_team_id)"
147
+ JOURNAL_INSTALLER_CERTIFICATE_SHA256="$(read_journal_value installer_certificate_sha256)"
148
+ [[ "$JOURNAL_PACKAGE_SHA256" =~ ^[0-9a-f]{64}$ ]] && \
149
+ [[ "$JOURNAL_PACKAGE_BOOTSTRAP_ID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] && \
150
+ [[ "$JOURNAL_INSTALLER_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]] && \
151
+ [[ "$JOURNAL_INSTALLER_CERTIFICATE_SHA256" =~ ^[0-9a-f]{64}$ ]] || {
152
+ echo "Recordings updater authorization journal values are malformed." >&2
153
+ exit 1
154
+ }
155
+ [ "$JOURNAL_PACKAGE_BOOTSTRAP_ID" = "$PACKAGE_BOOTSTRAP_ID" ] || {
156
+ echo "Recordings updater authorization journal belongs to a different authenticated package." >&2
157
+ exit 1
158
+ }
159
+ }
160
+
161
+ write_authorization_phase() {
162
+ local phase="$1"
163
+ local replacement
164
+ replacement="$AUTHORIZATION_NEXT"
165
+ if path_exists_or_symlink "$replacement"; then
166
+ [ -f "$replacement" ] && [ ! -L "$replacement" ] && \
167
+ [ "$(/usr/bin/stat -f '%u:%g' "$replacement")" = "0:0" ] && \
168
+ [ "$(/usr/bin/stat -f '%l' "$replacement")" = "1" ] || {
169
+ echo "Recordings updater authorization journal staging is unsafe." >&2
170
+ exit 1
171
+ }
172
+ case "$(/usr/bin/stat -f '%Lp' "$replacement")" in 400|600) ;; *) echo "Recordings updater authorization journal staging mode is unsafe." >&2; exit 1 ;; esac
173
+ require_no_extended_acl "$replacement"
174
+ /bin/rm -f "$replacement"
175
+ fi
176
+ /usr/bin/printf 'schema_version=1\nphase=%s\npackage_sha256=%s\npackage_bootstrap_id=%s\ninstaller_team_id=%s\ninstaller_certificate_sha256=%s\n' \
177
+ "$phase" "$JOURNAL_PACKAGE_SHA256" "$JOURNAL_PACKAGE_BOOTSTRAP_ID" \
178
+ "$JOURNAL_INSTALLER_TEAM_ID" "$JOURNAL_INSTALLER_CERTIFICATE_SHA256" >"$replacement"
179
+ /usr/sbin/chown root:wheel "$replacement"
180
+ /bin/chmod -N "$replacement"
181
+ /bin/chmod 0400 "$replacement"
182
+ require_no_extended_acl "$replacement"
183
+ /bin/mv -f "$replacement" "$AUTHORIZATION_JOURNAL"
184
+ /bin/sync
185
+ }
186
+
187
+ require_safe_existing_cohort_path() {
188
+ local path="$1"
189
+ local expected_mode="$2"
190
+ local entry listing device mode owner links invalid=0
191
+ [ ! -L "$path" ] && [ "$(/usr/bin/stat -f '%u' "$path")" = "0" ] || {
192
+ echo "Recordings updater authorized exact-package repair found linked or non-root-owned cohort evidence: $path" >&2
193
+ exit 1
194
+ }
195
+ mode="$(/usr/bin/stat -f '%Lp' "$path")"
196
+ case "$expected_mode:$mode" in
197
+ "$mode:$mode"|700-or-755:700|700-or-755:755) ;;
198
+ *)
199
+ echo "Recordings updater authorized exact-package repair found cohort mode drift: $path" >&2
200
+ exit 1
201
+ ;;
202
+ esac
203
+ require_tree_without_extended_acl "$path"
204
+ if [ -f "$path" ]; then
205
+ [ "$(/usr/bin/stat -f '%l' "$path")" = "1" ] || { echo "Recordings updater cohort evidence is hard-linked: $path" >&2; exit 1; }
206
+ return
207
+ fi
208
+ [ -d "$path" ] || { echo "Recordings updater cohort evidence is not a regular file or directory: $path" >&2; exit 1; }
209
+ listing="$(/usr/bin/mktemp -t recordings-preinstall-cohort)"
210
+ device="$(/usr/bin/stat -f '%d' "$path")"
211
+ /usr/bin/find -x "$path" -print0 >"$listing" || { /bin/rm -f "$listing"; exit 1; }
212
+ while IFS= read -r -d '' entry; do
213
+ owner="$(/usr/bin/stat -f '%u' "$entry")" || { invalid=1; break; }
214
+ mode="$(/usr/bin/stat -f '%Lp' "$entry")" || { invalid=1; break; }
215
+ [ ! -L "$entry" ] && [ "$owner" = "0" ] && \
216
+ [ "$(/usr/bin/stat -f '%d' "$entry")" = "$device" ] && \
217
+ [ $((8#$mode & 8#022)) -eq 0 ] || { invalid=1; break; }
218
+ if [ -f "$entry" ]; then
219
+ links="$(/usr/bin/stat -f '%l' "$entry")" || { invalid=1; break; }
220
+ [ "$links" = "1" ] || { invalid=1; break; }
221
+ elif [ ! -d "$entry" ]; then
222
+ invalid=1
223
+ break
224
+ fi
225
+ done <"$listing"
226
+ /bin/rm -f "$listing"
227
+ [ "$invalid" -eq 0 ] || { echo "Recordings updater authorized exact-package repair found unsafe cohort structure: $path" >&2; exit 1; }
228
+ }
229
+
230
+ require_expected_var_link
231
+ require_managed_applications_directory
232
+ for required_ancestor in \
233
+ "/" "/Library" "/Library/Application Support" "/Library/PrivilegedHelperTools" "/Library/LaunchDaemons" \
234
+ "/private" "/private/var" "/private/var/db"; do
235
+ require_root_owned_nonwritable_directory "$required_ancestor"
236
+ done
237
+ for optional_ancestor in "/Library/Application Support/Hasna" "/Library/Application Support/Hasna/Recordings"; do
238
+ if path_exists_or_symlink "$optional_ancestor"; then require_root_owned_nonwritable_directory "$optional_ancestor"; fi
239
+ done
240
+
241
+ require_authorization_journal
242
+ path_exists_or_symlink "$STATE" && {
243
+ echo "Recordings uses an immutable updater cohort; a bootstrap PKG cannot replace committed anti-rollback state." >&2
244
+ exit 1
245
+ }
246
+
247
+ COHORT_PATHS=(
248
+ "/Library/PrivilegedHelperTools/com.hasna.recordings.updater:555"
249
+ "/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier:555"
250
+ "/Library/LaunchDaemons/com.hasna.recordings.updater.plist:444"
251
+ "/Applications/Recordings.app:755"
252
+ "/Applications/Recordings.app/Contents/Helpers/recordings-update-client:755"
253
+ "/Library/Application Support/Hasna/Recordings/Trust:700-or-755"
254
+ "/Library/Application Support/Hasna/Recordings/Bootstrap:700"
255
+ "/Library/Application Support/Hasna/Recordings/Updates:700"
256
+ "/var/db/com.hasna.recordings.updater:700"
257
+ )
258
+ present_count=0
259
+ for cohort_spec in "${COHORT_PATHS[@]}"; do
260
+ cohort_path="${cohort_spec%:*}"
261
+ cohort_mode="${cohort_spec##*:}"
262
+ if path_exists_or_symlink "$cohort_path"; then
263
+ present_count=$((present_count + 1))
264
+ require_safe_existing_cohort_path "$cohort_path" "$cohort_mode"
265
+ fi
266
+ done
267
+ service_present=false
268
+ if /bin/launchctl print system/com.hasna.recordings.updater >/dev/null 2>&1; then service_present=true; fi
269
+
270
+ case "$JOURNAL_PHASE" in
271
+ authorized)
272
+ JOURNAL_PHASE="authorized"
273
+ [ "$present_count" -eq 0 ] && [ "$service_present" = false ] && \
274
+ ! /usr/bin/dscl . -read "/Groups/${VERIFIER_GROUP}" >/dev/null 2>&1 && \
275
+ ! /usr/bin/dscl . -read "/Users/${VERIFIER_USER}" >/dev/null 2>&1 || {
276
+ echo "Recordings updater found a partial updater cohort without exact-package authorization or another existing unrelated cohort." >&2
277
+ exit 1
278
+ }
279
+ write_authorization_phase "installer-started"
280
+ ;;
281
+ installer-started)
282
+ JOURNAL_PHASE="installer-started"
283
+ [ "$service_present" = false ] || {
284
+ echo "Recordings updater refuses Installer replay while the authorized service is already registered." >&2
285
+ exit 1
286
+ }
287
+ ;;
288
+ *) echo "Recordings updater authorization journal has an invalid phase." >&2; exit 1 ;;
289
+ esac
290
+
291
+ # This package remains an immutable, one-time bootstrap cohort. Only an
292
+ # interrupted install bearing this exact signed-script identity may repair its
293
+ # own partial updater cohort; all other replacement requires managed reprovisioning.
294
+ if [ "$present_count" -gt 0 ]; then
295
+ echo "Recordings updater continuing authorized exact-package repair of a partial updater cohort." >&2
296
+ fi
297
+ # Once installed, only app-artifact updates are supported.
@@ -0,0 +1,337 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
5
+ echo "Usage: $0 <output-path> <explicit absolute Bun executable path> [native|universal]" >&2
6
+ exit 2
7
+ fi
8
+
9
+ BUILD_KIND="${3:-native}"
10
+ case "$BUILD_KIND" in
11
+ native|universal) ;;
12
+ *)
13
+ echo "Companion CLI build kind must be native or universal." >&2
14
+ exit 2
15
+ ;;
16
+ esac
17
+
18
+ HOST_UNAME_EXECUTABLE="/usr/bin/uname"
19
+ SYSTEM_DIRNAME_EXECUTABLE="/usr/bin/dirname"
20
+ SYSTEM_PWD_EXECUTABLE="/bin/pwd"
21
+ SANITIZED_PATH="/usr/bin:/bin:/usr/sbin:/sbin"
22
+
23
+ require_executable() {
24
+ local label="$1"
25
+ local executable="$2"
26
+ case "$executable" in
27
+ /*) ;;
28
+ *)
29
+ echo "$label must be configured as an absolute executable path." >&2
30
+ exit 1
31
+ ;;
32
+ esac
33
+ if [ ! -f "$executable" ] || [ ! -x "$executable" ]; then
34
+ echo "$label is missing or is not an executable file: $executable" >&2
35
+ exit 1
36
+ fi
37
+ }
38
+
39
+ require_executable "HOST_UNAME_EXECUTABLE" "$HOST_UNAME_EXECUTABLE"
40
+ require_executable "SYSTEM_DIRNAME_EXECUTABLE" "$SYSTEM_DIRNAME_EXECUTABLE"
41
+ require_executable "SYSTEM_PWD_EXECUTABLE" "$SYSTEM_PWD_EXECUTABLE"
42
+ HOST_PLATFORM="$("$HOST_UNAME_EXECUTABLE" -s)"
43
+ readonly HOST_PLATFORM
44
+
45
+ select_executable() {
46
+ local system_executable="$1"
47
+ local test_override="${2:-}"
48
+ if [ "$HOST_PLATFORM" = "Darwin" ] || [ -z "$test_override" ]; then
49
+ printf '%s\n' "$system_executable"
50
+ else
51
+ printf '%s\n' "$test_override"
52
+ fi
53
+ }
54
+
55
+ DIRNAME_EXECUTABLE="$(select_executable "$SYSTEM_DIRNAME_EXECUTABLE" "${RECORDINGS_TEST_COMPANION_DIRNAME_EXECUTABLE:-}")"
56
+ PWD_EXECUTABLE="$(select_executable "$SYSTEM_PWD_EXECUTABLE" "${RECORDINGS_TEST_COMPANION_PWD_EXECUTABLE:-}")"
57
+ CHMOD_EXECUTABLE="$(select_executable "/bin/chmod" "${RECORDINGS_TEST_COMPANION_CHMOD_EXECUTABLE:-}")"
58
+ CP_EXECUTABLE="$(select_executable "/bin/cp" "${RECORDINGS_TEST_COMPANION_CP_EXECUTABLE:-}")"
59
+ ENV_EXECUTABLE="$(select_executable "/usr/bin/env" "${RECORDINGS_TEST_COMPANION_ENV_EXECUTABLE:-}")"
60
+ GREP_EXECUTABLE="$(select_executable "/usr/bin/grep" "${RECORDINGS_TEST_COMPANION_GREP_EXECUTABLE:-}")"
61
+ MKDIR_EXECUTABLE="$(select_executable "/bin/mkdir" "${RECORDINGS_TEST_COMPANION_MKDIR_EXECUTABLE:-}")"
62
+ MKTEMP_EXECUTABLE="$(select_executable "/usr/bin/mktemp" "${RECORDINGS_TEST_COMPANION_MKTEMP_EXECUTABLE:-}")"
63
+ MV_EXECUTABLE="$(select_executable "/bin/mv" "${RECORDINGS_TEST_COMPANION_MV_EXECUTABLE:-}")"
64
+ RM_EXECUTABLE="$(select_executable "/bin/rm" "${RECORDINGS_TEST_COMPANION_RM_EXECUTABLE:-}")"
65
+ LIPO_EXECUTABLE="$(select_executable "/usr/bin/lipo" "${RECORDINGS_TEST_COMPANION_LIPO_EXECUTABLE:-}")"
66
+
67
+ for executable_spec in \
68
+ "DIRNAME_EXECUTABLE:$DIRNAME_EXECUTABLE" \
69
+ "PWD_EXECUTABLE:$PWD_EXECUTABLE" \
70
+ "CHMOD_EXECUTABLE:$CHMOD_EXECUTABLE" \
71
+ "CP_EXECUTABLE:$CP_EXECUTABLE" \
72
+ "ENV_EXECUTABLE:$ENV_EXECUTABLE" \
73
+ "GREP_EXECUTABLE:$GREP_EXECUTABLE" \
74
+ "MKDIR_EXECUTABLE:$MKDIR_EXECUTABLE" \
75
+ "MKTEMP_EXECUTABLE:$MKTEMP_EXECUTABLE" \
76
+ "MV_EXECUTABLE:$MV_EXECUTABLE" \
77
+ "RM_EXECUTABLE:$RM_EXECUTABLE"; do
78
+ require_executable "${executable_spec%%:*}" "${executable_spec#*:}"
79
+ done
80
+ if [ "$BUILD_KIND" = "universal" ]; then
81
+ require_executable "LIPO_EXECUTABLE" "$LIPO_EXECUTABLE"
82
+ fi
83
+
84
+ BUN_EXECUTABLE="$2"
85
+ require_bun_executable() {
86
+ local executable="$1"
87
+ require_executable "BUN_EXECUTABLE" "$executable"
88
+ if ! "$ENV_EXECUTABLE" -i \
89
+ HOME="/tmp" \
90
+ PATH="$SANITIZED_PATH" \
91
+ "$executable" -e '
92
+ import { realpathSync, statSync } from "node:fs";
93
+ const expected = process.argv[1];
94
+ const actual = process.execPath;
95
+ if (!expected || realpathSync(actual) !== realpathSync(expected)) process.exit(66);
96
+ if (!statSync(actual).isFile()) process.exit(66);
97
+ if (!/^\d+\.\d+\.\d+(?:[-+].*)?$/.test(Bun.version)) process.exit(66);
98
+ ' "$executable"; then
99
+ echo "BUN_EXECUTABLE did not identify the Bun executable that actually ran." >&2
100
+ exit 1
101
+ fi
102
+ }
103
+ require_bun_executable "$BUN_EXECUTABLE"
104
+
105
+ ROOT="$(cd "$("$DIRNAME_EXECUTABLE" "${BASH_SOURCE[0]}")/.." && "$PWD_EXECUTABLE" -P)"
106
+ CURRENT_DIRECTORY="$($PWD_EXECUTABLE -P)"
107
+ OUTPUT="$1"
108
+ case "$OUTPUT" in
109
+ /*) ;;
110
+ *) OUTPUT="${CURRENT_DIRECTORY}/${OUTPUT}" ;;
111
+ esac
112
+ case "$OUTPUT" in
113
+ */)
114
+ echo "Companion CLI output must be a file path, not a directory path: ${OUTPUT}." >&2
115
+ exit 1
116
+ ;;
117
+ esac
118
+ if [ -d "$OUTPUT" ]; then
119
+ echo "Companion CLI output must not be an existing directory: ${OUTPUT}." >&2
120
+ exit 1
121
+ fi
122
+
123
+ ENTRY="${ROOT}/src/cli/index.ts"
124
+ if [ ! -f "$ENTRY" ]; then
125
+ echo "Recordings CLI source is missing from ${ROOT}." >&2
126
+ exit 1
127
+ fi
128
+ for required_source in package.json bun.lock bunfig.toml tsconfig.json; do
129
+ if [ ! -f "${ROOT}/${required_source}" ]; then
130
+ echo "Recordings companion build input is missing: ${ROOT}/${required_source}." >&2
131
+ exit 1
132
+ fi
133
+ done
134
+
135
+ WORK_DIR="$($MKTEMP_EXECUTABLE -d /tmp/recordings-companion-build.XXXXXX)"
136
+ PUBLISH_OUTPUT=""
137
+ cleanup() {
138
+ if [ -n "$PUBLISH_OUTPUT" ]; then
139
+ "$RM_EXECUTABLE" -f "$PUBLISH_OUTPUT"
140
+ fi
141
+ "$RM_EXECUTABLE" -rf "$WORK_DIR"
142
+ }
143
+ trap cleanup EXIT
144
+
145
+ BUILD_HOME="$WORK_DIR/home"
146
+ BUN_CACHE="$WORK_DIR/bun-cache"
147
+ STAGED_ROOT="$WORK_DIR/package"
148
+ COMPILE_DIR="$WORK_DIR/compile"
149
+ COMPILED_OUTPUT="$COMPILE_DIR/recordings"
150
+ COMPILED_ARM64="$COMPILE_DIR/recordings.arm64"
151
+ COMPILED_X86_64="$COMPILE_DIR/recordings.x86_64"
152
+ "$MKDIR_EXECUTABLE" -p "$BUILD_HOME" "$BUN_CACHE" "$STAGED_ROOT" "$COMPILE_DIR"
153
+ "$CP_EXECUTABLE" "$ROOT/package.json" "$ROOT/bun.lock" "$ROOT/bunfig.toml" "$ROOT/tsconfig.json" "$STAGED_ROOT/"
154
+ "$CP_EXECUTABLE" -R "$ROOT/src" "$STAGED_ROOT/src"
155
+ if [ -d "$ROOT/migrations" ]; then
156
+ "$CP_EXECUTABLE" -R "$ROOT/migrations" "$STAGED_ROOT/migrations"
157
+ fi
158
+
159
+ run_bun() {
160
+ "$ENV_EXECUTABLE" -i \
161
+ HOME="$BUILD_HOME" \
162
+ PATH="$SANITIZED_PATH" \
163
+ TMPDIR="$WORK_DIR" \
164
+ "$BUN_EXECUTABLE" "$@"
165
+ }
166
+
167
+ run_lipo() {
168
+ "$ENV_EXECUTABLE" -i \
169
+ HOME="$BUILD_HOME" \
170
+ PATH="$SANITIZED_PATH" \
171
+ TMPDIR="$WORK_DIR" \
172
+ "$LIPO_EXECUTABLE" "$@"
173
+ }
174
+
175
+ verify_exact_binary_architectures() {
176
+ local binary="$1"
177
+ shift
178
+ local actual_architectures
179
+ local actual_architecture
180
+ local expected_architecture
181
+ local actual_count=0
182
+ local match_count
183
+
184
+ if ! actual_architectures="$(run_lipo -archs "$binary")"; then
185
+ echo "Could not read companion CLI architectures: $binary" >&2
186
+ return 1
187
+ fi
188
+ for actual_architecture in $actual_architectures; do
189
+ actual_count=$((actual_count + 1))
190
+ case " $* " in
191
+ *" $actual_architecture "*) ;;
192
+ *)
193
+ echo "Companion CLI contains unsupported architecture: $actual_architecture" >&2
194
+ return 1
195
+ ;;
196
+ esac
197
+ done
198
+ if [ "$actual_count" -ne "$#" ]; then
199
+ echo "Companion CLI architecture count does not match the universal release policy." >&2
200
+ return 1
201
+ fi
202
+ for expected_architecture in "$@"; do
203
+ match_count=0
204
+ for actual_architecture in $actual_architectures; do
205
+ if [ "$actual_architecture" = "$expected_architecture" ]; then
206
+ match_count=$((match_count + 1))
207
+ fi
208
+ done
209
+ if [ "$match_count" -ne 1 ]; then
210
+ echo "Companion CLI must contain exactly one $expected_architecture slice." >&2
211
+ return 1
212
+ fi
213
+ done
214
+ }
215
+
216
+ run_bun install \
217
+ --cwd "$STAGED_ROOT" \
218
+ --production \
219
+ --frozen-lockfile \
220
+ --ignore-scripts \
221
+ --minimum-release-age=604800 \
222
+ --cache-dir "$BUN_CACHE" \
223
+ --no-progress
224
+
225
+ if [ "$BUILD_KIND" = "universal" ]; then
226
+ (
227
+ cd "$COMPILE_DIR"
228
+ run_bun build \
229
+ --compile \
230
+ --target="bun-darwin-arm64" \
231
+ --reject-unresolved \
232
+ --no-compile-autoload-dotenv \
233
+ --no-compile-autoload-bunfig \
234
+ --no-compile-autoload-tsconfig \
235
+ --no-compile-autoload-package-json \
236
+ "$STAGED_ROOT/src/cli/index.ts" \
237
+ --outfile "$COMPILED_ARM64"
238
+ run_bun build \
239
+ --compile \
240
+ --target="bun-darwin-x64" \
241
+ --reject-unresolved \
242
+ --no-compile-autoload-dotenv \
243
+ --no-compile-autoload-bunfig \
244
+ --no-compile-autoload-tsconfig \
245
+ --no-compile-autoload-package-json \
246
+ "$STAGED_ROOT/src/cli/index.ts" \
247
+ --outfile "$COMPILED_X86_64"
248
+ )
249
+ run_lipo -create "$COMPILED_ARM64" "$COMPILED_X86_64" -output "$COMPILED_OUTPUT"
250
+ verify_exact_binary_architectures "$COMPILED_OUTPUT" arm64 x86_64
251
+ else
252
+ (
253
+ cd "$COMPILE_DIR"
254
+ run_bun build \
255
+ --compile \
256
+ --reject-unresolved \
257
+ --no-compile-autoload-dotenv \
258
+ --no-compile-autoload-bunfig \
259
+ --no-compile-autoload-tsconfig \
260
+ --no-compile-autoload-package-json \
261
+ "$STAGED_ROOT/src/cli/index.ts" \
262
+ --outfile "$COMPILED_OUTPUT"
263
+ )
264
+ fi
265
+
266
+ EXPECTED_VERSION="$(
267
+ cd "$STAGED_ROOT"
268
+ run_bun -e 'const pkg = await Bun.file("package.json").json(); process.stdout.write(pkg.version)'
269
+ )"
270
+ run_output() {
271
+ "$ENV_EXECUTABLE" -i \
272
+ HOME="$BUILD_HOME" \
273
+ PATH="$SANITIZED_PATH" \
274
+ TMPDIR="$WORK_DIR" \
275
+ "$COMPILED_OUTPUT" "$@"
276
+ }
277
+ ACTUAL_VERSION="$(run_output --version)"
278
+
279
+ if [ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ]; then
280
+ echo "Companion CLI version mismatch: expected ${EXPECTED_VERSION}, got ${ACTUAL_VERSION}." >&2
281
+ exit 1
282
+ fi
283
+
284
+ require_command() {
285
+ if ! run_output "$@" --help >/dev/null 2>&1; then
286
+ echo "Companion CLI is missing command: $*." >&2
287
+ exit 1
288
+ fi
289
+ }
290
+
291
+ require_flag() {
292
+ local command_name="$1"
293
+ local flag="$2"
294
+ shift 2
295
+ if ! run_output "$@" --help | "$GREP_EXECUTABLE" -Fq -- "$flag"; then
296
+ echo "Companion CLI is missing ${command_name} ${flag}." >&2
297
+ exit 1
298
+ fi
299
+ }
300
+
301
+ # Keep this contract in lockstep with every command and option emitted by the Swift app.
302
+ for command in list show search stats delete transcribe save-text rewrite; do
303
+ require_command "$command"
304
+ done
305
+ require_command project register
306
+
307
+ require_flag "root" "--json"
308
+ require_flag "root" "--project"
309
+ for flag in --name --path --description; do
310
+ require_flag "project register" "$flag" project register
311
+ done
312
+ for flag in -n --limit --offset; do
313
+ require_flag "list" "$flag" list
314
+ done
315
+ require_flag "search" "-n" search
316
+ require_flag "search" "--limit" search
317
+ for flag in --post-processing --prompt --transcriber-prompt --language --recording-id --transcription-model --transcriber-model --enhancement-model --enhance-triggers-json --keyword-transforms-json; do
318
+ require_flag "transcribe" "$flag" transcribe
319
+ done
320
+ for flag in --text-file --source --model-used --post-processing --audio-path --duration-ms --language --transcriber-prompt --recording-id --transcription-model --transcriber-model --enhancement-model --enhance-triggers-json --keyword-transforms-json; do
321
+ require_flag "save-text" "$flag" save-text
322
+ done
323
+ for flag in --instruction --post-processing --language --prompt --transcriber-prompt --transcription-model --transcriber-model --enhancement-model --enhance-triggers-json --keyword-transforms-json; do
324
+ require_flag "rewrite" "$flag" rewrite
325
+ done
326
+
327
+ OUTPUT_DIRECTORY="$("$DIRNAME_EXECUTABLE" "$OUTPUT")"
328
+ "$MKDIR_EXECUTABLE" -p "$OUTPUT_DIRECTORY"
329
+ PUBLISH_OUTPUT="$($MKTEMP_EXECUTABLE "$OUTPUT_DIRECTORY/.recordings-companion.XXXXXX")"
330
+ "$MV_EXECUTABLE" "$COMPILED_OUTPUT" "$PUBLISH_OUTPUT"
331
+ "$CHMOD_EXECUTABLE" 0755 "$PUBLISH_OUTPUT"
332
+ run_bun -e '
333
+ import { renameSync } from "node:fs";
334
+ renameSync(process.argv[1], process.argv[2]);
335
+ ' "$PUBLISH_OUTPUT" "$OUTPUT"
336
+ PUBLISH_OUTPUT=""
337
+ echo "Built Recordings companion CLI ${ACTUAL_VERSION}: ${OUTPUT}"