@hasna/recordings 0.2.14 → 0.3.2

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 (123) hide show
  1. package/Dockerfile.package +4 -5
  2. package/README.md +113 -8
  3. package/bun.lock +2 -2
  4. package/dist/__tests__/helpers/installer-guard-execution.d.ts +22 -0
  5. package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +1 -0
  6. package/dist/__tests__/helpers/installer-preflight.d.ts +22 -0
  7. package/dist/__tests__/helpers/installer-preflight.d.ts.map +1 -0
  8. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -1
  9. package/dist/__tests__/helpers/source-assertions.d.ts +169 -0
  10. package/dist/__tests__/helpers/source-assertions.d.ts.map +1 -0
  11. package/dist/__tests__/preload.d.ts +1 -4
  12. package/dist/__tests__/preload.d.ts.map +1 -1
  13. package/dist/cli/desktop-snapshot.d.ts +20 -0
  14. package/dist/cli/desktop-snapshot.d.ts.map +1 -0
  15. package/dist/cli/index.js +2094 -280
  16. package/dist/cli/macos-permissions.d.ts +72 -0
  17. package/dist/cli/macos-permissions.d.ts.map +1 -1
  18. package/dist/cli/macos-shortcut.d.ts +139 -0
  19. package/dist/cli/macos-shortcut.d.ts.map +1 -0
  20. package/dist/cli/trigger-probe.d.ts +172 -0
  21. package/dist/cli/trigger-probe.d.ts.map +1 -0
  22. package/dist/db/database.d.ts +9 -0
  23. package/dist/db/database.d.ts.map +1 -1
  24. package/dist/http/client.d.ts +19 -7
  25. package/dist/http/client.d.ts.map +1 -1
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +635 -50
  29. package/dist/lib/capture-probe.d.ts +216 -0
  30. package/dist/lib/capture-probe.d.ts.map +1 -0
  31. package/dist/lib/config.d.ts.map +1 -1
  32. package/dist/lib/macos-bundle.d.ts +50 -0
  33. package/dist/lib/macos-bundle.d.ts.map +1 -0
  34. package/dist/lib/persistence-probe.d.ts +208 -0
  35. package/dist/lib/persistence-probe.d.ts.map +1 -0
  36. package/dist/lib/recorder.d.ts.map +1 -1
  37. package/dist/lib/retired-deployment-modes.d.ts +24 -0
  38. package/dist/lib/retired-deployment-modes.d.ts.map +1 -0
  39. package/dist/lib/transcriber.d.ts +60 -0
  40. package/dist/lib/transcriber.d.ts.map +1 -1
  41. package/dist/mcp/index.js +141 -57
  42. package/dist/server/cloud-config.d.ts +25 -3
  43. package/dist/server/cloud-config.d.ts.map +1 -1
  44. package/dist/server/cloud-readiness.d.ts.map +1 -1
  45. package/dist/server/cloud.d.ts +8 -7
  46. package/dist/server/cloud.d.ts.map +1 -1
  47. package/dist/server/index.js +188 -80
  48. package/dist/server/migrate-command.d.ts.map +1 -1
  49. package/dist/server/serve.d.ts.map +1 -1
  50. package/dist/storage.d.ts +2 -2
  51. package/dist/storage.d.ts.map +1 -1
  52. package/dist/storage.js +109 -50
  53. package/dist/store.d.ts +1 -1
  54. package/dist/store.d.ts.map +1 -1
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.d.ts.map +1 -1
  57. package/package.json +25 -6
  58. package/scripts/ci-linux-suite.ts +403 -0
  59. package/scripts/ci-native-build.ts +402 -0
  60. package/scripts/enforce_identity_migration.sh +110 -0
  61. package/scripts/generate-sdk.ts +1 -1
  62. package/scripts/install_macos_app.sh +192 -31
  63. package/scripts/macos_artifact.ts +323 -43
  64. package/scripts/native/realtime-settle-probe/README.md +39 -0
  65. package/scripts/native/realtime-settle-probe/main.swift +135 -0
  66. package/scripts/policy/local-only-approved-targets.txt +21 -0
  67. package/scripts/read_local_only_targets.sh +203 -0
  68. package/scripts/scan-artifact.ts +40 -0
  69. package/scripts/set-version.ts +145 -0
  70. package/scripts/smoke_macos_app.sh +62 -16
  71. package/scripts/vacuity-manifest-gen.ts +96 -0
  72. package/scripts/vacuity-manifests/enhancement-screen-mirror.tsv +44 -0
  73. package/scripts/vacuity-manifests/install-chain.tsv +13 -0
  74. package/scripts/vacuity-manifests/must-survive-controls.tsv +10 -0
  75. package/scripts/vacuity-manifests/reorder.tsv +4 -0
  76. package/scripts/vacuity-manifests/source-side.tsv +15 -0
  77. package/scripts/vacuity-manifests/variable-operands.tsv +16 -0
  78. package/scripts/vacuity-manifests/version-sites.tsv +61 -0
  79. package/scripts/vacuity-mutation-battery.ts +266 -0
  80. package/src/native/Recordings/App/MenuBarStatusView.swift +23 -11
  81. package/src/native/Recordings/App/RecordingsApp.swift +37 -1
  82. package/src/native/Recordings/App/RuntimeSmoke.swift +5 -1
  83. package/src/native/Recordings/App/SidebarView.swift +1 -1
  84. package/src/native/Recordings/Package.swift +24 -5
  85. package/src/native/Recordings/RecordingsLib/EnhancementScreen.swift +91 -0
  86. package/src/native/Recordings/RecordingsLib/FnKeyMonitor.swift +27 -4
  87. package/src/native/Recordings/RecordingsLib/Info.plist +2 -2
  88. package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +56 -3
  89. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +13 -0
  90. package/src/native/Recordings/RecordingsLib/PasteDeliveryVerification.swift +460 -0
  91. package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +21 -1
  92. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +41 -7
  93. package/src/native/Recordings/RecordingsLib/RecordingAttemptAlert.swift +36 -0
  94. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +1071 -75
  95. package/src/native/Recordings/RecordingsLib/SettingsView.swift +47 -3
  96. package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +11 -6
  97. package/src/native/Recordings/RecordingsTests/BarOnlyLaunchPlanTests.swift +99 -0
  98. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +3 -3
  99. package/src/native/Recordings/RecordingsTests/EnhancementScreenTests.swift +191 -0
  100. package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +144 -4
  101. package/src/native/Recordings/RecordingsTests/PasteDeliveryVerificationTests.swift +463 -0
  102. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +33 -20
  103. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +1 -1
  104. package/src/native/Recordings/RecordingsTests/RealtimeSettleBenchmark.swift +200 -0
  105. package/src/native/Recordings/RecordingsTests/RealtimeSettleBudgetTests.swift +80 -0
  106. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +22 -4
  107. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +12 -6
  108. package/src/native/Recordings/RecordingsTests/RecordingEngineHomeIsolationTests.swift +67 -0
  109. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +12 -3
  110. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +207 -11
  111. package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +17 -6
  112. package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +23 -0
  113. package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +7 -1
  114. package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +10 -10
  115. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +5 -1
  116. package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +1 -0
  117. package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +59 -3
  118. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +21 -2
  119. package/src/native/Recordings/Updater/ProtocolTests/ExpectationIntegrityTests.swift +60 -0
  120. package/src/native/Recordings/Updater/ProtocolTests/UpdateXPCInterfaceTests.swift +113 -0
  121. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +106 -5
  122. package/src/native/Recordings/build.sh +226 -20
  123. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
