@hasna/recordings 0.3.2 → 0.3.8

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 (58) hide show
  1. package/README.md +38 -34
  2. package/bun.lock +96 -36
  3. package/dist/__tests__/helpers/source-assertions.d.ts +6 -4
  4. package/dist/__tests__/helpers/source-assertions.d.ts.map +1 -1
  5. package/dist/cli/index.js +182 -93
  6. package/dist/cli/macos-shortcut.d.ts +2 -2
  7. package/dist/db/pg-migrations.d.ts +1 -1
  8. package/dist/http/client.d.ts +0 -10
  9. package/dist/http/client.d.ts.map +1 -1
  10. package/dist/index.js +162 -73
  11. package/dist/lib/capture-probe.d.ts +3 -3
  12. package/dist/lib/capture-probe.d.ts.map +1 -1
  13. package/dist/lib/macos-bundle.d.ts +1 -1
  14. package/dist/lib/release-install-policy.d.ts +21 -0
  15. package/dist/lib/release-install-policy.d.ts.map +1 -1
  16. package/dist/mcp/index.js +144 -59
  17. package/dist/sdk/index.d.ts +2 -2
  18. package/dist/sdk/index.js +7 -3
  19. package/dist/sdk/v1.generated.d.ts.map +1 -1
  20. package/dist/server/cloud-config.d.ts +4 -19
  21. package/dist/server/cloud-config.d.ts.map +1 -1
  22. package/dist/server/cloud.d.ts +1 -1
  23. package/dist/server/cloud.d.ts.map +1 -1
  24. package/dist/server/index.js +168 -110
  25. package/dist/server/serve.d.ts.map +1 -1
  26. package/dist/storage.d.ts +1 -1
  27. package/dist/storage.d.ts.map +1 -1
  28. package/dist/storage.js +144 -60
  29. package/dist/store.d.ts.map +1 -1
  30. package/dist/version.d.ts +1 -1
  31. package/dist/version.d.ts.map +1 -1
  32. package/package.json +4 -3
  33. package/packaging/macos/build_release_pkg.sh +10 -4
  34. package/packaging/macos/managed_bootstrap.sh +4 -4
  35. package/packaging/macos/scripts/postinstall +2 -2
  36. package/packaging/macos/scripts/preinstall +2 -0
  37. package/scripts/generate-sdk.ts +17 -16
  38. package/scripts/install_macos_app.sh +22 -22
  39. package/scripts/macos_artifact.ts +48 -39
  40. package/scripts/migrate.ts +2 -2
  41. package/scripts/release-suite-gate.ts +181 -0
  42. package/scripts/set-version.ts +5 -5
  43. package/scripts/smoke_macos_app.sh +21 -21
  44. package/scripts/vacuity-manifests/version-sites.tsv +8 -4
  45. package/src/native/Recordings/RecordingsLib/Info.plist +3 -3
  46. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +2 -2
  47. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +1 -1
  48. package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +1 -1
  49. package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +2 -2
  50. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +1 -1
  51. package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +1 -1
  52. package/src/native/Recordings/Updater/Broker/InstallJournal.swift +1 -1
  53. package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +2 -2
  54. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +1 -1
  55. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +2 -2
  56. package/src/native/Recordings/build.sh +14 -20
  57. package/dist/lib/retired-deployment-modes.d.ts +0 -24
  58. package/dist/lib/retired-deployment-modes.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -30,10 +30,14 @@ class RecordingsV1Client {
30
30
  const url = new URL(this.baseUrl + path);
31
31
  if (opts.query) {
32
32
  for (const [key, value] of Object.entries(opts.query)) {
33
+ if (value === undefined || value === null)
34
+ continue;
33
35
  if (Array.isArray(value)) {
34
- for (const item of value)
35
- url.searchParams.append(key, String(item));
36
- } else if (value !== undefined && value !== null) {
36
+ for (const item of value) {
37
+ if (item !== undefined && item !== null)
38
+ url.searchParams.append(key, String(item));
39
+ }
40
+ } else {
37
41
  url.searchParams.set(key, String(value));
38
42
  }
39
43
  }
@@ -1184,9 +1188,118 @@ function setAgentFocus(idOrName, projectId, db) {
1184
1188
  d.query("UPDATE agents SET active_project_id = ?, last_seen_at = ? WHERE id = ?").run(resolvedProjectId, new Date().toISOString(), agent.id);
1185
1189
  return getAgent(agent.id, d);
1186
1190
  }
1191
+ // package.json
1192
+ var package_default = {
1193
+ name: "@hasna/recordings",
1194
+ version: "0.3.8",
1195
+ type: "module",
1196
+ description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
1197
+ repository: {
1198
+ type: "git",
1199
+ url: "git+https://github.com/hasna/apps"
1200
+ },
1201
+ main: "dist/index.js",
1202
+ types: "dist/index.d.ts",
1203
+ bin: {
1204
+ recordings: "dist/cli/index.js",
1205
+ "recordings-mcp": "dist/mcp/index.js",
1206
+ "recordings-serve": "dist/server/index.js"
1207
+ },
1208
+ exports: {
1209
+ ".": {
1210
+ import: "./dist/index.js",
1211
+ types: "./dist/index.d.ts"
1212
+ },
1213
+ "./storage": {
1214
+ import: "./dist/storage.js",
1215
+ types: "./dist/storage.d.ts"
1216
+ },
1217
+ "./sdk": {
1218
+ import: "./dist/sdk/index.js",
1219
+ types: "./dist/sdk/index.d.ts"
1220
+ }
1221
+ },
1222
+ engines: {
1223
+ bun: ">=1.0.0"
1224
+ },
1225
+ scripts: {
1226
+ clean: "rm -rf dist",
1227
+ build: "bun run clean && bun run build:cli && bun run build:mcp && bun run build:serve && bun run build:lib && tsc --emitDeclarationOnly --outDir dist",
1228
+ "build:cli": "bun build src/cli/index.ts --target=bun --outfile=dist/cli/index.js --external=commander --external=chalk --external=openai",
1229
+ "build:mcp": "bun build src/mcp/index.ts --target=bun --outfile=dist/mcp/index.js --external=@modelcontextprotocol/sdk --external=openai",
1230
+ "build:serve": "bun build src/server/index.ts --target=bun --outfile=dist/server/index.js --external=@modelcontextprotocol/sdk --external=@hasna/contracts --external=openai --external=pg",
1231
+ "build:lib": "bun build src/index.ts src/storage.ts src/sdk/index.ts --target=bun --outdir=dist --external=openai",
1232
+ "build:native-fs-guard": "/bin/bash scripts/build_native_fs_guard.sh",
1233
+ "generate:sdk": "bun run scripts/generate-sdk.ts",
1234
+ migrate: "bun run scripts/migrate.ts",
1235
+ typecheck: "tsc --noEmit",
1236
+ "typecheck:tcc-contract": "tsc --noEmit -p tsconfig.tcc-contract.json",
1237
+ test: "bun test",
1238
+ "verify:ci-suite": "bun scripts/ci-linux-suite.ts --check",
1239
+ "verify:ci-suite:run": "bun scripts/ci-linux-suite.ts --verify-run",
1240
+ "verify:ci-native": "bun scripts/ci-native-build.ts",
1241
+ "test:gated": 'bun scripts/ci-linux-suite.ts --check && RECORDINGS_TEST_TIMEOUT_MS="${RECORDINGS_TEST_TIMEOUT_MS:-120000}" bun test --timeout "$RECORDINGS_TEST_TIMEOUT_MS" $(bun scripts/ci-linux-suite.ts --gated)',
1242
+ "test:vacuity-battery": 'bun scripts/vacuity-manifest-gen.ts > "${TMPDIR:-/tmp}/vacuity-corrupt-sites.tsv" && bun scripts/vacuity-mutation-battery.ts "${TMPDIR:-/tmp}/vacuity-corrupt-sites.tsv"',
1243
+ "test:vacuity-battery:source": "bun scripts/vacuity-mutation-battery.ts scripts/vacuity-manifests/source-side.tsv",
1244
+ "test:vacuity-battery:chain": "bun scripts/vacuity-mutation-battery.ts scripts/vacuity-manifests/install-chain.tsv",
1245
+ "test:vacuity-battery:vars": "bun scripts/vacuity-mutation-battery.ts scripts/vacuity-manifests/variable-operands.tsv",
1246
+ "test:vacuity-battery:reorder": "bun scripts/vacuity-mutation-battery.ts scripts/vacuity-manifests/reorder.tsv",
1247
+ "test:coverage": "bun test --coverage",
1248
+ "dev:cli": "bun run src/cli/index.ts",
1249
+ "desktop:snapshot": "bun run src/cli/index.ts app snapshot",
1250
+ "dev:mcp": "bun run src/mcp/index.ts",
1251
+ "verify:release": "bun run scripts/release-guard.ts",
1252
+ "scan:artifact": "bun run scripts/scan-artifact.ts",
1253
+ "version:set": "bun run scripts/set-version.ts",
1254
+ "version:check": "bun run scripts/set-version.ts --check",
1255
+ prepack: "bun run prepack:platform-gate && bun run version:check && bun run build && bun run verify:release && bun run scan:artifact",
1256
+ "prepack:platform-gate": `bash -c 'if [ "$(uname -s)" = Darwin ]; then bun run build:native-fs-guard; else echo "WARN: native fs-guard build skipped on $(uname -s) \u2014 dry-run/CI pack; publish recordings from macOS"; fi'`,
1257
+ prepublishOnly: "bun run typecheck && bun run release-suite-gate",
1258
+ "release-suite-gate": "bun run scripts/release-suite-gate.ts",
1259
+ "typecheck:shortcut-contract": "tsc --noEmit -p tsconfig.test.json"
1260
+ },
1261
+ files: [
1262
+ "dist/",
1263
+ "scripts/",
1264
+ "Dockerfile.package",
1265
+ "bun.lock",
1266
+ "src/native/Recordings/App/",
1267
+ "src/native/Recordings/RecordingsLib/",
1268
+ "src/native/Recordings/RecordingsTests/",
1269
+ "src/native/Recordings/Updater/",
1270
+ "src/native/Recordings/Package.swift",
1271
+ "src/native/Recordings/Package.resolved",
1272
+ "src/native/Recordings/build.sh",
1273
+ "packaging/macos/",
1274
+ "README.md",
1275
+ "LICENSE"
1276
+ ],
1277
+ dependencies: {
1278
+ "@hasna/contracts": "0.13.3",
1279
+ "@hasna/events": "0.1.11",
1280
+ "@modelcontextprotocol/sdk": "^1.12.1",
1281
+ chalk: "^5.4.1",
1282
+ commander: "^13.1.0",
1283
+ openai: "^5.1.0",
1284
+ pg: "^8.13.3",
1285
+ zod: "^3.24.2"
1286
+ },
1287
+ devDependencies: {
1288
+ "@types/bun": "^1.2.5",
1289
+ "@types/pg": "^8.11.11",
1290
+ "node-api-headers": "1.9.0",
1291
+ typescript: "^5.8.2"
1292
+ },
1293
+ publishConfig: {
1294
+ registry: "https://registry.npmjs.org",
1295
+ access: "public"
1296
+ },
1297
+ license: "Apache-2.0",
1298
+ author: "Hasna <andrei@hasna.com>"
1299
+ };
1187
1300
 
1188
1301
  // src/version.ts
1189
- var VERSION = "0.3.2";
1302
+ var VERSION = package_default.version;
1190
1303
 
1191
1304
  // src/db/feedback.ts
1192
1305
  function saveFeedback(input) {
@@ -1194,58 +1307,26 @@ function saveFeedback(input) {
1194
1307
  db.query("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)").run(input.message, input.email ?? null, input.category ?? "general", input.version ?? VERSION);
1195
1308
  }
1196
1309
 
1197
- // src/lib/retired-deployment-modes.ts
1198
- var RETIRED_DEPLOYMENT_MODES = [
1199
- "local",
1200
- "self_hosted",
1201
- "cloud",
1202
- "remote",
1203
- "hybrid"
1204
- ];
1205
- function normalizeModeToken(value) {
1206
- return value.trim().toLowerCase().replace(/-/g, "_");
1207
- }
1208
- function asRetiredDeploymentMode(value) {
1209
- const token = normalizeModeToken(value);
1210
- return RETIRED_DEPLOYMENT_MODES.includes(token) ? token : null;
1211
- }
1212
- function retiredDeploymentModeError(rawValue, sourceEnvKey, replacement) {
1213
- const mode = asRetiredDeploymentMode(rawValue);
1214
- const value = mode === "local" ? replacement.onBox : replacement.offBox;
1215
- return new Error(`${sourceEnvKey}=${rawValue} names a deployment mode, and deployment modes are removed: ` + `${RETIRED_DEPLOYMENT_MODES.join(" | ")} no longer select anything. ` + `Set ${replacement.envKey}=${value} instead ` + `(${replacement.envKey} takes ${replacement.onBox} or ${replacement.offBox}).`);
1216
- }
1217
-
1218
1310
  // src/http/client.ts
1219
- var SERVER_BACKEND_VALUES = ["sqlite", "postgresql", "postgres"];
1220
1311
  function envToken(name) {
1221
1312
  return name.toUpperCase().replace(/-/g, "_");
1222
1313
  }
1223
- function clientStoreReplacement(envKey) {
1224
- return { envKey, onBox: "sqlite", offBox: "http" };
1314
+ function envKeys(name) {
1315
+ const token = envToken(name);
1316
+ return {
1317
+ storeKeys: [`HASNA_${token}_CLIENT_STORE`, `${token}_CLIENT_STORE`],
1318
+ apiUrlKeys: [`HASNA_${token}_API_URL`],
1319
+ apiKeyKeys: [`HASNA_${token}_API_KEY`]
1320
+ };
1225
1321
  }
1226
- function normalizeClientStore(value, sourceEnvKey, replacementEnvKey) {
1227
- const normalized = normalizeModeToken(value);
1322
+ function normalizeClientStore(value) {
1323
+ const normalized = value.trim().toLowerCase();
1228
1324
  if (normalized === "sqlite")
1229
1325
  return "sqlite";
1230
1326
  if (normalized === "http" || normalized === "https")
1231
1327
  return "http";
1232
- if (asRetiredDeploymentMode(value)) {
1233
- throw retiredDeploymentModeError(value, sourceEnvKey, clientStoreReplacement(replacementEnvKey));
1234
- }
1235
1328
  throw new Error(`Unknown client store: ${value}. Use sqlite or http.`);
1236
1329
  }
1237
- function defaultApiBaseUrl(name) {
1238
- return `http://localhost:8874`;
1239
- }
1240
- function envKeys(name) {
1241
- const token = envToken(name);
1242
- return {
1243
- storeKeys: [`HASNA_${token}_CLIENT_STORE`, `${token}_CLIENT_STORE`],
1244
- retiredModeKeys: [`HASNA_${token}_STORAGE_MODE`, `HASNA_${token}_MODE`, `${token}_STORAGE_MODE`, `${token}_MODE`],
1245
- apiUrlKeys: [`HASNA_${token}_API_URL`, `${token}_API_URL`],
1246
- apiKeyKeys: [`HASNA_${token}_API_KEY`, `${token}_API_KEY`]
1247
- };
1248
- }
1249
1330
  function firstEnv(env, keys) {
1250
1331
  for (const key of keys) {
1251
1332
  const value = env[key]?.trim();
@@ -1254,20 +1335,6 @@ function firstEnv(env, keys) {
1254
1335
  }
1255
1336
  return null;
1256
1337
  }
1257
- function assertNoRetiredMode(env, keys) {
1258
- for (const key of keys.retiredModeKeys) {
1259
- const value = env[key]?.trim();
1260
- if (!value)
1261
- continue;
1262
- const normalized = normalizeModeToken(value);
1263
- if (SERVER_BACKEND_VALUES.includes(normalized))
1264
- continue;
1265
- if (asRetiredDeploymentMode(value)) {
1266
- throw retiredDeploymentModeError(value, key, clientStoreReplacement(keys.storeKeys[0]));
1267
- }
1268
- throw new Error(`${key}=${value} is not a client store. ${key} no longer selects one; ` + `set ${keys.storeKeys[0]}=sqlite or ${keys.storeKeys[0]}=http instead.`);
1269
- }
1270
- }
1271
1338
  function toV1BaseUrl(apiUrl) {
1272
1339
  const url = new URL(apiUrl);
1273
1340
  if (url.protocol !== "http:" && url.protocol !== "https:") {
@@ -1283,22 +1350,44 @@ function toV1BaseUrl(apiUrl) {
1283
1350
  }
1284
1351
  function resolveTransport(name, env = process.env) {
1285
1352
  const keys = envKeys(name);
1286
- assertNoRetiredMode(env, keys);
1287
1353
  const storeHit = firstEnv(env, keys.storeKeys);
1288
1354
  const urlHit = firstEnv(env, keys.apiUrlKeys);
1289
1355
  const keyHit = firstEnv(env, keys.apiKeyKeys);
1290
1356
  let requested = "sqlite";
1291
1357
  let modeSource = "default";
1292
1358
  if (storeHit) {
1293
- requested = normalizeClientStore(storeHit.value, storeHit.key, keys.storeKeys[0]);
1359
+ requested = normalizeClientStore(storeHit.value);
1294
1360
  modeSource = storeHit.key;
1295
1361
  } else if (urlHit && keyHit) {
1296
1362
  requested = "http";
1297
1363
  modeSource = "auto:api-url+api-key";
1364
+ } else if (urlHit || keyHit) {
1365
+ const missing = urlHit ? keys.apiKeyKeys[0] : keys.apiUrlKeys[0];
1366
+ const present = urlHit ? keys.apiUrlKeys[0] : keys.apiKeyKeys[0];
1367
+ return {
1368
+ transport: "sqlite",
1369
+ requested,
1370
+ modeSource,
1371
+ baseUrl: null,
1372
+ apiKeyPresent: Boolean(keyHit),
1373
+ misconfigured: true,
1374
+ warning: `${present} is set but ${missing} is not: the hosted API is only ` + `selected when BOTH are present. Set ${missing}, or unset ${present} to ` + `use the on-box store.`
1375
+ };
1298
1376
  }
1299
1377
  if (requested === "sqlite") {
1300
1378
  return { transport: "sqlite", requested, modeSource, baseUrl: null, apiKeyPresent: Boolean(keyHit), misconfigured: false, warning: null };
1301
1379
  }
1380
+ if (!urlHit) {
1381
+ return {
1382
+ transport: "sqlite",
1383
+ requested,
1384
+ modeSource,
1385
+ baseUrl: null,
1386
+ apiKeyPresent: Boolean(keyHit),
1387
+ misconfigured: true,
1388
+ warning: `${modeSource}=http but no API URL is set (${keys.apiUrlKeys[0]}). Refusing to route to the API.`
1389
+ };
1390
+ }
1302
1391
  if (!keyHit) {
1303
1392
  return {
1304
1393
  transport: "sqlite",
@@ -1310,7 +1399,7 @@ function resolveTransport(name, env = process.env) {
1310
1399
  warning: `${modeSource}=http but no API key is set (${keys.apiKeyKeys[0]}). Refusing to route to the API.`
1311
1400
  };
1312
1401
  }
1313
- const rawUrl = urlHit?.value ?? defaultApiBaseUrl(name);
1402
+ const rawUrl = urlHit.value;
1314
1403
  let baseUrl;
1315
1404
  try {
1316
1405
  baseUrl = toV1BaseUrl(rawUrl);
@@ -2360,37 +2449,37 @@ function captureProbeSubject(env = process.env, options = {}) {
2360
2449
  const termProgram = env.TERM_PROGRAM?.trim();
2361
2450
  const hasTty = options.hasTty ?? Boolean(process.stdin.isTTY || process.stdout.isTTY);
2362
2451
  if (overSsh) {
2363
- const subject2 = "the SSH session (sshd), not Recordings.app";
2452
+ const subject2 = "the SSH session (sshd), not HasnaRecordings.app";
2364
2453
  return {
2365
2454
  headless: true,
2366
2455
  subject_known: true,
2367
2456
  subject: subject2,
2368
- note: "Running over SSH: macOS cannot display a consent prompt to a session with no GUI, " + "so Microphone stays not_determined and a silent capture here says NOTHING about " + "whether Recordings.app can record. Judge the app by its own TCC entry and its log."
2457
+ note: "Running over SSH: macOS cannot display a consent prompt to a session with no GUI, " + "so Microphone stays not_determined and a silent capture here says NOTHING about " + "whether HasnaRecordings.app can record. Judge the app by its own TCC entry and its log."
2369
2458
  };
2370
2459
  }
2371
2460
  if (inTmux) {
2372
- const subject2 = "the tmux server, not Recordings.app and not this pane's shell";
2461
+ const subject2 = "the tmux server, not HasnaRecordings.app and not this pane's shell";
2373
2462
  return {
2374
2463
  headless: false,
2375
2464
  subject_known: true,
2376
2465
  subject: subject2,
2377
- note: "Running inside tmux: TCC attributes this capture to the tmux binary, which holds its " + "own grant. tmux also snapshots the environment at pane creation, so SSH variables may " + "be missing even in a remote session \u2014 treat a silent result as inconclusive about both " + "Recordings.app and about whether anyone could have been prompted."
2466
+ note: "Running inside tmux: TCC attributes this capture to the tmux binary, which holds its " + "own grant. tmux also snapshots the environment at pane creation, so SSH variables may " + "be missing even in a remote session \u2014 treat a silent result as inconclusive about both " + "HasnaRecordings.app and about whether anyone could have been prompted."
2378
2467
  };
2379
2468
  }
2380
2469
  if (!termProgram && !hasTty) {
2381
2470
  return {
2382
2471
  headless: true,
2383
2472
  subject_known: false,
2384
- subject: "an unidentified responsible process (not Recordings.app)",
2385
- note: "The responsible process could not be identified: no SSH variables, no TERM_PROGRAM and " + "no tty, which is what launchd, cron, CI and sudo look like. Whatever holds the grant, it " + "is not Recordings.app, and nothing here can be shown a consent prompt. Inconclusive."
2473
+ subject: "an unidentified responsible process (not HasnaRecordings.app)",
2474
+ note: "The responsible process could not be identified: no SSH variables, no TERM_PROGRAM and " + "no tty, which is what launchd, cron, CI and sudo look like. Whatever holds the grant, it " + "is not HasnaRecordings.app, and nothing here can be shown a consent prompt. Inconclusive."
2386
2475
  };
2387
2476
  }
2388
- const subject = `${termProgram || "the terminal application running this command"}, not Recordings.app`;
2477
+ const subject = `${termProgram || "the terminal application running this command"}, not HasnaRecordings.app`;
2389
2478
  return {
2390
2479
  headless: false,
2391
2480
  subject_known: Boolean(termProgram),
2392
2481
  subject,
2393
- note: `Grants are per responsible process: this probe exercises ${subject}. ` + "A pass proves the microphone hardware and the input device work; it does not " + "transfer to Recordings.app, which needs its own grant."
2482
+ note: `Grants are per responsible process: this probe exercises ${subject}. ` + "A pass proves the microphone hardware and the input device work; it does not " + "transfer to HasnaRecordings.app, which needs its own grant."
2394
2483
  };
2395
2484
  }
2396
2485
  var TCC_UNREADABLE_STATE = TCC_DATABASE_UNREADABLE_STATE;
@@ -2420,7 +2509,7 @@ function microphoneGrantInstruction(options) {
2420
2509
  const bundlePath = candidates[0] ?? null;
2421
2510
  const steps = [];
2422
2511
  if (!bundlePath) {
2423
- steps.push("No Recordings.app bundle was found on disk, so there is nothing to grant Microphone to yet. " + "Install the app first ('recordings app install').");
2512
+ steps.push("No HasnaRecordings.app bundle was found on disk, so there is nothing to grant Microphone to yet. " + "Install the app first ('recordings app install').");
2424
2513
  return {
2425
2514
  bundle_path: null,
2426
2515
  bundle_identifier: RECORDINGS_BUNDLE_IDENTIFIER,
@@ -2429,7 +2518,7 @@ function microphoneGrantInstruction(options) {
2429
2518
  };
2430
2519
  }
2431
2520
  if (candidates.length > 1) {
2432
- steps.push(`AMBIGUOUS: ${candidates.length} Recordings.app bundles exist (${candidates.join(", ")}). ` + "A TCC grant is bound to the bundle's code signature, so granting one does not grant the " + "other, and the toggle in Settings does not say which is which. Remove the bundles you are " + "not running before granting, or the grant may attach to the wrong one.");
2521
+ steps.push(`AMBIGUOUS: ${candidates.length} HasnaRecordings.app bundles exist (${candidates.join(", ")}). ` + "A TCC grant is bound to the bundle's code signature, so granting one does not grant the " + "other, and the toggle in Settings does not say which is which. Remove the bundles you are " + "not running before granting, or the grant may attach to the wrong one.");
2433
2522
  }
2434
2523
  steps.push(`At the keyboard on the machine itself (not over SSH), launch ${bundlePath} and start a ` + "recording once. macOS shows the consent sheet titled " + `"\u201CRecordings\u201D would like to access the microphone" \u2014 click Allow.`);
2435
2524
  steps.push("If no sheet appears, open System Settings \u2192 Privacy & Security \u2192 Microphone " + "and switch ON the row named \u201CRecordings\u201D. " + `That row is bundle ${RECORDINGS_BUNDLE_IDENTIFIER} at ${bundlePath}; the binary that ` + `receives the grant is ${join4(bundlePath, "Contents", "MacOS", "Recordings")}.`);
@@ -2512,7 +2601,7 @@ function describeActiveStore(config, env = process.env) {
2512
2601
  warnings.push(`writes go to ${safeBaseUrl(resolution.baseUrl)}, but ${localDbPath} still holds ` + `${localDbRecordings} recordings from an earlier on-box-only period. ` + "That file is NOT the live store \u2014 auditing it undercounts and looks like data loss.");
2513
2602
  }
2514
2603
  if (resolution.transport === "http" && resolution.modeSource === AUTO_FLIP_MODE_SOURCE) {
2515
- warnings.push("the API transport was selected by the mere PRESENCE of " + "HASNA_RECORDINGS_API_URL + HASNA_RECORDINGS_API_KEY, with no store variable set. " + "Check `launchctl getenv HASNA_RECORDINGS_API_URL` too: a launchd session variable " + "is inherited by the GUI app as well as by shells, and is invisible in a login profile.");
2604
+ warnings.push("the API transport was selected by the mere PRESENCE of " + "HASNA_RECORDINGS_API_URL + HASNA_RECORDINGS_API_KEY. " + "Check `launchctl getenv HASNA_RECORDINGS_API_URL` too: a launchd session variable " + "is inherited by the GUI app as well as by shells, and is invisible in a login profile.");
2516
2605
  }
2517
2606
  return {
2518
2607
  transport: resolution.transport,
@@ -118,7 +118,7 @@ export interface CaptureProbeSubject {
118
118
  * Name whose Microphone grant this probe actually exercises.
119
119
  *
120
120
  * macOS attributes microphone access to the RESPONSIBLE process — for a CLI that
121
- * is the terminal application (or sshd), never Recordings.app. So a silent probe
121
+ * is the terminal application (or sshd), never HasnaRecordings.app. So a silent probe
122
122
  * means "the thing running this command has no grant", which is a different
123
123
  * finding, with a different fix, from "the app has no grant". Reporting the one
124
124
  * as the other is how a diagnostic sends someone to the wrong settings pane.
@@ -141,7 +141,7 @@ export interface MicrophoneGrantInstruction {
141
141
  /** The bundle that must receive the grant, or null when none was found. */
142
142
  bundle_path: string | null;
143
143
  bundle_identifier: string;
144
- /** Every Recordings.app on disk — more than one makes the grant ambiguous. */
144
+ /** Every HasnaRecordings.app on disk — more than one makes the grant ambiguous. */
145
145
  candidate_bundle_paths: string[];
146
146
  /** Ordered steps, already naming the pane, the section and the bundle. */
147
147
  steps: string[];
@@ -197,7 +197,7 @@ export declare function classifyPermissionState(permissionState: string): Permis
197
197
  export declare function microphoneGrantInstruction(options: {
198
198
  /** Path the installer treats as canonical. */
199
199
  installedAppPath?: string | null;
200
- /** Other Recordings.app bundles found on disk (e.g. legacy install sites). */
200
+ /** Other HasnaRecordings.app bundles found on disk (e.g. legacy install sites). */
201
201
  otherAppPaths?: string[];
202
202
  /**
203
203
  * Whether the app has ever prompted. `unknown` means the TCC state could not
@@ -1 +1 @@
1
- {"version":3,"file":"capture-probe.d.ts","sourceRoot":"","sources":["../../src/lib/capture-probe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"capture-probe.d.ts","sourceRoot":"","sources":["../../src/lib/capture-probe.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Canonical macOS bundle identity for Recordings.app.
2
+ * Canonical macOS bundle identity for HasnaRecordings.app.
3
3
  *
4
4
  * Why this module exists at all, rather than the constant living next to its first caller:
5
5
  * three branches in the #24-#29 queue each introduced their own copy of this literal —
@@ -24,6 +24,27 @@ export type PreparedReleaseInstall = {
24
24
  envelopePath: string;
25
25
  cleanup: () => void;
26
26
  };
27
+ /**
28
+ * Recorded strong reason for these release-path refusals (local-only-capability-
29
+ * removal workflow, 2026-08-18; reviewer-ruled — do not remove or relax this
30
+ * gate by assumption). The local-only artifact policy — ad-hoc or Developer ID
31
+ * signed, NON-NOTARIZED builds bound to one approved target machine
32
+ * (tailscale_node_id_sha256), built on a different authenticated Tailscale
33
+ * node, with signing acknowledgement and identity-migration controls — is a
34
+ * native macOS distribution policy, not a data-backend capability. The
35
+ * two-backend contract (local store vs hosted API) does not reach code
36
+ * signing: the signing identity is keychain-held on the build Mac
37
+ * (`security find-identity`, see src/native/Recordings/build.sh), so no
38
+ * hosted/server path can carry it. The release path IS the notarized
39
+ * counterpart (README.md: "Explicit local-only alternative when Developer ID
40
+ * credentials are unavailable"); it must keep rejecting the local-only fields
41
+ * so that only notarized, Team-ID-pinned, envelope-signed artifacts install
42
+ * fleet-wide through the root-owned update client. Relaxing this gate would
43
+ * let a non-notarized or ad-hoc artifact install on any machine — the
44
+ * supply-chain class this policy blocks. Behavior locks:
45
+ * src/__tests__/local-only-target-policy.test.ts,
46
+ * src/__tests__/cli.test.ts, src/__tests__/macos-artifact.test.ts.
47
+ */
27
48
  export declare function assertReleaseOnlyOptions(options: ReleaseOnlyOptions): void;
28
49
  export declare function assertExpectedReleaseHostname(expected: string, actual: string): void;
29
50
  export declare function parseLaunchTimeout(value: string | undefined): string;
@@ -1 +1 @@
1
- {"version":3,"file":"release-install-policy.d.ts","sourceRoot":"","sources":["../../src/lib/release-install-policy.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,4BAA4B,EAAE,MAAM,CAAC;IACrC,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CA4B1E;AAED,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CASpF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAMpE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,mBAAmB,GAAG,sBAAsB,CAgE9F"}
1
+ {"version":3,"file":"release-install-policy.d.ts","sourceRoot":"","sources":["../../src/lib/release-install-policy.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,4BAA4B,EAAE,MAAM,CAAC;IACrC,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CA4B1E;AAED,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CASpF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAMpE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,mBAAmB,GAAG,sBAAsB,CAgE9F"}