package/dist/mcp/index.js CHANGED
@@ -65,7 +65,7 @@ function startMcpHttpServer(options) {
65
65
  return server;
66
66
  }
67
67
 
68
- // node_modules/zod/v3/external.js
68
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
69
69
  var exports_external = {};
70
70
  __export(exports_external, {
71
71
  void: () => voidType,
@@ -177,7 +177,7 @@ __export(exports_external, {
177
177
  BRAND: () => BRAND
178
178
  });
179
179
 
180
- // node_modules/zod/v3/helpers/util.js
180
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
181
181
  var util;
182
182
  (function(util2) {
183
183
  util2.assertEqual = (_) => {};
@@ -308,7 +308,7 @@ var getParsedType = (data) => {
308
308
  }
309
309
  };
310
310
 
311
- // node_modules/zod/v3/ZodError.js
311
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
312
312
  var ZodIssueCode = util.arrayToEnum([
313
313
  "invalid_type",
314
314
  "invalid_literal",
@@ -427,7 +427,7 @@ ZodError.create = (issues) => {
427
427
  return error;
428
428
  };
429
429
 
430
- // node_modules/zod/v3/locales/en.js
430
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
431
431
  var errorMap = (issue, _ctx) => {
432
432
  let message;
433
433
  switch (issue.code) {
@@ -530,7 +530,7 @@ var errorMap = (issue, _ctx) => {
530
530
  };
531
531
  var en_default = errorMap;
532
532
 
533
- // node_modules/zod/v3/errors.js
533
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
534
534
  var overrideErrorMap = en_default;
535
535
  function setErrorMap(map) {
536
536
  overrideErrorMap = map;
@@ -538,7 +538,7 @@ function setErrorMap(map) {
538
538
  function getErrorMap() {
539
539
  return overrideErrorMap;
540
540
  }
541
- // node_modules/zod/v3/helpers/parseUtil.js
541
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
542
542
  var makeIssue = (params) => {
543
543
  const { data, path, errorMaps, issueData } = params;
544
544
  const fullPath = [...path, ...issueData.path || []];
@@ -644,14 +644,14 @@ var isAborted = (x) => x.status === "aborted";
644
644
  var isDirty = (x) => x.status === "dirty";
645
645
  var isValid = (x) => x.status === "valid";
646
646
  var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
647
- // node_modules/zod/v3/helpers/errorUtil.js
647
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
648
648
  var errorUtil;
649
649
  (function(errorUtil2) {
650
650
  errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
651
651
  errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
652
652
  })(errorUtil || (errorUtil = {}));
653
653
 
654
- // node_modules/zod/v3/types.js
654
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
655
655
  class ParseInputLazyPath {
656
656
  constructor(parent, value, path, key) {
657
657
  this._cachedPath = [];
@@ -4464,7 +4464,8 @@ function findProjectRecordingsPath(entry) {
4464
4464
  function findRepositoryRoot(start) {
4465
4465
  let dir = start;
4466
4466
  while (true) {
4467
- if (existsSync(join2(dir, ".git")))
4467
+ const gitPath = join2(dir, ".git");
4468
+ if (isRepositoryMarker(gitPath))
4468
4469
  return dir;
4469
4470
  const parent = dirname2(dir);
4470
4471
  if (parent === dir)
@@ -4472,6 +4473,15 @@ function findRepositoryRoot(start) {
4472
4473
  dir = parent;
4473
4474
  }
4474
4475
  }
4476
+ function isRepositoryMarker(gitPath) {
4477
+ try {
4478
+ if (statSync(gitPath).isFile())
4479
+ return true;
4480
+ return existsSync(join2(gitPath, "HEAD"));
4481
+ } catch {
4482
+ return false;
4483
+ }
4484
+ }
4475
4485
  function canonicalExistingPath(path) {
4476
4486
  try {
4477
4487
  return realpathSync2(path);
@@ -4500,14 +4510,17 @@ function loadSecretKey(keyName) {
4500
4510
  try {
4501
4511
  const content = readFileSync(candidate, "utf-8");
4502
4512
  const match = content.match(new RegExp(`export\\s+${keyName}\\s*=\\s*"([^"]+)"`));
4503
- if (match)
4504
- return match[1];
4513
+ const doubleQuotedValue = match?.[1];
4514
+ if (doubleQuotedValue !== undefined)
4515
+ return doubleQuotedValue;
4505
4516
  const match2 = content.match(new RegExp(`export\\s+${keyName}\\s*=\\s*'([^']+)'`));
4506
- if (match2)
4507
- return match2[1];
4517
+ const singleQuotedValue = match2?.[1];
4518
+ if (singleQuotedValue !== undefined)
4519
+ return singleQuotedValue;
4508
4520
  const match3 = content.match(new RegExp(`${keyName}\\s*=\\s*(.+)`));
4509
- if (match3)
4510
- return match3[1].trim().replace(/^["']|["']$/g, "");
4521
+ const unquotedValue = match3?.[1];
4522
+ if (unquotedValue !== undefined)
4523
+ return unquotedValue.trim().replace(/^["']|["']$/g, "");
4511
4524
  } catch {}
4512
4525
  }
4513
4526
  return "";
@@ -4665,6 +4678,7 @@ var MIGRATIONS = [
4665
4678
  ALTER TABLE agents ADD COLUMN active_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
4666
4679
  `
4667
4680
  ];
4681
+ var CURRENT_MIGRATION_LEVEL = MIGRATIONS.length - 1;
4668
4682
  function getDatabase(dbPath) {
4669
4683
  if (_db)
4670
4684
  return _db;
@@ -4688,7 +4702,11 @@ function runMigrations(db) {
4688
4702
  const currentLevel = result?.max_id ?? -1;
4689
4703
  for (let i = currentLevel + 1;i < MIGRATIONS.length; i++) {
4690
4704
  try {
4691
- db.run(MIGRATIONS[i]);
4705
+ const migration = MIGRATIONS[i];
4706
+ if (migration === undefined) {
4707
+ throw new Error(`Missing SQLite migration at version ${i}`);
4708
+ }
4709
+ db.run(migration);
4692
4710
  db.query("INSERT INTO _migrations (id) VALUES (?)").run(i);
4693
4711
  } catch (e) {
4694
4712
  if (isBenignMigrationError(e)) {
@@ -5038,7 +5056,7 @@ function setAgentFocus(idOrName, projectId, db) {
5038
5056
  }
5039
5057
 
5040
5058
  // src/version.ts
5041
- var VERSION = "0.2.14";
5059
+ var VERSION = "0.3.2";
5042
5060
 
5043
5061
  // src/db/feedback.ts
5044
5062
  function saveFeedback(input) {
@@ -5046,29 +5064,54 @@ function saveFeedback(input) {
5046
5064
  db.query("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)").run(input.message, input.email ?? null, input.category ?? "general", input.version ?? VERSION);
5047
5065
  }
5048
5066
 
5067
+ // src/lib/retired-deployment-modes.ts
5068
+ var RETIRED_DEPLOYMENT_MODES = [
5069
+ "local",
5070
+ "self_hosted",
5071
+ "cloud",
5072
+ "remote",
5073
+ "hybrid"
5074
+ ];
5075
+ function normalizeModeToken(value) {
5076
+ return value.trim().toLowerCase().replace(/-/g, "_");
5077
+ }
5078
+ function asRetiredDeploymentMode(value) {
5079
+ const token = normalizeModeToken(value);
5080
+ return RETIRED_DEPLOYMENT_MODES.includes(token) ? token : null;
5081
+ }
5082
+ function retiredDeploymentModeError(rawValue, sourceEnvKey, replacement) {
5083
+ const mode = asRetiredDeploymentMode(rawValue);
5084
+ const value = mode === "local" ? replacement.onBox : replacement.offBox;
5085
+ 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}).`);
5086
+ }
5087
+
5049
5088
  // src/http/client.ts
5050
- var DEPRECATED_MODE_ALIASES = ["self_hosted", "remote", "hybrid"];
5089
+ var SERVER_BACKEND_VALUES = ["sqlite", "postgresql", "postgres"];
5051
5090
  function envToken(name) {
5052
5091
  return name.toUpperCase().replace(/-/g, "_");
5053
5092
  }
5054
- function normalizeMode(value) {
5055
- const normalized = value.trim().toLowerCase().replace(/-/g, "_");
5056
- if (normalized === "local")
5057
- return { mode: "local", deprecatedAlias: null };
5058
- if (normalized === "cloud")
5059
- return { mode: "cloud", deprecatedAlias: null };
5060
- if (DEPRECATED_MODE_ALIASES.includes(normalized)) {
5061
- return { mode: "cloud", deprecatedAlias: normalized };
5093
+ function clientStoreReplacement(envKey) {
5094
+ return { envKey, onBox: "sqlite", offBox: "http" };
5095
+ }
5096
+ function normalizeClientStore(value, sourceEnvKey, replacementEnvKey) {
5097
+ const normalized = normalizeModeToken(value);
5098
+ if (normalized === "sqlite")
5099
+ return "sqlite";
5100
+ if (normalized === "http" || normalized === "https")
5101
+ return "http";
5102
+ if (asRetiredDeploymentMode(value)) {
5103
+ throw retiredDeploymentModeError(value, sourceEnvKey, clientStoreReplacement(replacementEnvKey));
5062
5104
  }
5063
- throw new Error(`Unknown storage mode: ${value}. Use local or cloud.`);
5105
+ throw new Error(`Unknown client store: ${value}. Use sqlite or http.`);
5064
5106
  }
5065
- function defaultCloudBaseUrl(name) {
5066
- return `https://${name}.hasna.xyz`;
5107
+ function defaultApiBaseUrl(name) {
5108
+ return `http://localhost:8874`;
5067
5109
  }
5068
5110
  function envKeys(name) {
5069
5111
  const token = envToken(name);
5070
5112
  return {
5071
- modeKeys: [`HASNA_${token}_STORAGE_MODE`, `HASNA_${token}_MODE`, `${token}_STORAGE_MODE`, `${token}_MODE`],
5113
+ storeKeys: [`HASNA_${token}_CLIENT_STORE`, `${token}_CLIENT_STORE`],
5114
+ retiredModeKeys: [`HASNA_${token}_STORAGE_MODE`, `HASNA_${token}_MODE`, `${token}_STORAGE_MODE`, `${token}_MODE`],
5072
5115
  apiUrlKeys: [`HASNA_${token}_API_URL`, `${token}_API_URL`],
5073
5116
  apiKeyKeys: [`HASNA_${token}_API_KEY`, `${token}_API_KEY`]
5074
5117
  };
@@ -5081,6 +5124,20 @@ function firstEnv(env, keys) {
5081
5124
  }
5082
5125
  return null;
5083
5126
  }
5127
+ function assertNoRetiredMode(env, keys) {
5128
+ for (const key of keys.retiredModeKeys) {
5129
+ const value = env[key]?.trim();
5130
+ if (!value)
5131
+ continue;
5132
+ const normalized = normalizeModeToken(value);
5133
+ if (SERVER_BACKEND_VALUES.includes(normalized))
5134
+ continue;
5135
+ if (asRetiredDeploymentMode(value)) {
5136
+ throw retiredDeploymentModeError(value, key, clientStoreReplacement(keys.storeKeys[0]));
5137
+ }
5138
+ 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.`);
5139
+ }
5140
+ }
5084
5141
  function toV1BaseUrl(apiUrl) {
5085
5142
  const url = new URL(apiUrl);
5086
5143
  if (url.protocol !== "http:" && url.protocol !== "https:") {
@@ -5096,45 +5153,42 @@ function toV1BaseUrl(apiUrl) {
5096
5153
  }
5097
5154
  function resolveTransport(name, env = process.env) {
5098
5155
  const keys = envKeys(name);
5099
- const modeHit = firstEnv(env, keys.modeKeys);
5156
+ assertNoRetiredMode(env, keys);
5157
+ const storeHit = firstEnv(env, keys.storeKeys);
5100
5158
  const urlHit = firstEnv(env, keys.apiUrlKeys);
5101
5159
  const keyHit = firstEnv(env, keys.apiKeyKeys);
5102
- let mode = "local";
5103
- let deprecatedAlias = null;
5160
+ let requested = "sqlite";
5104
5161
  let modeSource = "default";
5105
- if (modeHit) {
5106
- const normalized = normalizeMode(modeHit.value);
5107
- mode = normalized.mode;
5108
- deprecatedAlias = normalized.deprecatedAlias;
5109
- modeSource = modeHit.key;
5162
+ if (storeHit) {
5163
+ requested = normalizeClientStore(storeHit.value, storeHit.key, keys.storeKeys[0]);
5164
+ modeSource = storeHit.key;
5110
5165
  } else if (urlHit && keyHit) {
5111
- mode = "cloud";
5166
+ requested = "http";
5112
5167
  modeSource = "auto:api-url+api-key";
5113
5168
  }
5114
- if (mode === "local") {
5115
- return { transport: "local", mode, deprecatedAlias, modeSource, baseUrl: null, apiKeyPresent: Boolean(keyHit), misconfigured: false, warning: null };
5169
+ if (requested === "sqlite") {
5170
+ return { transport: "sqlite", requested, modeSource, baseUrl: null, apiKeyPresent: Boolean(keyHit), misconfigured: false, warning: null };
5116
5171
  }
5117
5172
  if (!keyHit) {
5118
5173
  return {
5119
- transport: "local",
5120
- mode,
5121
- deprecatedAlias,
5174
+ transport: "sqlite",
5175
+ requested,
5122
5176
  modeSource,
5123
5177
  baseUrl: null,
5124
5178
  apiKeyPresent: false,
5125
5179
  misconfigured: true,
5126
- warning: `${modeSource}=cloud but no API key is set (${keys.apiKeyKeys[0]}). Refusing to route to cloud.`
5180
+ warning: `${modeSource}=http but no API key is set (${keys.apiKeyKeys[0]}). Refusing to route to the API.`
5127
5181
  };
5128
5182
  }
5129
- const rawUrl = urlHit?.value ?? defaultCloudBaseUrl(name);
5183
+ const rawUrl = urlHit?.value ?? defaultApiBaseUrl(name);
5130
5184
  let baseUrl;
5131
5185
  try {
5132
5186
  baseUrl = toV1BaseUrl(rawUrl);
5133
5187
  } catch (error) {
5134
5188
  const message = error instanceof Error ? error.message : String(error);
5135
- return { transport: "local", mode, deprecatedAlias, modeSource, baseUrl: null, apiKeyPresent: true, misconfigured: true, warning: `Invalid API URL: ${message}.` };
5189
+ return { transport: "sqlite", requested, modeSource, baseUrl: null, apiKeyPresent: true, misconfigured: true, warning: `Invalid API URL: ${message}.` };
5136
5190
  }
5137
- return { transport: "cloud-http", mode, deprecatedAlias, modeSource, baseUrl, apiKeyPresent: true, misconfigured: false, warning: null };
5191
+ return { transport: "http", requested, modeSource, baseUrl, apiKeyPresent: true, misconfigured: false, warning: null };
5138
5192
  }
5139
5193
 
5140
5194
  class HasnaHttpError extends Error {
@@ -5246,6 +5300,8 @@ function createHttpTransport(options) {
5246
5300
  const jitter = Math.floor(Math.random() * (backoff / 2 + 1));
5247
5301
  await sleep(backoff + jitter);
5248
5302
  }
5303
+ if (last === null)
5304
+ throw new Error(`Request to ${rel} completed without a result`);
5249
5305
  throw last.error;
5250
5306
  }
5251
5307
  return {
@@ -5317,17 +5373,17 @@ function createStorageClient(name, transport) {
5317
5373
  function resolveStorageClient(name, env = process.env, fetchImpl) {
5318
5374
  const resolution = resolveTransport(name, env);
5319
5375
  if (resolution.misconfigured) {
5320
- throw new Error(resolution.warning ?? `Client for '${name}' is misconfigured for cloud mode.`);
5376
+ throw new Error(resolution.warning ?? `Client for '${name}' is misconfigured for the /v1 API.`);
5321
5377
  }
5322
- if (resolution.transport === "local" || !resolution.baseUrl) {
5323
- return { transport: "local", client: null, resolution };
5378
+ if (resolution.transport === "sqlite" || !resolution.baseUrl) {
5379
+ return { transport: "sqlite", client: null, resolution };
5324
5380
  }
5325
5381
  const keys = envKeys(name);
5326
5382
  const apiKey = firstEnv(env, keys.apiKeyKeys)?.value;
5327
5383
  if (!apiKey)
5328
- throw new Error(`Client for '${name}' resolved to cloud-http without an API key.`);
5384
+ throw new Error(`Client for '${name}' resolved to the /v1 API without an API key.`);
5329
5385
  const transport = createHttpTransport({ name, baseUrl: resolution.baseUrl, apiKey, ...fetchImpl ? { fetchImpl } : {} });
5330
- return { transport: "cloud-http", client: createStorageClient(name, transport), resolution };
5386
+ return { transport: "http", client: createStorageClient(name, transport), resolution };
5331
5387
  }
5332
5388
 
5333
5389
  // src/store.ts
@@ -5351,12 +5407,15 @@ function listQuery(filter) {
5351
5407
  }
5352
5408
  function unwrap(res, key) {
5353
5409
  if (res && typeof res === "object" && key in res) {
5354
- return res[key];
5410
+ const value = res[key];
5411
+ if (value === undefined)
5412
+ throw new Error(`Response field ${key} is undefined`);
5413
+ return value;
5355
5414
  }
5356
5415
  return res;
5357
5416
  }
5358
5417
  var localStore = {
5359
- mode: "local",
5418
+ mode: "sqlite",
5360
5419
  baseUrl: null,
5361
5420
  async createRecording(input, idempotencyKey) {
5362
5421
  return withLocalStoreReaderLease(() => createRecording(input, undefined, idempotencyKey));
@@ -5409,7 +5468,7 @@ var localStore = {
5409
5468
  };
5410
5469
  function apiStore(client) {
5411
5470
  return {
5412
- mode: "cloud-http",
5471
+ mode: "http",
5413
5472
  baseUrl: client.baseUrl,
5414
5473
  async createRecording(input, idempotencyKey) {
5415
5474
  const keyCandidate = idempotencyKey === undefined && (input.id === undefined || input.id === null) ? randomUUID2() : idempotencyKey;
@@ -5577,7 +5636,7 @@ function getStore(env = process.env) {
5577
5636
  if (env === process.env && cached)
5578
5637
  return cached;
5579
5638
  const resolved = resolveStorageClient(APP, env);
5580
- const store = resolved.transport === "cloud-http" ? apiStore(resolved.client) : localStore;
5639
+ const store = resolved.transport === "http" ? apiStore(resolved.client) : localStore;
5581
5640
  if (env === process.env)
5582
5641
  cached = store;
5583
5642
  return store;
@@ -6497,8 +6556,33 @@ Hints: ${models.length - limit} more model(s); call recording_stats with verbose
6497
6556
  });
6498
6557
  return server;
6499
6558
  }
6559
+ function printHelp() {
6560
+ console.log(`Usage: recordings-mcp [options]
6561
+
6562
+ Start the @hasna/recordings MCP server.
6563
+
6564
+ Options:
6565
+ --stdio Serve MCP over stdio (Codex/Claude agent form)
6566
+ --http Serve MCP over the shared Streamable HTTP endpoint
6567
+ --port <port> HTTP port to bind. Defaults to ${DEFAULT_MCP_HTTP_PORT} (or $MCP_HTTP_PORT)
6568
+ -V, --version output the version number
6569
+ -h, --help display help for command
6570
+
6571
+ Environment:
6572
+ MCP_STDIO 1 to select stdio transport
6573
+ MCP_HTTP 1 to select the HTTP transport
6574
+ MCP_HTTP_PORT HTTP port override`);
6575
+ }
6500
6576
  async function main() {
6501
6577
  const args = process.argv.slice(2);
6578
+ if (args.includes("--version") || args.includes("-V")) {
6579
+ console.log(VERSION);
6580
+ return;
6581
+ }
6582
+ if (args.includes("--help") || args.includes("-h")) {
6583
+ printHelp();
6584
+ return;
6585
+ }
6502
6586
  if (isStdioMode(args)) {
6503
6587
  const transport = new StdioServerTransport;
6504
6588
  await buildServer().connect(transport);
@@ -1,9 +1,31 @@
1
- /** Resolve the remote DATABASE_URL from the supported env vars (priority order). */
1
+ /** The server's internal storage engine. Two arms, no third. */
2
+ export type DataBackend = "sqlite" | "postgresql";
3
+ /** Variables that select the backend, in priority order. */
4
+ export declare const DATA_BACKEND_ENV_KEYS: readonly ["HASNA_RECORDINGS_STORAGE_MODE", "RECORDINGS_STORAGE_MODE"];
5
+ /** Resolve the Postgres DSN from the supported env vars (priority order). */
2
6
  export declare function resolveCloudDatabaseUrl(env?: NodeJS.ProcessEnv): string | undefined;
7
+ /**
8
+ * The explicitly configured backend, or null when no variable named one.
9
+ *
10
+ * Throws — never normalizes — on a retired deployment word or an unrecognized
11
+ * value. Before this change, ONLY `remote` and `hybrid` were recognized here and
12
+ * every other value (including `self_hosted`, including a typo) fell silently
13
+ * through to "is a DATABASE_URL set?", so the operator's stated intent was
14
+ * discarded without a word.
15
+ */
16
+ export declare function configuredDataBackend(env?: NodeJS.ProcessEnv): {
17
+ backend: DataBackend;
18
+ source: string;
19
+ } | null;
20
+ /**
21
+ * The backend this process will use. An explicit setting wins; otherwise the
22
+ * presence of a DSN is taken as the request for `postgresql`.
23
+ */
24
+ export declare function resolveDataBackend(env?: NodeJS.ProcessEnv): DataBackend;
25
+ /** True when this process serves `/v1` out of PostgreSQL. */
26
+ export declare function isPostgresBackendEnabled(env?: NodeJS.ProcessEnv): boolean;
3
27
  /** Resolve the HMAC signing secret used to verify API keys. */
4
28
  export declare function resolveSigningSecret(env?: NodeJS.ProcessEnv): string | undefined;
5
29
  /** Validate auth configuration without opening a database connection or exposing the secret. */
6
30
  export declare function requireSigningSecret(env?: NodeJS.ProcessEnv): string;
7
- /** True when this process is configured to serve the cloud `/v1` API. */
8
- export declare function isCloudModeEnabled(env?: NodeJS.ProcessEnv): boolean;
9
31
  //# sourceMappingURL=cloud-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-config.d.ts","sourceRoot":"","sources":["../../src/server/cloud-config.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAOhG;AAED,+DAA+D;AAC/D,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAO7F;AAED,gGAAgG;AAChG,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAWjF;AAED,yEAAyE;AACzE,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAIhF"}
1
+ {"version":3,"file":"cloud-config.d.ts","sourceRoot":"","sources":["../../src/server/cloud-config.ts"],"names":[],"mappings":"AAeA,gEAAgE;AAChE,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,CAAC;AAElD,4DAA4D;AAC5D,eAAO,MAAM,qBAAqB,uEAGxB,CAAC;AAMX,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAOhG;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAgBjD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,WAAW,CAIpF;AAED,6DAA6D;AAC7D,wBAAgB,wBAAwB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAEtF;AAED,+DAA+D;AAC/D,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAO7F;AAED,gGAAgG;AAChG,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAWjF"}
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-readiness.d.ts","sourceRoot":"","sources":["../../src/server/cloud-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAkL9D,sFAAsF;AACtF,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAiHf;AAED,kFAAkF;AAClF,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAoHf;AAED,gFAAgF;AAChF,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAI7F;AAED,qFAAqF;AACrF,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,CAAC,GACtC,OAAO,CAAC,OAAO,CAAC,CAIlB"}
1
+ {"version":3,"file":"cloud-readiness.d.ts","sourceRoot":"","sources":["../../src/server/cloud-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAkL9D,sFAAsF;AACtF,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAqHf;AAED,kFAAkF;AAClF,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAoHf;AAED,gFAAgF;AAChF,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAI7F;AAED,qFAAqF;AACrF,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,CAAC,GACtC,OAAO,CAAC,OAAO,CAAC,CAIlB"}
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Cloud (A1 pure-remote) service wiring for `recordings-serve`.
2
+ * PostgreSQL-backed `/v1` service wiring for `recordings-serve`.
3
3
  *
4
- * Per Amendment A1 the serve process reads and writes the shared cloud Postgres
5
- * DIRECTLY through the repo-native async adapter (`PgAdapterAsync`) — there is
6
- * NO local sync/cache in the service. Everything is lazy: nothing touches
7
- * Postgres or crypto until the first `/v1` (or `/ready`) request, so the
8
- * local-first CLI/MCP paths keep ZERO cloud dependencies.
4
+ * When the server's data backend is `postgresql` it reads and writes that
5
+ * database DIRECTLY through the repo-native async adapter (`PgAdapterAsync`) —
6
+ * there is NO on-box sync/cache in the service. Everything is lazy: nothing
7
+ * touches Postgres or crypto until the first `/v1` (or `/ready`) request, so the
8
+ * SQLite-backed CLI/MCP paths keep ZERO Postgres dependencies.
9
9
  *
10
10
  * Auth is enforced by the vendored `@hasna/contracts` kit: stateless HMAC-signed
11
11
  * API keys, hashed at rest in the cloud Postgres `api_keys` table, verified per request.
@@ -14,7 +14,8 @@ import { type ApiKeyVerifier } from "@hasna/contracts/auth";
14
14
  import { ApiKeyStore } from "@hasna/contracts/auth";
15
15
  import { PgAdapterAsync } from "../db/remote-storage.js";
16
16
  export declare const RECORDINGS_APP_SLUG = "recordings";
17
- export { isCloudModeEnabled, requireSigningSecret, resolveCloudDatabaseUrl, resolveSigningSecret } from "./cloud-config.js";
17
+ export { isPostgresBackendEnabled, resolveDataBackend, configuredDataBackend, requireSigningSecret, resolveCloudDatabaseUrl, resolveSigningSecret, } from "./cloud-config.js";
18
+ export type { DataBackend } from "./cloud-config.js";
18
19
  /** The pure-remote Postgres adapter backing every `/v1` handler. */
19
20
  export declare function getCloudPg(): PgAdapterAsync;
20
21
  export declare function getApiKeyStore(): ApiKeyStore;
@@ -1 +1 @@
1
- {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/server/cloud.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAwB,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAazD,eAAO,MAAM,mBAAmB,eAAe,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAO5H,oEAAoE;AACpE,wBAAgB,UAAU,IAAI,cAAc,CAU3C;AAsBD,wBAAgB,cAAc,IAAI,WAAW,CAI5C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,cAAc,CAUjD;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAExD;AAED,mFAAmF;AACnF,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAOvD;AAED,wBAAsB,qBAAqB,CACzC,EAAE,GAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAgB,GAC7C,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wEAAwE;AACxE,wBAAsB,SAAS,CAC7B,EAAE,GAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,CAAgB,GACrD,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,4BAA4B;AAC5B,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAMhD"}
1
+ {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/server/cloud.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAwB,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAYzD,eAAO,MAAM,mBAAmB,eAAe,CAAC;AAChD,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAOrD,oEAAoE;AACpE,wBAAgB,UAAU,IAAI,cAAc,CAU3C;AAsBD,wBAAgB,cAAc,IAAI,WAAW,CAI5C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,cAAc,CAUjD;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAExD;AAED,mFAAmF;AACnF,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAOvD;AAED,wBAAsB,qBAAqB,CACzC,EAAE,GAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAgB,GAC7C,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wEAAwE;AACxE,wBAAsB,SAAS,CAC7B,EAAE,GAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,CAAgB,GACrD,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,4BAA4B;AAC5B,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAMhD"}